grx 0.2.0

Abstraction layer for UI development
// SPDX-License-Identifier: GPL-3.0-or-later

//! A grid container.
//!
//! A grid can contain multiple children in a grid structure.
//!
//! # Example
//! ```ignore
//! grid (id="my_grid") [
//!     #[col=0, row=0]
//!     text(text="test 1".into())
//!     #[col=1, row=0]
//!     text(text="test 2".into())
//!     #[col=2, row=0]
//!     text(text="test 3".into())
//! ]
//! ```

use std::rc::Rc;

use glib::Cast;
use grx_macros::gtk_component;
use gtk::prelude::ObjectExt;
use gtk::traits::GridExt;
use gtk::{glib, traits::WidgetExt};

use crate::{new_gc, props, Component, ComponentExt};

use super::gtk_props::apply;

#[props]
#[derive(Default, Debug)]
pub struct Props {
    pub col_spacing: u32,
    pub row_spacing: u32,
    pub row_homogeneous: bool,
    pub col_homogeneous: bool,
}

pub fn grid(mut props: Props) -> Rc<Grid> {
    let grid = gtk::Grid::builder().build();
    let s = props.col_spacing;
    grid.set_column_spacing(s);

    let s = props.row_spacing;
    grid.set_row_spacing(s);

    let b = props.col_homogeneous;
    grid.set_column_homogeneous(b);

    let b = props.row_homogeneous;
    grid.set_row_homogeneous(b);

    let comp = new_gc!(Grid { grid, props });

    for c in comp.children().iter() {
        let c: &Component = c;
        if let Some(row) = c.annotations().get("row").and_then(|v| v.as_i64()) {
            if let Some(col) = c.annotations().get("col").and_then(|v| v.as_i64()) {
                let w = c
                    .annotations()
                    .get("width")
                    .and_then(|v| v.as_i64().map(|i| i as i32));
                let h = c
                    .annotations()
                    .get("height")
                    .and_then(|v| v.as_i64().map(|i| i as i32));

                comp.append_internal(c, col as i32, row as i32, w.unwrap_or(1), h.unwrap_or(1));
            }
        }
    }
    apply(comp.clone());
    comp
}

#[gtk_component(gtk::Grid)]
#[derive(Debug)]
pub struct Grid {}

impl Grid {
    pub fn append(
        self: &Rc<Self>,
        child: &crate::grx::Component,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
    ) {
        let c = child.clone();
        c.annotations_mut().insert("row".into(), y.into());
        c.annotations_mut().insert("col".into(), x.into());
        self.append_internal(&c, x, y, width, height);
        self.children.borrow_mut().push(c);
    }

    pub fn get(self: &Rc<Self>, x: i32, y: i32) -> Option<crate::grx::Component> {
        for child in self.children.borrow().iter() {
            if let Some(row) = child.annotations().get("row").and_then(|r| r.as_i64()) {
                if let Some(col) = child.annotations().get("col").and_then(|c| c.as_i64()) {
                    if row == y as i64 && col == x as i64 {
                        return Some(child.clone());
                    }
                }
            }
        }
        None
    }

    pub fn append_internal(
        self: &Rc<Self>,
        child: &crate::grx::Component,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
    ) {
        let inner = child.clone().inner();
        let w: &gtk::Widget = inner.downcast_ref().unwrap();
        self.widget.attach(w, x, y, width, height);
    }

    pub fn remove_row(self: &Rc<Self>, row: i32) {
        self.widget.remove_row(row);
        self.children_mut().retain_mut(|c| {
            let annotations = c.annotations();
            let value = annotations
                .get("row")
                .and_then(|v| v.as_i64().map(|v| v as i32));
            drop(annotations);
            if let Some(r) = value {
                // this is easier to read for me
                #[allow(clippy::comparison_chain)]
                if r == row {
                    //
                    false
                } else if r < row {
                    true
                } else {
                    c.annotations_mut().insert("row".into(), (r - 1).into());
                    true
                }
            } else {
                true
            }
        })
        // TODO: retain children
    }

    pub fn clear(self: &Rc<Self>) {
        let mut c = self.widget.first_child();
        while let Some(child) = c {
            self.widget.remove(&child);
            c = child.next_sibling();
        }
    }

    pub fn remove_child(self: &Rc<Self>, child: &Rc<dyn ComponentExt>) {
        let w = child.inner();
        if let Ok(child_widget) = w.downcast::<gtk::Widget>() {
            if child_widget.parent().is_some() {
                self.widget.remove(child_widget.as_ref());
            } else {
                unsafe {
                    child_widget.run_dispose();
                }
            }
        }
    }
}