grx 0.3.2

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

//! The entry/input component

use glib::prelude::{Cast, CastNone};
use grx_macros::{gtk_component, props};
use gtk::glib;
use gtk::{
    prelude::EditableExtManual,
    prelude::{EditableExt, WidgetExt},
};
use std::rc::Rc;

use crate::{default_on_click, default_on_swipe, handlers::Handler, new_gc, Interactable, Textual};

use super::gtk_props::apply;

#[props]
#[derive(Default)]
pub struct Props {
    pub on_change: Option<Handler<Entry>>,
    pub on_blur: Option<Handler<Entry>>,
    pub password: Option<bool>,
}

impl std::fmt::Debug for Props {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Props")
            .field("id", &self.id)
            .field("classes", &self.classes)
            .field("styles", &self.styles)
            .field("children", &self.children)
            .field("on_change", &self.on_change.is_some())
            .field("on_blur", &self.on_blur.is_some())
            .field("password", &self.password)
            .finish()
    }
}

pub fn entry(mut props: Props) -> Rc<Entry> {
    let entry: gtk::Editable = match props.password {
        Some(true) => gtk::PasswordEntry::builder().build().upcast(),
        _ => gtk::Entry::builder().build().upcast(),
    };
    let on_blur = props.on_blur.take();
    let on_change = props.on_change.take();

    let w = entry.clone();
    let component = new_gc!(Entry { w, props });

    apply(component.clone());

    if let Some(onc) = on_change {
        let comp = component.clone();
        entry.connect_insert_text(move |_, _, _| {
            onc(comp.clone());
        });
    }

    if let Some(onb) = on_blur {
        let comp = component.clone();
        if let Some(text) = entry.first_child().and_downcast_ref::<gtk::Text>() {
            text.connect_has_focus_notify(move |w| {
                if !w.has_focus() {
                    onb(comp.clone())
                }
            });
        }
    }

    component
}

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

impl Interactable for Entry {
    default_on_click! {}
    default_on_swipe! {}

    fn on_change(self: &Rc<Self>, handler: impl Fn(&Rc<Self>) + 'static) {
        let s = self.clone();
        self.widget.connect_insert_text(move |_, _, _| handler(&s));
    }

    fn on_blur(self: &Rc<Self>, handler: impl Fn(&Rc<Self>) + 'static) {
        let s = self.clone();
        if let Some(text) = self.widget.first_child().and_downcast_ref::<gtk::Text>() {
            text.connect_has_focus_notify(move |w| {
                if !w.has_focus() {
                    handler(&s)
                }
            });
        }
    }
}

impl Textual for Entry {
    fn text(self: &Rc<Self>) -> String {
        self.widget.text().to_string()
    }

    fn set_text(self: &Rc<Self>, text: &str) {
        self.widget.set_text(text)
    }
}