← luna

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 }