nrelm 0.1.0

An idiomatic GUI library inspired by Elm and based on gtk3-rs
use gtk::glib::prelude::ObjectExt;
use gtk::prelude::{
    BinExt, BoxExt, Cast, ContainerExt, FixedExt, FlowBoxChildExt, FlowBoxExt, GridExt, ListBoxExt,
    ListBoxRowExt, StackExt, WidgetExt,
};

use crate::factory::{FactoryView, positions};

impl FactoryView for gtk::Box {
    type Children = gtk::Widget;
    type ReturnedWidget = gtk::Widget;
    type Position = ();

    fn factory_remove(&self, widget: &Self::ReturnedWidget) {
        self.remove(widget);
    }

    fn factory_append(
        &self,
        widget: impl AsRef<Self::Children>,
        _position: &(),
    ) -> Self::ReturnedWidget {
        self.add(widget.as_ref());
        widget.as_ref().clone()
    }

    fn factory_prepend(
        &self,
        widget: impl AsRef<Self::Children>,
        _position: &(),
    ) -> Self::ReturnedWidget {
        self.add(widget.as_ref());
        self.reorder_child(widget.as_ref(), 0);
        widget.as_ref().clone()
    }

    fn factory_insert_after(
        &self,
        widget: impl AsRef<Self::Children>,
        _position: &(),
        other: &Self::ReturnedWidget,
    ) -> Self::ReturnedWidget {
        self.add(widget.as_ref());
        let children = self.children();
        if let Some(pos) = children.iter().position(|c| c == other) {
            self.reorder_child(widget.as_ref(), pos as i32 + 1);
        }
        widget.as_ref().clone()
    }

    fn returned_widget_to_child(returned_widget: &Self::ReturnedWidget) -> Self::Children {
        returned_widget.clone()
    }

    fn factory_move_after(&self, widget: &Self::ReturnedWidget, other: &Self::ReturnedWidget) {
        let children = self.children();
        if let Some(pos) = children.iter().position(|c| c == other) {
            self.reorder_child(widget, pos as i32 + 1);
        }
    }

    fn factory_move_start(&self, widget: &Self::ReturnedWidget) {
        self.reorder_child(widget, 0);
    }
}

impl FactoryView for gtk::Grid {
    type Children = gtk::Widget;
    type ReturnedWidget = gtk::Widget;
    type Position = positions::GridPosition;

    fn factory_remove(&self, widget: &Self::ReturnedWidget) {
        self.remove(widget);
    }

    fn factory_append(
        &self,
        widget: impl AsRef<Self::Children>,
        position: &Self::Position,
    ) -> Self::ReturnedWidget {
        self.attach(
            widget.as_ref(),
            position.column,
            position.row,
            position.width,
            position.height,
        );
        widget.as_ref().clone()
    }

    fn factory_prepend(
        &self,
        widget: impl AsRef<Self::Children>,
        position: &Self::Position,
    ) -> Self::ReturnedWidget {
        self.factory_append(widget, position)
    }

    fn factory_insert_after(
        &self,
        widget: impl AsRef<Self::Children>,
        position: &Self::Position,
        _other: &Self::ReturnedWidget,
    ) -> Self::ReturnedWidget {
        self.factory_append(widget, position)
    }

    fn factory_move_after(&self, _widget: &Self::ReturnedWidget, _other: &Self::ReturnedWidget) {}

    fn factory_move_start(&self, _widget: &Self::ReturnedWidget) {}

    fn returned_widget_to_child(returned_widget: &Self::ReturnedWidget) -> Self::Children {
        returned_widget.clone()
    }

    fn factory_update_position(&self, widget: &Self::ReturnedWidget, position: &Self::Position) {
        self.factory_remove(widget);
        self.factory_append(widget, position);
    }
}

impl FactoryView for gtk::Fixed {
    type ReturnedWidget = gtk::Widget;
    type Children = gtk::Widget;
    type Position = (f64, f64);

    fn factory_remove(&self, widget: &Self::ReturnedWidget) {
        self.remove(widget);
    }

    fn factory_append(
        &self,
        widget: impl AsRef<Self::Children>,
        position: &Self::Position,
    ) -> Self::ReturnedWidget {
        self.put(widget.as_ref(), position.0 as i32, position.1 as i32);
        widget.as_ref().clone()
    }

    fn factory_prepend(
        &self,
        widget: impl AsRef<Self::Children>,
        position: &Self::Position,
    ) -> Self::ReturnedWidget {
        self.factory_append(widget, position)
    }

    fn factory_insert_after(
        &self,
        widget: impl AsRef<Self::Children>,
        position: &Self::Position,
        _other: &Self::ReturnedWidget,
    ) -> Self::ReturnedWidget {
        self.factory_append(widget, position)
    }

    fn returned_widget_to_child(returned_widget: &Self::ReturnedWidget) -> Self::Children {
        returned_widget.clone()
    }

    fn factory_move_after(&self, _widget: &Self::ReturnedWidget, _other: &Self::ReturnedWidget) {}

    fn factory_move_start(&self, _widget: &Self::ReturnedWidget) {}

    fn factory_update_position(&self, widget: &Self::ReturnedWidget, position: &Self::Position) {
        self.factory_remove(widget);
        self.factory_append(widget, position);
    }
}

impl FactoryView for gtk::Stack {
    type Children = gtk::Widget;
    type ReturnedWidget = gtk::Widget;
    type Position = ();

    fn factory_remove(&self, widget: &Self::ReturnedWidget) {
        self.remove(widget);
    }

    fn factory_append(
        &self,
        widget: impl AsRef<Self::Children>,
        _position: &Self::Position,
    ) -> Self::ReturnedWidget {
        let widget = widget.as_ref();
        let name = widget.widget_name();
        if !name.is_empty() {
            self.add_named(widget, &name);
        } else {
            self.add(widget);
        }
        // GTK3's `GtkStack` derives the child name from the widget's name at
        // insertion time. Factory widgets set their name in `init_widgets`,
        // which runs after the widget was added, so follow the `name` property
        // to keep `visible_child_name()` in sync with the widget's name.
        let stack = gtk::glib::clone::Downgrade::downgrade(self);
        widget.connect_notify_local(Some("name"), move |widget, _| {
            if let Some(stack) = stack.upgrade() {
                let name = widget.widget_name();
                if !name.is_empty()
                    && widget.parent().as_ref() == Some(stack.upcast_ref::<gtk::Widget>())
                {
                    stack.set_child_name(widget, Some(&name));
                }
            }
        });
        widget.clone()
    }

    fn factory_prepend(
        &self,
        widget: impl AsRef<Self::Children>,
        _position: &(),
    ) -> Self::ReturnedWidget {
        self.factory_append(widget, &())
    }

    fn factory_insert_after(
        &self,
        widget: impl AsRef<Self::Children>,
        _position: &(),
        _other: &Self::ReturnedWidget,
    ) -> Self::ReturnedWidget {
        self.factory_append(widget, &())
    }

    fn factory_move_after(&self, _widget: &Self::ReturnedWidget, _other: &Self::ReturnedWidget) {}

    fn factory_move_start(&self, _widget: &Self::ReturnedWidget) {}

    fn returned_widget_to_child(returned_widget: &Self::ReturnedWidget) -> Self::Children {
        returned_widget.clone()
    }
}

impl FactoryView for gtk::ListBox {
    type Children = gtk::Widget;
    type ReturnedWidget = gtk::ListBoxRow;
    type Position = ();

    fn factory_remove(&self, widget: &Self::ReturnedWidget) {
        self.remove(widget);
    }

    fn factory_append(
        &self,
        widget: impl AsRef<Self::Children>,
        _position: &(),
    ) -> Self::ReturnedWidget {
        let widget = widget.as_ref();

        self.add(widget);

        match widget.downcast_ref::<gtk::ListBoxRow>() {
            Some(row) => row.clone(),
            None => widget.parent().unwrap().downcast().unwrap(),
        }
    }

    fn factory_prepend(
        &self,
        widget: impl AsRef<Self::Children>,
        _position: &(),
    ) -> Self::ReturnedWidget {
        let widget = widget.as_ref();

        self.prepend(widget);

        match widget.downcast_ref::<gtk::ListBoxRow>() {
            Some(row) => row.clone(),
            None => widget.parent().unwrap().downcast().unwrap(),
        }
    }

    fn factory_insert_after(
        &self,
        widget: impl AsRef<Self::Children>,
        _position: &(),
        other: &Self::ReturnedWidget,
    ) -> Self::ReturnedWidget {
        let widget = widget.as_ref();

        self.insert(widget, other.index() + 1);

        match widget.downcast_ref::<gtk::ListBoxRow>() {
            Some(row) => row.clone(),
            None => widget.parent().unwrap().downcast().unwrap(),
        }
    }

    fn factory_move_after(&self, widget: &Self::ReturnedWidget, other: &Self::ReturnedWidget) {
        self.remove(widget);
        self.insert(widget, other.index() + 1);
    }

    fn factory_move_start(&self, widget: &Self::ReturnedWidget) {
        self.remove(widget);
        self.prepend(widget);
    }

    fn returned_widget_to_child(returned_widget: &Self::ReturnedWidget) -> Self::Children {
        returned_widget
            .child()
            .unwrap_or_else(|| returned_widget.upcast_ref::<gtk::Widget>().clone())
    }
}

impl FactoryView for gtk::FlowBox {
    type Children = gtk::Widget;
    type ReturnedWidget = gtk::FlowBoxChild;
    type Position = ();

    fn factory_remove(&self, widget: &Self::ReturnedWidget) {
        self.remove(widget);
    }

    fn factory_append(
        &self,
        widget: impl AsRef<Self::Children>,
        _position: &(),
    ) -> Self::ReturnedWidget {
        let widget = widget.as_ref();

        self.insert(widget, -1);

        match widget.downcast_ref::<gtk::FlowBoxChild>() {
            Some(child) => child.clone(),
            None => widget.parent().unwrap().downcast().unwrap(),
        }
    }

    fn factory_prepend(
        &self,
        widget: impl AsRef<Self::Children>,
        _position: &(),
    ) -> Self::ReturnedWidget {
        let widget = widget.as_ref();

        self.insert(widget, 0);

        match widget.downcast_ref::<gtk::FlowBoxChild>() {
            Some(child) => child.clone(),
            None => widget.parent().unwrap().downcast().unwrap(),
        }
    }

    fn factory_insert_after(
        &self,
        widget: impl AsRef<Self::Children>,
        _position: &(),
        other: &Self::ReturnedWidget,
    ) -> Self::ReturnedWidget {
        let widget = widget.as_ref();

        self.insert(widget, other.index() + 1);

        match widget.downcast_ref::<gtk::FlowBoxChild>() {
            Some(child) => child.clone(),
            None => widget.parent().unwrap().downcast().unwrap(),
        }
    }

    fn factory_move_after(&self, widget: &Self::ReturnedWidget, other: &Self::ReturnedWidget) {
        self.remove(widget);
        self.insert(widget, other.index() + 1);
    }

    fn factory_move_start(&self, widget: &Self::ReturnedWidget) {
        self.remove(widget);
        self.insert(widget, 0);
    }

    fn returned_widget_to_child(returned_widget: &Self::ReturnedWidget) -> Self::Children {
        returned_widget
            .child()
            .unwrap_or_else(|| returned_widget.upcast_ref::<gtk::Widget>().clone())
    }
}