halley-wl 0.3.1

Wayland backend and rendering implementation for the Halley Wayland compositor.
use std::time::Instant;

use crate::backend::interface::BackendView;
use crate::compositor::actions::window::{
    focus_or_reveal_collapsed_node_from_click, promote_node_level,
};
use crate::compositor::interaction::state::PendingMovePress;
use crate::compositor::interaction::{HitNode, PointerState};
use crate::compositor::root::Halley;
use crate::compositor::surface::node_allows_interactive_resize;

use super::frame::ButtonFrame;
use super::release::{
    clear_pointer_activity, collapse_bloom_for_core_if_open, restore_fullscreen_click_focus,
};
use crate::input::pointer::motion::{begin_drag, node_is_pointer_draggable};
use crate::input::pointer::resize::begin_resize;

fn begin_pan_if_allowed(
    st: &mut Halley,
    ps: &mut PointerState,
    backend: &dyn BackendView,
    monitor: String,
    global_sx: f32,
    global_sy: f32,
) {
    if crate::compositor::monitor::camera::camera_controller(&*st)
        .pan_blocked_on_monitor(monitor.as_str())
    {
        backend.request_redraw();
        return;
    }
    ps.panning = true;
    ps.pan_monitor = Some(monitor);
    ps.pan_last_screen = (global_sx, global_sy);
    crate::compositor::interaction::pointer::set_cursor_override_icon(
        st,
        Some(smithay::input::pointer::CursorIcon::Grabbing),
    );
    backend.request_redraw();
}

pub(super) fn handle_pan_binding_press(
    st: &mut Halley,
    ps: &mut PointerState,
    backend: &dyn BackendView,
    hit: Option<HitNode>,
    frame: ButtonFrame,
) {
    if let Some(hit) = hit
        && node_is_pointer_draggable(st, hit.node_id)
    {
        if hit.is_core {
            let _ = collapse_bloom_for_core_if_open(st, hit.node_id);
        }
        begin_drag(st, ps, backend, hit, frame, frame.world_now, false, true);
        return;
    }
    let monitor = st.monitor_for_screen_or_current(frame.global_sx, frame.global_sy);
    begin_pan_if_allowed(st, ps, backend, monitor, frame.global_sx, frame.global_sy);
}

pub(super) fn begin_bloom_pull_preview(
    st: &mut Halley,
    cluster_id: halley_core::cluster::ClusterId,
    member_id: halley_core::field::NodeId,
    core_sx: i32,
    core_sy: i32,
    slot_sx: i32,
    slot_sy: i32,
    monitor: &str,
) {
    st.input.interaction_state.bloom_pull_preview =
        Some(crate::compositor::interaction::state::BloomPullPreview {
            cluster_id,
            member_id,
            monitor: monitor.to_string(),
            core_screen: halley_core::field::Vec2 {
                x: core_sx as f32,
                y: core_sy as f32,
            },
            slot_screen: halley_core::field::Vec2 {
                x: slot_sx as f32,
                y: slot_sy as f32,
            },
            pointer_screen: halley_core::field::Vec2 {
                x: slot_sx as f32,
                y: slot_sy as f32,
            },
            display_offset: halley_core::field::Vec2 { x: 0.0, y: 0.0 },
            hold_progress: 0.0,
            phase: crate::compositor::interaction::state::BloomPullPhase::Pressed,
        });
}

pub(super) fn handle_left_press(
    st: &mut Halley,
    ps: &mut PointerState,
    backend: &dyn BackendView,
    drag_binding_active: bool,
    allow_monitor_transfer: bool,
    hit: Option<HitNode>,
    frame: ButtonFrame,
) {
    let Some(hit) = hit else {
        let now = Instant::now();
        let monitor = st.monitor_for_screen_or_current(frame.global_sx, frame.global_sy);
        let _ = st.close_cluster_bloom_for_monitor(monitor.as_str());
        st.focus_monitor_view(monitor.as_str(), now);
        begin_pan_if_allowed(st, ps, backend, monitor, frame.global_sx, frame.global_sy);
        return;
    };
    if st.runtime.tuning.input.raise_on_click {
        let _ = st.raise_overlap_policy_node(hit.node_id);
    }
    if frame.workspace_active {
        let drag_target_ok = node_is_pointer_draggable(st, hit.node_id);
        if drag_binding_active && drag_target_ok && !hit.is_core {
            begin_drag(
                st,
                ps,
                backend,
                hit,
                frame,
                frame.world_now,
                allow_monitor_transfer,
                drag_binding_active,
            );
            return;
        }
        if hit.move_surface && drag_target_ok && !hit.is_core {
            let now = Instant::now();
            st.focus_pointer_target(hit.node_id, 700, now);
            st.input.interaction_state.pending_move_press = Some(PendingMovePress {
                node_id: hit.node_id,
                press_global_sx: frame.global_sx,
                press_global_sy: frame.global_sy,
                workspace_active: true,
            });
            backend.request_redraw();
            return;
        }
        handle_workspace_left_press(st, ps, backend, hit);
        return;
    }

    if !drag_binding_active && hit.is_core {
        handle_core_left_press(st, ps, backend, hit, frame);
        return;
    }

    if !drag_binding_active && restore_fullscreen_click_focus(st, hit.node_id, Instant::now()) {
        backend.request_redraw();
    }

    let drag_target_ok = node_is_pointer_draggable(st, hit.node_id);
    if hit.move_surface && drag_target_ok && !hit.is_core {
        let now = Instant::now();
        st.focus_pointer_target(hit.node_id, 700, now);
        st.input.interaction_state.pending_move_press = Some(PendingMovePress {
            node_id: hit.node_id,
            press_global_sx: frame.global_sx,
            press_global_sy: frame.global_sy,
            workspace_active: false,
        });
        backend.request_redraw();
        return;
    }

    if !drag_binding_active
        && !hit.move_surface
        && st.model.field.node(hit.node_id).is_some_and(|n| {
            n.kind == halley_core::field::NodeKind::Surface
                && st.model.field.is_visible(hit.node_id)
        })
    {
        st.focus_pointer_target(hit.node_id, 30_000, Instant::now());
    }

    let mut handled_node_click = false;
    if !drag_binding_active && !hit.move_surface && !hit.is_core {
        let is_node = st
            .model
            .field
            .node(hit.node_id)
            .is_some_and(|n| n.state == halley_core::field::NodeState::Node);
        if is_node {
            let now = Instant::now();
            if handle_collapsed_node_left_press(st, hit.node_id, now) {
                backend.request_redraw();
            }
            handled_node_click = true;
        }
    }

    if drag_binding_active && drag_target_ok && !handled_node_click {
        if hit.is_core {
            let _ = collapse_bloom_for_core_if_open(st, hit.node_id);
        }
        begin_drag(
            st,
            ps,
            backend,
            hit,
            frame,
            frame.world_now,
            allow_monitor_transfer,
            drag_binding_active,
        );
        return;
    }

    if hit.is_core {
        let now = Instant::now();
        st.focus_pointer_target(hit.node_id, 700, now);
        backend.request_redraw();
    }
}

pub(super) fn handle_right_press(
    st: &mut Halley,
    ps: &mut PointerState,
    backend: &dyn BackendView,
    resize_binding_active: bool,
    hit: Option<HitNode>,
    frame: ButtonFrame,
) {
    let Some(hit) = hit else {
        if frame.workspace_active {
            clear_pointer_activity(st, ps);
            return;
        }
        backend.request_redraw();
        return;
    };
    let can_resize = node_allows_interactive_resize(st, hit.node_id);
    if resize_binding_active && can_resize {
        begin_resize(st, ps, backend, hit, frame);
    }
}

pub(super) fn handle_move_binding_press(
    st: &mut Halley,
    ps: &mut PointerState,
    backend: &dyn BackendView,
    hit: Option<HitNode>,
    frame: ButtonFrame,
    allow_monitor_transfer: bool,
) {
    let Some(hit) = hit else {
        if frame.workspace_active {
            clear_pointer_activity(st, ps);
            return;
        }
        let now = Instant::now();
        let monitor = st.monitor_for_screen_or_current(frame.global_sx, frame.global_sy);
        st.focus_monitor_view(monitor.as_str(), now);
        backend.request_redraw();
        return;
    };
    let drag_target_ok = node_is_pointer_draggable(st, hit.node_id);
    if drag_target_ok {
        if hit.is_core {
            let _ = collapse_bloom_for_core_if_open(st, hit.node_id);
        }
        begin_drag(
            st,
            ps,
            backend,
            hit,
            frame,
            frame.world_now,
            allow_monitor_transfer,
            true,
        );
    }
}

pub(super) fn handle_resize_binding_press(
    st: &mut Halley,
    ps: &mut PointerState,
    backend: &dyn BackendView,
    hit: Option<HitNode>,
    frame: ButtonFrame,
) {
    if frame.workspace_active {
        clear_pointer_activity(st, ps);
        return;
    }

    let Some(hit) = hit else {
        let now = Instant::now();
        let monitor = st.monitor_for_screen_or_current(frame.global_sx, frame.global_sy);
        st.focus_monitor_view(monitor.as_str(), now);
        begin_pan_if_allowed(st, ps, backend, monitor, frame.global_sx, frame.global_sy);
        return;
    };
    let can_resize = node_allows_interactive_resize(st, hit.node_id);
    if can_resize {
        begin_resize(st, ps, backend, hit, frame);
    }
}

fn handle_collapsed_node_left_press(
    st: &mut Halley,
    node_id: halley_core::field::NodeId,
    now: Instant,
) -> bool {
    let now_ms = st.now_ms(now);
    if st
        .input
        .interaction_state
        .pending_collapsed_node_click
        .as_ref()
        .is_some_and(|pending| pending.node_id == node_id && pending.deadline_ms > now_ms)
    {
        st.input.interaction_state.pending_collapsed_node_click = None;
        st.input.interaction_state.pending_collapsed_node_press = None;
        st.input.interaction_state.pending_core_click = None;
        return promote_node_level(st, node_id, now);
    }

    st.input.interaction_state.pending_core_click = None;
    st.input.interaction_state.pending_collapsed_node_click = None;
    st.input.interaction_state.pending_collapsed_node_press =
        Some(crate::compositor::interaction::state::PendingCollapsedNodePress { node_id });
    focus_or_reveal_collapsed_node_from_click(st, node_id, now)
}

pub(crate) fn handle_core_left_press(
    st: &mut Halley,
    _ps: &mut PointerState,
    backend: &dyn BackendView,
    hit: HitNode,
    frame: ButtonFrame,
) {
    let now = Instant::now();
    let now_ms = st.now_ms(now);
    st.input.interaction_state.pending_core_hover = None;
    st.input.interaction_state.pending_collapsed_node_press = None;
    st.input.interaction_state.pending_collapsed_node_click = None;
    st.set_interaction_focus(Some(hit.node_id), 700, now);
    if st
        .input
        .interaction_state
        .pending_core_click
        .as_ref()
        .is_some_and(|pending| {
            pending.node_id == hit.node_id
                && pending.monitor == st.model.monitor_state.current_monitor
                && pending.deadline_ms > now_ms
        })
    {
        let _ = st.toggle_cluster_workspace_by_core(hit.node_id, now);
        st.input.interaction_state.pending_core_click = None;
    } else {
        st.input.interaction_state.pending_core_press =
            Some(crate::compositor::interaction::state::PendingCorePress {
                node_id: hit.node_id,
                monitor: st.model.monitor_state.current_monitor.clone(),
                press_global_sx: frame.global_sx,
                press_global_sy: frame.global_sy,
            });
    }
    backend.request_redraw();
}

pub(crate) fn handle_workspace_left_press(
    st: &mut Halley,
    ps: &mut PointerState,
    backend: &dyn BackendView,
    hit: HitNode,
) {
    let now = Instant::now();
    let monitor = st.model.monitor_state.current_monitor.clone();
    if let Some(rect) = st.cluster_overflow_rect_for_monitor(monitor.as_str()) {
        let (.., local_sx, local_sy) =
            st.local_screen_in_monitor(monitor.as_str(), ps.screen.0, ps.screen.1);
        let inside = local_sx >= rect.x
            && local_sx <= rect.x + rect.w
            && local_sy >= rect.y
            && local_sy <= rect.y + rect.h;
        if inside {
            st.reveal_cluster_overflow_for_monitor(monitor.as_str(), st.now_ms(now));
        } else {
            st.hide_cluster_overflow_for_monitor(monitor.as_str());
        }
    }
    if hit.move_surface && !hit.is_core {
        backend.request_redraw();
        return;
    }
    let focus_hold_ms = if hit.is_core { 700 } else { 30_000 };
    st.focus_pointer_target(hit.node_id, focus_hold_ms, now);
    backend.request_redraw();
}