Skip to main content

everything_plugin/ui/
winio.rs

1//! ## Design
2//! Embedding: https://github.com/compio-rs/winio/issues/24
3//!
4//! Dynamic component management: https://github.com/compio-rs/winio/issues/28
5
6use futures_channel::mpsc;
7use futures_util::StreamExt;
8use tracing::debug;
9use windows_sys::Win32::{Foundation::HWND, UI::WindowsAndMessaging::WS_OVERLAPPEDWINDOW};
10
11use crate::ui::{OptionsPageInternalMessage, OptionsPageLoadArgs, PageHandle};
12
13pub use winio;
14
15pub mod prelude {
16    pub use super::{super::OptionsPageMessage, OptionsPageInit};
17    pub use crate::PluginApp;
18    // pub use winio::prelude::*;
19    // Do not use Result
20    pub use winio::{Error, elm::*, handle::*, layout::*, primitive::*, ui::*, widgets::*};
21}
22use prelude::*;
23
24pub trait OptionsPageComponent<'a>:
25    Component<
26        Init<'a> = OptionsPageInit<'a, Self::App>,
27        Message: From<OptionsPageMessage<Self::App>>,
28    > + 'static
29{
30    type App: PluginApp;
31}
32
33impl<'a, T, A: PluginApp> OptionsPageComponent<'a> for T
34where
35    T: Component<Init<'a> = OptionsPageInit<'a, A>, Message: From<OptionsPageMessage<A>>> + 'static,
36{
37    type App = A;
38}
39
40pub fn spawn<'a, T: OptionsPageComponent<'a>>(args: OptionsPageLoadArgs) -> PageHandle<T::App> {
41    // *c_void, HWND: !Send
42    let parent: usize = args.parent as usize;
43
44    let (tx, rx) = mpsc::unbounded();
45    let thread_handle = std::thread::spawn(move || {
46        let parent: HWND = parent as HWND;
47        run::<T>(OptionsPageInit {
48            parent: unsafe { BorrowedContainer::win32(parent) },
49            rx: Some(rx),
50        })
51        .ok();
52        // widgets::main(page_hwnd)
53    });
54    PageHandle { thread_handle, tx }
55}
56
57pub fn run<'a, T: OptionsPageComponent<'a>>(
58    init: OptionsPageInit<'a, T::App>,
59) -> Result<T::Event, T::Error> {
60    // The name is only used on Qt and GTK
61    // https://github.com/compio-rs/winio/commit/f25828cc80fc5a39e188e7ed1c158f53ea9b5d56
62    App::new("").unwrap().run::<T>(init)
63}
64
65pub struct OptionsPageInit<'a, A: PluginApp> {
66    /// `MaybeBorrowedWindow`: !Clone
67    parent: BorrowedContainer<'a>,
68
69    /// Workaround for listening to external messages.
70    ///
71    /// A new channel is used instead of [`ComponentSender<T>`] to erase the type and keep dyn compatible.
72    rx: Option<mpsc::UnboundedReceiver<OptionsPageInternalMessage<A>>>,
73}
74
75impl<'a, A: PluginApp> From<()> for OptionsPageInit<'a, A> {
76    fn from(_: ()) -> Self {
77        Self {
78            parent: unsafe { BorrowedContainer::win32(Default::default()) },
79            rx: None,
80        }
81    }
82}
83
84impl<'a, A: PluginApp> OptionsPageInit<'a, A> {
85    /// Do not call `set_size()` after calling this in `init()`, otherwise the initial size will be overridden.
86    pub async fn window<T: OptionsPageComponent<'a, App = A>>(
87        &mut self,
88        sender: &ComponentSender<T>,
89    ) -> Result<Child<View>, Error> {
90        let mut window = Child::<View>::init(self.parent.clone()).await?;
91        self.init(&mut window, sender);
92        Ok(window)
93    }
94
95    /// Do not call `set_size()` after calling this in `init()`, otherwise the initial size will be overridden.
96    pub fn init<T: OptionsPageComponent<'a, App = A>>(
97        &mut self,
98        window: &mut View,
99        sender: &ComponentSender<T>,
100    ) {
101        // Put before spawn to avoid unnecessary runtime check
102        // adjust_window(window);
103
104        if let Some(mut rx) = self.rx.take() {
105            let window = window.as_container().as_win32();
106            let sender = sender.clone();
107            winio::compio::runtime::spawn(async move {
108                // We cannot defer initial size setting because `set_size()` will run this task many times
109                // See https://github.com/compio-rs/compio/issues/459
110                while let Some(m) = rx.next().await {
111                    if let Some(m) = m.try_into(window) {
112                        debug!(?m, "Options page message");
113                        sender.post(m.into());
114                    }
115                }
116                debug!("Options page message channel closed");
117            })
118            .detach();
119        }
120    }
121}
122
123/// Adjust a window to be used in an options page.
124///
125/// Should be called in [`Component::init`] for the window.
126#[allow(unused_must_use)]
127pub fn adjust_window(window: &mut Window) {
128    // Btw, if `window` is Window instead of &mut:
129    // error[E0502]: cannot borrow `window` as immutable because it is also borrowed as mutable
130    window.set_style(window.style().unwrap() & !WS_OVERLAPPEDWINDOW);
131
132    // TODO: Transparent background / background color
133
134    // Mitigate the occasional misplacement bug.
135    // It can be stably reproduced by enabling `tracing-appender` and blocking the console.
136    // The root cause is still unclear. Not because of `CW_USEDEFAULT`; probably related to multiple threading and Everything positioning behavior.
137    window.set_loc(Point::new(0.0, 0.0));
138}