Merge work from menu branch

This commit is contained in:
Daniel Collin 2016-03-04 17:36:28 +01:00
parent 73813b63f4
commit 5d9ca9e585
15 changed files with 1622 additions and 232 deletions

View file

@ -2,6 +2,13 @@
This project follows semantic versioning. 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) ### v0.4.0 (2016-01-31)
This release breaks some of the API by changing names and parameters to some functions. This release breaks some of the API by changing names and parameters to some functions.

View file

@ -1,6 +1,6 @@
[package] [package]
name = "minifb" name = "minifb"
version = "0.4.0" version = "0.5.0"
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
authors = ["Daniel Collin <daniel@collin.com>"] authors = ["Daniel Collin <daniel@collin.com>"]
description = "Cross-platform window setup with optional bitmap rendering" description = "Cross-platform window setup with optional bitmap rendering"

View file

@ -14,7 +14,7 @@ Usage
```toml ```toml
# Cargo.toml # Cargo.toml
[dependencies] [dependencies]
minifb = "0.4.0" minifb = "0.5.0"
``` ```
Example Example

126
examples/menu.rs Normal file
View file

@ -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<u32> = 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);
}
}

48
src/error.rs Normal file
View file

@ -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)
}
}
}
}

128
src/key.rs Normal file
View file

@ -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,
}

View file

@ -42,133 +42,6 @@ pub enum MouseButton
Right, 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 /// Key is used by the get key functions to check if some keys on the keyboard has been pressed
#[derive(PartialEq, Clone, Copy)] #[derive(PartialEq, Clone, Copy)]
@ -186,10 +59,24 @@ extern crate libc;
use std::os::raw; use std::os::raw;
#[doc(hidden)] #[doc(hidden)]
mod error;
pub use self::error::Error;
pub type Result<T> = std::result::Result<T, Error>;
pub mod key;
pub use key::Key as Key;
pub mod os; pub mod os;
mod mouse_handler; mod mouse_handler;
mod key_handler; mod key_handler;
mod window_flags; 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")] #[cfg(target_os = "macos")]
use self::os::macos as imp; use self::os::macos as imp;
@ -277,7 +164,7 @@ impl Window {
/// } /// }
///}; ///};
/// ``` /// ```
pub fn new(name: &str, width: usize, height: usize, opts: WindowOptions) -> Result<Window, &str> { pub fn new(name: &str, width: usize, height: usize, opts: WindowOptions) -> Result<Window> {
imp::Window::new(name, width, height, opts).map(Window) imp::Window::new(name, width, height, opts).map(Window)
} }
@ -522,6 +409,58 @@ impl Window {
pub fn set_key_repeat_rate(&mut self, rate: f32) { pub fn set_key_repeat_rate(&mut self, rate: f32) {
self.0.set_key_repeat_rate(rate) 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<Menu>) -> 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<Menu>) -> 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<usize> {
self.0.is_menu_pressed()
}
} }
// Impl for WindowOptions // Impl for WindowOptions

59
src/menu.rs Normal file
View file

@ -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<Menu<'a>>>,
}
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,
}
}
}

View file

@ -11,6 +11,8 @@ const uint32_t WINDOW_BORDERLESS = 1 << 1;
const uint32_t WINDOW_RESIZE = 1 << 2; const uint32_t WINDOW_RESIZE = 1 << 2;
const uint32_t WINDOW_TITLE = 1 << 3; const uint32_t WINDOW_TITLE = 1 << 3;
static void create_standard_menu();
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef __clang__ #ifdef __clang__
@ -21,11 +23,14 @@ const uint32_t WINDOW_TITLE = 1 << 3;
void* mfb_open(const char* name, int width, int height, uint32_t flags, int scale) 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) { if (!s_init) {
[NSApplication sharedApplication]; [NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
create_standard_menu();
s_init = true; s_init = true;
} }
@ -57,6 +62,10 @@ void* mfb_open(const char* name, int width, int height, uint32_t flags, int scal
window->scale = scale; window->scale = scale;
window->key_callback = 0; window->key_callback = 0;
window->shared_data = 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]; [window updateSize];
@ -69,11 +78,124 @@ void* mfb_open(const char* name, int width, int height, uint32_t flags, int scal
[NSApp activateIgnoringOtherApps:YES]; [NSApp activateIgnoringOtherApps:YES];
[pool drain]; if (!prev_init)
[NSApp finishLaunching];
//[pool drain];
return window; 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) void mfb_close(void* win)
@ -92,13 +214,28 @@ void mfb_close(void* win)
static int update_events() static int update_events()
{ {
int state = 0; NSEvent* event;
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; 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]; [pool release];
return state; /*
int state = 0;
NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES];
[NSApp sendEvent:event];
*/
return 0;
} }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -193,4 +330,104 @@ void mfb_set_mouse_data(void* window, SharedData* shared_data)
win->shared_data = 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;
}
}
}

View file

@ -1,6 +1,42 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#include "shared_data.h" #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 @interface OSXWindow : NSWindow
{ {
NSView* childContentView; NSView* childContentView;
@ -12,6 +48,11 @@
@public void* rust_data; @public void* rust_data;
@public SharedData* shared_data; @public SharedData* shared_data;
@public bool should_close; @public bool should_close;
@public bool is_active;
@public int active_menu_id;
@public MenuData* menu_data;
} }
@end @end

View file

@ -1,5 +1,6 @@
#import "OSXWindow.h" #import "OSXWindow.h"
#import "OSXWindowFrameView.h" #import "OSXWindowFrameView.h"
#include <Carbon/Carbon.h>
@implementation OSXWindow @implementation OSXWindow
@ -58,21 +59,19 @@
// Right Super // Right Super
key_callback(rust_data, 0x36, flags == 0x100110 ? 1 : 0); key_callback(rust_data, 0x36, flags == 0x100110 ? 1 : 0);
[super flagsChanged:event];
} }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)keyDown:(NSEvent *)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) { if (key_callback) {
key_callback(rust_data, [event keyCode], 1); key_callback(rust_data, [event keyCode], 1);
} }
[super keyDown:event];
} }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -82,12 +81,21 @@
if (key_callback) { if (key_callback) {
key_callback(rust_data, [event keyCode], 0); 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;
}
} }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -118,6 +126,18 @@
OSXWindowFrameView* frameView = [super contentView]; OSXWindowFrameView* frameView = [super contentView];
if (!frameView) 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 = [[[OSXWindowFrameView alloc] initWithFrame:bounds] autorelease];
frameView->width = width; frameView->width = width;
frameView->height = height; frameView->height = height;
@ -180,4 +200,137 @@
} }
} }
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (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 @end

View file

@ -2,8 +2,11 @@
use {MouseButton, MouseMode, Scale, Key, KeyRepeat, WindowOptions}; use {MouseButton, MouseMode, Scale, Key, KeyRepeat, WindowOptions};
use key_handler::KeyHandler; use key_handler::KeyHandler;
use error::Error;
use Result;
use mouse_handler; use mouse_handler;
use window_flags; use window_flags;
use menu::Menu;
use libc::{c_void, c_char, c_uchar}; use libc::{c_void, c_char, c_uchar};
use std::ffi::{CString}; use std::ffi::{CString};
@ -144,7 +147,23 @@ static KEY_MAPPINGS: [Key; 128] = [
/* 7f */ Key::Unknown, /* 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 = "Cocoa", kind = "framework")]
#[link(name = "Carbon", kind = "framework")]
extern { extern {
fn mfb_open(name: *const c_char, width: u32, height: u32, flags: u32, scale: i32) -> *mut c_void; 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); 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_set_mouse_data(window_handle: *mut c_void, shared_data: *mut SharedData);
fn mfb_should_close(window: *mut c_void) -> i32; fn mfb_should_close(window: *mut c_void) -> i32;
fn mfb_get_screen_size() -> u32; 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)] #[derive(Default)]
@ -190,11 +214,11 @@ unsafe extern "C" fn key_callback(window: *mut c_void, key: i32, state: i32) {
} }
impl Window { impl Window {
pub fn new(name: &str, width: usize, height: usize, opts: WindowOptions) -> Result<Window, &str> { pub fn new(name: &str, width: usize, height: usize, opts: WindowOptions) -> Result<Window> {
let n = match CString::new(name) { let n = match CString::new(name) {
Err(_) => { Err(_) => {
println!("Unable to convert {} to c_string", name); 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, Ok(n) => n,
}; };
@ -204,7 +228,7 @@ impl Window {
let handle = mfb_open(n.as_ptr(), width as u32, height as u32, window_flags::get_flags(opts), scale_factor as i32); 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() { 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 {
@ -313,11 +337,62 @@ impl Window {
self.key_handler.is_key_pressed(key, repeat) self.key_handler.is_key_pressed(key, repeat)
} }
pub fn is_menu_pressed(&mut self) -> Option<usize> {
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<Menu>) -> Result<()> {
let mut build_menu = Vec::<Vec<CMenu>>::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<Menu>) -> Result<()> {
let mut build_menu = Vec::<Vec<CMenu>>::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] #[inline]
pub fn is_open(&self) -> bool { pub fn is_open(&self) -> bool {
unsafe { mfb_should_close(self.window_handle) == 0 } 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 { unsafe fn get_scale_factor(width: usize, height: usize, scale: Scale) -> i32 {
let factor: i32 = match scale { let factor: i32 = match scale {
Scale::X1 => 1, Scale::X1 => 1,
@ -350,6 +425,168 @@ impl Window {
return factor; 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<Vec<CMenu>>, in_menu: &Option<&Vec<Menu>>) -> *mut raw::c_void {
if in_menu.is_none() {
return ptr::null_mut();
}
let mut menu_build = Vec::<CMenu>::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 { impl Drop for Window {

View file

@ -8,7 +8,10 @@ extern crate x11_dl;
use {MouseMode, MouseButton, Scale, Key, KeyRepeat, WindowOptions}; use {MouseMode, MouseButton, Scale, Key, KeyRepeat, WindowOptions};
use key_handler::KeyHandler; use key_handler::KeyHandler;
use menu::Menu;
use self::x11_dl::keysym::*; use self::x11_dl::keysym::*;
use error::Error;
use Result;
use libc::{c_void, c_char, c_uchar}; use libc::{c_void, c_char, c_uchar};
use std::ffi::{CString}; use std::ffi::{CString};
@ -163,11 +166,11 @@ unsafe extern "C" fn key_callback(window: *mut c_void, key: i32, s: i32) {
} }
impl Window { impl Window {
pub fn new(name: &str, width: usize, height: usize, opts: WindowOptions) -> Result<Window, &str> { pub fn new(name: &str, width: usize, height: usize, opts: WindowOptions) -> Result<Window> {
let n = match CString::new(name) { let n = match CString::new(name) {
Err(_) => { Err(_) => {
println!("Unable to convert {} to c_string", name); 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, Ok(n) => n,
}; };
@ -181,7 +184,7 @@ impl Window {
scale); scale);
if handle == ptr::null_mut() { 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 {
@ -289,6 +292,12 @@ impl Window {
unsafe { mfb_should_close(self.window_handle) == 1 } 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 { unsafe fn get_scale_factor(width: usize, height: usize, scale: Scale) -> i32 {
let factor: i32 = match scale { let factor: i32 = match scale {
Scale::X1 => 1, Scale::X1 => 1,
@ -329,6 +338,19 @@ impl Window {
return factor; return factor;
} }
pub fn add_menu(&mut self, _menu_name: &str, _menu: &Vec<Menu>) -> Result<()> {
Err(Error::MenusNotSupported)
}
pub fn update_menu(&mut self, _menu_name: &str, _menu: &Vec<Menu>) -> 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<usize> {
None
}
} }
impl Drop for Window { impl Drop for Window {

View file

@ -6,9 +6,15 @@ extern crate winapi;
extern crate gdi32; extern crate gdi32;
extern crate time; extern crate time;
const INVALID_ACCEL: usize = 0xffffffff;
use {Scale, Key, KeyRepeat, MouseButton, MouseMode, WindowOptions}; use {Scale, Key, KeyRepeat, MouseButton, MouseMode, WindowOptions};
use key_handler::KeyHandler; 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::ptr;
use std::os::windows::ffi::OsStrExt; use std::os::windows::ffi::OsStrExt;
@ -17,11 +23,16 @@ use std::mem;
use std::os::raw; use std::os::raw;
use mouse_handler; use mouse_handler;
use self::winapi::windef::HWND; //use self::winapi::windef::HWND;
use self::winapi::windef::HDC; //use self::winapi::windef::HDC;
use self::winapi::winuser::WNDCLASSW; //use self::winapi::windef::HMENU;
use self::winapi::wingdi::BITMAPINFOHEADER; //use self::winapi::wingdi::BITMAPINFOHEADER;
use self::winapi::wingdi::RGBQUAD; //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 // Wrap this so we can have a proper numbef of bmiColors to write in
#[repr(C)] #[repr(C)]
@ -226,6 +237,12 @@ unsafe extern "system" fn wnd_proc(window: winapi::HWND,
return 0; return 0;
} }
winapi::winuser::WM_COMMAND => {
if lparam == 0 {
wnd.accel_key = (wparam & 0xffff) as usize;
}
}
winapi::winuser::WM_PAINT => { winapi::winuser::WM_PAINT => {
// if we have nothing to draw here we return the default function // if we have nothing to draw here we return the default function
@ -288,6 +305,12 @@ struct MouseData {
pub scroll: f32, pub scroll: f32,
} }
struct MenuStore {
name: String,
menu: HMENU,
accel_items: Vec<ACCEL>,
}
pub struct Window { pub struct Window {
mouse: MouseData, mouse: MouseData,
dc: Option<HDC>, dc: Option<HDC>,
@ -297,7 +320,18 @@ pub struct Window {
scale_factor: i32, scale_factor: i32,
width: i32, width: i32,
height: i32, height: i32,
menus: Vec<MenuStore>,
key_handler: KeyHandler, 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 { impl Window {
@ -389,14 +423,14 @@ impl Window {
width: usize, width: usize,
height: usize, height: usize,
opts: WindowOptions) opts: WindowOptions)
-> Result<Window, &str> { -> Result<Window> {
unsafe { unsafe {
let scale_factor = Self::get_scale_factor(width, height, opts.scale); let scale_factor = Self::get_scale_factor(width, height, opts.scale);
let handle = Self::open_window(name, width, height, opts, scale_factor); let handle = Self::open_window(name, width, height, opts, scale_factor);
if handle.is_none() { if handle.is_none() {
return Err("Unable to create Window"); return Err(Error::WindowCreate("Unable to create Window".to_owned()));
} }
let window = Window { let window = Window {
@ -409,6 +443,9 @@ impl Window {
scale_factor: scale_factor, scale_factor: scale_factor,
width: width as i32, width: width as i32,
height: height as i32, height: height as i32,
menus: Vec::new(),
accel_table: ptr::null_mut(),
accel_key: INVALID_ACCEL,
}; };
Ok(window) Ok(window)
@ -491,7 +528,6 @@ impl Window {
fn generic_update(&mut self, window: HWND) { fn generic_update(&mut self, window: HWND) {
unsafe { unsafe {
let mut point: winapi::POINT = mem::uninitialized(); let mut point: winapi::POINT = mem::uninitialized();
user32::GetCursorPos(&mut point); user32::GetCursorPos(&mut point);
user32::ScreenToClient(window, &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 { unsafe {
let mut msg = mem::uninitialized(); let mut msg = mem::uninitialized();
while user32::PeekMessageW(&mut msg, window, 0, 0, winapi::winuser::PM_REMOVE) != 0 { while user32::PeekMessageW(&mut msg, window, 0, 0, winapi::winuser::PM_REMOVE) != 0 {
user32::TranslateMessage(&mut msg); // Make this code a bit nicer
user32::DispatchMessageW(&mut msg); 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); 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 { unsafe fn get_scale_factor(width: usize, height: usize, scale: Scale) -> i32 {
let factor: i32 = match scale { let factor: i32 = match scale {
Scale::X1 => 1, Scale::X1 => 1,
@ -568,6 +618,349 @@ impl Window {
return factor; 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<ACCEL>, 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::<ACCEL>::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<Menu>) -> 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<ACCEL>, menu: &Vec<Menu>) {
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<Menu>) {
let mut items = Vec::<ACCEL>::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<Menu>) -> 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<Menu>) -> 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<usize> {
if self.accel_key == INVALID_ACCEL {
None
} else {
let t = self.accel_key;
self.accel_key = INVALID_ACCEL;
Some(t)
}
}
} }
impl Drop for Window { impl Drop for Window {