Commit 719aab0
4
files
+220
added
-16
deleted
@@ -104,6 +104,16 @@ impl Dispatch<WlDataSource, DataSourceData> for State {
104
104
}
105
105
}
106
106
}
107
+/// Stan trwającego przeciągania (drag & drop).
108
+#[derive(Default)]
109
+pub struct DndState {
110
+ /// Źródło danych przeciągania (jeśli klient je podał).
111
+ pub source: Option<WlDataSource>,
112
+ /// Okno docelowe pod kursorem.
113
+ pub slot: Option<usize>,
114
+ /// Oferta przekazana bieżącemu celowi.
115
+ pub offer: Option<WlDataOffer>,
116
+}
107
117
108
118
impl Dispatch<WlDataDevice, DataDeviceData> for State {
109
119
fn request(
@@ -123,10 +133,21 @@ impl Dispatch<WlDataDevice, DataDeviceData> for State {
123
133
state.selection = source.clone();
124
134
broadcast_selection(state, handle, resource, source.as_ref());
125
135
}
136
+ DeviceRequest::StartDrag {
137
+ source,
138
+ origin: _,
139
+ icon: _,
140
+ serial: _,
141
+ } => {
142
+ // Zaczynamy przeciąganie; cel wybierzemy przy ruchu wskaźnika.
143
+ state.dnd = Some(DndState {
144
+ source,
145
+ ..DndState::default()
146
+ });
147
+ }
126
148
DeviceRequest::Release => {
127
149
state.data_devices.retain(|d| d.id() != resource.id());
128
150
}
129
- // Drag & drop poza zakresem MVP.
130
151
_ => {}
131
152
}
132
153
}
@@ -134,23 +155,178 @@ impl Dispatch<WlDataDevice, DataDeviceData> for State {
134
155
135
156
impl Dispatch<WlDataOffer, DataOfferData> for State {
136
157
fn request(
137
- _state: &mut Self,
158
+ state: &mut Self,
138
159
_client: &Client,
139
- _resource: &WlDataOffer,
160
+ resource: &WlDataOffer,
140
161
request: OfferRequest,
141
162
data: &DataOfferData,
142
163
_handle: &DisplayHandle,
143
164
_data_init: &mut DataInit<'_, Self>,
144
165
) {
145
- if let OfferRequest::Receive { mime_type, fd } = request {
146
- // Przekaż żądanie danych do źródła.
147
- if let Some(src) = data.source.lock().unwrap().clone() {
148
- let _ = src.send_event(SourceEvent::Send {
149
- mime_type,
150
- fd: fd.as_fd(),
151
- });
166
+ match request {
167
+ OfferRequest::Receive { mime_type, fd } => {
168
+ // Przekaż żądanie danych do źródła.
169
+ if let Some(src) = data.source.lock().unwrap().clone() {
170
+ let _ = src.send_event(SourceEvent::Send {
171
+ mime_type,
172
+ fd: fd.as_fd(),
173
+ });
174
+ }
175
+ }
176
+ OfferRequest::Finish => {
177
+ // Cel zakończył transfer — domknij przeciąganie (jeśli to jego oferta).
178
+ let is_dnd = state
179
+ .dnd
180
+ .as_ref()
181
+ .and_then(|d| d.offer.as_ref())
182
+ .map(|o| o.id() == resource.id())
183
+ .unwrap_or(false);
184
+ if is_dnd {
185
+ finish_dnd(state);
186
+ }
152
187
}
188
+ _ => {}
189
+ }
190
+ }
191
+}
192
+
193
+/// Ruch wskaźnika w trakcie przeciągania: aktualizuje cel (`enter`/`leave`/`motion`).
194
+pub fn dnd_motion(state: &mut State, handle: &DisplayHandle, pos: (f64, f64)) {
195
+ if state.dnd.is_none() {
196
+ return;
197
+ }
198
+ let target = crate::seat::window_at(state, pos);
199
+ let changed = state.dnd.as_ref().and_then(|d| d.slot) != target;
200
+ if !changed {
201
+ if let Some(slot) = target {
202
+ send_motion(state, slot, pos);
203
+ }
204
+ return;
205
+ }
206
+ // Wyjście z poprzedniego celu.
207
+ let previous = state.dnd.as_mut().and_then(|d| {
208
+ d.offer = None;
209
+ d.slot
210
+ });
211
+ if let Some(prev) = previous {
212
+ if let Some(dev) = target_device(state, prev) {
213
+ let _ = dev.send_event(DeviceEvent::Leave);
214
+ }
215
+ }
216
+ // Wejście do nowego celu.
217
+ if let Some(dnd) = state.dnd.as_mut() {
218
+ dnd.slot = target;
219
+ }
220
+ if let Some(slot) = target {
221
+ enter_target(state, handle, slot, pos);
222
+ }
223
+}
224
+
225
+/// Upuszczenie w trakcie przeciągania: `drop` do celu, powiadomienie źródła.
226
+pub fn dnd_drop(state: &mut State, _handle: &DisplayHandle) {
227
+ let Some(dnd) = state.dnd.take() else {
228
+ return;
229
+ };
230
+ match dnd.slot {
231
+ Some(slot) => {
232
+ if let Some(dev) = target_device(state, slot) {
233
+ let _ = dev.send_event(DeviceEvent::Drop);
234
+ }
235
+ // Źródło dowiaduje się o wykonaniu (bez negocjacji akcji).
236
+ if let Some(src) = dnd.source {
237
+ let _ = src.send_event(SourceEvent::DndDropPerformed);
238
+ }
239
+ }
240
+ None => {
241
+ // Upuszczenie poza celem = anulowanie.
242
+ if let Some(src) = dnd.source {
243
+ let _ = src.send_event(SourceEvent::Cancelled);
244
+ }
245
+ }
246
+ }
247
+}
248
+
249
+fn finish_dnd(state: &mut State) {
250
+ if let Some(dnd) = state.dnd.take() {
251
+ if let Some(src) = dnd.source {
252
+ let _ = src.send_event(SourceEvent::DndFinished);
253
+ }
254
+ }
255
+}
256
+
257
+/// Urządzenie schowka klienta będącego właścicielem powierzchni okna `slot`.
258
+fn target_device(state: &State, slot: usize) -> Option<WlDataDevice> {
259
+ let surface = state.slots.get(slot)?.clone();
260
+ let client_id = surface.client()?.id();
261
+ state
262
+ .data_devices
263
+ .iter()
264
+ .find(|d| d.client().map(|c| c.id() == client_id).unwrap_or(false))
265
+ .cloned()
266
+}
267
+
268
+/// Wysyła `motion` do bieżącego celu.
269
+fn send_motion(state: &State, slot: usize, pos: (f64, f64)) {
270
+ let Some(dev) = target_device(state, slot) else {
271
+ return;
272
+ };
273
+ let (lx, ly) = local_coords(state, slot, pos);
274
+ let _ = dev.send_event(DeviceEvent::Motion {
275
+ time: 0,
276
+ x: lx,
277
+ y: ly,
278
+ });
279
+}
280
+
281
+/// Tworzy ofertę i wysyła `data_offer` + `enter` nowemu celowi.
282
+fn enter_target(state: &mut State, handle: &DisplayHandle, slot: usize, pos: (f64, f64)) {
283
+ let Some(dev) = target_device(state, slot) else {
284
+ return;
285
+ };
286
+ let Some(client) = dev.client() else { return };
287
+ let Some(surface) = state.slots.get(slot).cloned() else {
288
+ return;
289
+ };
290
+ let source = state.dnd.as_ref().and_then(|d| d.source.clone());
291
+ let mimes = source
292
+ .as_ref()
293
+ .and_then(|s| s.data::<DataSourceData>())
294
+ .map(|d| d.mimes.lock().unwrap().clone())
295
+ .unwrap_or_default();
296
+ let offer_data = DataOfferData {
297
+ source: Mutex::new(source),
298
+ };
299
+ let Ok(offer) =
300
+ client.create_resource::<WlDataOffer, DataOfferData, State>(handle, 3, offer_data)
301
+ else {
302
+ return;
303
+ };
304
+ let _ = dev.send_event(DeviceEvent::DataOffer { id: offer.clone() });
305
+ for mime in mimes {
306
+ let _ = offer.send_event(OfferEvent::Offer { mime_type: mime });
307
+ }
308
+ let serial = state.next_serial();
309
+ let (lx, ly) = local_coords(state, slot, pos);
310
+ let _ = dev.send_event(DeviceEvent::Enter {
311
+ serial,
312
+ surface,
313
+ x: lx,
314
+ y: ly,
315
+ id: Some(offer.clone()),
316
+ });
317
+ if let Some(dnd) = state.dnd.as_mut() {
318
+ dnd.offer = Some(offer);
319
+ }
320
+}
321
+
322
+/// Współrzędne powierzchniowe (lokalne dla treści okna `slot`).
323
+fn local_coords(state: &State, slot: usize, pos: (f64, f64)) -> (f64, f64) {
324
+ match state.windows.lock().unwrap().get(slot) {
325
+ Some(w) => {
326
+ let c = w.content();
327
+ (pos.0 - c.x(), pos.1 - c.y())
153
328
}
329
+ None => pos,
154
330
}
155
331
}
156
332
@@ -127,7 +127,7 @@ fn main() -> Result<(), Box<dyn Error>> {
127
127
std::mem::take(&mut *queue)
128
128
};
129
129
for event in &batch {
130
- seat::handle_input(&mut state, event);
130
+ seat::handle_input(&mut state, &handle, event);
131
131
}
132
132
}
133
133
@@ -16,6 +16,7 @@ use wayland_server::{
16
16
};
17
17
18
18
use crate::compositor::SurfaceData;
19
+use crate::data;
19
20
use crate::shell::XdgSurfaceData;
20
21
use crate::state::{DragState, ResizeState, SavedGeom, State, TITLEBAR};
21
22
@@ -165,10 +166,10 @@ enum Part {
165
166
}
166
167
167
168
/// Trasuje jedno zdarzenie wejścia do klientów/okien.
168
-pub fn handle_input(state: &mut State, event: &Event) {
169
+pub fn handle_input(state: &mut State, handle: &DisplayHandle, event: &Event) {
169
170
match event {
170
- Event::PointerMotion { position, .. } => pointer_motion(state, *position),
171
- Event::PointerButton(pe) => pointer_button(state, pe.position, pe.button, pe.state),
171
+ Event::PointerMotion { position, .. } => pointer_motion(state, handle, *position),
172
+ Event::PointerButton(pe) => pointer_button(state, handle, pe.position, pe.button, pe.state),
172
173
Event::PointerLeave => pointer_leave(state),
173
174
Event::Scroll(se) => pointer_axis(state, se.dx, se.dy),
174
175
Event::Key(ke) => keyboard_key(state, ke.keysym, ke.state),
@@ -268,6 +269,11 @@ fn dismiss_popups(state: &mut State, pos: Point) {
268
269
}
269
270
}
270
271
272
+/// Okno docelowe dla punktu (publiczne dla drag & drop).
273
+pub(crate) fn window_at(state: &State, pos: (f64, f64)) -> Option<usize> {
274
+ content_window(state, Point::new(pos.0, pos.1))
275
+}
276
+
271
277
fn window_geometry(state: &State, slot: usize) -> Option<(f64, f64, f64, f64)> {
272
278
let windows = state.windows.lock().unwrap();
273
279
windows
@@ -282,7 +288,8 @@ fn content_local(state: &State, slot: usize, pos: Point) -> (f64, f64) {
282
288
}
283
289
}
284
290
285
-fn pointer_motion(state: &mut State, pos: Point) {
291
+fn pointer_motion(state: &mut State, handle: &DisplayHandle, pos: Point) {
292
+ state.pointer_pos = (pos.x, pos.y);
286
293
if state.resize.is_some() {
287
294
do_resize(state, pos);
288
295
return;
@@ -294,6 +301,10 @@ fn pointer_motion(state: &mut State, pos: Point) {
294
301
w.y = pos.y - drag.off_y;
295
302
}
296
303
}
304
+ // Przeciąganie danych (drag & drop) aktualizuje cel.
305
+ if state.dnd.is_some() {
306
+ data::dnd_motion(state, handle, (pos.x, pos.y));
307
+ }
297
308
update_pointer_focus(state, pos);
298
309
if let Some((slot, _)) = state.pointer_focus.clone() {
299
310
let ptr = state.pointer.clone();
@@ -309,10 +320,20 @@ fn pointer_motion(state: &mut State, pos: Point) {
309
320
}
310
321
}
311
322
312
-fn pointer_button(state: &mut State, pos: Point, button: PointerButton, st: ButtonState) {
323
+fn pointer_button(
324
+ state: &mut State,
325
+ handle: &DisplayHandle,
326
+ pos: Point,
327
+ button: PointerButton,
328
+ st: ButtonState,
329
+) {
313
330
if st == ButtonState::Released {
314
331
state.drag = None;
315
332
state.resize = None;
333
+ // Zakończ przeciąganie danych, jeśli trwało.
334
+ if state.dnd.is_some() {
335
+ data::dnd_drop(state, handle);
336
+ }
316
337
} else {
317
338
// Klik poza popupem zamyka popup (przed trafieniem w okno).
318
339
dismiss_popups(state, pos);
@@ -11,6 +11,7 @@ use wayland_server::protocol::wl_keyboard::WlKeyboard;
11
11
use wayland_server::protocol::wl_pointer::WlPointer;
12
12
use wayland_server::protocol::wl_surface::WlSurface;
13
13
14
+use crate::data::DndState;
14
15
use crate::monitors::{OutputInfo, Outputs};
15
16
16
17
/// Wysokość paska tytułu dekoracji (SSD), w pikselach logicznych.
@@ -204,6 +205,10 @@ pub struct State {
204
205
pub selection: Option<WlDataSource>,
205
206
/// Urządzenia schowka zarejestrowane przez klientów (do rozsyłania selekcji).
206
207
pub data_devices: Vec<WlDataDevice>,
208
+ /// Stan trwającego przeciągania (drag & drop).
209
+ pub dnd: Option<DndState>,
210
+ /// Ostatnia pozycja wskaźnika (współrzędne globalne).
211
+ pub pointer_pos: (f64, f64),
207
212
/// Trwające przeciąganie okna.
208
213
pub drag: Option<DragState>,
209
214
/// Trwające przeskalowanie okna.
@@ -229,6 +234,8 @@ impl Default for State {
229
234
sent_mods: Modifiers::NONE,
230
235
selection: None,
231
236
data_devices: Vec::new(),
237
+ dnd: None,
238
+ pointer_pos: (0.0, 0.0),
232
239
drag: None,
233
240
resize: None,
234
241
}