🔒 Repository is read-only – file editing is disabled.

PaganDE/pagan-toolkit/src/input.rs main

157 linii Raw ← Powrót
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
//! Obsługa wejścia: `wl_seat` i `wl_pointer`.
//!
//! Ten moduł spina trzy światy:
//! * surowe zdarzenia Waylanda (motion, button, enter/leave),
//! * logikę okna ([`crate::window::MyToolkitWindow`]: hover, hit-testing, krawędzie resize),
//! * żądania protokołu xdg-shell (`xdg_toplevel.move` / `xdg_toplevel.resize`).
//!
//! Kluczowy szczegół protokołu: żądania `move` i `resize` wymagają **serialu**
//! zdarzenia `wl_pointer.button` o stanie „pressed". Serial zapamiętujemy przy
//! kliknięciu i używamy od razu — nie ma sensu odkładać tego na później, bo serial
//! może wygasnąć.

use wayland_client::protocol::wl_pointer::{self, ButtonState, WlPointer};
use wayland_client::protocol::wl_seat::{self, WlSeat};
use wayland_client::{Connection, Dispatch, QueueHandle, WEnum};

use crate::connection::CompositorState;
use crate::i18n::{tr, MsgKey};
use crate::window::WindowAction;

/// Kod przycisku lewego myszy z `linux/input-event-codes.h` (BTN_LEFT).
const BTN_LEFT: u32 = 0x110;

/// Reagujemy na zdolności seata: gdy pojawi się wskaźnik, tworzymy `wl_pointer`.
impl Dispatch<WlSeat, ()> for CompositorState {
    fn event(
        state: &mut Self,
        proxy: &WlSeat,
        event: wl_seat::Event,
        _data: &(),
        _conn: &Connection,
        qh: &QueueHandle<Self>,
    ) {
        if let wl_seat::Event::Capabilities { capabilities } = event {
            // API-NOTE: reprezentacja `wl_seat::Capability` (zwykły enum vs bitflags)
            // bywa różna między wersjami `wayland-rs`, dlatego świadomie nie odczytujemy
            // konkretnych flag. W kompozytorze desktopowym seat praktycznie zawsze ma
            // wskaźnik, więc prosimy o `wl_pointer` przy pierwszym zdarzeniu zdolności.
            let _ = capabilities;

            if state.pointer.is_none() {
                let pointer = proxy.get_pointer(qh, ());
                tracing::debug!("utworzono obiekt wl_pointer");
                state.pointer = Some(pointer);
            }
        }
    }
}

impl Dispatch<WlPointer, ()> for CompositorState {
    fn event(
        state: &mut Self,
        _proxy: &WlPointer,
        event: wl_pointer::Event,
        _data: &(),
        _conn: &Connection,
        _qh: &QueueHandle<Self>,
    ) {
        match event {
            wl_pointer::Event::Enter {
                serial,
                surface_x,
                surface_y,
                ..
            } => {
                state.pointer_serial = Some(serial);
                state.last_pointer = (surface_x, surface_y);
                // Wejście kursora na powierzchnię też może zmienić hover.
                if state
                    .window
                    .handle_mouse_motion(surface_x as i32, surface_y as i32)
                {
                    state.needs_redraw = true;
                }
            }
            wl_pointer::Event::Motion {
                surface_x,
                surface_y,
                ..
            } => {
                state.last_pointer = (surface_x, surface_y);
                if state
                    .window
                    .handle_mouse_motion(surface_x as i32, surface_y as i32)
                {
                    state.needs_redraw = true;
                }
            }
            wl_pointer::Event::Button {
                serial,
                button,
                state: button_state,
                ..
            } => {
                // Interesuje nas wyłącznie wciśnięcie lewego przycisku i tylko wtedy,
                // gdy to naprawdę „pressed" (a nie „released", który ignorujemy).
                if button == BTN_LEFT && matches!(button_state, WEnum::Value(ButtonState::Pressed))
                {
                    state.pointer_serial = Some(serial);
                    let (x, y) = state.last_pointer;
                    handle_press(state, x as i32, y as i32, serial);
                }
            }
            wl_pointer::Event::Leave { .. } => {
                // Kursor opuścił powierzchnię → gaśmy hover (poza oknem nic nie podświetlamy).
                if state.window.handle_mouse_motion(-1, -1) {
                    state.needs_redraw = true;
                }
            }
            _ => {}
        }
    }
}

/// Wspólna logika „press": najpierw resize za krawędź, potem zwykły hit-test kontrolek.
///
/// Rozdzielenie jest istotne, bo te dwie akcje mają różne wymagania protokołu:
/// `resize` potrzebuje `Edges`, a `move` tylko serialu.
fn handle_press(state: &mut CompositorState, x: i32, y: i32, serial: u32) {
    // Rozwinięte menu aplikacji obsługuje klik samo (wybór pozycji albo zamknięcie).
    // Sprawdzamy to PRZED krawędziami — inaczej klik przy krawędzi zaczynałby
    // resize zamiast zamykać menu.
    if state.window.menu_is_open() {
        let action = state.window.handle_mouse_click(x, y);
        state.pending_action = Some(action);
        state.needs_redraw = true;
        return;
    }

    // 1. Krawędzie okna mają priorytet — nawet jeśli nakładają się na pasek tytułu.
    if let Some(edges) = state.window.resize_edge_at(x, y) {
        if let (Some(toplevel), Some(seat)) = (state.toplevel.as_ref(), state.seat.as_ref()) {
            toplevel.resize(seat, serial, edges.to_xdg_resize_edge());
            // Log na poziomie `info`, żeby było widać w demo, że resize zadziałał
            // (przy `debug` ginęło w natłoku zdarzeń).
            tracing::info!("{}", tr(state.locale, MsgKey::LogResize));
        }
        state.pending_action = Some(WindowAction::None);
        return;
    }

    // 2. Zwykły hit-test przycisków / paska tytułu.
    let action = state.window.handle_mouse_click(x, y);

    if let WindowAction::StartMove = action {
        // `move` przekazujemy kompozytorowi od razu, z serialem button-press.
        // API-NOTE: w `wayland-protocols` 0.32 metodą żądania `move` jest `_move`
        // (kolidowałoby z `move` w Rust); w innych wersjach może to być `move_`.
        if let (Some(toplevel), Some(seat)) = (state.toplevel.as_ref(), state.seat.as_ref()) {
            toplevel._move(seat, serial);
            tracing::debug!("przekazano xdg_toplevel.move do kompozytora");
        }
    }

    // Akcję i tak wystawiamy aplikacji — niech zaloguje/zdecyduje o dalszych krokach.
    state.pending_action = Some(action);
}