nrelm 0.1.1

An idiomatic GUI library inspired by Elm and based on gtk3-rs
use gtk::prelude::{
    BoxExt, ButtonExt, GtkWindowExt, ImageExt, LabelExt, OrientableExt, WidgetExt, WidgetExtManual,
};
use gtk::{self, gdk};
use nrelm::{ComponentParts, ComponentSender, RelmApp, SimpleComponent};

// In GTK3 the payload of a drag and drop operation is transported through
// the selection mechanism. We distinguish the dragged values by the
// `info` id of the `TargetEntry` the drop was performed with.
// The payload itself must be stored with `SelectionData::set` using the
// target type requested by the drop site (`SelectionData::set_text` only
// works for text targets such as `STRING` or `UTF8_STRING`).

struct AppModel {
    status: String,
}

#[derive(Debug)]
enum AppIn {
    NewStatus(String),
}

#[nrelm::component]
impl SimpleComponent for AppModel {
    type Init = ();
    type Input = AppIn;
    type Output = ();

    fn init(
        _init: Self::Init,
        root: Self::Root,
        sender: ComponentSender<Self>,
    ) -> ComponentParts<Self> {
        let model = AppModel {
            status: String::from("Drag the key or lock pick to a door"),
        };

        let widgets = view_output!();
        ComponentParts { model, widgets }
    }

    view! {
        gtk::Window {
            set_title: "Drag and Drop Example",
            set_size_request: (500, 200),

            // outer box
            gtk::Box {
                set_orientation: gtk::Orientation::Vertical,

                // display status message
                gtk::Label {
                    set_margin_all: 10,
                    #[watch]
                    set_label: &model.status,
                },

                gtk::Label {
                    set_margin_all: 10,
                    set_label: "The blue key will only unlock the blue door\nThe pick can open both",
                },

                // first horizontal box
                gtk::Box {
                    set_orientation: gtk::Orientation::Horizontal,
                    set_halign: gtk::Align::Center,
                    set_spacing: 10,

                    // contains a image and label and serves as DropTarget
                    gtk::Button {
                        gtk::Box {
                            set_orientation: gtk::Orientation::Horizontal,
                            set_margin_all: 10,

                            gtk::Image {
                                set_from_icon_name: (Some("locked"), gtk::IconSize::Button),
                            },
                            gtk::Label {
                                set_label: "Blue Door",
                            }
                        },

                        // drops will be accepted on all widgets in Button
                        drag_dest_set: (
                            gtk::DestDefaults::ALL,
                            &[
                                gtk::TargetEntry::new(
                                    "application/x-nrelm-blue-key",
                                    gtk::TargetFlags::SAME_APP,
                                    0,
                                ),
                                gtk::TargetEntry::new(
                                    "application/x-nrelm-lock-pick",
                                    gtk::TargetFlags::SAME_APP,
                                    1,
                                ),
                            ],
                            gdk::DragAction::MOVE,
                        ),
                        connect_drag_data_received[sender] => move |_, context, _, _, data, info, time| {
                            let payload = String::from_utf8_lossy(&data.data()).to_string();
                            if info == 0 {
                                if payload == "blue-key" {
                                    sender.input(AppIn::NewStatus(
                                        String::from("Blue Door unlocked with Blue Key")
                                    ));
                                } else {
                                    sender.input(AppIn::NewStatus(
                                        String::from("Blue Door unlocked with Lock Pick")
                                    ));
                                }
                            } else if payload == "lock-pick" {
                                sender.input(AppIn::NewStatus(
                                    String::from("Blue Door unlocked with Lock Pick")
                                ));
                            } else {
                                sender.input(AppIn::NewStatus(
                                    String::from("Blue Door rejects this item")
                                ));
                            }
                            context.drop_finish(true, time);
                        }
                    },

                    gtk::Button {
                        set_label: "Blue Key",

                        // define the Button as a DragSource
                        drag_source_set: (
                            gdk::ModifierType::BUTTON1_MASK,
                            &[
                                gtk::TargetEntry::new(
                                    "application/x-nrelm-blue-key",
                                    gtk::TargetFlags::SAME_APP,
                                    0,
                                ),
                            ],
                            gdk::DragAction::MOVE,
                        ),
                        connect_drag_data_get => move |_, _, data, _, _| {
                            data.set(&data.target(), 8, b"blue-key");
                        },
                    }
                },

                // second horizontal box
                gtk::Box {
                    set_orientation: gtk::Orientation::Horizontal,
                    set_halign: gtk::Align::Center,
                    set_spacing: 10,

                    // contains image and label and serves as a DropTarget
                    gtk::Button {
                        gtk::Box {
                            set_orientation: gtk::Orientation::Horizontal,
                            set_margin_all: 10,

                            gtk::Image {
                                set_from_icon_name: (Some("locked"), gtk::IconSize::Button),
                            },
                            gtk::Label {
                                set_label: "Red Door",
                            }
                        },

                        drag_dest_set: (
                            gtk::DestDefaults::ALL,
                            &[
                                gtk::TargetEntry::new(
                                    "application/x-nrelm-lock-pick",
                                    gtk::TargetFlags::SAME_APP,
                                    1,
                                ),
                            ],
                            gdk::DragAction::MOVE,
                        ),
                        connect_drag_data_received[sender] => move |_, context, _, _, data, _, time| {
                            // there is only the pick lock to unlock the door
                            let payload = String::from_utf8_lossy(&data.data()).to_string();
                            if payload == "lock-pick" {
                                sender.input(AppIn::NewStatus(
                                    String::from("Red Door unlocked with Lock Pick")
                                ));
                            } else {
                                sender.input(AppIn::NewStatus(
                                    String::from("Red Door rejects this item")
                                ));
                            }
                            context.drop_finish(true, time);
                        }
                    },

                    gtk::Button {
                        set_label: "Lock Pick",
                        drag_source_set: (
                            gdk::ModifierType::BUTTON1_MASK,
                            &[
                                gtk::TargetEntry::new(
                                    "application/x-nrelm-lock-pick",
                                    gtk::TargetFlags::SAME_APP,
                                    1,
                                ),
                            ],
                            gdk::DragAction::MOVE,
                        ),
                        connect_drag_data_get => move |_, _, data, _, _| {
                            data.set(&data.target(), 8, b"lock-pick");
                        },
                    }
                }
            }
        }
    }

    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
        match msg {
            AppIn::NewStatus(status) => {
                self.status = status;
            }
        }
    }
}

fn main() {
    let app = RelmApp::new("nrelm.example.drag-and-drop");
    app.run::<AppModel>(());
}