Skip to main content

videocall_types/
callback.rs

1/*
2 * Copyright 2025 Security Union LLC
3 *
4 * Licensed under either of
5 *
6 * * Apache License, Version 2.0
7 *   (http://www.apache.org/licenses/LICENSE-2.0)
8 * * MIT license
9 *   (http://opensource.org/licenses/MIT)
10 *
11 * at your option.
12 *
13 * Unless you explicitly state otherwise, any contribution intentionally
14 * submitted for inclusion in the work by you, as defined in the Apache-2.0
15 * license, shall be dual licensed as above, without any additional terms or
16 * conditions.
17 */
18
19//! Framework-agnostic callback type.
20//!
21//! Originally based on the `Callback` type from the Yew framework (MIT licensed),
22//! extracted here so that `videocall-client` and its consumers do not depend on any
23//! specific UI framework.
24
25use std::fmt;
26use std::rc::Rc;
27
28/// Universal callback wrapper.
29///
30/// An `Rc` wrapper is used to make it cloneable.
31pub struct Callback<IN, OUT = ()> {
32    cb: Rc<dyn Fn(IN) -> OUT>,
33}
34
35impl<IN, OUT, F: Fn(IN) -> OUT + 'static> From<F> for Callback<IN, OUT> {
36    fn from(func: F) -> Self {
37        Callback { cb: Rc::new(func) }
38    }
39}
40
41impl<IN, OUT> Clone for Callback<IN, OUT> {
42    fn clone(&self) -> Self {
43        Self {
44            cb: self.cb.clone(),
45        }
46    }
47}
48
49#[allow(ambiguous_wide_pointer_comparisons)]
50impl<IN, OUT> PartialEq for Callback<IN, OUT> {
51    fn eq(&self, other: &Callback<IN, OUT>) -> bool {
52        Rc::ptr_eq(&self.cb, &other.cb)
53    }
54}
55
56impl<IN, OUT> fmt::Debug for Callback<IN, OUT> {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        write!(f, "Callback<_>")
59    }
60}
61
62impl<IN, OUT> Callback<IN, OUT> {
63    /// Calls the callback's function.
64    pub fn emit(&self, value: IN) -> OUT {
65        (*self.cb)(value)
66    }
67}
68
69impl<IN> Callback<IN> {
70    /// Creates a "no-op" callback which can be used when it is not suitable to use an
71    /// `Option<Callback>`.
72    pub fn noop() -> Self {
73        Self::from(|_| ())
74    }
75}
76
77impl<IN> Default for Callback<IN> {
78    fn default() -> Self {
79        Self::noop()
80    }
81}
82
83impl<IN: 'static, OUT: 'static> Callback<IN, OUT> {
84    /// Creates a new callback from another callback and a function.
85    /// When emitted, calls `func` first, then emits the result to the original callback.
86    pub fn reform<F, T>(&self, func: F) -> Callback<T, OUT>
87    where
88        F: Fn(T) -> IN + 'static,
89    {
90        let this = self.clone();
91        let func = move |input| {
92            let output = func(input);
93            this.emit(output)
94        };
95        Callback::from(func)
96    }
97
98    /// Creates a new callback from another callback and a function.
99    /// When emitted will call the function and, only if it returns `Some(value)`, will emit
100    /// `value` to the original callback.
101    pub fn filter_reform<F, T>(&self, func: F) -> Callback<T, Option<OUT>>
102    where
103        F: Fn(T) -> Option<IN> + 'static,
104    {
105        let this = self.clone();
106        let func = move |input| func(input).map(|output| this.emit(output));
107        Callback::from(func)
108    }
109}
110
111#[cfg(test)]
112mod test {
113    use std::sync::Mutex;
114
115    use super::*;
116
117    fn emit<T, I, R: 'static + Clone, F, OUT>(values: I, f: F) -> Vec<R>
118    where
119        I: IntoIterator<Item = T>,
120        F: FnOnce(Callback<R, ()>) -> Callback<T, OUT>,
121    {
122        let result = Rc::new(Mutex::new(Vec::new()));
123        let cb_result = result.clone();
124        let cb = f(Callback::<R, ()>::from(move |v| {
125            cb_result.lock().unwrap().push(v);
126        }));
127        for value in values {
128            cb.emit(value);
129        }
130        let x = result.lock().unwrap().clone();
131        x
132    }
133
134    #[test]
135    fn test_callback() {
136        assert_eq!(*emit([true, false], |cb| cb), vec![true, false]);
137    }
138
139    #[test]
140    fn test_reform() {
141        assert_eq!(
142            *emit([true, false], |cb| cb.reform(|v: bool| !v)),
143            vec![false, true]
144        );
145    }
146
147    #[test]
148    fn test_filter_reform() {
149        assert_eq!(
150            *emit([1, 2, 3], |cb| cb.filter_reform(|v| match v {
151                1 => Some(true),
152                2 => Some(false),
153                _ => None,
154            })),
155            vec![true, false]
156        );
157    }
158}