// Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 mod macros; use gtk4::prelude::*; use tokio::sync::mpsc; pub use gtk4 as gtk; pub use relm4_macros::view; pub type Sender = mpsc::UnboundedSender; pub type Receiver = mpsc::UnboundedReceiver; /// A newly-registered component which supports destructuring the handle /// by forwarding or ignoring outputs from the component. pub struct Registered, I, O> { /// Handle to the component that was registered. pub handle: Handle, /// The outputs being received by the component. pub receiver: Receiver, } impl, I: 'static, O: 'static> Registered { /// Forwards output events to the designated sender. pub fn forward X) + 'static>( self, sender: Sender, transform: F, ) -> Handle { let Registered { handle, receiver } = self; forward(receiver, sender, transform); handle } pub fn handle(self, mut func: F) -> Handle { let Registered { handle, mut receiver, } = self; spawn_local(async move { while let Some(event) = receiver.recv().await { func(event); } }); handle } /// Ignore outputs from the component and take the handle. pub fn ignore(self) -> Handle { self.handle } } /// Handle to an active widget component in the system. pub struct Handle { /// The widget that this component manages. widget: W, /// Used for emitting events to the component. sender: Sender, } impl Widget for Handle { fn widget(&self) -> &W { &self.widget } } impl Handle { pub fn emit(&self, event: I) { let _ = self.sender.send(event); } } /// Used to drop the component's event loop when the managed widget is destroyed. enum InnerMessage { Drop, Message(T), } /// Provides a convenience function for getting a widget out of a type. pub trait Widget { fn widget(&self) -> &W; } /// The basis of a COSMIC widget. /// /// A component takes care of constructing the UI of a widget, managing an event-loop /// which handles signals from within the widget, and supports forwarding messages to /// the consumer of the component. pub trait Component: Sized + 'static { /// The arguments that are passed to the init_view method. type InitialArgs; /// The message type that the component accepts as inputs. type Input: 'static; /// The message type that the component provides as outputs. type Output: 'static; /// The widget that was constructed by the component. type RootWidget: Clone + AsRef; /// The type that's used for storing widgets created for this component. type Widgets: 'static; /// Initializes the component and attaches it to the default local executor. /// /// Spawns an event loop on `glib::MainContext::default()`, which exists /// for as long as the root widget remains alive. fn register( mut self, args: Self::InitialArgs, ) -> Registered { let (mut sender, in_rx) = mpsc::unbounded_channel::(); let (mut out_tx, output) = mpsc::unbounded_channel::(); let (mut widgets, widget) = self.init_view(args, &mut sender, &mut out_tx); let handle = Handle { widget, sender: sender.clone(), }; let (inner_tx, mut inner_rx) = mpsc::unbounded_channel::>(); handle.widget.as_ref().connect_destroy({ let sender = inner_tx.clone(); move |_| { let _ = sender.send(InnerMessage::Drop); } }); spawn_local(async move { while let Some(event) = inner_rx.recv().await { match event { InnerMessage::Message(event) => { self.update(&mut widgets, event, &mut sender, &mut out_tx); } InnerMessage::Drop => break, } } }); forward(in_rx, inner_tx, |event| InnerMessage::Message(event)); Registered { handle, receiver: output, } } /// Creates the initial view and root widget. fn init_view( &mut self, args: Self::InitialArgs, input: &mut Sender, output: &mut Sender, ) -> (Self::Widgets, Self::RootWidget); /// Handles input messages and enables the programmer to update the model and view. #[allow(unused_variables)] fn update( &mut self, widgets: &mut Self::Widgets, message: Self::Input, input: &mut Sender, output: &mut Sender, ) { } } /// Convenience function for `Component::register()`. pub fn register( model: C, args: C::InitialArgs, ) -> Registered { model.register(args) } /// Convenience function for forwarding events from a receiver to different sender. pub fn forward O) + 'static>( mut receiver: Receiver, sender: Sender, transformer: F, ) { spawn_local(async move { while let Some(event) = receiver.recv().await { if sender.send(transformer(event)).is_err() { break; } } }) } /// Convenience function for launching an application. pub fn run(func: F) { use gtk4::prelude::*; let app = gtk4::Application::new(None, Default::default()); app.connect_activate(move |app| func(app.clone())); app.run(); } /// Convenience function for spawning tasks on the local executor pub fn spawn_local + 'static>(func: F) { gtk4::glib::MainContext::default().spawn_local(func); }