halley-wl 0.2.0

Wayland backend and rendering implementation for the Halley Wayland compositor.
use std::collections::HashMap;

use halley_core::cluster_layout::{ClusterWorkspaceLayoutKind, cluster_visible_limit};

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

pub(crate) fn is_active_cluster_workspace_member(
    st: &Halley,
    node_id: halley_core::field::NodeId,
) -> bool {
    st.model
        .field
        .cluster_id_for_member_public(node_id)
        .zip(st.model.monitor_state.node_monitor.get(&node_id))
        .is_some_and(|(cid, monitor)| st.active_cluster_workspace_for_monitor(monitor) == Some(cid))
}

pub(crate) fn active_stacking_front_member_for_monitor(
    st: &Halley,
    monitor: &str,
) -> Option<halley_core::field::NodeId> {
    if !matches!(
        st.runtime.tuning.cluster_layout_kind(),
        ClusterWorkspaceLayoutKind::Stacking
    ) {
        return None;
    }
    let cid = st.active_cluster_workspace_for_monitor(monitor)?;
    st.model.field.cluster(cid)?.members().first().copied()
}

pub(crate) fn active_stacking_visible_members_for_monitor(
    st: &Halley,
    monitor: &str,
) -> Vec<halley_core::field::NodeId> {
    if !matches!(
        st.runtime.tuning.cluster_layout_kind(),
        ClusterWorkspaceLayoutKind::Stacking
    ) {
        return Vec::new();
    }
    let Some(cid) = st.active_cluster_workspace_for_monitor(monitor) else {
        return Vec::new();
    };
    let Some(cluster) = st.model.field.cluster(cid) else {
        return Vec::new();
    };
    let visible_len = cluster_visible_limit(
        ClusterWorkspaceLayoutKind::Stacking,
        st.runtime.tuning.active_cluster_visible_limit(),
    )
    .min(cluster.members().len());
    cluster
        .members()
        .iter()
        .take(visible_len)
        .copied()
        .collect()
}

pub(crate) fn is_active_stacking_workspace_member(
    st: &Halley,
    node_id: halley_core::field::NodeId,
) -> bool {
    let Some(monitor) = st.model.monitor_state.node_monitor.get(&node_id) else {
        return false;
    };
    active_stacking_visible_members_for_monitor(st, monitor.as_str()).contains(&node_id)
}

pub(crate) fn node_allows_interactive_resize(
    st: &Halley,
    node_id: halley_core::field::NodeId,
) -> bool {
    st.model
        .field
        .node(node_id)
        .is_some_and(|node| node.state == halley_core::field::NodeState::Active)
        && !crate::compositor::workspace::state::node_in_maximize_session(st, node_id)
        && !is_active_stacking_workspace_member(st, node_id)
        && !(matches!(
            st.runtime.tuning.cluster_layout_kind(),
            ClusterWorkspaceLayoutKind::Tiling
        ) && is_active_cluster_workspace_member(st, node_id))
}

pub(crate) fn stacking_render_order_map(
    members: &[halley_core::field::NodeId],
    max_visible: usize,
) -> HashMap<halley_core::field::NodeId, usize> {
    let visible_len =
        cluster_visible_limit(ClusterWorkspaceLayoutKind::Stacking, max_visible).min(members.len());
    members
        .iter()
        .take(visible_len)
        .enumerate()
        .map(|(index, &node_id)| (node_id, visible_len.saturating_sub(index + 1)))
        .collect()
}

pub(crate) fn active_stacking_render_order_for_monitor(
    st: &Halley,
    monitor: &str,
) -> HashMap<halley_core::field::NodeId, usize> {
    if !matches!(
        st.runtime.tuning.cluster_layout_kind(),
        ClusterWorkspaceLayoutKind::Stacking
    ) {
        return HashMap::new();
    }
    let Some(cid) = st.active_cluster_workspace_for_monitor(monitor) else {
        return HashMap::new();
    };
    let Some(cluster) = st.model.field.cluster(cid) else {
        return HashMap::new();
    };
    stacking_render_order_map(
        cluster.members(),
        st.runtime.tuning.active_cluster_visible_limit(),
    )
}

pub(crate) fn stack_focus_target_for_node(
    st: &Halley,
    node_id: halley_core::field::NodeId,
) -> Option<halley_core::field::NodeId> {
    let monitor = st.model.monitor_state.node_monitor.get(&node_id)?;
    let cid = st.model.field.cluster_id_for_member_public(node_id)?;
    (st.active_cluster_workspace_for_monitor(monitor.as_str()) == Some(cid))
        .then(|| active_stacking_front_member_for_monitor(st, monitor.as_str()))
        .flatten()
}

#[cfg(test)]
mod tests {
    use super::*;
    use halley_core::field::Vec2;
    use smithay::reexports::wayland_server::Display;
    use std::time::Instant;

    #[test]
    fn active_cluster_workspace_member_matches_current_monitor_workspace() {
        let dh = Display::<Halley>::new().expect("display").handle();
        let mut st = Halley::new_for_test(&dh, halley_config::RuntimeTuning::default());

        let monitor = st.model.monitor_state.current_monitor.clone();
        let master = st.model.field.spawn_surface(
            "master",
            Vec2 { x: 100.0, y: 100.0 },
            Vec2 { x: 320.0, y: 240.0 },
        );
        let stack = st.model.field.spawn_surface(
            "stack",
            Vec2 { x: 500.0, y: 100.0 },
            Vec2 { x: 320.0, y: 240.0 },
        );
        st.assign_node_to_monitor(master, monitor.as_str());
        st.assign_node_to_monitor(stack, monitor.as_str());

        let cid = st
            .model
            .field
            .create_cluster(vec![master, stack])
            .expect("cluster");
        let core = st.model.field.collapse_cluster(cid).expect("core");
        st.assign_node_to_monitor(core, monitor.as_str());

        assert!(!is_active_cluster_workspace_member(&st, master));
        assert!(st.toggle_cluster_workspace_by_core(core, Instant::now()));
        assert!(is_active_cluster_workspace_member(&st, master));
        assert!(is_active_cluster_workspace_member(&st, stack));
        assert!(!is_active_cluster_workspace_member(&st, core));
    }

    #[test]
    fn active_stacking_helpers_report_front_member_and_render_order() {
        let dh = Display::<Halley>::new().expect("display").handle();
        let mut tuning = halley_config::RuntimeTuning::default();
        tuning.stacking_max_visible = 3;
        let mut st = Halley::new_for_test(&dh, tuning);

        let monitor = st.model.monitor_state.current_monitor.clone();
        let a = st.model.field.spawn_surface(
            "A",
            Vec2 { x: 100.0, y: 100.0 },
            Vec2 { x: 320.0, y: 240.0 },
        );
        let b = st.model.field.spawn_surface(
            "B",
            Vec2 { x: 120.0, y: 100.0 },
            Vec2 { x: 320.0, y: 240.0 },
        );
        let c = st.model.field.spawn_surface(
            "C",
            Vec2 { x: 140.0, y: 100.0 },
            Vec2 { x: 320.0, y: 240.0 },
        );
        let d = st.model.field.spawn_surface(
            "D",
            Vec2 { x: 160.0, y: 100.0 },
            Vec2 { x: 320.0, y: 240.0 },
        );
        for id in [a, b, c, d] {
            st.assign_node_to_monitor(id, monitor.as_str());
        }

        let cid = st
            .model
            .field
            .create_cluster(vec![a, b, c, d])
            .expect("cluster");
        let core = st.model.field.collapse_cluster(cid).expect("core");
        st.assign_node_to_monitor(core, monitor.as_str());
        assert!(st.toggle_cluster_workspace_by_core(core, Instant::now()));

        assert_eq!(
            active_stacking_front_member_for_monitor(&st, monitor.as_str()),
            Some(a)
        );

        let ranks = active_stacking_render_order_for_monitor(&st, monitor.as_str());
        assert_eq!(ranks.get(&a), Some(&2));
        assert_eq!(ranks.get(&b), Some(&1));
        assert_eq!(ranks.get(&c), Some(&0));
        assert_eq!(ranks.get(&d), None);
        assert_eq!(stack_focus_target_for_node(&st, c), Some(a));
    }

    #[test]
    fn maximize_session_blocks_interactive_resize() {
        let dh = Display::<Halley>::new().expect("display").handle();
        let mut tuning = halley_config::RuntimeTuning::default();
        tuning.animations.maximize.enabled = false;
        let mut st = Halley::new_for_test(&dh, tuning);

        let monitor = st.model.monitor_state.current_monitor.clone();
        let window = st.model.field.spawn_surface(
            "window",
            Vec2 { x: 100.0, y: 100.0 },
            Vec2 { x: 320.0, y: 240.0 },
        );
        st.assign_node_to_monitor(window, monitor.as_str());

        assert!(
            crate::compositor::actions::window::toggle_node_maximize_state(
                &mut st,
                window,
                Instant::now(),
                monitor.as_str(),
            )
        );

        assert!(!node_allows_interactive_resize(&st, window));
    }
}