From 5d9ca9e585184327f22e48d94c2088011ab8b646 Mon Sep 17 00:00:00 2001 From: Daniel Collin Date: Fri, 4 Mar 2016 17:36:28 +0100 Subject: [PATCH] Merge work from menu branch --- CHANGELOG.md | 7 + Cargo.toml | 2 +- README.md | 8 +- examples/menu.rs | 126 ++++++++++ examples/noise.rs | 4 +- src/error.rs | 48 ++++ src/key.rs | 128 +++++++++++ src/lib.rs | 229 +++++++----------- src/menu.rs | 59 +++++ src/native/macosx/MacMiniFB.m | 275 ++++++++++++++++++++-- src/native/macosx/OSXWindow.h | 41 ++++ src/native/macosx/OSXWindow.m | 191 +++++++++++++-- src/os/macos/mod.rs | 263 +++++++++++++++++++-- src/os/unix/mod.rs | 52 +++-- src/os/windows/mod.rs | 421 ++++++++++++++++++++++++++++++++-- 15 files changed, 1622 insertions(+), 232 deletions(-) create mode 100644 examples/menu.rs create mode 100644 src/error.rs create mode 100644 src/key.rs create mode 100644 src/menu.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 75f0233..bf357c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ This project follows semantic versioning. +### v0.5.0 (2016-03-04) + +- [changed] - Proper Errors which uses ```std::Error``` as base. ```Window::new``` uses this but the API itself hasn't changed. +- [added] - Menu support on Mac and Windows. See the Menu API functions [here](http://prodbg.com/minifb/minifb/struct.Window.html#method.add_menu) +- [known issue] - ```remove_menu``` doesn't work on Windows [issue](https://github.com/emoon/rust_minifb/issues/16) +- [known issue] - On Mac when running an application from terminal on has to switch to another application and back to get menu focus. [issue](https://github.com/emoon/rust_minifb/issues/17) + ### v0.4.0 (2016-01-31) This release breaks some of the API by changing names and parameters to some functions. diff --git a/Cargo.toml b/Cargo.toml index f561122..8707f55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "minifb" -version = "0.4.0" +version = "0.5.0" license = "MIT/Apache-2.0" authors = ["Daniel Collin "] description = "Cross-platform window setup with optional bitmap rendering" diff --git a/README.md b/README.md index 34748ff..470fa75 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Usage ```toml # Cargo.toml [dependencies] -minifb = "0.4.0" +minifb = "0.5.0" ``` Example @@ -31,7 +31,7 @@ const HEIGHT: usize = 360; fn main() { let mut buffer: Vec = vec![0; WIDTH * HEIGHT]; - let mut window = match minifb::Window::new("Test - ESC to exit", WIDTH, HEIGHT, + let mut window = match minifb::Window::new("Test - ESC to exit", WIDTH, HEIGHT, WindowOptions::default()) { Ok(win) => win, Err(err) => { @@ -52,7 +52,7 @@ fn main() { Status ------ -Currently Mac, Linux and Windows (64-bit and 32-bit) are the current supported platforms. X11 (Linux/FreeBSD/etc) support has been tested on Ubuntu (x64). Bug report(s) for other OSes/CPUs are welcome! +Currently Mac, Linux and Windows (64-bit and 32-bit) are the current supported platforms. X11 (Linux/FreeBSD/etc) support has been tested on Ubuntu (x64). Bug report(s) for other OSes/CPUs are welcome! Build instructions @@ -60,7 +60,7 @@ Build instructions ``` cargo build -cargo run --example noise +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) diff --git a/examples/menu.rs b/examples/menu.rs new file mode 100644 index 0000000..c665b3d --- /dev/null +++ b/examples/menu.rs @@ -0,0 +1,126 @@ +extern crate minifb; + +use minifb::{Window, Key, Scale, WindowOptions, Menu}; +use minifb::{MENU_KEY_CTRL, MENU_KEY_COMMAND}; + +const WIDTH: usize = 640; +const HEIGHT: usize = 360; + +const MENU_TEST_ID: usize = 1; +const OTHER_MENU_ID: usize = 2; +const COLOR_0_ID: usize = 3; +const COLOR_1_ID: usize = 4; +const COLOR_2_ID: usize = 5; +const CLOSE_MENU_ID: usize = 6; + +fn main() { + let mut buffer: Vec = vec![0; WIDTH * HEIGHT]; + + let mut window = Window::new("Noise Test - Press ESC to exit", + WIDTH, + HEIGHT, + WindowOptions { + resize: true, + scale: Scale::X2, + ..WindowOptions::default() + }) + .expect("Unable to Open Window"); + + // Setup a sub menu + + let sub_menu = vec![ + Menu { + name: "Color 0", + id: COLOR_0_ID, + ..Menu::default() + }, + Menu { + name: "Color 1", + id: COLOR_1_ID, + ..Menu::default() + }, + Menu { + name: "Color 2", + id: COLOR_2_ID, + ..Menu::default() + }, + ]; + + // Main menu + + let menu = vec![ + Menu { + name: "Menu Test", + key: Key::W, + id: MENU_TEST_ID, + modifier: MENU_KEY_CTRL, + mac_mod: MENU_KEY_COMMAND, + ..Menu::default() + }, + Menu::separotor(), + Menu { + name: "Other menu!", + key: Key::S, + modifier: MENU_KEY_CTRL, + mac_mod: MENU_KEY_CTRL, + id: OTHER_MENU_ID, + ..Menu::default() + }, + Menu { + name: "Remove Menu", + key: Key::R, + id: CLOSE_MENU_ID, + ..Menu::default() + }, + Menu { + name: "Select Color", + sub_menu: Some(&sub_menu), + ..Menu::default() + } + ]; + + window.add_menu("Test", &menu).expect("Unable to add menu"); + + let mut color_mul = 1; + + while window.is_open() && !window.is_key_down(Key::Escape) { + for y in 0..HEIGHT { + for x in 0..WIDTH { + buffer[(y * WIDTH) + x] = (((x ^ y) & 0xff) * color_mul) as u32; + } + } + + window.is_menu_pressed().map(|menu_id| { + match menu_id { + COLOR_0_ID => { + color_mul = 0xfe0000; + } + COLOR_1_ID => { + color_mul = 0xff00; + } + COLOR_2_ID => { + color_mul = 1; + } + CLOSE_MENU_ID => { + println!("remove menu"); + window.remove_menu("Test").expect("Unable to remove menu"); + } + _ => (), + } + + println!("Menu id {} pressed", menu_id); + }); + + window.get_keys().map(|keys| { + for t in keys { + match t { + Key::W => println!("holding w!"), + Key::T => println!("holding t!"), + _ => (), + } + } + }); + + window.update_with_buffer(&buffer); + } +} diff --git a/examples/noise.rs b/examples/noise.rs index 69edf97..d6e92bc 100644 --- a/examples/noise.rs +++ b/examples/noise.rs @@ -12,8 +12,8 @@ fn main() { let mut buffer: Vec = vec![0; WIDTH * HEIGHT]; - let mut window = match Window::new("Noise Test - Press ESC to exit", WIDTH, HEIGHT, - WindowOptions { + let mut window = match Window::new("Noise Test - Press ESC to exit", WIDTH, HEIGHT, + WindowOptions { resize: true, scale: Scale::X2, ..WindowOptions::default() diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..31f5f1d --- /dev/null +++ b/src/error.rs @@ -0,0 +1,48 @@ +use std::error::Error as StdError; +use std::fmt; + +/// Errors that can be return from various operatiors +/// +#[derive(Debug)] +pub enum Error { + /// Returned if menu Menu function isn't supported + MenusNotSupported, + /// Menu already exists + MenuExists(String), + /// Menu already exists + WindowCreate(String), +} + +impl StdError for Error { + fn description(&self) -> &str { + match *self { + Error::MenusNotSupported => "Menus not supported", + Error::MenuExists(_) => "Menu already exists", + Error::WindowCreate(_) => "Failed to create Window", + } + } + + fn cause(&self) -> Option<&StdError> { + match *self { + Error::MenusNotSupported => None, + Error::MenuExists(_) => None, + Error::WindowCreate(_) => None, + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::MenusNotSupported => { + write!(fmt, "{}", self.description()) + }, + Error::MenuExists(ref e) => { + write!(fmt, "{} {:?}", self.description(), e) + }, + Error::WindowCreate(ref e) => { + write!(fmt, "{} {:?}", self.description(), e) + } + } + } +} diff --git a/src/key.rs b/src/key.rs new file mode 100644 index 0000000..920ab88 --- /dev/null +++ b/src/key.rs @@ -0,0 +1,128 @@ +/// 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, +} + diff --git a/src/lib.rs b/src/lib.rs index 737f341..39170b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,133 +42,6 @@ pub enum MouseButton Right, } -/// 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, -} /// Key is used by the get key functions to check if some keys on the keyboard has been pressed #[derive(PartialEq, Clone, Copy)] @@ -186,10 +59,24 @@ extern crate libc; use std::os::raw; #[doc(hidden)] +mod error; +pub use self::error::Error; +pub type Result = std::result::Result; + +pub mod key; +pub use key::Key as Key; pub mod os; mod mouse_handler; mod key_handler; mod window_flags; +mod menu; +pub use menu::Menu as Menu; +pub use menu::MENU_KEY_COMMAND; +pub use menu::MENU_KEY_WIN; +pub use menu::MENU_KEY_SHIFT; +pub use menu::MENU_KEY_CTRL; +pub use menu::MENU_KEY_ALT; + #[cfg(target_os = "macos")] use self::os::macos as imp; @@ -206,14 +93,14 @@ pub struct Window(imp::Window); /// /// WindowOptions is creation settings for the window. By default the settings are defined for -/// displayng a 32-bit buffer (no scaling of window is possible) +/// displayng a 32-bit buffer (no scaling of window is possible) /// pub struct WindowOptions { /// If the window should be borderless (default: false) pub borderless: bool, /// If the window should have a title (default: true) pub title: bool, - /// If it should be possible to resize the window (default: false) + /// If it should be possible to resize the window (default: false) pub resize: bool, /// Scale of the window that used in conjunction with update_with_buffer (default: X1) pub scale: Scale @@ -265,10 +152,10 @@ impl Window { /// Open up a window that is resizeable /// /// ```ignore - /// let mut window = match Window::new("Test", 640, 400, + /// let mut window = match Window::new("Test", 640, 400, /// WindowOptions { /// resize: true, - /// ..WindowOptions::default() + /// ..WindowOptions::default() /// }) { /// Ok(win) => win, /// Err(err) => { @@ -277,7 +164,7 @@ impl Window { /// } ///}; /// ``` - pub fn new(name: &str, width: usize, height: usize, opts: WindowOptions) -> Result { + pub fn new(name: &str, width: usize, height: usize, opts: WindowOptions) -> Result { imp::Window::new(name, width, height, opts).map(Window) } @@ -297,7 +184,7 @@ impl Window { } /// - /// Updates the window with a 32-bit pixel buffer. Notice that the buffer needs to be at least + /// 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 @@ -315,7 +202,7 @@ impl Window { } /// - /// Updates the window (this is required to call in order to get keyboard/mouse input, etc) + /// Updates the window (this is required to call in order to get keyboard/mouse input, etc) /// /// # Examples /// @@ -334,7 +221,7 @@ impl Window { /// /// 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. + /// being checked and take action depending on the state. /// /// # Examples /// @@ -375,31 +262,31 @@ impl Window { /// println!("x {} y {}", mouse.0, mouse.1); /// }); /// ``` - /// + /// #[inline] pub fn get_mouse_pos(&self, mode: MouseMode) -> Option<(f32, f32)> { self.0.get_mouse_pos(mode) } /// - /// Check if a mouse button is down or not + /// Check if a mouse button is down or not /// /// # Examples /// /// ```ignore /// let left_down = window.get_mouse_down(MouseButton::Left); - /// println!("is left down? {}", left_down) + /// println!("is left down? {}", left_down) /// ``` - /// + /// #[inline] - pub fn get_mouse_down(&self, button: MouseButton) -> bool { + pub fn get_mouse_down(&self, button: MouseButton) -> bool { self.0.get_mouse_down(button) } /// /// Get the current movement of the scroll wheel. /// Scroll wheel can mean different thing depending on the device attach. - /// For example on Mac with trackpad "scroll wheel" means two finger + /// For example on Mac with trackpad "scroll wheel" means two finger /// swiping up/down (y axis) and to the sides (x-axis) /// When using a mouse this assumes the scroll wheel which often is only y direction. /// @@ -418,7 +305,7 @@ impl Window { } /// - /// Get the current keys that are down. + /// Get the current keys that are down. /// /// # Examples /// @@ -460,7 +347,7 @@ impl Window { self.0.get_keys_pressed(repeat) } - /// + /// /// Check if a single key is down. /// /// # Examples @@ -476,7 +363,7 @@ impl Window { 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. /// @@ -515,13 +402,65 @@ impl Window { /// # Examples /// /// ```ignore - /// window.set_key_repeat_rate(0.01) // 0.01 sec between keys + /// 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) } + + /// + /// Returns if this windows is the current active one + /// + #[inline] + pub fn is_active(&mut self) -> bool { + self.0.is_active() + } + + /// + /// This allows adding menus to your windows. As menus behaves a bit diffrently depending on + /// Operating system here is how it works. See [Menu] for description on each field. + /// + /// ```ignore + /// Windows: + /// Each window has their own menu and shortcuts are active depending on active window. + /// Mac: + /// As Mac uses one menu for the whole program the menu will change depending + /// on which window you have active. + /// Linux/BSD/etc: + /// Menus aren't supported as they depend on each WindowManager and is outside of the + /// scope for this library to support. + /// ``` + /// + #[inline] + pub fn add_menu(&mut self, menu_name: &str, menu: &Vec) -> Result<()> { + self.0.add_menu(menu_name, menu) + } + + /// + /// Updates an existing menu created with [add_menu] + /// + #[inline] + pub fn update_menu(&mut self, menu_name: &str, menu: &Vec) -> Result<()> { + self.0.update_menu(menu_name, menu) + } + + /// + /// Remove a menu that has been added with [add_menu] + /// + #[inline] + pub fn remove_menu(&mut self, menu_name: &str) -> Result<()> { + self.0.remove_menu(menu_name) + } + + /// + /// Check if a menu item has been pressed + /// + #[inline] + pub fn is_menu_pressed(&mut self) -> Option { + self.0.is_menu_pressed() + } } // Impl for WindowOptions diff --git a/src/menu.rs b/src/menu.rs new file mode 100644 index 0000000..7b24ed2 --- /dev/null +++ b/src/menu.rs @@ -0,0 +1,59 @@ +use Key; + +/// Command key on Mac OS +pub const MENU_KEY_COMMAND: usize = 1; +/// Windows key on Windows +pub const MENU_KEY_WIN: usize = 2; +/// Shift key +pub const MENU_KEY_SHIFT: usize = 4; +/// Control key +pub const MENU_KEY_CTRL: usize = 8; +/// Alt key +pub const MENU_KEY_ALT: usize = 16; + +const MENU_ID_SEPARATOR:usize = 0xffffffff; + +/// +/// Used to hold the data for creating menus for the Application +/// +pub struct Menu<'a> { + /// Name of the menu item + pub name: &'a str, + /// User-defined Id thot will be sent back to the application in [get_menu_event] + pub id: usize, + /// Shortcut key for the menu item + pub key: Key, + /// Modifier on Windows for the menu + pub modifier: usize, + /// Modifier on Mac OS + pub mac_mod: usize, + /// Menu item should be enabled on grayed out + pub enabled: bool, + /// Sub-menu. Vector of a sub-menu, otherwise None if no sub-menu + pub sub_menu: Option<&'a Vec>>, +} + +impl<'a> Menu<'a> { + pub fn separotor() -> Menu<'a> { + Menu { + id: MENU_ID_SEPARATOR, + .. Self::default() + } + } +} + +impl<'a> Default for Menu<'a> { + fn default() -> Menu<'a> { + Menu { + name: "", + id: 0, + key: Key::Unknown, + modifier: 0, + mac_mod: 0, + enabled: true, + sub_menu: None, + } + } +} + + diff --git a/src/native/macosx/MacMiniFB.m b/src/native/macosx/MacMiniFB.m index fda0dfa..5d8b157 100644 --- a/src/native/macosx/MacMiniFB.m +++ b/src/native/macosx/MacMiniFB.m @@ -7,9 +7,11 @@ static bool s_init = false; // window_handler.rs -const uint32_t WINDOW_BORDERLESS = 1 << 1; -const uint32_t WINDOW_RESIZE = 1 << 2; -const uint32_t WINDOW_TITLE = 1 << 3; +const uint32_t WINDOW_BORDERLESS = 1 << 1; +const uint32_t WINDOW_RESIZE = 1 << 2; +const uint32_t WINDOW_TITLE = 1 << 3; + +static void create_standard_menu(); /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -21,27 +23,30 @@ const uint32_t WINDOW_TITLE = 1 << 3; void* mfb_open(const char* name, int width, int height, uint32_t flags, int scale) { - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + bool prev_init = s_init; + + //NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; if (!s_init) { [NSApplication sharedApplication]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + create_standard_menu(); s_init = true; } uint32_t styles = NSClosableWindowMask | NSMiniaturizableWindowMask; if (flags & WINDOW_BORDERLESS) - styles |= NSBorderlessWindowMask; + styles |= NSBorderlessWindowMask; if (flags & WINDOW_RESIZE) - styles |= NSResizableWindowMask; + styles |= NSResizableWindowMask; if (flags & WINDOW_TITLE) - styles |= NSTitledWindowMask; - + styles |= NSTitledWindowMask; + NSRect rectangle = NSMakeRect(0, 0, width * scale, (height * scale)); - + OSXWindow* window = [[OSXWindow alloc] initWithContentRect:rectangle styleMask:styles backing:NSBackingStoreBuffered defer:NO]; if (!window) @@ -57,6 +62,10 @@ void* mfb_open(const char* name, int width, int height, uint32_t flags, int scal window->scale = scale; window->key_callback = 0; window->shared_data = 0; + window->active_menu_id = -1; + + window->menu_data = malloc(sizeof(MenuData)); + memset(window->menu_data, 0, sizeof(MenuData)); [window updateSize]; @@ -69,11 +78,124 @@ void* mfb_open(const char* name, int width, int height, uint32_t flags, int scal [NSApp activateIgnoringOtherApps:YES]; - [pool drain]; + if (!prev_init) + [NSApp finishLaunching]; + + //[pool drain]; return window; } +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static NSString* findAppName(void) +{ + size_t i; + NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary]; + + // Keys to search for as potential application names + NSString* GLFWNameKeys[] = + { + @"CFBundleDisplayName", + @"CFBundleName", + @"CFBundleExecutable", + }; + + for (i = 0; i < sizeof(GLFWNameKeys) / sizeof(GLFWNameKeys[0]); i++) + { + id name = [infoDictionary objectForKey:GLFWNameKeys[i]]; + if (name && + [name isKindOfClass:[NSString class]] && + ![name isEqualToString:@""]) + { + return name; + } + } + + extern char** _NSGetProgname(); + char* progname = *_NSGetProgname(); + + if (progname) + return [NSString stringWithUTF8String:progname]; + + // Really shouldn't get here + return @"Unknown"; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static void create_standard_menu(void) +{ + NSString* appName = findAppName(); + + NSMenu* bar = [[NSMenu alloc] init]; + [NSApp setMainMenu:bar]; + + NSMenuItem* appMenuItem = + [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""]; + NSMenu* appMenu = [[NSMenu alloc] init]; + [appMenuItem setSubmenu:appMenu]; + + [appMenu addItemWithTitle:[NSString stringWithFormat:@"About %@", appName] + action:@selector(orderFrontStandardAboutPanel:) + keyEquivalent:@""]; + [appMenu addItem:[NSMenuItem separatorItem]]; + NSMenu* servicesMenu = [[NSMenu alloc] init]; + [NSApp setServicesMenu:servicesMenu]; + [[appMenu addItemWithTitle:@"Services" + action:NULL + keyEquivalent:@""] setSubmenu:servicesMenu]; + [servicesMenu release]; + [appMenu addItem:[NSMenuItem separatorItem]]; + [appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", appName] + action:@selector(hide:) + keyEquivalent:@"h"]; + [[appMenu addItemWithTitle:@"Hide Others" + action:@selector(hideOtherApplications:) + keyEquivalent:@"h"] + setKeyEquivalentModifierMask:NSAlternateKeyMask | NSCommandKeyMask]; + [appMenu addItemWithTitle:@"Show All" + action:@selector(unhideAllApplications:) + keyEquivalent:@""]; + [appMenu addItem:[NSMenuItem separatorItem]]; + [appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", appName] + action:@selector(terminate:) + keyEquivalent:@"q"]; + + /* + NSMenuItem* windowMenuItem = + [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""]; + [bar release]; + NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + [NSApp setWindowsMenu:windowMenu]; + [windowMenuItem setSubmenu:windowMenu]; + + [windowMenu addItemWithTitle:@"Minimize" + action:@selector(performMiniaturize:) + keyEquivalent:@"m"]; + [windowMenu addItemWithTitle:@"Zoom" + action:@selector(performZoom:) + keyEquivalent:@""]; + [windowMenu addItem:[NSMenuItem separatorItem]]; + [windowMenu addItemWithTitle:@"Bring All to Front" + action:@selector(arrangeInFront:) + keyEquivalent:@""]; + // TODO: Make this appear at the bottom of the menu (for consistency) + [windowMenu addItem:[NSMenuItem separatorItem]]; + [[windowMenu addItemWithTitle:@"Enter Full Screen" + action:@selector(toggleFullScreen:) + keyEquivalent:@"f"] + setKeyEquivalentModifierMask:NSControlKeyMask | NSCommandKeyMask]; + */ + + // Prior to Snow Leopard, we need to use this oddly-named semi-private API + // to get the application menu working properly. + //SEL setAppleMenuSelector = NSSelectorFromString(@"setAppleMenu:"); + //[NSApp performSelector:setAppleMenuSelector withObject:appMenu]; +} + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void mfb_close(void* win) @@ -83,7 +205,7 @@ void mfb_close(void* win) NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; if (window) - [window close]; + [window close]; [pool drain]; } @@ -92,13 +214,28 @@ void mfb_close(void* win) static int update_events() { - int state = 0; + NSEvent* event; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; - NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; - [NSApp sendEvent:event]; + + do + { + event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; + + if (event) { + [NSApp sendEvent:event]; + } + } + while (event); + [pool release]; - return state; + /* + int state = 0; + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; + [NSApp sendEvent:event]; + */ + + return 0; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -142,14 +279,14 @@ int mfb_update_with_buffer(void* window, void* buffer) static float transformY(float y) { - float b = CGDisplayBounds(CGMainDisplayID()).size.height; + float b = CGDisplayBounds(CGMainDisplayID()).size.height; float t = b - y; return t; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void mfb_set_position(void* window, int x, int y) +void mfb_set_position(void* window, int x, int y) { OSXWindow* win = (OSXWindow*)window; const NSRect contentRect = [[win contentView] frame]; @@ -160,7 +297,7 @@ void mfb_set_position(void* window, int x, int y) /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -int mfb_should_close(void* window) +int mfb_should_close(void* window) { OSXWindow* win = (OSXWindow*)window; return win->should_close; @@ -168,7 +305,7 @@ int mfb_should_close(void* window) /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -uint32_t mfb_get_screen_size() +uint32_t mfb_get_screen_size() { NSRect e = [[NSScreen mainScreen] frame]; uint32_t w = (uint32_t)e.size.width; @@ -193,4 +330,104 @@ void mfb_set_mouse_data(void* window, SharedData* shared_data) win->shared_data = shared_data; } +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +uint32_t mfb_is_active(void* window) +{ + OSXWindow* win = (OSXWindow*)window; + return win->is_active; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void mfb_remove_menu(void* window, const char* name) +{ + OSXWindow* win = (OSXWindow*)window; + + NSString* ns_name = [NSString stringWithUTF8String: name]; + NSMenu* main_menu = [NSApp mainMenu]; + + int len = win->menu_data->menu_count; + + for (int i = 0; i < len; ++i) + { + Menu* menu = &win->menu_data->menus[i]; + + if (strcmp(menu->name, name)) + continue; + + Menu* menu_end = &win->menu_data->menus[len - 1]; + + [main_menu removeItem:menu->menu_item]; + + // swap remove + menu->name = menu_end->name; + menu->menu = menu_end->menu; + menu->menu_item = menu_end->menu_item; + + win->menu_data->menu_count--; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +int mfb_active_menu(void* window) { + OSXWindow* win = (OSXWindow*)window; + int active_menu_id = win->active_menu_id; + win->active_menu_id = -1; + return active_menu_id; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void mfb_add_menu(void* window, const char* name, void* m) +{ + OSXWindow* win = (OSXWindow*)window; + + const char* n = strdup(name); + + NSString* ns_name = [NSString stringWithUTF8String: n]; + + NSMenu* main_menu = [NSApp mainMenu]; + + NSMenuItem* windowMenuItem = [main_menu addItemWithTitle:@"" action:NULL keyEquivalent:@""]; + NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:ns_name]; + [NSApp setWindowsMenu:windowMenu]; + [windowMenuItem setSubmenu:windowMenu]; + + MenuDesc* menu_desc = (MenuDesc*)m; + + [windowMenu setAutoenablesItems:NO]; + + build_submenu(windowMenu, menu_desc); + + Menu* menu = &win->menu_data->menus[win->menu_data->menu_count++]; + + menu->name = n; + menu->menu = windowMenu; + menu->menu_item = windowMenuItem; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void mfb_update_menu(void* window, const char* name, void* m) +{ + OSXWindow* win = (OSXWindow*)window; + + NSString* ns_name = [NSString stringWithUTF8String: name]; + NSMenu* main_menu = [NSApp mainMenu]; + + int len = win->menu_data->menu_count; + + for (int i = 0; i < len; ++i) + { + Menu* menu = &win->menu_data->menus[i]; + + if (!strcmp(menu->name, name)) { + [menu->menu removeAllItems]; + build_submenu(menu->menu, (MenuDesc*)m); + return; + } + } +} diff --git a/src/native/macosx/OSXWindow.h b/src/native/macosx/OSXWindow.h index ced6eec..5d0dd20 100644 --- a/src/native/macosx/OSXWindow.h +++ b/src/native/macosx/OSXWindow.h @@ -1,6 +1,42 @@ #import #include "shared_data.h" +#define MAX_MENUS 512 + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct Menu +{ + const char* name; + NSMenu* menu; + NSMenuItem* menu_item; +} Menu; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct MenuData +{ + Menu menus[MAX_MENUS]; + int menu_count; +} MenuData; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct MenuDesc { + char name[512]; + struct MenuDesc* sub_menu; + int menu_id; + int key; + int special_key; + int modifier; + int modifier_mac; + int enabled; +} MenuDesc; + +void build_submenu(NSMenu* menu, MenuDesc* desc); + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + @interface OSXWindow : NSWindow { NSView* childContentView; @@ -12,6 +48,11 @@ @public void* rust_data; @public SharedData* shared_data; @public bool should_close; + @public bool is_active; + @public int active_menu_id; + @public MenuData* menu_data; } + + @end diff --git a/src/native/macosx/OSXWindow.m b/src/native/macosx/OSXWindow.m index 7f047ca..c76bb3b 100644 --- a/src/native/macosx/OSXWindow.m +++ b/src/native/macosx/OSXWindow.m @@ -1,5 +1,6 @@ #import "OSXWindow.h" #import "OSXWindowFrameView.h" +#include @implementation OSXWindow @@ -20,7 +21,7 @@ NSSize childBoundsSize = [childContentView bounds].size; sizeDelta.width -= childBoundsSize.width; sizeDelta.height -= childBoundsSize.height; - + OSXWindowFrameView *frameView = [super contentView]; NSSize newFrameSize = [frameView bounds].size; newFrameSize.width += sizeDelta.width; @@ -37,7 +38,7 @@ // Left Shift key_callback(rust_data, 0x38, flags == 0x20102 ? 1 : 0); - + // RightShift key_callback(rust_data, 0x3c, flags == 0x20104 ? 1 : 0); @@ -58,21 +59,19 @@ // Right Super key_callback(rust_data, 0x36, flags == 0x100110 ? 1 : 0); + + [super flagsChanged:event]; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (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); } + + [super keyDown:event]; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -82,17 +81,26 @@ if (key_callback) { key_callback(rust_data, [event keyCode], 0); } + + [super keyDown:event]; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- (void)mainWindowChanged:(NSNotification *)aNotification +- (void)mainWindowChanged:(NSNotification *)note { + void* window = [note object]; + + if (window == self) { + self->is_active = true; + } else { + self->is_active = false; + } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- (void)windowWillClose:(NSNotification *)notification +- (void)windowWillClose:(NSNotification *)notification { should_close = true; } @@ -111,21 +119,33 @@ { if ([childContentView isEqualTo:aView]) return; - + NSRect bounds = [self frame]; bounds.origin = NSZeroPoint; OSXWindowFrameView* frameView = [super contentView]; if (!frameView) { + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(mainWindowChanged:) + name:NSWindowDidBecomeMainNotification + object:self]; + + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(mainWindowChanged:) + name:NSWindowDidResignMainNotification + object:self]; + frameView = [[[OSXWindowFrameView alloc] initWithFrame:bounds] autorelease]; - frameView->width = width; - frameView->height = height; - frameView->draw_buffer = draw_buffer; + frameView->width = width; + frameView->height = height; + frameView->draw_buffer = draw_buffer; frameView->scale = scale; [super setContentView:frameView]; } - + if (childContentView) [childContentView removeFromSuperview]; @@ -168,16 +188,149 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- (void)updateSize +- (void)updateSize { OSXWindowFrameView* frameView = [super contentView]; if (frameView) { - frameView->width = width; - frameView->height = height; - frameView->draw_buffer = draw_buffer; + frameView->width = width; + frameView->height = height; + frameView->draw_buffer = draw_buffer; frameView->scale = scale; } } +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)onMenuPress:(id)sender +{ + int menu_id = (int)((NSButton*)sender).tag; + self->active_menu_id = menu_id; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static CFStringRef create_string_for_key(CGKeyCode keyCode) +{ + TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); + CFDataRef layoutData = TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); + + if (!layoutData) + return 0; + + const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); + + UInt32 keysDown = 0; + UniChar chars[4]; + UniCharCount realLength; + + UCKeyTranslate(keyboardLayout, + keyCode, + kUCKeyActionDisplay, + 0, + LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, + &keysDown, + sizeof(chars) / sizeof(chars[0]), + &realLength, + chars); + CFRelease(currentKeyboard); + + return CFStringCreateWithCharacters(kCFAllocatorDefault, chars, 1); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static NSString* convert_key_code_to_string(int key) +{ + if (key < 128) + { + NSString* charName = (NSString*)create_string_for_key(key); + + if (charName) + return charName; + + return [NSString stringWithFormat:@"%c", (char)key]; + } + + return [NSString stringWithFormat:@"%C", (uint16_t)key]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +const uint32_t MENU_KEY_COMMAND = 1; +const uint32_t MENU_KEY_WIN = 2; +const uint32_t MENU_KEY_SHIFT= 4; +const uint32_t MENU_KEY_CTRL = 8; +const uint32_t MENU_KEY_ALT = 16; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void build_submenu(NSMenu* menu, MenuDesc* desc) +{ + [menu removeAllItems]; + + while (desc->menu_id != -2) + { + NSString* name = [NSString stringWithUTF8String: desc->name]; + + if (desc->menu_id == -1) + { + [menu addItem:[NSMenuItem separatorItem]]; + } + else if (desc->sub_menu) + { + NSMenuItem* newItem = [[NSMenuItem alloc] initWithTitle:name action:NULL keyEquivalent:@""]; + NSMenu* newMenu = [[NSMenu alloc] initWithTitle:name]; + [newItem setSubmenu:newMenu]; + + build_submenu(newMenu, desc->sub_menu); + + [newMenu release]; + [menu addItem:newItem]; + [newItem release]; + } + else + { + int mask = 0; + NSMenuItem* newItem = [[NSMenuItem alloc] initWithTitle:name action:@selector(onMenuPress:) keyEquivalent:@""]; + [newItem setTag:desc->menu_id]; + + if (desc->modifier_mac & MENU_KEY_COMMAND) { + mask |= NSCommandKeyMask; + } + if (desc->modifier_mac & MENU_KEY_SHIFT) { + mask |= NSShiftKeyMask; + } + if (desc->modifier_mac & MENU_KEY_CTRL) { + mask |= NSControlKeyMask; + } + if (desc->modifier_mac & MENU_KEY_ALT) { + mask |= NSAlternateKeyMask; + } + + if (desc->key != 0x7f) { + NSString* key = convert_key_code_to_string(desc->key); + + if (key) { + [newItem setKeyEquivalentModifierMask: mask]; + [newItem setKeyEquivalent:key]; + } + } + + if (desc->enabled) { + [newItem setEnabled:YES]; + } else { + [newItem setEnabled:NO]; + } + + [newItem setOnStateImage: newItem.offStateImage]; + [menu addItem:newItem]; + [newItem release]; + } + + desc++; + } +} + @end diff --git a/src/os/macos/mod.rs b/src/os/macos/mod.rs index 5847fa4..8b9ca35 100644 --- a/src/os/macos/mod.rs +++ b/src/os/macos/mod.rs @@ -2,8 +2,11 @@ use {MouseButton, MouseMode, Scale, Key, KeyRepeat, WindowOptions}; use key_handler::KeyHandler; +use error::Error; +use Result; use mouse_handler; use window_flags; +use menu::Menu; use libc::{c_void, c_char, c_uchar}; use std::ffi::{CString}; @@ -13,7 +16,7 @@ use std::os::raw; // Table taken from GLFW and slightly modified -static KEY_MAPPINGS: [Key; 128] = [ +static KEY_MAPPINGS: [Key; 128] = [ /* 00 */ Key::A, /* 01 */ Key::S, /* 02 */ Key::D, @@ -80,7 +83,7 @@ static KEY_MAPPINGS: [Key; 128] = [ /* 3f */ Key::Unknown, // Function /* 40 */ Key::Unknown, // F17 /* 41 */ Key::Unknown, // Decimal - /* 42 */ Key::Unknown, + /* 42 */ Key::Unknown, /* 43 */ Key::Unknown, // Multiply /* 44 */ Key::Unknown, /* 45 */ Key::Unknown, // Add @@ -89,7 +92,7 @@ static KEY_MAPPINGS: [Key; 128] = [ /* 48 */ Key::Unknown, // VolumeUp /* 49 */ Key::Unknown, // VolumeDown /* 4a */ Key::Unknown, // Mute - /* 4b */ Key::Unknown, + /* 4b */ Key::Unknown, /* 4c */ Key::Enter, /* 4d */ Key::Unknown, /* 4e */ Key::Unknown, // Subtrackt @@ -144,7 +147,23 @@ static KEY_MAPPINGS: [Key; 128] = [ /* 7f */ Key::Unknown, ]; + +const STRING_SIZE: usize = 512; + +#[repr(C)] +struct CMenu { + name: [i8; STRING_SIZE], + sub_menu: *mut raw::c_void, + id: raw::c_int, + key: raw::c_int, + special_key: raw::c_int, + modifier: raw::c_int, + mac_mod: raw::c_int, + enabled: raw::c_int, +} + #[link(name = "Cocoa", kind = "framework")] +#[link(name = "Carbon", kind = "framework")] extern { fn mfb_open(name: *const c_char, width: u32, height: u32, flags: u32, scale: i32) -> *mut c_void; fn mfb_close(window: *mut c_void); @@ -155,6 +174,11 @@ extern { fn mfb_set_mouse_data(window_handle: *mut c_void, shared_data: *mut SharedData); fn mfb_should_close(window: *mut c_void) -> i32; fn mfb_get_screen_size() -> u32; + fn mfb_is_active(window: *mut c_void) -> u32; + fn mfb_add_menu(window: *mut c_void, name: *const c_char, menu: *mut c_void); + fn mfb_remove_menu(window: *mut c_void, name: *const c_char); + fn mfb_update_menu(window: *mut c_void, name: *const c_char, menu: *mut c_void); + fn mfb_active_menu(window: *mut c_void) -> i32; } #[derive(Default)] @@ -171,7 +195,7 @@ pub struct SharedData { pub struct Window { window_handle: *mut c_void, - scale_factor: usize, + scale_factor: usize, pub shared_data: SharedData, key_handler: KeyHandler, pub has_set_data: bool, @@ -190,11 +214,11 @@ unsafe extern "C" fn key_callback(window: *mut c_void, key: i32, state: i32) { } impl Window { - pub fn new(name: &str, width: usize, height: usize, opts: WindowOptions) -> Result { + pub fn new(name: &str, width: usize, height: usize, opts: WindowOptions) -> Result { let n = match CString::new(name) { - Err(_) => { + Err(_) => { println!("Unable to convert {} to c_string", name); - return Err("Unable to set correct name"); + return Err(Error::WindowCreate("Unable to set correct name".to_owned())); } Ok(n) => n, }; @@ -204,13 +228,13 @@ impl Window { let handle = mfb_open(n.as_ptr(), width as u32, height as u32, window_flags::get_flags(opts), scale_factor as i32); if handle == ptr::null_mut() { - return Err("Unable to open Window"); + return Err(Error::WindowCreate("Unable to open Window".to_owned())); } - Ok(Window { + Ok(Window { window_handle: handle, scale_factor: scale_factor, - shared_data: SharedData { + shared_data: SharedData { width: width as u32 * scale_factor as u32, height: height as u32 * scale_factor as u32, .. SharedData::default() @@ -223,7 +247,7 @@ impl Window { #[inline] pub fn get_window_handle(&self) -> *mut raw::c_void { - self.window_handle as *mut raw::c_void + self.window_handle as *mut raw::c_void } #[inline] @@ -313,11 +337,62 @@ impl Window { self.key_handler.is_key_pressed(key, repeat) } + pub fn is_menu_pressed(&mut self) -> Option { + let menu_id = unsafe { mfb_active_menu(self.window_handle) }; + + if menu_id < 0 { + None + } else { + Some(menu_id as usize) + } + } + + pub fn add_menu(&mut self, name: &str, menu: &Vec) -> Result<()> { + let mut build_menu = Vec::>::new(); + + unsafe { + Self::recursive_convert(&mut build_menu, &Some(menu)); + let menu_len = build_menu.len(); + mfb_add_menu(self.window_handle, + CString::new(name).unwrap().as_ptr(), + build_menu[menu_len - 1].as_mut_ptr() as *mut c_void); + } + + Ok(()) + } + + pub fn update_menu(&mut self, name: &str, menu: &Vec) -> Result<()> { + let mut build_menu = Vec::>::new(); + + unsafe { + Self::recursive_convert(&mut build_menu, &Some(menu)); + let menu_len = build_menu.len(); + mfb_update_menu(self.window_handle, + CString::new(name).unwrap().as_ptr(), + build_menu[menu_len - 1].as_mut_ptr() as *mut c_void); + } + + Ok(()) + } + + pub fn remove_menu(&mut self, name: &str) -> Result<()> { + unsafe { + mfb_remove_menu(self.window_handle, CString::new(name).unwrap().as_ptr()); + } + + Ok(()) + } + #[inline] pub fn is_open(&self) -> bool { unsafe { mfb_should_close(self.window_handle) == 0 } } + #[inline] + pub fn is_active(&mut self) -> bool { + unsafe { mfb_is_active(self.window_handle) == 0 } + } + unsafe fn get_scale_factor(width: usize, height: usize, scale: Scale) -> i32 { let factor: i32 = match scale { Scale::X1 => 1, @@ -328,8 +403,8 @@ impl Window { 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 screen_x = (wh >> 16) as i32; + let screen_y = (wh & 0xffff) as i32; let mut scale = 1i32; @@ -350,6 +425,168 @@ impl Window { return factor; } + + unsafe fn map_key_to_menu_key(key: Key) -> i32 { + match key { + Key::A => 0x00, + Key::S => 0x01, + Key::D => 0x02, + Key::F => 0x03, + Key::H => 0x04, + Key::G => 0x05, + Key::Z => 0x06, + Key::X => 0x07, + Key::C => 0x08, + Key::V => 0x09, + Key::B => 0x0b, + Key::Q => 0x0c, + Key::W => 0x0d, + Key::E => 0x0e, + Key::R => 0x0f, + Key::Y => 0x10, + Key::T => 0x11, + Key::Key1 => 0x12, + Key::Key2 => 0x13, + Key::Key3 => 0x14, + Key::Key4 => 0x15, + Key::Key6 => 0x16, + Key::Key5 => 0x17, + Key::Equal => 0x18, + Key::Key9 => 0x19, + Key::Key7 => 0x1a, + Key::Minus => 0x1b, + Key::Key8 => 0x1c, + Key::Key0 => 0x1d, + Key::RightBracket => 0x1e, + Key::O => 0x1f, + Key::U => 0x20, + Key::LeftBracket => 0x21, + Key::I => 0x22, + Key::P => 0x23, + Key::Enter => 0x24, + Key::L => 0x25, + Key::J => 0x26, + Key::Apostrophe => 0x27, + Key::K => 0x28, + Key::Semicolon => 0x29, + Key::Backslash => 0x2a, + Key::Comma => 0x2b, + Key::Slash => 0x2c, + Key::N => 0x2d, + Key::M => 0x2e, + Key::Period => 0x2f, + //Key::Tab => 0x30, + Key::Space => 0x31, + //Key::Backspace => 0x33, + //Key::Escape => 0x35, + Key::RightSuper => 0x36, + Key::LeftSuper => 0x37, + Key::LeftShift => 0x38, + Key::CapsLock => 0x39, + Key::LeftAlt => 0x3a, + Key::LeftCtrl => 0x3b, + Key::RightShift => 0x3c, + Key::RightAlt => 0x3d, + Key::RightCtrl => 0x3e, + //Key::Equal => 0x51, + Key::NumPad0 => 0x52, + Key::NumPad1 => 0x53, + Key::NumPad2 => 0x54, + Key::NumPad3 => 0x55, + Key::NumPad4 => 0x56, + Key::NumPad5 => 0x57, + Key::NumPad6 => 0x58, + Key::NumPad7 => 0x59, + Key::NumPad8 => 0x5b, + Key::NumPad9 => 0x5c, + Key::F5 => 0x60, + Key::F6 => 0x61, + Key::F7 => 0x62, + Key::F3 => 0x63, + Key::F8 => 0x64, + Key::F9 => 0x65, + Key::F11 => 0x67, + Key::F14 => 0x6b, + Key::F10 => 0x6d, + Key::F12 => 0x6f, + Key::F15 => 0x71, + Key::Insert => 0x72, /* Really Help... */ + Key::Home => 0x73, + //Key::PageUp => 0x74, + Key::Delete => 0x75, + Key::F4 => 0x76, + Key::End => 0x77, + Key::F2 => 0x78, + //Key::PageDown => 0x79, + Key::F1 => 0x7a, + //Key::Left => 0x7b, + //Key::Right => 0x7c, + //Key::Down => 0x7d, + //Key::Up => 0x7e, + Key::Left => 0x2190, + Key::Up => 0x2191, + Key::Down => 0x2193, + Key::Right => 0x2192, + Key::Escape => 0x238b, + //Key::Enter => 0x000d, + Key::Backspace => 0x232b, + Key::Tab => 0x21e4, + Key::PageUp => 0x21de, + Key::PageDown => 0x21df, + _ => 0x7f, + } + } + + unsafe fn recursive_convert(menu_build_vec: &mut Vec>, in_menu: &Option<&Vec>) -> *mut raw::c_void { + if in_menu.is_none() { + return ptr::null_mut(); + } + + let mut menu_build = Vec::::new(); + let menu_vec = in_menu.as_ref().unwrap(); + + for m in menu_vec.iter() { + let key_map = Self::map_key_to_menu_key(m.key); + + let mut menu = CMenu { + name: mem::uninitialized(), + id: m.id as raw::c_int, + key: key_map as raw::c_int, + special_key: 0, + modifier: m.modifier as raw::c_int, + mac_mod: m.mac_mod as raw::c_int, + enabled: m.enabled as raw::c_int, + sub_menu : Self::recursive_convert(menu_build_vec, &m.sub_menu), + }; + + let name = CString::new(m.name).unwrap(); + let name_len = m.name.len(); + + ptr::copy_nonoverlapping(name.as_ptr(), + menu.name.as_mut_ptr() as *mut i8, + name_len); + menu.name[name_len] = 0; + + menu_build.push(menu); + } + + // end marker + + menu_build.push(CMenu { + name: [0; STRING_SIZE], + id: -2, + key: 0, + special_key: 0, + modifier: 0, + mac_mod: 0, + enabled: 0, + sub_menu : ptr::null_mut(), + }); + + let ptr = menu_build.as_mut_ptr() as *mut raw::c_void ; + menu_build_vec.push(menu_build); + ptr + } } impl Drop for Window { diff --git a/src/os/unix/mod.rs b/src/os/unix/mod.rs index 5c7710b..6b6bdcc 100644 --- a/src/os/unix/mod.rs +++ b/src/os/unix/mod.rs @@ -8,7 +8,10 @@ extern crate x11_dl; use {MouseMode, MouseButton, Scale, Key, KeyRepeat, WindowOptions}; use key_handler::KeyHandler; +use menu::Menu; use self::x11_dl::keysym::*; +use error::Error; +use Result; use libc::{c_void, c_char, c_uchar}; use std::ffi::{CString}; @@ -163,31 +166,31 @@ unsafe extern "C" fn key_callback(window: *mut c_void, key: i32, s: i32) { } impl Window { - pub fn new(name: &str, width: usize, height: usize, opts: WindowOptions) -> Result { + pub fn new(name: &str, width: usize, height: usize, opts: WindowOptions) -> Result { let n = match CString::new(name) { - Err(_) => { + Err(_) => { println!("Unable to convert {} to c_string", name); - return Err("Unable to set correct name"); + return Err(Error::WindowCreate("Unable to set correct name".to_owned())); } Ok(n) => n, }; unsafe { let scale = Self::get_scale_factor(width, height, opts.scale); - let handle = mfb_open(n.as_ptr(), - width as u32, - height as u32, - window_flags::get_flags(opts), + let handle = mfb_open(n.as_ptr(), + width as u32, + height as u32, + window_flags::get_flags(opts), scale); if handle == ptr::null_mut() { - return Err("Unable to open Window"); + return Err(Error::WindowCreate("Unable to open Window".to_owned())); } - Ok(Window { + Ok(Window { window_handle: handle, - shared_data: SharedData { - scale: scale as f32, + shared_data: SharedData { + scale: scale as f32, .. SharedData::default() }, key_handler: KeyHandler::new(), @@ -238,7 +241,7 @@ impl Window { } pub fn get_mouse_down(&self, button: MouseButton) -> bool { - match button { + match button { MouseButton::Left => self.shared_data.state[0] > 0, MouseButton::Middle => self.shared_data.state[1] > 0, MouseButton::Right => self.shared_data.state[2] > 0, @@ -246,7 +249,7 @@ impl Window { } pub fn get_scroll_wheel(&self) -> Option<(f32, f32)> { - if self.shared_data.scroll_x.abs() > 0.0 || + if self.shared_data.scroll_x.abs() > 0.0 || self.shared_data.scroll_y.abs() > 0.0 { Some((self.shared_data.scroll_x, self.shared_data.scroll_y)) } else { @@ -289,6 +292,12 @@ impl Window { unsafe { mfb_should_close(self.window_handle) == 1 } } + #[inline] + pub fn is_active(&mut self) -> bool { + // TODO: Proper implementation + true + } + unsafe fn get_scale_factor(width: usize, height: usize, scale: Scale) -> i32 { let factor: i32 = match scale { Scale::X1 => 1, @@ -296,8 +305,8 @@ impl Window { Scale::X4 => 4, Scale::FitScreen => { let wh: u32 = mfb_get_screen_size(); - let screen_x = (wh >> 16) as i32; - let screen_y = (wh & 0xffff) as i32; + let screen_x = (wh >> 16) as i32; + let screen_y = (wh & 0xffff) as i32; println!("{} - {}", screen_x, screen_y); @@ -329,6 +338,19 @@ impl Window { return factor; } + + pub fn add_menu(&mut self, _menu_name: &str, _menu: &Vec) -> Result<()> { + Err(Error::MenusNotSupported) + } + pub fn update_menu(&mut self, _menu_name: &str, _menu: &Vec) -> Result<()> { + Err(Error::MenusNotSupported) + } + pub fn remove_menu(&mut self, _menu_name: &str) -> Result<()> { + Err(Error::MenusNotSupported) + } + pub fn is_menu_pressed(&mut self) -> Option { + None + } } impl Drop for Window { diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs index 06b3d53..3030e9e 100644 --- a/src/os/windows/mod.rs +++ b/src/os/windows/mod.rs @@ -6,9 +6,15 @@ extern crate winapi; extern crate gdi32; extern crate time; +const INVALID_ACCEL: usize = 0xffffffff; + use {Scale, Key, KeyRepeat, MouseButton, MouseMode, WindowOptions}; use key_handler::KeyHandler; +use menu::Menu; +use error::Error; +use Result; +use menu::{MENU_KEY_WIN, MENU_KEY_SHIFT, MENU_KEY_CTRL, MENU_KEY_ALT}; use std::ptr; use std::os::windows::ffi::OsStrExt; @@ -17,11 +23,16 @@ use std::mem; use std::os::raw; use mouse_handler; -use self::winapi::windef::HWND; -use self::winapi::windef::HDC; -use self::winapi::winuser::WNDCLASSW; -use self::winapi::wingdi::BITMAPINFOHEADER; -use self::winapi::wingdi::RGBQUAD; +//use self::winapi::windef::HWND; +//use self::winapi::windef::HDC; +//use self::winapi::windef::HMENU; +//use self::winapi::wingdi::BITMAPINFOHEADER; +//use self::winapi::wingdi::RGBQUAD; +//use self::winapi::winuser::WNDCLASSW; +//use self::winapi::winuser::ACCEL; +//use self::winapi::basetsd::UINT_PTR; +use self::winapi::*; +//use self::winapi::winmindefs::BYTE; // Wrap this so we can have a proper numbef of bmiColors to write in #[repr(C)] @@ -226,6 +237,12 @@ unsafe extern "system" fn wnd_proc(window: winapi::HWND, return 0; } + winapi::winuser::WM_COMMAND => { + if lparam == 0 { + wnd.accel_key = (wparam & 0xffff) as usize; + } + } + winapi::winuser::WM_PAINT => { // if we have nothing to draw here we return the default function @@ -288,6 +305,12 @@ struct MouseData { pub scroll: f32, } +struct MenuStore { + name: String, + menu: HMENU, + accel_items: Vec, +} + pub struct Window { mouse: MouseData, dc: Option, @@ -297,7 +320,18 @@ pub struct Window { scale_factor: i32, width: i32, height: i32, + menus: Vec, key_handler: KeyHandler, + accel_table: HACCEL, + accel_key: usize, +} + +// TranslateAccelerator is currently missing in win-rs +#[cfg(target_family = "windows")] +#[link(name = "user32")] +#[allow(non_snake_case)] +extern "system" { + fn TranslateAcceleratorW(hWnd: HWND, accel: *const ACCEL, pmsg: *const MSG) -> INT; } impl Window { @@ -347,7 +381,7 @@ impl Window { let mut flags = 0; if opts.title { - flags |= winapi::WS_OVERLAPPEDWINDOW as u32; + flags |= winapi::WS_OVERLAPPEDWINDOW as u32; } if opts.resize { @@ -363,7 +397,7 @@ impl Window { } let handle = user32::CreateWindowExW(0, - class_name.as_ptr(), + class_name.as_ptr(), window_name.as_ptr(), flags, winapi::CW_USEDEFAULT, @@ -389,14 +423,14 @@ impl Window { width: usize, height: usize, opts: WindowOptions) - -> Result { + -> Result { unsafe { let scale_factor = Self::get_scale_factor(width, height, opts.scale); let handle = Self::open_window(name, width, height, opts, scale_factor); if handle.is_none() { - return Err("Unable to create Window"); + return Err(Error::WindowCreate("Unable to create Window".to_owned())); } let window = Window { @@ -409,6 +443,9 @@ impl Window { scale_factor: scale_factor, width: width as i32, height: height as i32, + menus: Vec::new(), + accel_table: ptr::null_mut(), + accel_key: INVALID_ACCEL, }; Ok(window) @@ -423,7 +460,7 @@ impl Window { #[inline] pub fn set_position(&mut self, x: isize, y: isize) { unsafe { - user32::SetWindowPos(self.window.unwrap(), ptr::null_mut(), x as i32, y as i32, + user32::SetWindowPos(self.window.unwrap(), ptr::null_mut(), x as i32, y as i32, 0, 0, winapi::SWP_SHOWWINDOW | winapi::SWP_NOSIZE); } } @@ -491,7 +528,6 @@ impl Window { fn generic_update(&mut self, window: HWND) { unsafe { - let mut point: winapi::POINT = mem::uninitialized(); user32::GetCursorPos(&mut point); user32::ScreenToClient(window, &mut point); @@ -506,13 +542,21 @@ impl Window { } } - fn message_loop(&mut self, window: HWND) { + fn message_loop(&self, window: HWND) { unsafe { let mut msg = mem::uninitialized(); while user32::PeekMessageW(&mut msg, window, 0, 0, winapi::winuser::PM_REMOVE) != 0 { - user32::TranslateMessage(&mut msg); - user32::DispatchMessageW(&mut msg); + // Make this code a bit nicer + if self.accel_table == ptr::null_mut() { + user32::TranslateMessage(&mut msg); + user32::DispatchMessageW(&mut msg); + } else { + if TranslateAcceleratorW(msg.hwnd, mem::transmute(self.accel_table), &mut msg) == 0 { + user32::TranslateMessage(&mut msg); + user32::DispatchMessageW(&mut msg); + } + } } } } @@ -537,6 +581,12 @@ impl Window { Self::message_loop(self, window); } + #[inline] + pub fn is_active(&mut self) -> bool { + // TODO: Proper implementation + true + } + unsafe fn get_scale_factor(width: usize, height: usize, scale: Scale) -> i32 { let factor: i32 = match scale { Scale::X1 => 1, @@ -568,6 +618,349 @@ impl Window { return factor; } + + fn map_key_to_vk_accel(key: Key) -> (raw::c_int, &'static str) { + match key { + Key::Key0 => (0x30, "0"), + Key::Key1 => (0x31, "1"), + Key::Key2 => (0x32, "2"), + Key::Key3 => (0x33, "3"), + Key::Key4 => (0x34, "4"), + Key::Key5 => (0x35, "5"), + Key::Key6 => (0x36, "6"), + Key::Key7 => (0x37, "7"), + Key::Key8 => (0x38, "8"), + Key::Key9 => (0x39, "9"), + + Key::A => (0x41, "a"), + Key::B => (0x42, "b"), + Key::C => (0x43, "c"), + Key::D => (0x44, "d"), + Key::E => (0x45, "e"), + Key::F => (0x46, "f"), + Key::G => (0x47, "g"), + Key::H => (0x48, "h"), + Key::I => (0x49, "i"), + Key::J => (0x4a, "j"), + Key::K => (0x4b, "k"), + Key::L => (0x4c, "l"), + Key::M => (0x4d, "m"), + Key::N => (0x4e, "n"), + Key::O => (0x4f, "o"), + Key::P => (0x50, "p"), + Key::Q => (0x51, "q"), + Key::R => (0x52, "r"), + Key::S => (0x53, "s"), + Key::T => (0x54, "t"), + Key::U => (0x55, "u"), + Key::V => (0x56, "v"), + Key::W => (0x57, "w"), + Key::X => (0x58, "x"), + Key::Y => (0x59, "y"), + Key::Z => (0x5a, "z"), + + Key::F1 => (winapi::winuser::VK_F1, "F1"), + Key::F2 => (winapi::winuser::VK_F2, "F2"), + Key::F3 => (winapi::winuser::VK_F3, "F3"), + Key::F4 => (winapi::winuser::VK_F4, "F4"), + Key::F5 => (winapi::winuser::VK_F5, "F5"), + Key::F6 => (winapi::winuser::VK_F6, "F6"), + Key::F7 => (winapi::winuser::VK_F7, "F7"), + Key::F8 => (winapi::winuser::VK_F8, "F8"), + Key::F9 => (winapi::winuser::VK_F9, "F9"), + Key::F10 => (winapi::winuser::VK_F10, "F10"), + Key::F11 => (winapi::winuser::VK_F11, "F11"), + Key::F12 => (winapi::winuser::VK_F12, "F12"), + Key::F13 => (winapi::winuser::VK_F13, "F14"), + Key::F14 => (winapi::winuser::VK_F14, "F14"), + Key::F15 => (winapi::winuser::VK_F15, "F15"), + + Key::Down => (winapi::winuser::VK_DOWN, "Down"), + Key::Left => (winapi::winuser::VK_LEFT, "Left"), + Key::Right => (winapi::winuser::VK_RIGHT, "Right"), + Key::Up => (winapi::winuser::VK_UP, "Up"), + + Key::Backslash => (winapi::winuser::VK_OEM_102, "Backslash"), + Key::Comma => (winapi::winuser::VK_OEM_COMMA, ","), + Key::Minus => (winapi::winuser::VK_OEM_MINUS, "-"), + Key::Period => (winapi::winuser::VK_OEM_PERIOD, "."), + + Key::Backspace => (winapi::winuser::VK_BACK, "Back"), + Key::Delete => (winapi::winuser::VK_DELETE, "Delete"), + Key::End => (winapi::winuser::VK_END, "End"), + Key::Enter => (winapi::winuser::VK_RETURN, "Enter"), + + Key::Escape => (winapi::winuser::VK_ESCAPE, "Esc"), + + Key::Home => (winapi::winuser::VK_HOME, "Home"), + Key::Insert => (winapi::winuser::VK_INSERT, "Insert"), + Key::Menu => (winapi::winuser::VK_MENU, "Menu"), + + Key::PageDown => (winapi::winuser::VK_NEXT, "PageDown"), + Key::PageUp => (winapi::winuser::VK_PRIOR, "PageUp"), + + Key::Pause => (winapi::winuser::VK_PAUSE, "Pause"), + Key::Space => (winapi::winuser::VK_SPACE, "Space"), + Key::Tab => (winapi::winuser::VK_TAB, "Tab"), + Key::NumLock => (winapi::winuser::VK_NUMLOCK, "NumLock"), + Key::CapsLock => (winapi::winuser::VK_CAPITAL, "CapsLock"), + Key::ScrollLock => (winapi::winuser::VK_SCROLL, "Scroll"), + + Key::LeftShift => (winapi::winuser::VK_LSHIFT, "LeftShift"), + Key::RightShift => (winapi::winuser::VK_RSHIFT, "RightShift"), + Key::LeftCtrl => (winapi::winuser::VK_CONTROL, "Ctrl"), + Key::RightCtrl => (winapi::winuser::VK_CONTROL, "Ctrl"), + + Key::NumPad0 => (winapi::winuser::VK_NUMPAD0, "NumPad0"), + Key::NumPad1 => (winapi::winuser::VK_NUMPAD1, "NumPad1"), + Key::NumPad2 => (winapi::winuser::VK_NUMPAD2, "NumPad2"), + Key::NumPad3 => (winapi::winuser::VK_NUMPAD3, "NumPad3"), + Key::NumPad4 => (winapi::winuser::VK_NUMPAD4, "NumPad4"), + Key::NumPad5 => (winapi::winuser::VK_NUMPAD5, "NumPad5"), + Key::NumPad6 => (winapi::winuser::VK_NUMPAD6, "NumPad6"), + Key::NumPad7 => (winapi::winuser::VK_NUMPAD7, "NumPad7"), + Key::NumPad8 => (winapi::winuser::VK_NUMPAD8, "NumPad8"), + Key::NumPad9 => (winapi::winuser::VK_NUMPAD9, "NumPad9"), + + Key::LeftAlt => (winapi::winuser::VK_MENU, "Alt"), + Key::RightAlt => (winapi::winuser::VK_MENU, "Alt"), + + Key::LeftSuper => (winapi::winuser::VK_LWIN, "LeftWin"), + Key::RightSuper => (winapi::winuser::VK_RWIN, "RightWin"), + + _ => (0, "Unsupported"), + } + } + + + // + // When attaching a menu to the window we need to resize it so + // the current client size is preserved and still show all pixels + // + unsafe fn adjust_window_size_for_menu(handle: HWND) { + let mut rect: winapi::RECT = mem::uninitialized(); + + let menu_height = user32::GetSystemMetrics(winapi::winuser::SM_CYMENU); + + user32::GetWindowRect(handle, &mut rect); + user32::MoveWindow(handle, + rect.left, + rect.top, + rect.right - rect.left, + (rect.bottom - rect.top) + menu_height, + 1); + } + + fn format_name(menu_item: &Menu, key_name: &'static str) -> String { + let mut name = menu_item.name.to_owned(); + + name.push_str("\t"); + + if (menu_item.modifier & MENU_KEY_WIN) == MENU_KEY_WIN { + name.push_str("Win-"); + } + + if (menu_item.modifier & MENU_KEY_SHIFT) == MENU_KEY_SHIFT { + name.push_str("Shift-"); + } + + if (menu_item.modifier & MENU_KEY_CTRL) == MENU_KEY_CTRL { + name.push_str("Ctrl-"); + } + + if (menu_item.modifier & MENU_KEY_ALT) == MENU_KEY_ALT { + name.push_str("Alt-"); + } + + name.push_str(key_name); + + name + } + + fn is_key_virtual_range(key: raw::c_int) -> u32 { + if (key >= 0x30 && key <= 0x30) || + (key >= 0x41 && key <= 0x5a) { + 1 + } else { + 0 + } + } + + fn get_virt_key(menu_item: &Menu, key: raw::c_int) -> u32 { + let mut virt = Self::is_key_virtual_range(key); + + if (menu_item.modifier & MENU_KEY_ALT) == MENU_KEY_ALT { + virt |= 0x10; + } + + if (menu_item.modifier & MENU_KEY_CTRL) == MENU_KEY_CTRL { + virt |= 0x8; + } + + if (menu_item.modifier & MENU_KEY_SHIFT) == MENU_KEY_SHIFT { + virt |= 0x4; + } + + virt + } + + fn add_accel(accel_table: &mut Vec, menu_item: &Menu) { + let vk_accel = Self::map_key_to_vk_accel(menu_item.key); + let virt = Self::get_virt_key(menu_item, vk_accel.0); + let accel = winuser::ACCEL { + fVirt: virt as BYTE, + cmd: menu_item.id as WORD, + key: vk_accel.0 as WORD }; + + accel_table.push(accel); + } + + unsafe fn add_menu_item(&mut self, parent_menu: HMENU, menu_item: &Menu) { + let item_name = to_wstring(menu_item.name); + let vk_accel = Self::map_key_to_vk_accel(menu_item.key); + + match vk_accel.0 { + 0 => { + user32::AppendMenuW(parent_menu, 0x10, menu_item.id as UINT_PTR, item_name.as_ptr()); + }, + _ => { + let menu_name = Self::format_name(menu_item, vk_accel.1); + let w_name = to_wstring(&menu_name); + user32::AppendMenuW(parent_menu, 0x10, menu_item.id as UINT_PTR, w_name.as_ptr()); + } + } + } + + unsafe fn set_accel_table(&mut self) { + let mut temp_accel_table = Vec::::new(); + + for menu in self.menus.iter() { + for item in menu.accel_items.iter() { + println!("virt {} - cmd {} - key {}", item.fVirt, item.cmd, item.key); + temp_accel_table.push(item.clone()); + } + } + + if self.accel_table != ptr::null_mut() { + user32::DestroyAcceleratorTable(self.accel_table); + } + + self.accel_table = user32::CreateAcceleratorTableW(temp_accel_table.as_mut_ptr(), + temp_accel_table.len() as i32); + } + + + unsafe fn recursive_add_menu(&mut self, parent_menu: HMENU, name: &str, menu: &Vec) -> HMENU { + let menu_name = to_wstring(name); + + let popup_menu = user32::CreatePopupMenu(); + + user32::AppendMenuW(parent_menu, 0x10, popup_menu as UINT_PTR, menu_name.as_ptr()); + + for m in menu.iter() { + if let Some(ref sub_menu) = m.sub_menu { + Self::recursive_add_menu(self, popup_menu, m.name, sub_menu); + } else { + if m.id == 0xffffffff { + user32::AppendMenuW(popup_menu, 0x800, 0, ptr::null()); // separator + } else { + Self::add_menu_item(self, popup_menu, m); + } + } + } + + popup_menu + } + + pub fn menu_exists(&mut self, menu_name: &str) -> bool { + for menu in self.menus.iter() { + if menu.name == menu_name { + return true; + } + } + + false + } + + fn clone_menu(accel_dest: &mut Vec, menu: &Vec) { + for m in menu.iter() { + if let Some(ref sub_menu) = m.sub_menu { + Self::clone_menu(accel_dest, sub_menu); + } + + if m.key != Key::Unknown { + Self::add_accel(accel_dest, m); + } + } + } + + unsafe fn add_menu_store(&mut self, parent_menu: HMENU, menu_name: &str, menu: &Vec) { + let mut items = Vec::::new(); + let menu_handle = Self::recursive_add_menu(self, parent_menu, menu_name, menu); + + Self::clone_menu(&mut items, menu); + + self.menus.push(MenuStore { + name: menu_name.to_owned(), + menu: menu_handle, + accel_items: items + }); + } + + pub fn add_menu(&mut self, menu_name: &str, menu: &Vec) -> Result<()> { + if Self::menu_exists(self, menu_name) { + return Err(Error::MenuExists(menu_name.to_owned())); + } + + unsafe { + let window = self.window.unwrap(); + let mut main_menu = user32::GetMenu(window); + + if main_menu == ptr::null_mut() { + main_menu = user32::CreateMenu(); + user32::SetMenu(window, main_menu); + Self::adjust_window_size_for_menu(window); + } + + Self::add_menu_store(self, main_menu, menu_name, menu); + Self::set_accel_table(self); + + user32::DrawMenuBar(window); + } + + Ok(()) + } + + pub fn update_menu(&mut self, menu_name: &str, menu: &Vec) -> Result<()> { + try!(Self::remove_menu(self, menu_name)); + Self::add_menu(self, menu_name, menu) + } + + pub fn remove_menu(&mut self, menu_name: &str) -> Result<()> { + for i in 0..self.menus.len() { + if self.menus[i].name == menu_name { + unsafe { + user32::DestroyMenu(self.menus[i].menu); + user32::DrawMenuBar(self.window.unwrap()); + self.menus.swap_remove(i); + break; + } + } + } + + // TODO: Proper return here + Ok(()) + } + + pub fn is_menu_pressed(&mut self) -> Option { + if self.accel_key == INVALID_ACCEL { + None + } else { + let t = self.accel_key; + self.accel_key = INVALID_ACCEL; + Some(t) + } + } } impl Drop for Window {