halley-wl 0.1.0

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

use crate::compositor::root::Halley;

pub(super) fn cluster_join_dwell_ms(st: &Halley) -> u64 {
    st.runtime.tuning.cluster_dwell_ms
}

pub(super) fn update_cluster_join_candidate(
    st: &mut Halley,
    node_id: halley_core::field::NodeId,
    monitor: &str,
    desired_center: halley_core::field::Vec2,
    now: Instant,
) -> bool {
    if st
        .model
        .field
        .cluster_id_for_member_public(node_id)
        .is_some()
    {
        st.input.interaction_state.cluster_join_candidate = None;
        return false;
    }
    let Some(node) = st.model.field.node(node_id) else {
        st.input.interaction_state.cluster_join_candidate = None;
        return false;
    };
    if node.kind != halley_core::field::NodeKind::Surface {
        st.input.interaction_state.cluster_join_candidate = None;
        return false;
    }

    let mover_ext = if matches!(
        node.state,
        halley_core::field::NodeState::Active | halley_core::field::NodeState::Drifting
    ) {
        st.surface_window_collision_extents(node)
    } else {
        st.collision_extents_for_node(node)
    };
    let candidate = st.cluster_bloom_for_monitor(monitor).and_then(|open_cid| {
        let cluster = st.model.field.cluster(open_cid)?;
        if !cluster.is_collapsed() {
            return None;
        }
        let core_id = cluster.core?;
        let core = st.model.field.node(core_id)?;
        let core_monitor = st
            .model
            .monitor_state
            .node_monitor
            .get(&core_id)
            .map(String::as_str)
            .unwrap_or(monitor);
        if core_monitor != monitor {
            return None;
        }
        let core_ext = st.collision_extents_for_node(core);
        let gap = st.non_overlap_gap_world();
        let mover_left = desired_center.x - mover_ext.left;
        let mover_right = desired_center.x + mover_ext.right;
        let mover_top = desired_center.y - mover_ext.top;
        let mover_bottom = desired_center.y + mover_ext.bottom;
        let core_left = core.pos.x - core_ext.left - gap;
        let core_right = core.pos.x + core_ext.right + gap;
        let core_top = core.pos.y - core_ext.top - gap;
        let core_bottom = core.pos.y + core_ext.bottom + gap;
        let touching_gap = mover_right >= core_left
            && mover_left <= core_right
            && mover_bottom >= core_top
            && mover_top <= core_bottom;
        touching_gap.then_some(open_cid)
    });

    let Some(cluster_id) = candidate else {
        st.input.interaction_state.cluster_join_candidate = None;
        return false;
    };
    let now_ms = st.now_ms(now);
    let keep_started_at = st
        .input
        .interaction_state
        .cluster_join_candidate
        .as_ref()
        .filter(|existing| {
            existing.cluster_id == cluster_id
                && existing.node_id == node_id
                && existing.monitor == monitor
        })
        .map(|existing| existing.started_at_ms)
        .unwrap_or(now_ms);
    let dwell_ms = cluster_join_dwell_ms(st);
    st.input.interaction_state.cluster_join_candidate = Some(
        crate::compositor::interaction::state::ClusterJoinCandidate {
            cluster_id,
            node_id,
            monitor: monitor.to_string(),
            started_at_ms: keep_started_at,
            ready: now_ms.saturating_sub(keep_started_at) >= dwell_ms,
        },
    );
    false
}