nrelm 0.1.0

An idiomatic GUI library inspired by Elm and based on gtk3-rs
use gtk::prelude::*;

use crate::ContainerChild;

/// Extension trait that adds a typed `add` operation to containers.
///
/// [`container_add`](RelmContainerExt::container_add) adds a child widget
/// whose concrete type is determined by [`ContainerChild`].
pub trait RelmContainerExt: ContainerChild {
    /// Adds a child widget to the container.
    fn container_add(&self, widget: &impl AsRef<Self::Child>);
}

macro_rules! container_add_impl {
    ($($type:ty),+) => {
        $(
            impl RelmContainerExt for $type {
                fn container_add(&self, widget: &impl AsRef<<$type as ContainerChild>::Child>) {
                    self.add(widget.as_ref());
                }
            }
        )+
    }
}

// GTK3 `GtkBin` subclasses can only hold a single child, so adding a second
// child (for example an async component's real view replacing its loading
// widgets) fails with a warning and silently drops the new widget. Replace any
// existing child instead, mirroring GTK4's `set_child` semantics.
//
// A custom titlebar set via `set_titlebar` is reported as a child of the
// window in GTK3, but it must never be removed: doing so tears down the whole
// window. Skip the widget returned by `GtkWindow::titlebar()`.
macro_rules! container_add_replace_impl {
    ($($type:ty),+) => {
        $(
            impl RelmContainerExt for $type {
                fn container_add(&self, widget: &impl AsRef<<$type as ContainerChild>::Child>) {
                    let self_container: &gtk::Container = self.upcast_ref();
                    let window: &gtk::Window = self.upcast_ref();
                    let titlebar = window.titlebar();
                    for child in self_container.children() {
                        if titlebar.as_ref().is_some_and(|titlebar| titlebar == &child) {
                            continue;
                        }
                        self_container.remove(&child);
                    }
                    self.add(widget.as_ref());
                }
            }
        )+
    }
}

container_add_replace_impl! {
    gtk::Window,
    gtk::ApplicationWindow
}

// GTK3 `GtkDialog` keeps its content in an internal `GtkBox`, so adding a
// child to the dialog itself (a `GtkBin`) fails and silently drops the widget.
// Add to the content area instead, which is what `add` should do anyway.
impl RelmContainerExt for gtk::Dialog {
    fn container_add(&self, widget: &impl AsRef<<gtk::Dialog as ContainerChild>::Child>) {
        let content = self.content_area();
        for child in content.children() {
            content.remove(&child);
        }
        content.add(widget.as_ref());
    }
}

container_add_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::ListBox,
    gtk::ListBoxRow,
    gtk::ScrolledWindow,
    gtk::LinkButton,
    gtk::ToggleButton,
    gtk::Overlay,
    gtk::Revealer,
    gtk::Expander,
    gtk::AspectFrame,
    gtk::SearchBar,
    gtk::Viewport,
    gtk::MenuButton
}