1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// SPDX-License-Identifier: MPL-2.0
//! A graphical user interface provided by *libui-ng*.
use std::{ffi::{CStr, CString}, os::raw::c_char, ptr};
use rodeo::bumpalo::Rodeo;
use crate::prelude::*;
impl Ui {
/// Creates a new `Ui`.
///
/// # Errors
///
/// This function may only be called once. Calling `new` a second time will return
/// [`Error::AlreadyInitedLibui`](crate::Error::AlreadyInitedLibui).
///
/// # Examples
///
/// ```no_run
/// use boing::Ui;
///
/// assert!(Ui::new().is_ok());
/// assert_eq!(Ui::new(), Err(boing::Error::AlreadyInitedLibui));
/// ```
pub fn new() -> Result<Self, crate::Error> {
use std::sync::Once;
static INIT: Once = Once::new();
let mut result = Err(crate::Error::AlreadyInitedLibui);
INIT.call_once(|| unsafe {
// TODO: Calling `Self::init_unchecked` inside `INIT.call_once` prevents the caller from
// retrying `Ui::new` if it fails the first time.
result = Self::init_unchecked();
});
result.map(|_| Self { arena: Rodeo::new() })
}
/// Initializes *libui-ng* without checking if *libui-ng* has already been initialized.
///
/// # Safety
///
/// *libui-ng* must not already be initialized.
unsafe fn init_unchecked() -> Result<(), crate::Error> {
// TODO: What is `uiInitOptions`? What does the `Size` field mean?
let mut init_options = uiInitOptions { Size: 0 };
let err_msg = uiInit(ptr::addr_of_mut!(init_options));
Self::result_from_err_msg(err_msg).map_err(|cause| {
uiFreeInitError(err_msg);
crate::Error::LibuiFn {
name: "uiInit",
cause: Some(cause),
}
})
}
fn result_from_err_msg(err_msg: *const c_char) -> Result<(), String> {
if err_msg.is_null() {
Ok(())
} else {
let err_msg = unsafe { CStr::from_ptr(err_msg) }
.to_string_lossy()
.into_owned();
Err(err_msg)
}
}
}
/// A graphical user interface provided by *libui-ng*.
///
/// Access to a `Ui` object is necessary for creating widgets.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> Result<(), boing::Error> {
/// use boing::Ui;
///
/// let ui = Ui::new()?;
///
/// let window = ui.create_window(
/// // Title.
/// "Hello World!",
/// // Width, in px.
/// 640u16,
/// // Height, in px.
/// 480u16,
/// // Does this window have a menubar?
/// false,
/// // Should this window quit the application when closed?
/// true,
/// )?;
///
/// window.show();
/// ui.run();
/// #
/// # Ok(())
/// # }
/// ```
pub struct Ui {
// Spooky! Nearly nothing's here! `Ui` serves no functional purpose besides instructing the
// compiler as to when it is valid for widgets to be created, used, and destroyed. The `arena`
// here ensures that widgets are destroyed simultaneously, which is necessary to prevent callers
// from destroying a parent control and then continuing to use its children.
arena: Rodeo,
}
impl Ui {
/// Runs *libui-ng*.
///
/// Due to the nature of this method entering the OS' main UI event loop, `run` does not return
/// instantaneously; rather, it returns only once the user clicks a "Quit" menu item or closes a
/// window created with `should_quit_on_close = true`.
///
/// It is permissible to call this method multiple times.
#[inline]
pub fn run(&self) {
unsafe { uiMain() };
}
/// This function returns whether or not the GUI was exited. In the case it was, it can be
/// recreated by calling this function or [`main`](Self::main).
#[inline]
pub fn step(&self) -> bool {
unsafe { uiMainStep(0) == 1 }
}
/// Allocates an object in the internal `Ui` arena.
///
/// Wrap a value in this method when you need it to live for as long as `Ui`.
#[inline]
pub fn alloc_object<'ui, T: 'ui>(&'ui self, value: T) -> &'ui mut T {
self.arena.alloc(value)
}
pub(crate) fn make_cstring<'ui>(
&'ui self,
s: impl Into<Vec<u8>>,
) -> Result<&'ui CStr, crate::Error> {
CString::new(s)
.map_err(crate::Error::ConvertRustString)
.map(|s| self.alloc_object(s).as_c_str())
}
}