#![cfg_attr(feature = "use_impl_trait", feature(conservative_impl_trait))]
#![warn(missing_docs, trivial_casts, trivial_numeric_casts, unused_extern_crates, unused_import_braces, unused_qualifications, unused_results)]
extern crate futures;
extern crate glib;
extern crate glib_itc;
extern crate gobject_sys;
extern crate gtk;
extern crate gtk_sys;
#[macro_use]
extern crate log;
extern crate relm_core;
pub mod gtk_ext;
mod macros;
mod stream;
mod widget;
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use futures::{Future, Stream};
use glib::Continue;
#[doc(hidden)]
pub use glib::object::Downcast;
#[doc(hidden)]
pub use glib::translate::{FromGlibPtrNone, ToGlib};
use glib_itc::{Receiver, channel};
#[doc(hidden)]
pub use gobject_sys::g_object_new;
use gtk::{ContainerExt, IsA, Object, WidgetExt};
use relm_core::Core;
#[doc(hidden)]
pub use relm_core::{EventStream, Handle, Remote};
use self::stream::ToStream;
pub use self::widget::*;
#[macro_export]
macro_rules! impl_widget {
($($tt:tt)*) => {
()
};
}
macro_rules! relm_connect {
($_self:expr, $to_stream:expr, $success_callback:expr, $failure_callback:expr) => {{
let event_stream = $_self.stream.clone();
let fail_event_stream = $_self.stream.clone();
let stream = $to_stream.to_stream();
stream.map_err(move |error| {
fail_event_stream.emit($failure_callback(error));
()
})
.for_each(move |result| {
event_stream.emit($success_callback(result));
Ok::<(), STREAM::Error>(())
}
.map_err(|_| ()))
}};
}
macro_rules! relm_connect_ignore {
($_self:expr, $to_stream:expr, $success_callback:expr) => {{
let event_stream = $_self.stream.clone();
let stream = $to_stream.to_stream();
stream.map_err(|_| ())
.for_each(move |result| {
event_stream.emit($success_callback(result));
Ok::<(), STREAM::Error>(())
}
.map_err(|_| ()))
}};
}
#[macro_export]
macro_rules! relm_widget {
($($tts:tt)*) => {
mod __relm_gen_private {
use super::*;
#[derive(Widget)]
struct __RelmPrivateWidget {
widget: impl_widget! {
$($tts)*
}
}
}
use_impl_self_type!($($tts)*);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! use_impl_self_type {
(impl Widget<$msg_type:ty> for $self_type:ident { $($tts:tt)* }) => {
pub use __relm_gen_private::$self_type;
};
}
#[must_use]
#[derive(Clone)]
pub struct Component<MODEL, MSG: Clone + DisplayVariant, WIDGET> {
model: Arc<Mutex<MODEL>>,
_receiver: Arc<Receiver>,
stream: EventStream<MSG>,
widget: WIDGET,
}
impl<MODEL, MSG: Clone + DisplayVariant, WIDGET> Component<MODEL, MSG, WIDGET> {
pub fn stream(&self) -> &EventStream<MSG> {
&self.stream
}
}
impl<MODEL, MSG: Clone + DisplayVariant, WIDGET> Drop for Component<MODEL, MSG, WIDGET> {
fn drop(&mut self) {
let _ = self.stream.close();
}
}
pub struct Relm<MSG: Clone + DisplayVariant> {
handle: Handle,
stream: EventStream<MSG>,
}
impl<MSG: Clone + DisplayVariant + Send + 'static> Relm<MSG> {
#[cfg(feature = "use_impl_trait")]
pub fn connect<CALLBACK, FAILCALLBACK, STREAM, TOSTREAM>(&self, to_stream: TOSTREAM, success_callback: CALLBACK, failure_callback: FAILCALLBACK) -> impl Future<Item=(), Error=()>
where CALLBACK: Fn(STREAM::Item) -> MSG + 'static,
FAILCALLBACK: Fn(STREAM::Error) -> MSG + 'static,
STREAM: Stream + 'static,
TOSTREAM: ToStream<STREAM, Item=STREAM::Item, Error=STREAM::Error> + 'static,
{
relm_connect!(self, to_stream, success_callback, failure_callback)
}
#[cfg(not(feature = "use_impl_trait"))]
pub fn connect<CALLBACK, FAILCALLBACK, STREAM, TOSTREAM>(&self, to_stream: TOSTREAM, success_callback: CALLBACK, failure_callback: FAILCALLBACK) -> Box<Future<Item=(), Error=()>>
where CALLBACK: Fn(STREAM::Item) -> MSG + 'static,
FAILCALLBACK: Fn(STREAM::Error) -> MSG + 'static,
STREAM: Stream + 'static,
TOSTREAM: ToStream<STREAM, Item=STREAM::Item, Error=STREAM::Error> + 'static,
{
let future = relm_connect!(self, to_stream, success_callback, failure_callback);
Box::new(future)
}
#[cfg(feature = "use_impl_trait")]
pub fn connect_ignore_err<CALLBACK, STREAM, TOSTREAM>(&self, to_stream: TOSTREAM, success_callback: CALLBACK) -> impl Future<Item=(), Error=()>
where CALLBACK: Fn(STREAM::Item) -> MSG + 'static,
STREAM: Stream + 'static,
TOSTREAM: ToStream<STREAM, Item=STREAM::Item, Error=STREAM::Error> + 'static,
{
relm_connect_ignore!(self, to_stream, success_callback)
}
#[cfg(not(feature = "use_impl_trait"))]
pub fn connect_ignore_err<CALLBACK, STREAM, TOSTREAM>(&self, to_stream: TOSTREAM, success_callback: CALLBACK) -> Box<Future<Item=(), Error=()>>
where CALLBACK: Fn(STREAM::Item) -> MSG + 'static,
STREAM: Stream + 'static,
TOSTREAM: ToStream<STREAM, Item=STREAM::Item, Error=STREAM::Error> + 'static,
{
Box::new(relm_connect_ignore!(self, to_stream, success_callback))
}
pub fn connect_exec<CALLBACK, FAILCALLBACK, STREAM, TOSTREAM>(&self, to_stream: TOSTREAM, callback: CALLBACK, failure_callback: FAILCALLBACK)
where CALLBACK: Fn(STREAM::Item) -> MSG + 'static,
FAILCALLBACK: Fn(STREAM::Error) -> MSG + 'static,
STREAM: Stream + 'static,
TOSTREAM: ToStream<STREAM, Item=STREAM::Item, Error=STREAM::Error> + 'static,
{
self.exec(self.connect(to_stream, callback, failure_callback));
}
pub fn connect_exec_ignore_err<CALLBACK, STREAM, TOSTREAM>(&self, to_stream: TOSTREAM, callback: CALLBACK)
where CALLBACK: Fn(STREAM::Item) -> MSG + 'static,
STREAM: Stream + 'static,
TOSTREAM: ToStream<STREAM, Item=STREAM::Item, Error=STREAM::Error> + 'static,
{
self.exec(self.connect_ignore_err(to_stream, callback));
}
pub fn exec<FUTURE: Future<Item=(), Error=()> + 'static>(&self, future: FUTURE) {
self.handle.spawn(future);
}
pub fn handle(&self) -> &Handle {
&self.handle
}
pub fn run<WIDGET>() -> Result<(), ()>
where WIDGET: Widget<MSG> + 'static,
WIDGET::Model: Send,
{
let _component = init::<WIDGET, MSG>()?;
gtk::main();
Ok(())
}
pub fn stream(&self) -> &EventStream<MSG> {
&self.stream
}
}
pub struct RemoteRelm<MSG: Clone + DisplayVariant> {
remote: Remote,
stream: EventStream<MSG>,
}
impl<'a, MSG: Clone + DisplayVariant> RemoteRelm<MSG> {
pub fn stream(&self) -> &EventStream<MSG> {
&self.stream
}
}
fn create_widget_test<WIDGET, MSG>(remote: &Remote) -> (Component<WIDGET::Model, MSG, WIDGET::Container>, WIDGET)
where WIDGET: Widget<MSG> + Clone + 'static,
MSG: Clone + DisplayVariant + 'static,
{
let (sender, mut receiver) = channel();
let stream = EventStream::new(Arc::new(sender));
let (widget, model) = {
let relm = RemoteRelm {
remote: remote.clone(),
stream: stream.clone(),
};
let model = WIDGET::model();
(WIDGET::view(relm, &model), model)
};
let container = widget.container().clone();
let model = Arc::new(Mutex::new(model));
{
let mut widget = widget.clone();
let stream = stream.clone();
let model = model.clone();
receiver.connect_recv(move || {
if let Some(event) = stream.pop_ui_events() {
let mut model = model.lock().unwrap();
update_widget(&mut widget, event, &mut *model);
}
Continue(true)
});
}
let component = Component {
model: model,
_receiver: Arc::new(receiver),
stream: stream,
widget: container,
};
(component, widget)
}
fn create_widget<WIDGET, MSG>(remote: &Remote) -> Component<WIDGET::Model, MSG, WIDGET::Container>
where WIDGET: Widget<MSG> + 'static,
MSG: Clone + DisplayVariant + 'static,
{
let (sender, mut receiver) = channel();
let stream = EventStream::new(Arc::new(sender));
let (mut widget, model) = {
let relm = RemoteRelm {
remote: remote.clone(),
stream: stream.clone(),
};
let model = WIDGET::model();
(WIDGET::view(relm, &model), model)
};
let container = widget.container().clone();
let model = Arc::new(Mutex::new(model));
{
let stream = stream.clone();
let model = model.clone();
receiver.connect_recv(move || {
if let Some(event) = stream.pop_ui_events() {
let mut model = model.lock().unwrap();
update_widget(&mut widget, event, &mut *model);
}
Continue(true)
});
}
Component {
model: model,
_receiver: Arc::new(receiver),
stream: stream,
widget: container,
}
}
fn init_gtk() {
let mut argc = 0;
unsafe {
gtk_sys::gtk_init(&mut argc, std::ptr::null_mut());
gtk::set_initialized();
}
}
pub fn init_test<WIDGET, MSG: Clone + DisplayVariant + Send + 'static>() -> Result<(Component<WIDGET::Model, MSG, WIDGET::Container>, WIDGET), ()>
where WIDGET: Widget<MSG> + Clone + 'static,
WIDGET::Model: Send,
{
init_gtk();
let remote = Core::run();
let (component, widgets) = create_widget_test::<WIDGET, MSG>(&remote);
init_component::<WIDGET, MSG>(&component, &remote);
Ok((component, widgets))
}
fn init<WIDGET, MSG: Clone + DisplayVariant + Send + 'static>() -> Result<Component<WIDGET::Model, MSG, WIDGET::Container>, ()>
where WIDGET: Widget<MSG> + 'static,
WIDGET::Model: Send,
{
gtk::init()?;
let remote = Core::run();
let component = create_widget::<WIDGET, MSG>(&remote);
init_component::<WIDGET, MSG>(&component, &remote);
Ok(component)
}
fn init_component<WIDGET, MSG>(component: &Component<WIDGET::Model, MSG, WIDGET::Container>, remote: &Remote)
where WIDGET: Widget<MSG> + 'static,
WIDGET::Model: Send,
MSG: Clone + DisplayVariant + Send + 'static,
{
let stream = component.stream.clone();
let model = component.model.clone();
remote.spawn(move |handle| {
let relm = Relm {
handle: handle.clone(),
stream: stream.clone(),
};
WIDGET::subscriptions(&relm);
let event_future = stream.for_each(move |event| {
let mut model = model.lock().unwrap();
WIDGET::update_command(&relm, event, &mut *model);
Ok(())
});
handle.spawn(event_future);
Ok(())
});
}
fn update_widget<MSG, WIDGET>(widget: &mut WIDGET, event: MSG, model: &mut WIDGET::Model)
where MSG: Clone + DisplayVariant,
WIDGET: Widget<MSG>,
{
if cfg!(debug_assertions) {
let time = SystemTime::now();
let debug = event.display_variant();
let debug =
if debug.len() > 100 {
format!("{}…", &debug[..100])
}
else {
debug.to_string()
};
widget.update(event, model);
if let Ok(duration) = time.elapsed() {
let ms = duration.subsec_nanos() as u64 / 1_000_000 + duration.as_secs() * 1000;
if ms >= 200 {
warn!("The update function was slow to execute for message {}: {}ms", debug, ms);
}
}
}
else {
widget.update(event, model)
}
}
pub trait ContainerWidget
where Self: ContainerExt
{
fn add_widget<WIDGET, MSG, WIDGETMSG>(&self, relm: &RemoteRelm<MSG>) -> Component<WIDGET::Model, WIDGETMSG, WIDGET::Container>
where MSG: Clone + DisplayVariant + Send + 'static,
WIDGET: Widget<WIDGETMSG> + 'static,
WIDGET::Container: IsA<Object> + WidgetExt,
WIDGET::Model: Send,
WIDGETMSG: Clone + DisplayVariant + Send + 'static,
{
let component = create_widget::<WIDGET, WIDGETMSG>(&relm.remote);
self.add(&component.widget);
component.widget.show_all();
init_component::<WIDGET, WIDGETMSG>(&component, &relm.remote);
component
}
fn remove_widget<MODEL, MSG: Clone + DisplayVariant + 'static, WIDGET>(&self, component: Component<MODEL, MSG, WIDGET>)
where WIDGET: IsA<gtk::Widget>,
{
self.remove(&component.widget);
}
}
impl<W: ContainerExt> ContainerWidget for W { }
pub trait DisplayVariant {
fn display_variant(&self) -> &'static str;
}