feat: implement CheckMenuItem (#13)

* feat: implement `CheckMenuItem`

* linux
This commit is contained in:
Amr Bashir 2022-06-12 15:42:50 +02:00 committed by GitHub
parent 01e7a2a848
commit ccf548d199
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 328 additions and 59 deletions

View file

@ -106,11 +106,11 @@ impl Menu {
///
/// ## Platform-specific:
///
/// - **Windows / Linux:** The menu label can containt `&` to indicate which letter should get a generated accelerator.
/// - **Windows / Linux:** The menu label can contain `&` to indicate which letter should get a generated accelerator.
/// For example, using `&File` for the File menu would result in the label gets an underline under the `F`,
/// and the `&` character is not displayed on menu label.
/// Then the menu can be activated by press `Alt+F`.
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
pub fn add_submenu<S: AsRef<str>>(&mut self, label: S, enabled: bool) -> Submenu {
Submenu(self.0.add_submenu(label, enabled))
}
@ -232,7 +232,7 @@ impl Menu {
}
}
/// This is a submenu within another [`Submenu`] or [`Menu`].
/// This is a Submenu within another [`Submenu`] or [`Menu`].
#[derive(Clone)]
pub struct Submenu(platform_impl::Submenu);
@ -243,7 +243,7 @@ impl Submenu {
}
/// Sets a new label for the submenu.
pub fn set_label(&mut self, label: impl AsRef<str>) {
pub fn set_label<S: AsRef<str>>(&mut self, label: S) {
self.0.set_label(label)
}
@ -261,11 +261,11 @@ impl Submenu {
///
/// ## Platform-specific:
///
/// - **Windows / Linux:** The menu label can containt `&` to indicate which letter should get a generated accelerator.
/// - **Windows / Linux:** The menu label can contain `&` to indicate which letter should get a generated accelerator.
/// For example, using `&File` for the File menu would result in the label gets an underline under the `F`,
/// and the `&` character is not displayed on menu label.
/// Then the menu can be activated by press `F` when its parent menu is active.
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
pub fn add_submenu<S: AsRef<str>>(&mut self, label: S, enabled: bool) -> Submenu {
Submenu(self.0.add_submenu(label, enabled))
}
@ -273,22 +273,34 @@ impl Submenu {
///
/// ## Platform-specific:
///
/// - **Windows / Linux:** The menu item label can containt `&` to indicate which letter should get a generated accelerator.
/// - **Windows / Linux:** The menu item label can contain `&` to indicate which letter should get a generated accelerator.
/// For example, using `&Save` for the save menu item would result in the label gets an underline under the `S`,
/// and the `&` character is not displayed on menu item label.
/// Then the menu item can be activated by press `S` when its parent menu is active.
pub fn add_text_item(
pub fn add_text_item<S: AsRef<str>>(
&mut self,
label: impl AsRef<str>,
label: S,
enabled: bool,
accelerator: Option<&str>,
) -> TextMenuItem {
TextMenuItem(self.0.add_text_item(label, enabled, accelerator))
}
/// Creates a new [`NativeMenuItem`] within this submenu.
pub fn add_native_item(&mut self, item: NativeMenuItem) {
self.0.add_native_item(item)
}
/// Creates a new [`CheckMenuItem`] within this submenu.
pub fn add_check_item<S: AsRef<str>>(
&mut self,
label: S,
enabled: bool,
checked: bool,
accelerator: Option<&str>,
) -> CheckMenuItem {
CheckMenuItem(self.0.add_check_item(label, enabled, checked, accelerator))
}
}
/// This is a Text menu item within a [`Submenu`].
@ -302,7 +314,7 @@ impl TextMenuItem {
}
/// Sets a new label for the menu item.
pub fn set_label(&mut self, label: impl AsRef<str>) {
pub fn set_label<S: AsRef<str>>(&mut self, label: S) {
self.0.set_label(label)
}
@ -322,6 +334,48 @@ impl TextMenuItem {
}
}
/// This is a Check menu item within a [`Submenu`].
#[derive(Clone)]
pub struct CheckMenuItem(platform_impl::CheckMenuItem);
impl CheckMenuItem {
/// Gets the menu item's current label.
pub fn label(&self) -> String {
self.0.label()
}
/// Sets a new label for the menu item.
pub fn set_label<S: AsRef<str>>(&mut self, label: S) {
self.0.set_label(label)
}
/// Gets the menu item's current state, whether enabled or not.
pub fn enabled(&self) -> bool {
self.0.enabled()
}
/// Enables or disables the menu item.
pub fn set_enabled(&mut self, enabled: bool) {
self.0.set_enabled(enabled)
}
/// Gets the menu item's current state, whether checked or not.
pub fn checked(&self) -> bool {
self.0.checked()
}
/// Enables or disables the menu item.
pub fn set_checked(&mut self, checked: bool) {
self.0.set_checked(checked)
}
/// Gets the unique id for this menu item.
pub fn id(&self) -> u64 {
self.0.id()
}
}
/// This is a Native menu item within a [`Submenu`] with a predefined behavior.
#[non_exhaustive]
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum NativeMenuItem {

View file

@ -1,4 +1,4 @@
pub fn to_gtk_menemenoic(string: impl AsRef<str>) -> String {
pub fn to_gtk_menemenoic<S: AsRef<str>>(string: S) -> String {
string
.as_ref()
.replace("&&", "[~~]")
@ -6,7 +6,7 @@ pub fn to_gtk_menemenoic(string: impl AsRef<str>) -> String {
.replace("[~~]", "&&")
}
pub fn to_gtk_accelerator(accelerator: impl AsRef<str>) -> String {
pub fn to_gtk_accelerator<S: AsRef<str>>(accelerator: S) -> String {
let accelerator = accelerator.as_ref();
let mut s = accelerator.split("+");
let count = s.clone().count();

View file

@ -8,13 +8,14 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc};
static COUNTER: Counter = Counter::new();
/// Generic shared type describing a menu entry. It can be one of [`MenuEntryType`]
#[derive(Debug)]
#[derive(Debug, Default)]
struct MenuEntry {
label: String,
enabled: bool,
r#type: MenuEntryType,
item_id: Option<u64>,
checked: bool,
id: u64,
accelerator: Option<String>,
r#type: MenuEntryType,
entries: Option<Vec<Rc<RefCell<MenuEntry>>>>,
}
@ -26,9 +27,16 @@ enum MenuEntryType {
// and push to it every time [`Menu::init_for_gtk_window`] is called.
Submenu(Vec<(gtk::MenuItem, gtk::Menu)>),
Text(Vec<gtk::MenuItem>),
Check(Vec<gtk::CheckMenuItem>),
Native(NativeMenuItem),
}
impl Default for MenuEntryType {
fn default() -> Self {
Self::Text(Default::default())
}
}
struct InnerMenu {
entries: Vec<Rc<RefCell<MenuEntry>>>,
// NOTE(amrbashir): because gtk doesn't allow using the same [`gtk::MenuBar`] and [`gtk::Box`]
@ -51,7 +59,7 @@ impl Menu {
})))
}
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
pub fn add_submenu<S: AsRef<str>>(&mut self, label: S, enabled: bool) -> Submenu {
let label = label.as_ref().to_string();
let entry = Rc::new(RefCell::new(MenuEntry {
@ -59,8 +67,7 @@ impl Menu {
enabled,
entries: Some(Vec::new()),
r#type: MenuEntryType::Submenu(Vec::new()),
item_id: Default::default(),
accelerator: None,
..Default::default()
}));
let mut inner = self.0.borrow_mut();
@ -185,7 +192,7 @@ impl Submenu {
self.0.borrow().label.clone()
}
pub fn set_label(&mut self, label: impl AsRef<str>) {
pub fn set_label<S: AsRef<str>>(&mut self, label: S) {
let label = label.as_ref().to_string();
let mut entry = self.0.borrow_mut();
if let MenuEntryType::Submenu(native_menus) = &mut entry.r#type {
@ -210,7 +217,7 @@ impl Submenu {
}
}
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
pub fn add_submenu<S: AsRef<str>>(&mut self, label: S, enabled: bool) -> Submenu {
let label = label.as_ref().to_string();
let entry = Rc::new(RefCell::new(MenuEntry {
@ -218,8 +225,7 @@ impl Submenu {
enabled,
entries: Some(Vec::new()),
r#type: MenuEntryType::Submenu(Vec::new()),
item_id: Default::default(),
accelerator: None,
..Default::default()
}));
let mut inner = self.0.borrow_mut();
@ -237,9 +243,9 @@ impl Submenu {
Submenu(entry, Rc::clone(&self.1))
}
pub fn add_text_item(
pub fn add_text_item<S: AsRef<str>>(
&mut self,
label: impl AsRef<str>,
label: S,
enabled: bool,
accelerator: Option<&str>,
) -> TextMenuItem {
@ -250,9 +256,9 @@ impl Submenu {
label: label.clone(),
enabled,
r#type: MenuEntryType::Text(Vec::new()),
item_id: Some(id),
id,
accelerator: accelerator.map(|s| s.to_string()),
entries: None,
..Default::default()
}));
let mut inner = self.0.borrow_mut();
@ -288,14 +294,53 @@ impl Submenu {
let entry = Rc::new(RefCell::new(MenuEntry {
r#type: MenuEntryType::Native(item),
label: Default::default(),
enabled: Default::default(),
item_id: Default::default(),
accelerator: Default::default(),
entries: Default::default(),
..Default::default()
}));
inner.entries.as_mut().unwrap().push(entry);
}
pub fn add_check_item<S: AsRef<str>>(
&mut self,
label: S,
enabled: bool,
checked: bool,
accelerator: Option<&str>,
) -> CheckMenuItem {
let label = label.as_ref().to_string();
let id = COUNTER.next();
let entry = Rc::new(RefCell::new(MenuEntry {
label: label.clone(),
enabled,
checked,
r#type: MenuEntryType::Check(Vec::new()),
id,
accelerator: accelerator.map(|s| s.to_string()),
..Default::default()
}));
let mut inner = self.0.borrow_mut();
if let MenuEntryType::Submenu(native_menus) = &mut inner.r#type {
for (_, menu) in native_menus {
let item = create_gtk_check_menu_item(
&label,
enabled,
checked,
&accelerator.map(|s| s.to_string()),
id,
&*self.1,
);
menu.append(&item);
if let MenuEntryType::Check(native_items) = &mut entry.borrow_mut().r#type {
native_items.push(item);
}
}
}
inner.entries.as_mut().unwrap().push(entry.clone());
CheckMenuItem(entry)
}
}
#[derive(Clone)]
@ -306,7 +351,7 @@ impl TextMenuItem {
self.0.borrow().label.clone()
}
pub fn set_label(&mut self, label: impl AsRef<str>) {
pub fn set_label<S: AsRef<str>>(&mut self, label: S) {
let label = label.as_ref().to_string();
let mut entry = self.0.borrow_mut();
if let MenuEntryType::Text(native_items) = &mut entry.r#type {
@ -332,7 +377,68 @@ impl TextMenuItem {
}
pub fn id(&self) -> u64 {
self.0.borrow().item_id.unwrap()
self.0.borrow().id
}
}
#[derive(Clone)]
pub struct CheckMenuItem(Rc<RefCell<MenuEntry>>);
impl CheckMenuItem {
pub fn label(&self) -> String {
self.0.borrow().label.clone()
}
pub fn set_label<S: AsRef<str>>(&mut self, label: S) {
let label = label.as_ref().to_string();
let mut entry = self.0.borrow_mut();
if let MenuEntryType::Text(native_items) = &mut entry.r#type {
for item in native_items {
item.set_label(&to_gtk_menemenoic(&label));
}
}
entry.label = label;
}
pub fn enabled(&self) -> bool {
self.0.borrow().enabled
}
pub fn set_enabled(&mut self, enabled: bool) {
let mut entry = self.0.borrow_mut();
if let MenuEntryType::Check(native_items) = &mut entry.r#type {
for item in native_items {
item.set_sensitive(enabled);
}
}
entry.enabled = enabled;
}
pub fn checked(&self) -> bool {
let entry = self.0.borrow();
let mut checked = entry.checked;
if let MenuEntryType::Check(native_items) = &entry.r#type {
if let Some(item) = native_items.get(0) {
checked = item.is_active();
}
}
checked
}
pub fn set_checked(&mut self, checked: bool) {
let mut entry = self.0.borrow_mut();
if let MenuEntryType::Check(native_items) = &mut entry.r#type {
for item in native_items {
item.set_active(checked);
}
}
entry.checked = checked;
}
pub fn id(&self) -> u64 {
self.0.borrow().id
}
}
@ -343,7 +449,7 @@ fn add_entries_to_menu<M: IsA<gtk::MenuShell>>(
) {
for entry in entries {
let mut entry = entry.borrow_mut();
let (item, submenu) = match &entry.r#type {
let (item, submenu) = match &mut entry.r#type {
MenuEntryType::Submenu(_) => {
let (item, submenu) = create_gtk_submenu(&entry.label, entry.enabled);
gtk_menu.append(&item);
@ -355,12 +461,24 @@ fn add_entries_to_menu<M: IsA<gtk::MenuShell>>(
&entry.label,
entry.enabled,
&entry.accelerator,
entry.item_id.unwrap(),
entry.id,
accel_group,
);
gtk_menu.append(&item);
(Some(item), None)
}
MenuEntryType::Check(_) => {
let item = create_gtk_check_menu_item(
&entry.label,
entry.enabled,
entry.checked,
&entry.accelerator,
entry.id,
accel_group,
);
gtk_menu.append(&item);
(Some(item.upcast::<gtk::MenuItem>()), None)
}
MenuEntryType::Native(native_menu_item) => {
native_menu_item.add_to_gtk_menu(gtk_menu);
(None, None)
@ -373,6 +491,9 @@ fn add_entries_to_menu<M: IsA<gtk::MenuShell>>(
}
MenuEntryType::Text(native_items) => {
native_items.push(item.unwrap());
}
MenuEntryType::Check(native_items) => {
native_items.push(item.unwrap().downcast().unwrap());
}
MenuEntryType::Native(_) => {}
};
@ -414,6 +535,34 @@ fn create_gtk_text_menu_item(
item
}
fn create_gtk_check_menu_item(
label: &str,
enabled: bool,
checked: bool,
accelerator: &Option<String>,
id: u64,
accel_group: &gtk::AccelGroup,
) -> gtk::CheckMenuItem {
let item = gtk::CheckMenuItem::with_mnemonic(&to_gtk_menemenoic(label));
item.set_sensitive(enabled);
item.set_active(checked);
if let Some(accelerator) = accelerator {
let (key, modifiers) = gtk::accelerator_parse(&to_gtk_accelerator(accelerator));
item.add_accelerator(
"activate",
accel_group,
key,
modifiers,
gtk::AccelFlags::VISIBLE,
);
}
item.connect_activate(move |_| {
let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id });
});
item.show();
item
}
impl NativeMenuItem {
fn add_to_gtk_menu<M: IsA<gtk::MenuShell>>(&self, gtk_menu: &M) {
match self {

View file

@ -2,9 +2,7 @@ use cocoa::appkit::NSEventModifierFlags;
/// Mnemonic is deprecated since macOS 10
pub fn remove_mnemonic(string: impl AsRef<str>) -> String {
string
.as_ref()
.replace("&", "")
string.as_ref().replace("&", "")
}
/// Returns a tuple of (Key, Modifier)

View file

@ -25,8 +25,8 @@ pub struct TextMenuItem {
}
impl TextMenuItem {
pub fn new(
label: impl AsRef<str>,
pub fn new<S: AsRef<str>>(
label: S,
enabled: bool,
selector: Sel,
accelerator: Option<&str>,
@ -52,7 +52,7 @@ impl TextMenuItem {
self.label.to_string()
}
pub fn set_label(&mut self, label: impl AsRef<str>) {
pub fn set_label<S: AsRef<str>>(&mut self, label: S) {
unsafe {
let title = NSString::alloc(nil).init_str(&remove_mnemonic(&label));
self.ns_menu_item.setTitle_(title);

View file

@ -25,7 +25,7 @@ impl Menu {
}
}
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
pub fn add_submenu<S: AsRef<str>>(&mut self, label: S, enabled: bool) -> Submenu {
let menu = Menu::new();
let menu_item = TextMenuItem::new("", enabled, sel!(fireMenubarAction:), None);
@ -64,7 +64,7 @@ impl Submenu {
self.menu_item.label()
}
pub fn set_label(&mut self, label: impl AsRef<str>) {
pub fn set_label<S: AsRef<str>>(&mut self, label: S) {
let label = remove_mnemonic(label);
self.menu_item.set_label(&label);
unsafe {
@ -81,13 +81,13 @@ impl Submenu {
self.menu_item.set_enabled(_enabled)
}
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
pub fn add_submenu<S: AsRef<str>>(&mut self, label: S, enabled: bool) -> Submenu {
self.menu.add_submenu(label, enabled)
}
pub fn add_text_item(
pub fn add_text_item<S: AsRef<str>>(
&mut self,
label: impl AsRef<str>,
label: S,
enabled: bool,
accelerator: Option<&str>,
) -> TextMenuItem {

View file

@ -1,7 +1,7 @@
use windows_sys::Win32::UI::WindowsAndMessaging::{FALT, FCONTROL, FSHIFT, FVIRTKEY};
/// Returns a tuple of (Key, Modifier, a string representation to be used in menu items)
pub fn parse_accelerator(accelerator: impl AsRef<str>) -> (u16, u32, String) {
pub fn parse_accelerator<S: AsRef<str>>(accelerator: S) -> (u16, u32, String) {
let accelerator = accelerator.as_ref();
let mut s = accelerator.split("+");
let count = s.clone().count();

View file

@ -16,19 +16,17 @@ use windows_sys::Win32::{
AppendMenuW, CloseWindow, CreateAcceleratorTableW, CreateMenu, DrawMenuBar,
EnableMenuItem, GetMenuItemInfoW, MessageBoxW, PostQuitMessage, SetMenu,
SetMenuItemInfoW, ShowWindow, ACCEL, HACCEL, HMENU, MB_ICONINFORMATION, MENUITEMINFOW,
MFS_DISABLED, MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_POPUP, MF_SEPARATOR, MF_STRING,
MIIM_STATE, MIIM_STRING, SW_MINIMIZE, WM_COMMAND,
MFS_CHECKED, MFS_DISABLED, MF_CHECKED, MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_POPUP,
MF_SEPARATOR, MF_STRING, MF_UNCHECKED, MIIM_STATE, MIIM_STRING, SW_MINIMIZE,
WM_COMMAND,
},
},
};
use self::accelerator::parse_accelerator;
const MENU_SUBCLASS_ID: usize = 200;
const COUNTER_START: u64 = 1000;
static COUNTER: Counter = Counter::new_with_start(COUNTER_START);
const ABOUT_COUNTER_START: u64 = 400;
static ABOUT_COUNTER: Counter = Counter::new_with_start(ABOUT_COUNTER_START);
struct InnerMenu {
hmenu: HMENU,
@ -48,7 +46,7 @@ impl Menu {
})))
}
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
pub fn add_submenu<S: AsRef<str>>(&mut self, label: S, enabled: bool) -> Submenu {
let hmenu = unsafe { CreateMenu() };
let mut flags = MF_POPUP;
if !enabled {
@ -111,7 +109,10 @@ impl Menu {
}
}
const ABOUT_COUNTER_START: u64 = 400;
static ABOUT_COUNTER: Counter = Counter::new_with_start(ABOUT_COUNTER_START);
static mut ABOUT_MENU_ITEMS: Lazy<HashMap<u64, NativeMenuItem>> = Lazy::new(|| HashMap::new());
static mut CHECK_MENU_ITEMS: Lazy<Vec<CheckMenuItem>> = Lazy::new(|| Vec::new());
#[derive(Clone)]
pub struct Submenu {
@ -140,7 +141,7 @@ impl Submenu {
decode_wide(info.dwTypeData)
}
pub fn set_label(&mut self, label: impl AsRef<str>) {
pub fn set_label<S: AsRef<str>>(&mut self, label: S) {
let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() };
info.cbSize = std::mem::size_of::<MENUITEMINFOW>() as _;
info.fMask = MIIM_STRING;
@ -169,7 +170,7 @@ impl Submenu {
};
}
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
pub fn add_submenu<S: AsRef<str>>(&mut self, label: S, enabled: bool) -> Submenu {
let hmenu = unsafe { CreateMenu() };
let mut flags = MF_POPUP;
if !enabled {
@ -190,9 +191,9 @@ impl Submenu {
}
}
pub fn add_text_item(
pub fn add_text_item<S: AsRef<str>>(
&mut self,
label: impl AsRef<str>,
label: S,
enabled: bool,
accelerator: Option<&str>,
) -> TextMenuItem {
@ -261,6 +262,19 @@ impl Submenu {
)
};
}
pub fn add_check_item<S: AsRef<str>>(
&mut self,
label: S,
enabled: bool,
checked: bool,
accelerator: Option<&str>,
) -> CheckMenuItem {
let mut item = CheckMenuItem(self.add_text_item(label, enabled, accelerator));
item.set_checked(checked);
unsafe { CHECK_MENU_ITEMS.push(item.clone()) };
item
}
}
#[derive(Clone)]
@ -296,7 +310,7 @@ impl TextMenuItem {
decode_wide(info.dwTypeData)
}
pub fn set_label(&mut self, label: impl AsRef<str>) {
pub fn set_label<S: AsRef<str>>(&mut self, label: S) {
let mut label = label.as_ref().to_string();
let prev_label = self.label_with_accel();
if let Some(accel_str) = prev_label.split("\t").nth(1) {
@ -337,6 +351,54 @@ impl TextMenuItem {
}
}
#[derive(Clone)]
pub struct CheckMenuItem(TextMenuItem);
impl CheckMenuItem {
pub fn label(&self) -> String {
self.0.label()
}
pub fn set_label<S: AsRef<str>>(&mut self, label: S) {
self.0.set_label(label)
}
pub fn enabled(&self) -> bool {
self.0.enabled()
}
pub fn set_enabled(&mut self, enabled: bool) {
self.0.set_enabled(enabled)
}
pub fn checked(&self) -> bool {
let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() };
info.cbSize = std::mem::size_of::<MENUITEMINFOW>() as _;
info.fMask = MIIM_STATE;
unsafe { GetMenuItemInfoW(self.0.parent_hmenu, self.0.id as _, false.into(), &mut info) };
!((info.fState & MFS_CHECKED) == 0)
}
pub fn set_checked(&mut self, checked: bool) {
use windows_sys::Win32::UI::WindowsAndMessaging;
unsafe {
WindowsAndMessaging::CheckMenuItem(
self.0.parent_hmenu,
self.0.id as _,
if checked { MF_CHECKED } else { MF_UNCHECKED },
)
};
}
pub fn id(&self) -> u64 {
self.0.id()
}
}
const MENU_SUBCLASS_ID: usize = 200;
unsafe extern "system" fn menu_subclass_proc(
hwnd: HWND,
msg: u32,
@ -351,6 +413,12 @@ unsafe extern "system" fn menu_subclass_proc(
// Custom menu items
if COUNTER_START <= id && id <= COUNTER.current() {
// Toggle check menu items
// TODO: check the behavior in gtk
if let Some(item) = CHECK_MENU_ITEMS.iter_mut().find(|i| i.id() == id) {
item.set_checked(!item.checked());
}
let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id });
ret = 0;
};

View file

@ -1,5 +1,5 @@
#[cfg(target_os = "windows")]
pub fn encode_wide(string: impl AsRef<std::ffi::OsStr>) -> Vec<u16> {
pub fn encode_wide<S: AsRef<std::ffi::OsStr>>(string: S) -> Vec<u16> {
std::os::windows::prelude::OsStrExt::encode_wide(string.as_ref())
.chain(std::iter::once(0))
.collect()