relm4/
lib.rs

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