native-windows-gui2 0.1.1

A rust library to develop native GUI applications on the desktop for Microsoft Windows. Native-windows-gui wraps the native win32 window controls in a rustic API
Documentation
/*!
Native Windows GUI menu base.
*/
use super::base_helper::{CUSTOM_ID_BEGIN, to_utf16};
use crate::NwgError;
use crate::controls::ControlHandle;
use std::sync::atomic::{AtomicU32, Ordering};
use std::{mem, ptr};
use winapi::shared::basetsd::UINT_PTR;
use winapi::shared::minwindef::UINT;
use winapi::shared::windef::{HMENU, HWND};

static MENU_ITEMS_ID: AtomicU32 = AtomicU32::new(CUSTOM_ID_BEGIN);

/// Build a system menu
pub fn build_hmenu_control(
    text: Option<String>,
    item: bool,
    separator: bool,
    popup: bool,
    hmenu: Option<HMENU>,
    hwnd: Option<HWND>,
) -> Result<ControlHandle, NwgError> {
    use winapi::um::winuser::{
        AppendMenuW, CreateMenu, CreatePopupMenu, DrawMenuBar, GetMenu, SetMenu,
    };
    use winapi::um::winuser::{MF_POPUP, MF_STRING};

    if separator {
        if hmenu.is_none() {
            return Err(NwgError::menu_create("Separator without parent"));
        }
        return Ok(build_hmenu_separator(hmenu.unwrap()));
    }

    if popup {
        if hwnd.is_none() {
            return Err(NwgError::menu_create("Popup menu without parent"));
        }

        let menu = unsafe { CreatePopupMenu() };
        if menu.is_null() {
            return Err(NwgError::menu_create("Popup menu creation failed"));
        }

        use_menu_command(menu);

        return Ok(ControlHandle::PopMenu(hwnd.unwrap(), menu));
    }

    let mut parent_menu: HMENU = ptr::null_mut();
    let mut menu: HMENU = ptr::null_mut();
    let mut item_id = 0;

    let mut flags = MF_STRING;
    if !item {
        flags |= MF_POPUP;
    }

    let text = to_utf16(text.unwrap_or("".to_string()).as_ref());

    if hwnd.is_some() {
        let hwnd = hwnd.unwrap();
        let mut menubar = unsafe { GetMenu(hwnd) };
        if menubar.is_null() {
            // If the window do not have a menu bar, create one
            menubar = unsafe { CreateMenu() };
            use_menu_command(menubar);
            unsafe { SetMenu(hwnd, menubar) };
        }

        if item {
            menu = menubar;
            item_id = MENU_ITEMS_ID.fetch_add(1, Ordering::SeqCst);
            unsafe { AppendMenuW(menubar, flags, item_id as usize, text.as_ptr()) };
        } else {
            parent_menu = menubar;
            menu = unsafe { CreateMenu() };
            if menu.is_null() {
                return Err(NwgError::menu_create("Menu without parent"));
            }
            use_menu_command(menu);
            unsafe { AppendMenuW(menubar, flags, menu as UINT_PTR, text.as_ptr()) };
        }

        // Draw the menu bar to make sure the changes are visible
        unsafe { DrawMenuBar(hwnd) };
    } else if hmenu.is_some() {
        let parent = hmenu.unwrap();

        if item {
            menu = parent;
            item_id = MENU_ITEMS_ID.fetch_add(1, Ordering::SeqCst);
            unsafe { AppendMenuW(parent, flags, item_id as usize, text.as_ptr()) };
        } else {
            parent_menu = parent;
            menu = unsafe { CreateMenu() };
            if menu.is_null() {
                return Err(NwgError::menu_create("Menu without parent"));
            }
            use_menu_command(menu);
            unsafe { AppendMenuW(parent, flags, menu as UINT_PTR, text.as_ptr()) };
        }
    }

    if item {
        Ok(ControlHandle::MenuItem(menu, item_id))
    } else {
        Ok(ControlHandle::Menu(parent_menu, menu))
    }
}

/**
    Enable or disable a menuitem at the selected position or using the selected ID. If the position is None and id is None, the last item is selected.
*/
pub fn enable_menuitem(h: HMENU, pos: Option<UINT>, id: Option<UINT>, enabled: bool) {
    use winapi::shared::minwindef::BOOL;
    use winapi::um::winuser::{GetMenuItemCount, SetMenuItemInfoW};
    use winapi::um::winuser::{MENUITEMINFOW, MFS_DISABLED, MFS_ENABLED, MIIM_STATE};

    let use_position = id.is_none();
    let choice = if use_position { pos } else { id };
    let value = match choice {
        Some(p) => p,
        None => unsafe { (GetMenuItemCount(h) - 1) as u32 },
    };

    let state = match enabled {
        true => MFS_ENABLED,
        false => MFS_DISABLED,
    };

    let mut info = MENUITEMINFOW {
        cbSize: mem::size_of::<MENUITEMINFOW>() as UINT,
        fMask: MIIM_STATE,
        fType: 0,
        fState: state,
        wID: 0,
        hSubMenu: ptr::null_mut(),
        hbmpChecked: ptr::null_mut(),
        hbmpUnchecked: ptr::null_mut(),
        dwItemData: 0,
        dwTypeData: ptr::null_mut(),
        cch: 0,
        hbmpItem: ptr::null_mut(),
    };

    unsafe { SetMenuItemInfoW(h, value, use_position as BOOL, &mut info) };
}

/**
    Return the state of a menuitem. Panic if both pos and id are None.
*/
pub fn is_menuitem_enabled(h: HMENU, pos: Option<UINT>, id: Option<UINT>) -> bool {
    use winapi::shared::minwindef::BOOL;
    use winapi::um::winuser::GetMenuItemInfoW;
    use winapi::um::winuser::{MENUITEMINFOW, MFS_DISABLED, MIIM_STATE};

    if id.is_none() && pos.is_none() {
        panic!("Both pos and id are None");
    }

    let use_position = id.is_none();
    let choice = if use_position { pos } else { id };
    let value = match choice {
        Some(p) => p,
        None => unreachable!(),
    };

    let mut info = MENUITEMINFOW {
        cbSize: mem::size_of::<MENUITEMINFOW>() as UINT,
        fMask: MIIM_STATE,
        fType: 0,
        fState: 0,
        wID: 0,
        hSubMenu: ptr::null_mut(),
        hbmpChecked: ptr::null_mut(),
        hbmpUnchecked: ptr::null_mut(),
        dwItemData: 0,
        dwTypeData: ptr::null_mut(),
        cch: 0,
        hbmpItem: ptr::null_mut(),
    };

    unsafe { GetMenuItemInfoW(h, value, use_position as BOOL, &mut info) };

    (info.fState & MFS_DISABLED) != MFS_DISABLED
}

/// Set the state of a menuitem
pub fn enable_menu(parent_menu: HMENU, menu: HMENU, e: bool) {
    let menu_index = menu_index_in_parent(parent_menu, menu);
    enable_menuitem(parent_menu, Some(menu_index), None, e);
}

/// Return the state of a menu.
pub fn is_menu_enabled(parent_menu: HMENU, menu: HMENU) -> bool {
    let menu_index = menu_index_in_parent(parent_menu, menu);
    is_menuitem_enabled(parent_menu, Some(menu_index), None)
}

pub fn check_menu_item(parent_menu: HMENU, id: u32, check: bool) {
    use winapi::um::winuser::{CheckMenuItem, MF_BYCOMMAND, MF_CHECKED, MF_UNCHECKED};

    let check = match check {
        true => MF_CHECKED,
        false => MF_UNCHECKED,
    };

    unsafe { CheckMenuItem(parent_menu, id, MF_BYCOMMAND | check) };
}

pub fn menu_item_checked(parent_menu: HMENU, id: u32) -> bool {
    use winapi::um::winuser::{GetMenuState, MF_BYCOMMAND, MF_CHECKED};
    unsafe { GetMenuState(parent_menu, id, MF_BYCOMMAND) & MF_CHECKED == MF_CHECKED }
}

fn build_hmenu_separator(menu: HMENU) -> ControlHandle {
    use winapi::shared::minwindef::BOOL;
    use winapi::um::winuser::{AppendMenuW, GetMenuItemCount, SetMenuItemInfoW};
    use winapi::um::winuser::{MENUITEMINFOW, MF_SEPARATOR, MIIM_ID};

    let item_id = MENU_ITEMS_ID.fetch_add(1, Ordering::SeqCst);

    // MF_SEPARATOR ignore the lpNewItem and uIDNewItem parameters, so they must be set using SetMenuItemInfo
    unsafe { AppendMenuW(menu, MF_SEPARATOR, 0, ptr::null()) };

    // Set the unique id of the separator
    let pos = unsafe { GetMenuItemCount(menu) - 1 };
    let mut info = MENUITEMINFOW {
        cbSize: mem::size_of::<MENUITEMINFOW>() as UINT,
        fMask: MIIM_ID,
        fType: 0,
        fState: 0,
        wID: item_id,
        hSubMenu: ptr::null_mut(),
        hbmpChecked: ptr::null_mut(),
        hbmpUnchecked: ptr::null_mut(),
        dwItemData: 0,
        dwTypeData: ptr::null_mut(),
        cch: 0,
        hbmpItem: ptr::null_mut(),
    };

    unsafe { SetMenuItemInfoW(menu, pos as UINT, true as BOOL, &mut info) };

    ControlHandle::MenuItem(menu, item_id)
}

/**
    Configure the menu to use a WM_MENUCOMMAND instead of a WM_COMMAND when its action are triggered.
    Required in order to allow nwg to dispatch the events correctly
*/
fn use_menu_command(h: HMENU) {
    use winapi::shared::minwindef::DWORD;
    use winapi::um::winuser::{MENUINFO, MIM_STYLE, MNS_NOTIFYBYPOS, SetMenuInfo};

    let mut info = MENUINFO {
        cbSize: mem::size_of::<MENUINFO>() as DWORD,
        fMask: MIM_STYLE,
        dwStyle: MNS_NOTIFYBYPOS,
        cyMax: 0,
        hbrBack: ptr::null_mut(),
        dwContextHelpID: 0,
        dwMenuData: 0,
    };

    unsafe { SetMenuInfo(h, &mut info) };
}

/**
    Return the index of a children menu/menuitem in a parent menu.
    Panic if the menu is not found in the parent.
*/
pub fn menu_index_in_parent(parent: HMENU, menu: HMENU) -> UINT {
    use winapi::um::winuser::{GetMenuItemCount, GetSubMenu};

    let children_count = unsafe { GetMenuItemCount(parent) };
    let mut sub_menu: HMENU;

    for i in 0..children_count {
        sub_menu = unsafe { GetSubMenu(parent, i as i32) };
        if sub_menu.is_null() {
            continue;
        } else if sub_menu == menu {
            return i as UINT;
        }
    }

    panic!("Menu/MenuItem not found in parent!")
}