drag_and_drop/
drag_and_drop.rs

1use gtk::prelude::{ButtonExt, GtkWindowExt, OrientableExt, StaticType, ToValue, WidgetExt};
2use gtk::{self, gdk, glib};
3use relm4::{ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent};
4
5// we need a glib type that can be send through drag and drop
6// basic rust types like String, u8, i8 and others are ready to go
7// see: https://gtk-rs.org/gtk-rs-core/stable/0.18/docs/glib/value/index.html
8//
9// for custom structs this can be accomplished by registering it as
10// a boxed type
11// see: https://gtk-rs.org/gtk-rs-core/stable/latest/docs/glib/subclass/index.html#example-for-registering-a-boxed-type-for-a-rust-struct
12// or wrapped with BoxedAnyObject but it has the downside of not as easily
13// being distinguishable in conjunction with the set_types method
14// see: https://docs.rs/glib/latest/glib/struct.BoxedAnyObject.html
15
16#[derive(Clone, glib::Boxed)]
17#[boxed_type(name = "BlueKey")] // the name for the glib Type, needs to be unique
18struct BlueKey;
19
20#[derive(Clone, glib::Boxed)]
21#[boxed_type(name = "LockPick")]
22struct LockPick;
23
24struct AppModel {
25    status: String,
26}
27
28#[derive(Debug)]
29enum AppIn {
30    NewStatus(String),
31}
32
33#[relm4::component]
34impl SimpleComponent for AppModel {
35    type Init = ();
36    type Input = AppIn;
37    type Output = ();
38
39    fn init(
40        _init: Self::Init,
41        root: Self::Root,
42        sender: ComponentSender<Self>,
43    ) -> ComponentParts<Self> {
44        let model = AppModel {
45            status: String::from("Drag the key or lock pick to a door"),
46        };
47
48        let widgets = view_output!();
49        ComponentParts { model, widgets }
50    }
51
52    view! {
53        gtk::Window {
54            set_title: Some("Drag and Drop Expample"),
55            set_default_width: 300,
56            set_default_height: 100,
57
58            // outer box
59            gtk::Box {
60                set_orientation: gtk::Orientation::Vertical,
61
62                // display status message
63                gtk::Label {
64                    set_margin_all: 10,
65                    #[watch]
66                    set_label: &model.status,
67                },
68
69                gtk::Label {
70                    set_margin_all: 10,
71                    set_label: "The blue key will only unlock the blue door\nThe pick can open both",
72                },
73
74                // first horizontal box
75                gtk::CenterBox {
76                    // contains a image and label and serves as DropTarget
77                    #[wrap(Some)]
78                    set_start_widget = &gtk::Button {
79                        gtk::Box {
80                            set_margin_all: 10,
81
82                            gtk::Image {
83                                set_icon_name: Some("locked"),
84                            },
85                            gtk::Label {
86                                set_label: "Blue Door",
87                            }
88                        },
89
90                        // drops will be accepted on all widgets in Button
91                        add_controller = gtk::DropTarget {
92                            // this DropTarget will accept DragAction::MOVE and
93                            // drops with the types BlueKey and LockPick
94                            set_actions: gdk::DragAction::MOVE,
95                            set_types: &[BlueKey::static_type(),
96                                        LockPick::static_type()],
97                            connect_drop[sender] => move |_widget, value, _x, _y| {
98                                if let Ok(_key) = value.get::<BlueKey>() {
99                                    // handle your dropping of BlueKey here
100                                    sender.input(AppIn::NewStatus(
101                                        String::from("Blue Door unlocked with Blue Key")
102                                    ));
103                                    return true;
104                                }
105                                if let Ok(_pick) = value.get::<LockPick>() {
106                                    // handle your dropping of LockPick here
107                                    sender.input(AppIn::NewStatus(
108                                        String::from("Blue Door unlocked with Lock Pick")
109                                    ));
110                                    return true;
111                                }
112                                false
113                            }
114                        }
115                    },
116
117                    #[wrap(Some)]
118                    set_end_widget = &gtk::Button {
119                        set_label: "Blue Key",
120
121                        // define the Button as a DragSource
122                        add_controller = gtk::DragSource {
123                            set_actions: gdk::DragAction::MOVE,
124                            // which value will be send when dropping
125                            set_content: Some(&gdk::ContentProvider::for_value(&BlueKey.to_value())),
126                        }
127                    }
128                },
129
130                // second horizontal box
131                gtk::CenterBox {
132                    // contains image and label and serves as a DropTarget
133                    #[wrap(Some)]
134                    set_start_widget = &gtk::Button {
135                        gtk::Box {
136                            set_margin_all: 10,
137
138                            gtk::Image {
139                                set_icon_name: Some("locked"),
140                            },
141                            gtk::Label {
142                                set_label: "Red Door",
143                            }
144                        },
145
146                        add_controller = gtk::DropTarget {
147                            set_actions: gdk::DragAction::MOVE,
148                            set_types: &[LockPick::static_type()],
149                            connect_drop[sender] => move |_widget, _value, _x, _y| {
150                                // there is only the pick lock to unlock the door
151                                sender.input(AppIn::NewStatus(
152                                   String::from("Red Door unlocked with Lock Pick")
153                                ));
154                                true
155                            }
156                        }
157                    },
158
159                    #[wrap(Some)]
160                    set_end_widget = &gtk::Button {
161                        set_label: "Lock Pick",
162                        add_controller = gtk::DragSource {
163                            set_actions: gdk::DragAction::MOVE,
164                            set_content: Some(&gdk::ContentProvider::for_value(&LockPick.to_value())),
165                        }
166                    }
167                }
168            }
169        }
170    }
171
172    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
173        match msg {
174            AppIn::NewStatus(status) => {
175                self.status = status;
176            }
177        }
178    }
179}
180
181fn main() {
182    let app = RelmApp::new("relm4.example.drag-and-drop");
183    app.run::<AppModel>(());
184}