Skip to main content

nifty_rt/
dialog.rs

1//! `pick_file(…)` and the other file dialogs in compiled apps. The call names the state its
2//! answer is bound to as a path from a state, which the answer is written through later.
3
4use nifty_runtime::{DialogKind, Failure, FileDialog, Picked};
5
6use crate::abi::borrow_context;
7use crate::context::{AppError, Context};
8use crate::place::{Location, PlaceDesc};
9use crate::value::{self, LIST_OF_STRINGS, STRING, Slot};
10
11/// Where a file dialog's answer goes, and the span of its call, which errors writing it
12/// point at.
13#[derive(Debug)]
14pub(crate) struct Answer {
15    place: Location,
16    kind: DialogKind,
17    span: u32,
18}
19
20impl Context {
21    /// Writes the path or paths the user picked into the state the dialog's call bound;
22    /// cancelling changes nothing.
23    pub(crate) fn answer_dialog(&self, answer: &Answer, picked: Picked) -> Result<(), AppError> {
24        let fail = |failure| self.error(&failure, answer.span);
25        let paths = match picked {
26            Picked::Chosen(paths) => paths,
27            Picked::Cancelled => return Ok(()),
28            Picked::Failed(reason) => return Err(fail(Failure::Dialog(reason))),
29        };
30        let written = if answer.kind.picks_many() {
31            self.write_location(&answer.place, value::strings_slot(paths), &LIST_OF_STRINGS)
32        } else {
33            let path = paths
34                .into_iter()
35                .next()
36                .expect("backends answer a one-path dialog with exactly one path");
37            self.write_location(&answer.place, value::string_slot(path), &STRING)
38        };
39        written.map_err(fail)
40    }
41}
42
43/// Asks for a file dialog of `kind`, shown once the current event finishes, whose answer
44/// goes to the state at `answer`. `title`, `folder` and `name` are borrowed strings, and
45/// `types` a borrowed list of strings; `""` and `[]` leave a setting to the platform.
46///
47/// # Safety
48/// See the `abi` module's contract; the place's steps are readable.
49#[unsafe(no_mangle)]
50pub(crate) unsafe extern "C" fn nf_pick(
51    context: *const Context,
52    kind: DialogKind,
53    answer: &PlaceDesc,
54    title: Slot,
55    types: Slot,
56    folder: Slot,
57    name: Slot,
58    span: u32,
59) -> bool {
60    // SAFETY: by the caller's contract.
61    let context = unsafe { borrow_context(context) };
62    // SAFETY: by the caller's contract.
63    let place = unsafe { Location::from_place(answer) };
64    let types: Vec<&str> = value::as_list(types)
65        .items
66        .iter()
67        .map(|item| value::as_str(*item))
68        .collect();
69    let asked = place.and_then(|place| {
70        let dialog = FileDialog::new(
71            kind,
72            value::as_str(title).to_owned(),
73            &types,
74            value::as_str(folder),
75            value::as_str(name).to_owned(),
76        )
77        .map_err(|error| Failure::Dialog(error.to_string()))?;
78        Ok((dialog, Answer { place, kind, span }))
79    });
80    match asked {
81        Ok((dialog, answer)) => {
82            context.ask_dialog(dialog, answer);
83            true
84        }
85        Err(failure) => {
86            context.fail(failure, span);
87            false
88        }
89    }
90}
91
92/// Fails a file dialog whose answer is bound to a variable rather than state.
93///
94/// # Safety
95/// See the `abi` module's contract.
96#[unsafe(no_mangle)]
97pub(crate) unsafe extern "C" fn nf_fail_dialog_into_variable(context: *const Context, span: u32) {
98    // SAFETY: by the caller's contract.
99    unsafe { borrow_context(context) }.fail(Failure::DialogIntoVariable, span);
100}