relm4/
lib.rs

1//! An idiomatic GUI library inspired by Elm and based on gtk4-rs.
2//!
3//! Docs of related crates:
4//! [relm4](https://docs.rs/relm4)
5//! | [relm4-macros](https://docs.rs/relm4_macros)
6//! | [relm4-components](https://docs.rs/relm4_components)
7//! | [relm4-css](https://docs.rs/relm4-css)
8//! | [gtk4-rs](https://gtk-rs.org/gtk4-rs/git/docs)
9//! | [gtk-rs-core](https://gtk-rs.org/gtk-rs-core/git/docs)
10//! | [libadwaita-rs](https://world.pages.gitlab.gnome.org/Rust/libadwaita-rs/git/docs/libadwaita)
11//! | [libpanel-rs](https://world.pages.gitlab.gnome.org/Rust/libpanel-rs/git/docs/libpanel)
12//!
13//! [GitHub](https://github.com/Relm4/Relm4)
14//! | [Website](https://relm4.org)
15//! | [Book](https://relm4.org/book/stable/)
16//! | [Blog](https://relm4.org/blog)
17 
18
19#![doc(html_logo_url = "https://relm4.org/icons/relm4_logo.svg")]
20#![doc(html_favicon_url = "https://relm4.org/icons/relm4_org.svg")]
21#![warn(
22    missing_debug_implementations,
23    missing_docs,
24    rust_2018_idioms,
25    unreachable_pub,
26    unused_qualifications,
27    clippy::cargo,
28    clippy::must_use_candidate,
29    clippy::used_underscore_binding
30)]
31#![allow(clippy::multiple_crate_versions)]
32// Configuration for doc builds on the nightly toolchain.
33#![cfg_attr(docsrs, feature(doc_cfg))]
34
35mod app;
36mod channel;
37mod extensions;
38pub(crate) mod late_initialization;
39mod runtime_util;
40
41pub mod abstractions;
42pub mod actions;
43pub mod binding;
44pub mod component;
45pub mod factory;
46pub mod loading_widgets;
47pub mod shared_state;
48pub mod typed_view;
49
50pub use channel::ComponentSender;
51pub use channel::*;
52pub use component::worker::{Worker, WorkerController, WorkerHandle};
53pub use component::{
54    Component, ComponentBuilder, ComponentController, ComponentParts, Controller, MessageBroker,
55    SimpleComponent,
56};
57pub use extensions::*;
58pub use shared_state::{AsyncReducer, AsyncReducible, Reducer, Reducible, SharedState};
59pub use shutdown::ShutdownReceiver;
60
61pub use app::RelmApp;
62pub use tokio::task::JoinHandle;
63
64use gtk::prelude::{Cast, IsA};
65use once_cell::sync::{Lazy, OnceCell};
66use runtime_util::{GuardedReceiver, RuntimeSenders, ShutdownOnDrop};
67use std::cell::Cell;
68use std::future::Future;
69use tokio::runtime::Runtime;
70
71/// Defines how many threads that Relm4 should use for background tasks.
72///
73/// NOTE: The default thread count is 1.
74pub static RELM_THREADS: OnceCell<usize> = OnceCell::new();
75
76/// Defines the maximum number of background threads to spawn for handling blocking tasks.
77///
78/// NOTE: The default max is 512.
79pub static RELM_BLOCKING_THREADS: OnceCell<usize> = OnceCell::new();
80
81pub mod prelude;
82
83/// Re-export of gtk4
84pub use gtk;
85
86/// Re-export of relm4-css
87#[cfg(feature = "css")]
88#[cfg_attr(docsrs, doc(cfg(feature = "css")))]
89pub use relm4_css as css;
90
91// Re-exports
92#[cfg(feature = "macros")]
93#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
94pub use relm4_macros::*;
95
96/// Re-export of libadwaita
97#[cfg(feature = "libadwaita")]
98#[cfg_attr(docsrs, doc(cfg(feature = "libadwaita")))]
99pub use adw;
100
101/// Re-export of libpanel
102#[cfg(feature = "libpanel")]
103#[cfg_attr(docsrs, doc(cfg(feature = "libpanel")))]
104pub use panel;
105
106pub use once_cell;
107pub use tokio;
108
109thread_local! {
110    static MAIN_APPLICATION: Cell<Option<gtk::Application>> = Cell::default();
111}
112
113fn set_main_application(app: impl IsA<gtk::Application>) {
114    MAIN_APPLICATION.with(move |cell| cell.set(Some(app.upcast())));
115}
116
117fn init() {
118    gtk::init().unwrap();
119    #[cfg(feature = "libadwaita")]
120    adw::init().unwrap();
121}
122
123/// Returns the global [`gtk::Application`] that's used internally
124/// by [`RelmApp`].
125///
126/// Retrieving this value can be useful for graceful shutdown
127/// by calling [`ApplicationExt::quit()`][gtk::prelude::ApplicationExt::quit] on it.
128///
129/// Note: The global application can be overwritten by calling
130/// [`RelmApp::from_app()`].
131#[must_use]
132pub fn main_application() -> gtk::Application {
133    #[cfg(feature = "libadwaita")]
134    fn new_application() -> gtk::Application {
135        adw::Application::default().upcast()
136    }
137
138    #[cfg(not(feature = "libadwaita"))]
139    fn new_application() -> gtk::Application {
140        gtk::Application::default()
141    }
142
143    MAIN_APPLICATION.with(|cell| {
144        let app = cell.take().unwrap_or_else(new_application);
145        cell.set(Some(app.clone()));
146        app
147    })
148}
149
150#[cfg(feature = "libadwaita")]
151#[cfg_attr(docsrs, doc(cfg(feature = "libadwaita")))]
152/// Returns the global [`adw::Application`] that's used internally
153/// by [`RelmApp`] if the `libadwaita` feature is enabled.
154///
155/// Note: The global application can be overwritten by calling
156/// [`RelmApp::from_app()`].
157#[must_use]
158pub fn main_adw_application() -> adw::Application {
159    main_application().downcast().unwrap()
160}
161
162/// Spawns a thread-local future on GLib's executor, for non-[`Send`] futures.
163pub fn spawn_local<F, Out>(func: F) -> gtk::glib::JoinHandle<Out>
164where
165    F: Future<Output = Out> + 'static,
166    Out: 'static,
167{
168    gtk::glib::MainContext::ref_thread_default().spawn_local(func)
169}
170
171/// Spawns a thread-local future on GLib's executor, for non-[`Send`] futures.
172pub fn spawn_local_with_priority<F, Out>(
173    priority: gtk::glib::Priority,
174    func: F,
175) -> gtk::glib::JoinHandle<Out>
176where
177    F: Future<Output = Out> + 'static,
178    Out: 'static,
179{
180    gtk::glib::MainContext::ref_thread_default().spawn_local_with_priority(priority, func)
181}
182
183static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
184    tokio::runtime::Builder::new_multi_thread()
185        .enable_all()
186        .worker_threads(*RELM_THREADS.get_or_init(|| 1))
187        .max_blocking_threads(*RELM_BLOCKING_THREADS.get_or_init(|| 512))
188        .build()
189        .unwrap()
190});
191
192/// Spawns a [`Send`]-able future to the shared component runtime.
193pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
194where
195    F: Future + Send + 'static,
196    F::Output: Send + 'static,
197{
198    RUNTIME.spawn(future)
199}
200
201/// Spawns a blocking task in a background thread pool.
202pub fn spawn_blocking<F, R>(func: F) -> JoinHandle<R>
203where
204    F: FnOnce() -> R + Send + 'static,
205    R: Send + 'static,
206{
207    RUNTIME.spawn_blocking(func)
208}
209
210/// Sets a custom global stylesheet, with the given priority.
211///
212/// The priority can be any value, but GTK [includes some][style-providers] that you can use.
213///
214/// [style-providers]: https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/index.html?search=const%3ASTYLE_PROVIDER&filter-crate=gtk4#constants
215pub fn set_global_css_with_priority(style_data: &str, priority: u32) {
216    let display = gtk::gdk::Display::default().unwrap();
217    let provider = gtk::CssProvider::new();
218    #[allow(deprecated)]
219    provider.load_from_data(style_data);
220
221    #[allow(deprecated)]
222    gtk::StyleContext::add_provider_for_display(&display, &provider, priority);
223}
224/// Sets a custom global stylesheet.
225pub fn set_global_css(style_data: &str) {
226    set_global_css_with_priority(style_data, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
227}
228
229/// Sets a custom global stylesheet from a file, with the given priority.
230///
231/// If the file doesn't exist a [`tracing::error`] message will be emitted and
232/// an [`std::io::Error`] will be returned.
233///
234/// The priority can be any value, but GTK [includes some][style-providers] that you can use.
235///
236/// [style-providers]: https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/index.html?search=const%3ASTYLE_PROVIDER&filter-crate=gtk4#constants
237pub fn set_global_css_from_file_with_priority<P: AsRef<std::path::Path>>(
238    path: P,
239    priority: u32,
240) -> Result<(), std::io::Error> {
241    std::fs::read_to_string(path)
242        .map(|bytes| set_global_css_with_priority(&bytes, priority))
243        .map_err(|err| {
244            tracing::error!("Couldn't load global CSS from file: {}", err);
245            err
246        })
247}
248
249/// Sets a custom global stylesheet from a file.
250///
251/// If the file doesn't exist a [`tracing::error`] message will be emitted and
252/// an [`std::io::Error`] will be returned.
253pub fn set_global_css_from_file<P: AsRef<std::path::Path>>(path: P) -> Result<(), std::io::Error> {
254    set_global_css_from_file_with_priority(path, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION)
255}