nativeshell/shell/platform/linux/
drag_context.rs1use std::{
2 cell::{Cell, RefCell},
3 collections::HashMap,
4 mem::take,
5 rc::{Rc, Weak},
6};
7
8use cairo::{Format, ImageSurface};
9use gdk::{Atom, DragAction, EventType};
10use glib::IsA;
11use gtk::{
12 prelude::{DragContextExtManual, WidgetExt, WidgetExtManual},
13 DestDefaults, SelectionData, TargetEntry, TargetFlags, TargetList, Widget,
14};
15
16use crate::{
17 codec::Value,
18 shell::{
19 api_model::{DragData, DragEffect, DragRequest, DraggingInfo, ImageData},
20 platform::drag_data::{FallThroughDragDataAdapter, UriListDataAdapter},
21 Context, ContextRef, PlatformWindowDelegate, Point,
22 },
23};
24
25use super::{
26 drag_data::{DragDataAdapter, DragDataSetter},
27 window::PlatformWindow,
28};
29
30pub struct DropContext {
31 context: Context,
32 window: Weak<PlatformWindow>,
33 data_adapters: Vec<Box<dyn DragDataAdapter>>,
34
35 current_data: RefCell<HashMap<String, Value>>,
36 pending_data: RefCell<Vec<Atom>>,
37 drag_location: RefCell<Point>,
38 pending_effect: Cell<DragEffect>,
39 drag_context: RefCell<Option<gdk::DragContext>>,
40 dropping: Cell<bool>,
41}
42
43impl DropContext {
44 pub fn new(context: &ContextRef, window: Weak<PlatformWindow>) -> Self {
45 Self {
46 context: context.weak(),
47 window,
48 data_adapters: vec![
49 Box::new(UriListDataAdapter::new()),
50 Box::new(FallThroughDragDataAdapter::new(&context.options)),
51 ],
52 current_data: RefCell::new(HashMap::new()),
53 pending_data: RefCell::new(Vec::new()),
54 drag_location: RefCell::new(Default::default()),
55 pending_effect: Cell::new(DragEffect::None),
56 drag_context: RefCell::new(None),
57 dropping: Cell::new(false),
58 }
59 }
60
61 fn data_adapters<'a>(&'a self, context: &'a ContextRef) -> Vec<&'a dyn DragDataAdapter> {
62 context
63 .options
64 .custom_drag_data_adapters
65 .iter()
66 .chain(self.data_adapters.iter())
67 .map(|a| a.as_ref())
68 .collect()
69 }
70
71 pub fn drag_motion<T: IsA<Widget>>(
72 &self,
73 widget: &T,
74 context: &gdk::DragContext,
75 x: i32,
76 y: i32,
77 time: u32,
78 ) {
79 *self.drag_location.borrow_mut() = Point::xy(x as f64, y as f64);
80 self.drag_context.borrow_mut().replace(context.clone());
81 self.dropping.replace(false);
82
83 if !self.pending_data.borrow().is_empty() {
84 return;
85 }
86
87 self.get_data(widget, context, time);
88 }
89
90 fn get_data<T: IsA<Widget>>(&self, widget: &T, context: &gdk::DragContext, time: u32) {
91 let pending_data = {
92 let mut pending_data = self.pending_data.borrow_mut();
93
94 if !pending_data.is_empty() {
95 return;
96 }
97
98 if let Some(ctx) = self.context.get() {
99 let mut adapters = self.data_adapters(&ctx);
100
101 for target in context.list_targets() {
102 let adapter_index = adapters
103 .iter()
104 .position(|p| p.data_formats().contains(&target));
105 if let Some(adapter_index) = adapter_index {
106 pending_data.push(target);
107 adapters.remove(adapter_index);
108 }
109 }
110 }
111
112 pending_data.clone()
113 };
114
115 for data in pending_data.iter() {
116 widget.drag_get_data(context, data, time);
117 }
118 }
119
120 fn cleanup(&self) {
121 self.current_data.borrow_mut().clear();
122 self.pending_data.borrow_mut().clear();
123 self.pending_effect.replace(DragEffect::None);
124 self.drag_context.borrow_mut().take();
125 self.dropping.replace(false);
126 }
127
128 pub fn drag_leave<T: IsA<Widget>>(&self, _widget: &T, _context: &gdk::DragContext, _time: u32) {
129 self.cleanup();
130
131 self.with_delegate(|d| d.dragging_exited());
132 }
133
134 pub fn drag_drop<T: IsA<Widget>>(
135 &self,
136 widget: &T,
137 context: &gdk::DragContext,
138 x: i32,
139 y: i32,
140 time: u32,
141 ) {
142 *self.drag_location.borrow_mut() = Point::xy(x as f64, y as f64);
143 self.dropping.replace(true);
144
145 if self.pending_data.borrow().is_empty() {
146 self.get_data(widget, context, time);
147 }
148 }
149
150 #[allow(clippy::too_many_arguments)]
151 pub fn drag_data_received<T: IsA<Widget>>(
152 &self,
153 _widget: &T,
154 context: &gdk::DragContext,
155 _x: i32, _y: i32, data: &SelectionData,
158 _info: u32,
159 time: u32,
160 ) {
161 {
162 let mut pending_data = self.pending_data.borrow_mut();
163 if pending_data.is_empty() {
164 return;
165 }
166
167 if let Some(ctx) = self.context.get() {
168 let adapters = self.data_adapters(&ctx);
169 let data_type = data.data_type();
170 if let Some(pos) = pending_data.iter().position(|d| d == &data_type) {
171 pending_data.remove(pos);
172 for adapter in adapters {
173 if adapter.data_formats().contains(&data_type) {
174 adapter.retrieve_drag_data(data, &mut self.current_data.borrow_mut());
175 }
176 }
177 }
178 }
179 }
180
181 if self.pending_data.borrow().is_empty() {
182 let info = DraggingInfo {
184 location: self.drag_location.borrow().clone(),
185 data: DragData {
186 properties: take(&mut self.current_data.borrow_mut()),
187 },
188 allowed_effects: Self::convert_drag_actions_from_gtk(context.actions()),
189 };
190 if !self.dropping.get() {
191 self.with_delegate(|d| d.dragging_updated(&info));
192 } else {
193 self.with_delegate(|d| d.perform_drop(&info));
194 context.drag_finish(true, self.pending_effect.get() == DragEffect::Move, time);
195 self.cleanup();
196 }
197 }
198 }
199
200 fn convert_drag_actions_from_gtk(actions: DragAction) -> Vec<DragEffect> {
201 let mut res = Vec::new();
202 if actions.contains(DragAction::MOVE) {
203 res.push(DragEffect::Move);
204 }
205 if actions.contains(DragAction::COPY) {
206 res.push(DragEffect::Copy);
207 }
208 if actions.contains(DragAction::LINK) {
209 res.push(DragEffect::Link);
210 }
211 res
212 }
213
214 fn convert_effect_to_gtk(effect: DragEffect) -> DragAction {
215 match effect {
216 DragEffect::None => DragAction::empty(),
217 DragEffect::Copy => DragAction::COPY,
218 DragEffect::Link => DragAction::LINK,
219 DragEffect::Move => DragAction::MOVE,
220 }
221 }
222
223 fn with_delegate<F>(&self, callback: F)
224 where
225 F: FnOnce(Rc<dyn PlatformWindowDelegate>),
226 {
227 let win = self.window.upgrade();
228 if let Some(delegate) = win.and_then(|w| w.delegate.upgrade()) {
229 callback(delegate);
230 }
231 }
232
233 pub fn set_pending_effect(&self, effect: DragEffect) {
234 self.pending_effect.replace(effect);
235 if let Some(drag_context) = self.drag_context.borrow().clone() {
236 drag_context.drag_status(Self::convert_effect_to_gtk(effect), 0);
237 }
238 }
239
240 pub fn register<T: IsA<Widget>>(&self, widget: &T) {
241 let mut atoms = Vec::<Atom>::new();
242 if let Some(ctx) = self.context.get() {
243 let adapters = self.data_adapters(&ctx);
244
245 for adapter in adapters {
246 adapter.data_formats().iter().for_each(|a| atoms.push(*a));
247 }
248 }
249 let entries: Vec<TargetEntry> = atoms
250 .iter()
251 .map(|a| TargetEntry::new(&a.name(), TargetFlags::empty(), 0))
252 .collect();
253 widget.drag_dest_set(
254 DestDefaults::empty(),
258 &entries,
259 DragAction::MOVE | DragAction::COPY | DragAction::LINK,
260 );
261 }
262}
263
264pub struct DragContext {
269 context: Context,
270 window: Weak<PlatformWindow>,
271 data_adapters: Vec<Box<dyn DragDataAdapter>>,
272 data: RefCell<Vec<Box<dyn DragDataSetter>>>,
273 dragging: Cell<bool>,
274}
275
276impl DragContext {
277 pub fn new(context: &ContextRef, window: Weak<PlatformWindow>) -> Self {
278 Self {
279 context: context.weak(),
280 window,
281 data_adapters: vec![
282 Box::new(UriListDataAdapter::new()),
283 Box::new(FallThroughDragDataAdapter::new(&context.options)),
284 ],
285 data: RefCell::new(Vec::new()),
286 dragging: Cell::new(false),
287 }
288 }
289
290 fn prepare_data(&self, request: &mut DragRequest) -> TargetList {
291 let properties = &mut request.data.properties;
292 let mut data = self.data.borrow_mut();
293 data.clear();
294
295 if let Some(context) = self.context.get() {
296 for a in &context.options.custom_drag_data_adapters {
297 data.append(&mut a.prepare_drag_data(properties));
298 }
299 }
300 for a in &self.data_adapters {
301 data.append(&mut a.prepare_drag_data(properties));
302 }
303
304 let targets = TargetList::new(&[]);
305 data.iter().enumerate().all(|(index, source)| {
306 for k in source.data_formats() {
307 targets.add(&k, 0, index as u32);
308 }
309 true
310 });
311
312 targets
313 }
314
315 fn convert_effects_to_gtk(effects: &[DragEffect]) -> DragAction {
316 let mut res = DragAction::empty();
317 for e in effects {
318 res |= DropContext::convert_effect_to_gtk(*e);
319 }
320 res
321 }
322
323 pub fn begin_drag<T: IsA<Widget>>(&self, mut request: DragRequest, widget: &T) {
324 let window = self.window.upgrade().unwrap();
325
326 let events = window.last_event.borrow();
327 let drag_event = events
328 .values()
329 .filter(|e| {
330 e.event_type() == EventType::ButtonPress
331 || e.event_type() == EventType::MotionNotify
332 })
333 .max_by(|e1, e2| e1.time().cmp(&e2.time()));
334
335 let button = events
336 .get(&EventType::ButtonPress)
337 .and_then(|e| e.button())
338 .unwrap_or(0);
339
340 let targets = self.prepare_data(&mut request);
341
342 let context = widget.drag_begin_with_coordinates(
343 &targets,
344 Self::convert_effects_to_gtk(&request.allowed_effects),
345 button as i32,
346 drag_event,
347 -1,
348 -1,
349 );
350
351 let event_coords = drag_event.and_then(|e| e.coords()).unwrap_or((0.0, 0.0));
352
353 if let Some(context) = context {
354 let surface = &Self::surface_from_image_data(request.image);
355 let scale_factor = widget.scale_factor() as f64;
356 surface.set_device_scale(widget.scale_factor() as f64, widget.scale_factor() as f64);
357 surface.set_device_offset(
358 (request.rect.x - event_coords.0) * scale_factor,
359 (request.rect.y - event_coords.1) * scale_factor,
360 );
361 context.drag_set_icon_surface(surface)
362 }
363
364 self.dragging.replace(true);
365 }
366
367 fn surface_from_image_data(image: ImageData) -> ImageSurface {
368 let mut data = image.data;
369 for offset in (0..data.len()).step_by(4) {
370 let (r, g, b, a) = (
371 data[offset],
372 data[offset + 1],
373 data[offset + 2],
374 data[offset + 3],
375 );
376 data[offset] = b;
377 data[offset + 1] = g;
378 data[offset + 2] = r;
379 data[offset + 3] = a;
380 }
381 let surface = ImageSurface::create_for_data(
382 data,
383 Format::ARgb32,
384 image.width,
385 image.height,
386 image.bytes_per_row,
387 );
388 surface.unwrap()
389 }
390
391 pub fn get_data(&self, selection_data: &SelectionData, target_info: u32) {
392 let data = self.data.borrow();
393 let data = data.get(target_info as usize);
394 if let Some(data) = data {
395 data.set(selection_data);
396 }
397 }
398
399 fn with_delegate<F>(&self, callback: F)
400 where
401 F: FnOnce(Rc<dyn PlatformWindowDelegate>),
402 {
403 let win = self.window.upgrade();
404 if let Some(delegate) = win.and_then(|w| w.delegate.upgrade()) {
405 callback(delegate);
406 }
407 }
408
409 fn cleanup(&self) {
410 self.data.borrow_mut().clear();
411 self.dragging.replace(false);
412 }
413
414 pub fn drag_failed(&self) {
415 self.cleanup();
416 self.with_delegate(|d| d.drag_ended(DragEffect::None));
417 }
418
419 pub fn drag_end(&self, context: &gdk::DragContext) {
420 if self.dragging.get() {
421 self.cleanup();
423 let action = context.selected_action();
424 let action = DropContext::convert_drag_actions_from_gtk(action)
425 .first()
426 .cloned()
427 .unwrap_or(DragEffect::None);
428 self.with_delegate(|d| d.drag_ended(action));
429 }
430 }
431}