use std::sync::Mutex;
use crate::abi::{Status, UiDialog};
use crate::{Host, Load, Off};
mod dialog;
mod frame;
mod hud;
#[cfg(feature = "unstable")]
mod map;
mod widgets;
pub use dialog::{Dialog, Response};
pub use frame::Frame;
pub use widgets::{Button, Dropdown, Label, Screen, Slider, Sound, Style, Toggle};
use dialog::{Answer, answered};
use frame::{Drawer, draw_frame};
pub use hud::{Anchor, Hud};
#[cfg(feature = "unstable")]
pub use map::{MAX_MARKERS, Marker, MarkerColor};
#[cfg(any(feature = "test", feature = "unstable"))]
pub(crate) const MARKER_LIMIT: usize = 256;
pub const MAX_OPTIONS: usize = 64;
#[derive(Clone, Copy, Debug)]
pub struct Ui {
host: Host,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum UiError {
#[error("no interface to show it in")]
Unavailable,
#[error("too many dialogs wait")]
Busy,
#[error("waited for on the thread that draws the interface")]
WrongThread,
#[error("the loader refused the dialog")]
Refused,
}
impl Ui {
pub fn dialog<F>(&self, dialog: &Dialog<'_>, on_answer: F) -> Result<(), UiError>
where
F: FnOnce(Option<Response>) + Send + 'static,
{
self.queue(dialog, 0, on_answer)
}
pub fn ask(&self, dialog: &Dialog<'_>) -> Result<Option<Response>, UiError> {
let (sender, receiver) = std::sync::mpsc::sync_channel(1);
self.queue(dialog, UiDialog::WAITING, move |answer| {
let _sent = sender.send(answer);
})?;
Ok(receiver.recv().unwrap_or(None))
}
fn queue<F>(&self, dialog: &Dialog<'_>, flags: u32, on_answer: F) -> Result<(), UiError>
where
F: FnOnce(Option<Response>) + Send + 'static,
{
let api = self.host.api();
let raw = dialog.to_raw(flags);
let user = Box::into_raw(Box::new(Answer { on_answer }));
let status = (api.ui_dialog)(
self.host.handle(),
&raw const raw,
answered::<F>,
user.cast(),
);
if status.is_ok() {
return Ok(());
}
drop(unsafe { Box::from_raw(user) });
Err(match status {
Status::MISSING => UiError::Unavailable,
Status::REFUSED => UiError::Busy,
Status::WRONG_THREAD => UiError::WrongThread,
_ => UiError::Refused,
})
}
}
fn client_only(host: Host, what: &str) -> Result<(), Off> {
if host.build().side == crate::abi::Side::SERVER {
return Err(Off::new(format!("{what}: on clients only")));
}
Ok(())
}
impl Load<'_> {
pub fn ui(&mut self) -> Result<Ui, Off> {
let host = self.host();
client_only(host, "the interface")?;
Ok(Ui { host })
}
pub fn on_frame<F>(&mut self, draw: F) -> Result<(), Off>
where
F: FnMut(&mut Frame<'_>) + Send + 'static,
{
let host = self.host();
client_only(host, "frame callbacks")?;
crate::load::hand_over(
Drawer {
draw: Mutex::new(draw),
},
|user| (host.api().ui_frame)(host.handle(), draw_frame::<F>, user),
"frame callback",
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn responses_round_trip_through_their_raw_form() {
for response in [
Response::Retry,
Response::Disable,
Response::Erase,
Response::Reset,
Response::Overwrite,
Response::Cancel,
Response::Ok,
Response::Ignore,
] {
assert_eq!(Response::from_raw(response.bit()), Some(response));
assert_ne!(UiDialog::BUTTONS & 1 << response.bit(), 0);
}
assert_eq!(Response::from_raw(4), None);
assert_eq!(Response::from_raw(UiDialog::CLOSED), None);
}
#[test]
fn a_dialog_keeps_ok_without_buttons_and_takes_the_ones_given() {
let plain = Dialog::new("t", "m");
assert_eq!(plain.to_raw(0).buttons, 1 << UiDialog::OK);
assert_eq!(plain.buttons(&[]).to_raw(0).buttons, 1 << UiDialog::OK);
let asked = plain.buttons(&[Response::Cancel, Response::Ok]);
assert_eq!(asked.to_raw(UiDialog::WAITING).buttons, 0xc0);
assert_eq!(asked.to_raw(UiDialog::WAITING).flags, UiDialog::WAITING);
}
}