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
//! `CommandSink` static instance and related types and functions.
//!
//! ovrt-sys must register some functions that are called by ovrt.
//! Those functions then must forward some events/callback calls
//! using the static instance of `CommandSink`, since there are no
//! other parameters for the events/callbacks.
//!
//! An UI user, such as an app using the druid framework, must then
//! register itself as the listener of `CommandSink`, by being the
//! first caller of `global_command_sink` and registering itself.

use once_cell::sync::OnceCell;

pub type Listener = fn(cmd: crate::cmd::Command) -> ();

pub struct CommandSink {
    pub(crate) listener: Option<Listener>,
}

impl CommandSink {
    pub fn has_listener(&self) -> bool {
        self.listener.is_some()
    }
    pub fn ignored_log(cmd: crate::cmd::Command) {
        crate::log!("(ignored) command: ", format!("{:?}", cmd));
    }
    pub fn forward_or_ignore_cmd(
        &self,
        cmd: crate::cmd::Command,
    ) {
        match self.listener {
            Some(l) => l(cmd),
            None => Self::ignored_log(cmd),
        };
    }
}

/// This function has two use-cases: getting the `CommandSink` or
/// initializing it.
///
/// The "get it" usage is simple and is used internally by ovrt-sys
/// to forward commands received from events/callbacks/etc.
///
/// The "initialize it" usage needs more elaboration:
///
/// The initialization occurs in the first global call to this
/// function. The caller may decide if a listener should be registered
/// (by passing `Some` `Listener`), otherwise, `None` `Listener` will
/// be registered.  
/// Note: This `None` `Listener` will ignore all commands, but will at
/// least log them.  
/// Note: to see if there is `Some` registered listener, you may call the
/// `has_listener` function on `CommandSink`.
///
/// So with the exception of the first global call, all other remaining
/// calls should pass in `None` as a listener parameter, since the
/// `CommandSink` would already be initialized by the first global call.  
/// Otherwise, if other calls are made passing a new `Listener` as a
/// parameter, that parameter will simply be ignored and `CommandSink` will
/// not change it's already registered `Listener` (even if it's registered
/// as `None`).
pub fn global_command_sink(
    may_set_listener: Option<Listener>,
) -> &'static CommandSink {
    static EVENT_SINK: OnceCell<CommandSink> = OnceCell::new();
    EVENT_SINK.get_or_init(|| CommandSink {
        listener: may_set_listener,
    })
}

/// Submits a new command to be forwarded to the listener, or logged.
pub fn submit_cmd(cmd: crate::cmd::Command) {
    global_command_sink(None).forward_or_ignore_cmd(cmd);
}