use std::collections::HashMap;
use std::time::Instant;
use halley_config::{CloseRestorePanMode, RuntimeTuning};
use halley_core::field::{Field, NodeId, Vec2};
use crate::compositor::clusters::state::ClusterState;
use crate::compositor::focus::state::FocusState;
use crate::compositor::fullscreen::state::FullscreenState;
use crate::compositor::monitor::state::MonitorState;
use crate::compositor::root::Halley;
pub(crate) struct FocusReadContext<'a> {
field: &'a Field,
cluster_state: &'a ClusterState,
focus_state: &'a FocusState,
fullscreen_state: &'a FullscreenState,
monitor_state: &'a MonitorState,
tuning: &'a RuntimeTuning,
viewport: halley_core::viewport::Viewport,
usable_viewports: HashMap<String, halley_core::viewport::Viewport>,
focused_monitor: &'a str,
}
enum CloseRestorePanPlan {
None,
PanTo(Vec2),
}
impl<'a> FocusReadContext<'a> {
fn surface_node_matches(
&self,
id: NodeId,
allow_active: bool,
allow_node: bool,
monitor: Option<&str>,
) -> bool {
self.field.node(id).is_some_and(|n| {
self.field.is_visible(id)
&& n.kind == halley_core::field::NodeKind::Surface
&& match n.state {
halley_core::field::NodeState::Active => allow_active,
halley_core::field::NodeState::Node => allow_node,
_ => false,
}
&& monitor.is_none_or(|monitor| {
self.monitor_state
.node_monitor
.get(&id)
.is_some_and(|m| m == monitor)
})
})
}
fn fullscreen_focus_override(&self, st: &Halley, requested: Option<NodeId>) -> Option<NodeId> {
match requested {
None => self
.fullscreen_state
.fullscreen_active_node
.get(self.focused_monitor)
.copied(),
Some(requested_id) => {
let requested_monitor = self
.fullscreen_monitor_for_node(requested_id)
.map(str::to_string)
.or_else(|| self.monitor_state.node_monitor.get(&requested_id).cloned());
let Some(requested_monitor) = requested_monitor else {
return requested;
};
let fullscreen_id = self
.fullscreen_state
.fullscreen_active_node
.get(requested_monitor.as_str())
.copied();
let fullscreen_monitor = fullscreen_id.and_then(|fullscreen_id| {
self.fullscreen_monitor_for_node(fullscreen_id).or_else(|| {
self.monitor_state
.node_monitor
.get(&fullscreen_id)
.map(String::as_str)
})
});
if fullscreen_id == Some(requested_id) {
return requested;
}
let requested_monitor = self
.monitor_state
.node_monitor
.get(&requested_id)
.map(String::as_str)
.or(fullscreen_monitor);
if requested_monitor == fullscreen_monitor {
if st.node_has_overlap_policy(requested_id) {
requested
} else {
fullscreen_id
}
} else {
requested
}
}
}
}
fn fullscreen_monitor_for_node(&self, id: NodeId) -> Option<&str> {
self.fullscreen_state
.fullscreen_active_node
.iter()
.find_map(|(monitor, nid)| (*nid == id).then_some(monitor.as_str()))
}
fn viewport_for_monitor(&self, monitor: &str) -> halley_core::viewport::Viewport {
self.usable_viewports
.get(monitor)
.copied()
.unwrap_or_else(|| {
if self.monitor_state.current_monitor == monitor {
self.viewport
} else {
self.monitor_state
.monitors
.get(monitor)
.map(|space| space.viewport)
.unwrap_or(self.viewport)
}
})
}
fn surface_is_fully_visible_on_monitor(&self, st: &Halley, monitor: &str, id: NodeId) -> bool {
let Some(node) = self.field.node(id) else {
return false;
};
let ext = st.spawn_obstacle_extents_for_node(node);
let viewport = self.viewport_for_monitor(monitor);
let min_x = viewport.center.x - viewport.size.x * 0.5;
let max_x = viewport.center.x + viewport.size.x * 0.5;
let min_y = viewport.center.y - viewport.size.y * 0.5;
let max_y = viewport.center.y + viewport.size.y * 0.5;
node.pos.x - ext.left >= min_x
&& node.pos.x + ext.right <= max_x
&& node.pos.y - ext.top >= min_y
&& node.pos.y + ext.bottom <= max_y
}
fn minimal_reveal_center_for_surface_on_monitor(
&self,
st: &Halley,
monitor: &str,
id: NodeId,
) -> Option<Vec2> {
let node = self.field.node(id)?;
let ext = st.spawn_obstacle_extents_for_node(node);
let viewport = self.viewport_for_monitor(monitor);
let margin_x = (viewport.size.x * 0.08).clamp(32.0, 160.0);
let margin_y = (viewport.size.y * 0.08).clamp(32.0, 120.0);
let avail_w = (viewport.size.x - margin_x * 2.0).max(1.0);
let avail_h = (viewport.size.y - margin_y * 2.0).max(1.0);
let mut target = viewport.center;
if ext.left + ext.right > avail_w {
target.x = node.pos.x;
} else {
let min_x = viewport.center.x - viewport.size.x * 0.5 + margin_x;
let max_x = viewport.center.x + viewport.size.x * 0.5 - margin_x;
let left = node.pos.x - ext.left;
let right = node.pos.x + ext.right;
if left < min_x {
target.x += left - min_x;
} else if right > max_x {
target.x += right - max_x;
}
}
if ext.top + ext.bottom > avail_h {
target.y = node.pos.y;
} else {
let min_y = viewport.center.y - viewport.size.y * 0.5 + margin_y;
let max_y = viewport.center.y + viewport.size.y * 0.5 - margin_y;
let top = node.pos.y - ext.top;
let bottom = node.pos.y + ext.bottom;
if top < min_y {
target.y += top - min_y;
} else if bottom > max_y {
target.y += bottom - max_y;
}
}
Some(target)
}
fn close_restore_pan_plan(
&self,
st: &Halley,
monitor: &str,
id: NodeId,
) -> CloseRestorePanPlan {
if self
.cluster_state
.active_cluster_workspaces
.contains_key(monitor)
{
return CloseRestorePanPlan::None;
}
if !self.tuning.close_restore_focus {
return CloseRestorePanPlan::None;
}
match self.tuning.close_restore_pan {
CloseRestorePanMode::Never => CloseRestorePanPlan::None,
CloseRestorePanMode::Always => self
.field
.node(id)
.map(|node| CloseRestorePanPlan::PanTo(node.pos))
.unwrap_or(CloseRestorePanPlan::None),
CloseRestorePanMode::IfOffscreen => {
if self.surface_is_fully_visible_on_monitor(st, monitor, id) {
CloseRestorePanPlan::None
} else {
self.minimal_reveal_center_for_surface_on_monitor(st, monitor, id)
.map(CloseRestorePanPlan::PanTo)
.unwrap_or(CloseRestorePanPlan::None)
}
}
}
}
fn last_focused_active_surface_node(&self) -> Option<NodeId> {
if let Some(id) = self.focus_state.primary_interaction_focus
&& self.surface_node_matches(id, true, false, None)
{
return Some(id);
}
self.focus_state
.last_surface_focus_ms
.iter()
.filter_map(|(&id, &at)| {
self.surface_node_matches(id, true, false, None)
.then_some((id, at))
})
.max_by_key(|entry: &(NodeId, u64)| (entry.1, entry.0.as_u64()))
.map(|(id, _)| id)
}
fn last_focused_surface_node(&self) -> Option<NodeId> {
if let Some(id) = self.focus_state.primary_interaction_focus
&& self.surface_node_matches(id, true, true, None)
{
return Some(id);
}
self.focus_state
.last_surface_focus_ms
.iter()
.filter_map(|(&id, &at)| {
self.surface_node_matches(id, true, true, None)
.then_some((id, at))
})
.max_by_key(|entry: &(NodeId, u64)| (entry.1, entry.0.as_u64()))
.map(|(id, _)| id)
}
fn last_focused_surface_node_for_monitor(&self, monitor: &str) -> Option<NodeId> {
if let Some(id) = self.focus_state.monitor_focus.get(monitor).copied()
&& self.surface_node_matches(id, true, true, Some(monitor))
{
return Some(id);
}
self.focus_state
.last_surface_focus_ms
.iter()
.filter_map(|(&id, &at)| {
self.surface_node_matches(id, true, true, Some(monitor))
.then_some((id, at))
})
.max_by_key(|entry: &(NodeId, u64)| (entry.1, entry.0.as_u64()))
.map(|(id, _)| id)
}
fn last_input_surface_node(&self) -> Option<NodeId> {
if let Some(id) = self.focus_state.primary_interaction_focus
&& self.surface_node_matches(id, true, true, None)
{
return Some(id);
}
self.focus_state
.last_surface_focus_ms
.iter()
.filter_map(|(&id, &at)| {
self.surface_node_matches(id, true, true, None)
.then_some((id, at))
})
.max_by_key(|entry: &(NodeId, u64)| (entry.1, entry.0.as_u64()))
.map(|(id, _)| id)
}
fn last_input_surface_node_for_monitor(&self, monitor: &str) -> Option<NodeId> {
let primary = self.focus_state.primary_interaction_focus.and_then(|id| {
self.surface_node_matches(id, true, true, Some(monitor))
.then_some((id, u64::MAX))
});
let monitor_focus = self
.focus_state
.monitor_focus
.get(monitor)
.copied()
.and_then(|id| {
self.surface_node_matches(id, true, true, Some(monitor))
.then_some((
id,
self.focus_state
.last_surface_focus_ms
.get(&id)
.copied()
.unwrap_or(0),
))
});
primary
.into_iter()
.chain(monitor_focus)
.chain(
self.focus_state
.last_surface_focus_ms
.iter()
.filter_map(|(&id, &at)| {
self.surface_node_matches(id, true, true, Some(monitor))
.then_some((id, at))
}),
)
.max_by_key(|entry: &(NodeId, u64)| (entry.1, entry.0.as_u64()))
.map(|(id, _)| id)
}
}
pub(crate) fn focus_read_context(st: &Halley) -> FocusReadContext<'_> {
FocusReadContext {
field: &st.model.field,
cluster_state: &st.model.cluster_state,
focus_state: &st.model.focus_state,
fullscreen_state: &st.model.fullscreen_state,
monitor_state: &st.model.monitor_state,
tuning: &st.runtime.tuning,
viewport: st.model.viewport,
usable_viewports: st
.model
.monitor_state
.monitors
.keys()
.map(|name| (name.clone(), st.usable_viewport_for_monitor(name)))
.collect(),
focused_monitor: st.focused_monitor(),
}
}
pub(crate) fn fullscreen_focus_override(st: &Halley, requested: Option<NodeId>) -> Option<NodeId> {
focus_read_context(st).fullscreen_focus_override(st, requested)
}
pub(crate) fn surface_is_fully_visible_on_monitor(st: &Halley, monitor: &str, id: NodeId) -> bool {
focus_read_context(st).surface_is_fully_visible_on_monitor(st, monitor, id)
}
pub(crate) fn minimal_reveal_center_for_surface_on_monitor(
st: &Halley,
monitor: &str,
id: NodeId,
) -> Option<Vec2> {
focus_read_context(st).minimal_reveal_center_for_surface_on_monitor(st, monitor, id)
}
pub(crate) fn maybe_pan_to_restored_focus_on_close(
st: &mut Halley,
monitor: &str,
id: NodeId,
now: Instant,
) -> bool {
match focus_read_context(st).close_restore_pan_plan(st, monitor, id) {
CloseRestorePanPlan::None => false,
CloseRestorePanPlan::PanTo(target) => st.animate_viewport_center_to(target, now),
}
}
pub(crate) fn last_focused_active_surface_node(st: &Halley) -> Option<NodeId> {
focus_read_context(st).last_focused_active_surface_node()
}
pub(crate) fn last_focused_surface_node(st: &Halley) -> Option<NodeId> {
focus_read_context(st).last_focused_surface_node()
}
pub(crate) fn last_focused_surface_node_for_monitor(st: &Halley, monitor: &str) -> Option<NodeId> {
focus_read_context(st).last_focused_surface_node_for_monitor(monitor)
}
pub(crate) fn last_input_surface_node(st: &Halley) -> Option<NodeId> {
focus_read_context(st).last_input_surface_node()
}
pub(crate) fn last_input_surface_node_for_monitor(st: &Halley, monitor: &str) -> Option<NodeId> {
focus_read_context(st).last_input_surface_node_for_monitor(monitor)
}