nrelm 0.1.0

An idiomatic GUI library inspired by Elm and based on gtk3-rs
mod container;
mod iter_children;
mod object_ext;
mod remove;
mod set_child;

#[cfg(test)]
mod tests;
mod widget_ext;

pub use container::RelmContainerExt;
pub use iter_children::RelmIterChildrenExt;
pub use object_ext::RelmObjectExt;
pub use remove::{RelmRemoveAllExt, RelmRemoveExt};
pub use set_child::RelmSetChildExt;
pub use widget_ext::RelmWidgetExt;

use gtk::prelude::{
    ApplicationExt, ApplicationExtManual, Cast, ContainerExt, IsA, ListBoxRowExt, StaticType,
    WidgetExt,
};

/// Extension trait that provides access to the underlying GTK widget of a
/// type that can be converted into a widget reference.
pub trait WidgetRef {
    /// Returns a reference to the underlying GTK widget.
    fn widget_ref(&self) -> &gtk::Widget;
}

impl<T: AsRef<gtk::Widget>> WidgetRef for T {
    fn widget_ref(&self) -> &gtk::Widget {
        self.as_ref()
    }
}

/// A reusable widget template that can be embedded into a [`view!`](crate::view)
/// macro invocation.
///
/// Implementations are usually generated with the
/// [`#[nrelm::widget_template]`](crate::widget_template) attribute macro.
pub trait WidgetTemplate:
    Sized + std::fmt::Debug + AsRef<Self::Root> + std::ops::Deref<Target = Self::Root>
{
    /// The root widget type of the template.
    type Root;
    /// The initialization data required to build the template.
    type Init;

    /// Creates a new template instance from the given initialization data.
    fn init(init: Self::Init) -> Self;
}

/// Extension trait for [`gtk::builders::ApplicationBuilder`] that launches
/// the application with an initialization callback.
pub trait ApplicationBuilderExt {
    /// Builds the application and runs it. The callback is invoked for every
    /// activation of the application with a new window.
    fn launch<F>(self, init: F)
    where
        F: Fn(gtk::Application, gtk::ApplicationWindow) + 'static;
}

impl ApplicationBuilderExt for gtk::builders::ApplicationBuilder {
    fn launch<F>(self, init: F)
    where
        F: Fn(gtk::Application, gtk::ApplicationWindow) + 'static,
    {
        let app = self.build();

        app.connect_activate(move |app| {
            let window = gtk::ApplicationWindow::new(app);

            init(app.clone(), window.clone());

            window.set_visible(true);
        });

        app.run();
    }
}

/// Extension trait for [`gtk::ListBox`] that simplifies working with the
/// rows of a list box.
pub trait RelmListBoxExt {
    /// Returns the index of the row that contains the given child widget.
    fn index_of_child(&self, widget: &impl AsRef<gtk::Widget>) -> Option<i32>;
    /// Removes the row that contains the given child widget.
    fn remove_row_of_child(&self, widget: &impl AsRef<gtk::Widget>);
    /// Returns the row that contains the given child widget.
    fn row_of_child(&self, widget: &impl AsRef<gtk::Widget>) -> Option<gtk::ListBoxRow>;
}

impl RelmListBoxExt for gtk::ListBox {
    fn index_of_child(&self, widget: &impl AsRef<gtk::Widget>) -> Option<i32> {
        self.row_of_child(widget).map(|row| row.index())
    }

    fn remove_row_of_child(&self, widget: &impl AsRef<gtk::Widget>) {
        if let Some(row) = self.row_of_child(widget) {
            let self_ref: &gtk::Container = self.upcast_ref::<gtk::Container>();
            self_ref.remove(&row);
        }
    }

    fn row_of_child(&self, widget: &impl AsRef<gtk::Widget>) -> Option<gtk::ListBoxRow> {
        if let Some(row) = widget.as_ref().ancestor(gtk::ListBoxRow::static_type())
            && let Some(row) = row.downcast_ref::<gtk::ListBoxRow>()
            && let Some(parent_widget) = row.parent()
            && let Some(parent_box) = parent_widget.downcast_ref::<Self>()
            && parent_box == self
        {
            return Some(row.clone());
        }

        None
    }
}

/// Associates a container type with the concrete child widget type it stores.
///
/// This is used by the container extension traits to provide typed add,
/// remove and iteration operations.
pub trait ContainerChild {
    /// The concrete child widget type of the container.
    type Child: IsA<gtk::Widget>;
}

macro_rules! container_child_impl {
    ($($type:ty: $child:ty), +) => {
        $(
            impl ContainerChild for $type {
                type Child = $child;
            }
        )+
    };
    ($($type:ty), +) => {
        $(
            #[allow(deprecated)]
            impl ContainerChild for $type {
                type Child = gtk::Widget;
            }
        )+
    };
}

container_child_impl! {
    gtk::Box,
    gtk::Fixed,
    gtk::Grid,
    gtk::ActionBar,
    gtk::Stack,
    gtk::HeaderBar,
    gtk::InfoBar,
    gtk::Button,
    gtk::ComboBox,
    gtk::FlowBox,
    gtk::FlowBoxChild,
    gtk::Frame,
    gtk::Popover,
    gtk::Window,
    gtk::ApplicationWindow,
    gtk::ListBox,
    gtk::ListBoxRow,
    gtk::ScrolledWindow,
    gtk::Dialog,
    gtk::LinkButton,
    gtk::ToggleButton,
    gtk::Overlay,
    gtk::Revealer,
    gtk::Expander,
    gtk::AspectFrame,
    gtk::SearchBar,
    gtk::Viewport,
    gtk::MenuButton
}