#![doc(html_logo_url = "https://nrelm.org/icons/nrelm_logo.svg")]
#![doc(html_favicon_url = "https://nrelm.org/icons/nrelm_org.svg")]
#![doc = "An idiomatic GUI library inspired by Elm and based on gtk3-rs"]
#![warn(
missing_debug_implementations,
missing_docs,
rust_2018_idioms,
unreachable_pub,
unused_qualifications,
clippy::cargo,
clippy::must_use_candidate,
clippy::used_underscore_binding
)]
#![allow(clippy::multiple_crate_versions)]
#![cfg_attr(docsrs, feature(doc_cfg))]
mod app;
mod channel;
mod extensions;
pub(crate) mod late_initialization;
mod runtime_util;
pub mod abstractions;
pub mod actions;
pub mod binding;
pub mod component;
pub mod factory;
pub mod loading_widgets;
#[doc(hidden)]
pub mod macro_helper;
pub mod shared_state;
pub use channel::ComponentSender;
pub use channel::*;
pub use component::worker::{Worker, WorkerController, WorkerHandle};
pub use component::{
Component, ComponentBuilder, ComponentController, ComponentParts, Controller, MessageBroker,
SimpleComponent,
};
pub use extensions::*;
pub use shared_state::{AsyncReducer, AsyncReducible, Reducer, Reducible, SharedState};
pub use shutdown::ShutdownReceiver;
pub use app::RelmApp;
pub use tokio::task::JoinHandle;
use gtk::prelude::{Cast, CssProviderExt, IsA};
use once_cell::sync::{Lazy, OnceCell};
use runtime_util::{GuardedReceiver, RuntimeSenders, ShutdownOnDrop};
use std::cell::Cell;
use std::future::Future;
use tokio::runtime::Runtime;
pub static RELM_THREADS: OnceCell<usize> = OnceCell::new();
pub static RELM_BLOCKING_THREADS: OnceCell<usize> = OnceCell::new();
pub mod prelude;
pub use gtk;
#[cfg(feature = "css")]
#[cfg_attr(docsrs, doc(cfg(feature = "css")))]
pub use nrelm_css as css;
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use nrelm_macros::*;
pub use once_cell;
pub use tokio;
thread_local! {
static MAIN_APPLICATION: Cell<Option<gtk::Application>> = Cell::default();
}
fn set_main_application(app: impl IsA<gtk::Application>) {
MAIN_APPLICATION.with(move |cell| cell.set(Some(app.upcast())));
}
fn init() {
gtk::init().unwrap();
}
#[must_use]
pub fn main_application() -> gtk::Application {
fn new_application() -> gtk::Application {
gtk::Application::new(None, gtk::gio::ApplicationFlags::FLAGS_NONE)
}
MAIN_APPLICATION.with(|cell| {
let app = cell.take().unwrap_or_else(new_application);
cell.set(Some(app.clone()));
app
})
}
pub fn spawn_local<F, Out>(func: F) -> gtk::glib::JoinHandle<Out>
where
F: Future<Output = Out> + 'static,
Out: 'static,
{
gtk::glib::MainContext::ref_thread_default().spawn_local(func)
}
pub fn spawn_local_with_priority<F, Out>(
priority: gtk::glib::Priority,
func: F,
) -> gtk::glib::JoinHandle<Out>
where
F: Future<Output = Out> + 'static,
Out: 'static,
{
gtk::glib::MainContext::ref_thread_default().spawn_local_with_priority(priority, func)
}
static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.worker_threads(*RELM_THREADS.get_or_init(|| 1))
.max_blocking_threads(*RELM_BLOCKING_THREADS.get_or_init(|| 512))
.build()
.unwrap()
});
pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
RUNTIME.spawn(future)
}
pub fn spawn_blocking<F, R>(func: F) -> JoinHandle<R>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
RUNTIME.spawn_blocking(func)
}
pub fn set_global_css_with_priority(style_data: &str, priority: u32) {
let screen = gtk::gdk::Screen::default().unwrap();
let provider = gtk::CssProvider::new();
provider.load_from_data(style_data.as_bytes()).unwrap();
gtk::StyleContext::add_provider_for_screen(&screen, &provider, priority);
}
pub fn set_global_css(style_data: &str) {
set_global_css_with_priority(style_data, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
}
pub fn set_global_css_from_file_with_priority<P: AsRef<std::path::Path>>(
path: P,
priority: u32,
) -> Result<(), std::io::Error> {
std::fs::read_to_string(path)
.map(|bytes| set_global_css_with_priority(&bytes, priority))
.map_err(|err| {
tracing::error!("Couldn't load global CSS from file: {}", err);
err
})
}
pub fn set_global_css_from_file<P: AsRef<std::path::Path>>(path: P) -> Result<(), std::io::Error> {
set_global_css_from_file_with_priority(path, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION)
}
#[cfg(test)]
pub(crate) fn run_gtk_test<F>(f: F)
where
F: FnOnce() + Send + 'static,
{
static TEST_THREAD_WORKER: Lazy<gtk::glib::ThreadPool> = Lazy::new(|| {
let pool = gtk::glib::ThreadPool::exclusive(1).unwrap();
pool.push(|| {
gtk::init().expect("failed to initialize gtk");
})
.expect("failed to schedule gtk initialization");
pool
});
let (tx, rx) = std::sync::mpsc::channel();
TEST_THREAD_WORKER
.push(move || {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
tx.send(result).unwrap();
})
.expect("failed to schedule test closure");
if let Err(payload) = rx.recv().unwrap() {
std::panic::resume_unwind(payload);
}
}