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 = >k::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 = >k::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 = >k::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 = >k::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}