Skip to content

Commit fdbb6f3

Browse files
committed
fix: prevent menus from being created after windows
1 parent 6ff9442 commit fdbb6f3

File tree

4 files changed

+59
-15
lines changed

4 files changed

+59
-15
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
2525
* The semi-unstable `iui::draw` subsystem is again exported to downstream consumers of the `iui` crate.
2626
* `UI::queue_main` and `UI::on_should_quit` now require passed closures to be `'static`, for soundness
2727
* All callback registration functions require that their callbacks live at least as long as the `UI` token, for soundness
28+
* `Menu` methods now returns `Option<>` because menus can't always be created or
29+
modified
2830

2931
### Deprecated
3032

@@ -44,6 +46,7 @@ compliance.
4446
* Text no longer uses incorrect newlines per platform.
4547
* `UI::run_delay` no longer spins on the callback, but actually calls it at the
4648
appropriate interval
49+
* Menus can no longer be created or modified after windows, as this causes crashes.
4750

4851
### Security
4952

iui/src/controls/window.rs

+5
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,21 @@ use callback_helpers::{from_void_ptr, to_heap_ptr};
44
use controls::Control;
55
use std::cell::RefCell;
66
use std::ffi::{CStr, CString};
7+
use std::sync::atomic::{Ordering};
78
use std::mem;
89
use std::os::raw::{c_int, c_void};
910
use std::path::PathBuf;
1011
use ui::UI;
12+
use menus::{HAS_FINALIZED_MENUS};
1113
use ui_sys::{self, uiControl, uiWindow};
1214

1315
thread_local! {
1416
static WINDOWS: RefCell<Vec<Window>> = RefCell::new(Vec::new())
1517
}
1618

1719
/// A `Window` can either have a menubar or not; this enum represents that decision.\
20+
///
21+
/// Once one window is created, no changes to menus can be made.
1822
#[derive(Clone, Copy, Debug)]
1923
pub enum WindowType {
2024
HasMenubar,
@@ -46,6 +50,7 @@ impl Window {
4650
));
4751

4852
WINDOWS.with(|windows| windows.borrow_mut().push(window.clone()));
53+
HAS_FINALIZED_MENUS.store(true, Ordering::SeqCst);
4954

5055
window
5156
};

iui/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,4 @@ pub mod prelude {
4747
pub use controls::{Window, WindowType};
4848
pub use ui::UI;
4949
}
50+

iui/src/menus.rs

+50-15
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,25 @@ use callback_helpers::{from_void_ptr, to_heap_ptr};
44
use controls::Window;
55
use std::ffi::CString;
66
use std::os::raw::{c_int, c_void};
7+
use std::sync::atomic::{AtomicBool, Ordering};
78
use ui_sys::{self, uiMenu, uiMenuItem, uiWindow};
89
use UI;
910

10-
/// A `MenuItem` represents an item that is shown in a `Menu`. Note that, unlike many controls,
11+
pub static HAS_FINALIZED_MENUS: AtomicBool = AtomicBool::new(false);
12+
13+
/// A `MenuItem` represents an item that is shown in a `Menu`.
14+
/// Note that, unlike many controls,
1115
/// the text on `MenuItem`s cannot be changed after creation.
1216
#[derive(Clone)]
1317
pub struct MenuItem {
1418
ui_menu_item: *mut uiMenuItem,
1519
}
1620

17-
/// A `Menu` represents one of the top-level menus at the top of a window. As that bar is unique
18-
/// per application, creating a new `Menu` shows it on all windows that support displaying menus.
21+
/// A `Menu` represents one of the top-level menus at the top of a window.
22+
/// That bar is unique per application, and creating a new `Menu` shows it
23+
/// on all windows that support displaying menus.
24+
///
25+
/// Once windows have been created, no more menus can be created.
1926
#[derive(Clone)]
2027
pub struct Menu {
2128
ui_menu: *mut uiMenu,
@@ -77,33 +84,49 @@ impl MenuItem {
7784
}
7885

7986
impl Menu {
80-
/// Creates a new menu with the given name to be displayed in the menubar at the top of the window.
81-
pub fn new(_ctx: &UI, name: &str) -> Menu {
82-
unsafe {
83-
let c_string = CString::new(name.as_bytes().to_vec()).unwrap();
84-
Menu {
85-
ui_menu: ui_sys::uiNewMenu(c_string.as_ptr()),
87+
/// Creates a new menu with the given name to be displayed in the menubar
88+
/// at the top of all windows with a menubar.
89+
///
90+
/// This is possible only if menus have not been finalized.
91+
pub fn new(_ctx: &UI, name: &str) -> Option<Menu> {
92+
if HAS_FINALIZED_MENUS.load(Ordering::SeqCst) { None }
93+
else {
94+
unsafe {
95+
let c_string = CString::new(name.as_bytes().to_vec()).unwrap();
96+
Some(Menu {
97+
ui_menu: ui_sys::uiNewMenu(c_string.as_ptr()),
98+
})
8699
}
87100
}
88101
}
89102

90103
/// Adds a new item with the given name to the menu.
91-
pub fn append_item(&self, name: &str) -> MenuItem {
104+
///
105+
/// This is possible only if menus have not been finalized.
106+
pub fn append_item(&self, name: &str) -> Option<MenuItem> {
107+
if HAS_FINALIZED_MENUS.load(Ordering::SeqCst) { None }
108+
else {
92109
unsafe {
93110
let c_string = CString::new(name.as_bytes().to_vec()).unwrap();
94-
MenuItem {
111+
Some(MenuItem {
95112
ui_menu_item: ui_sys::uiMenuAppendItem(self.ui_menu, c_string.as_ptr()),
96-
}
113+
})
114+
}
97115
}
98116
}
99117

100118
/// Adds a new togglable (checkbox) item with the given name to the menu.
101-
pub fn append_check_item(&self, name: &str) -> MenuItem {
119+
///
120+
/// This is possible only if menus have not been finalized.
121+
pub fn append_check_item(&self, name: &str) -> Option<MenuItem> {
122+
if HAS_FINALIZED_MENUS.load(Ordering::SeqCst) { None }
123+
else {
102124
unsafe {
103125
let c_string = CString::new(name.as_bytes().to_vec()).unwrap();
104-
MenuItem {
126+
Some(MenuItem {
105127
ui_menu_item: ui_sys::uiMenuAppendCheckItem(self.ui_menu, c_string.as_ptr()),
106-
}
128+
})
129+
}
107130
}
108131
}
109132

@@ -112,3 +135,15 @@ impl Menu {
112135
unsafe { ui_sys::uiMenuAppendSeparator(self.ui_menu) }
113136
}
114137
}
138+
139+
#[test]
140+
fn cannot_change_menus_late() {
141+
use crate::prelude::*;
142+
let ui = UI::init().expect("failed to init");
143+
let mut menu = Menu::new(&ui, "menu").unwrap();
144+
assert!(menu.append_item("test item").is_some());
145+
let win = Window::new(&ui, "Test App", 200, 200, WindowType::HasMenubar);
146+
assert!(Menu::new(&ui, "menu2").is_none());
147+
assert!(menu.append_item("test item 2").is_none());
148+
}
149+

0 commit comments

Comments
 (0)