use super::*;
impl<T: DerefMut<Target = Halley>> ClusterSystemController<T> {
pub(super) fn cluster_mutation_controller(&mut self) -> ClusterMutationController<'_> {
let crate::compositor::root::Halley {
model,
input,
runtime,
..
} = &mut **self;
ClusterMutationController {
field: &mut model.field,
cluster_state: &mut model.cluster_state,
interaction_state: &mut input.interaction_state,
tuning: &runtime.tuning,
}
}
pub(crate) fn sync_cluster_monitor(
&mut self,
cid: halley_core::cluster::ClusterId,
preferred: Option<&str>,
) -> bool {
let Some(target_monitor) = self.preferred_monitor_for_cluster(cid, preferred) else {
return false;
};
let (core_id, members) = if let Some(cluster) = self.model.field.cluster(cid) {
(cluster.core, cluster.members().to_vec())
} else {
return false;
};
if let Some(core_id) = core_id {
self.assign_node_to_monitor(core_id, target_monitor.as_str());
}
for member_id in members {
self.assign_node_to_monitor(member_id, target_monitor.as_str());
}
let _ = self.sync_cluster_name_for_monitor(cid, target_monitor.as_str());
true
}
fn dissolve_cluster(&mut self, cid: ClusterId) -> bool {
let core_id = self
.model
.field
.cluster(cid)
.and_then(|cluster| cluster.core);
self.clear_cluster_shell_state(cid);
if let Some(core_id) = core_id {
self.model.monitor_state.node_monitor.remove(&core_id);
}
self.remove_cluster_name_record(cid);
self.model.field.dissolve_cluster(cid)
}
pub(crate) fn remove_node_from_field(&mut self, id: NodeId, now_ms: u64) -> bool {
let stack_remove_transition = self
.model
.field
.cluster_id_for_member_public(id)
.and_then(|cid| {
self.preferred_monitor_for_cluster(cid, None)
.map(|monitor| (cid, monitor))
})
.filter(|(cid, monitor)| {
self.active_cluster_workspace_for_monitor(monitor.as_str()) == Some(*cid)
})
.filter(|_| {
matches!(
self.active_cluster_layout_kind(),
ClusterWorkspaceLayoutKind::Stacking
)
})
.map(|(_, monitor)| {
let old_visible =
crate::compositor::surface::active_stacking_visible_members_for_monitor(
self,
monitor.as_str(),
);
(monitor, old_visible)
});
let cluster_snapshot = self
.model
.field
.cluster_id_for_member_public(id)
.and_then(|cid| {
self.model
.field
.cluster(cid)
.map(|cluster| (cid, cluster.members().to_vec(), cluster.core))
});
let (snapshot_cid, snapshot_members, snapshot_core_id) =
cluster_snapshot.unwrap_or((ClusterId::new(0), Vec::new(), None));
let Some((_, effect)) = self.model.field.remove_node_cluster_safe(id) else {
return false;
};
match effect {
Some(RemoveNodeClusterEffect::RemovedMember(cid)) => {
if let Some(cluster_monitor) = self.preferred_monitor_for_cluster(cid, None)
&& self.active_cluster_workspace_for_monitor(cluster_monitor.as_str())
== Some(cid)
{
self.layout_active_cluster_workspace_for_monitor(
cluster_monitor.as_str(),
now_ms,
);
if let Some((transition_monitor, old_visible)) =
stack_remove_transition.as_ref()
&& transition_monitor == &cluster_monitor
{
let duration_ms = self.runtime.tuning.stack_animation_duration_ms();
let new_visible =
crate::compositor::surface::active_stacking_visible_members_for_monitor(
self,
cluster_monitor.as_str(),
);
if self.runtime.tuning.stack_animation_enabled() {
self.ui.render_state.start_stack_cycle_transition(
cluster_monitor.as_str(),
ClusterCycleDirection::Prev,
old_visible.clone(),
new_visible,
Instant::now(),
duration_ms,
);
self.request_maintenance();
}
}
}
}
Some(RemoveNodeClusterEffect::DissolvedCluster(cid)) => {
self.remove_cluster_name_record(cid);
let survivors = if snapshot_cid == cid {
snapshot_members
.iter()
.copied()
.filter(|member| *member != id && self.model.field.node(*member).is_some())
.collect::<Vec<_>>()
} else {
Vec::new()
};
self.clear_cluster_shell_state(cid);
if let Some(core_id) = snapshot_core_id.filter(|_| snapshot_cid == cid) {
self.model.monitor_state.node_monitor.remove(&core_id);
}
for survivor in survivors {
let _ = self.model.field.set_detached(survivor, false);
if let Some(size) = self
.model
.workspace_state
.last_active_size
.get(&survivor)
.copied()
{
if let Some(node) = self.model.field.node_mut(survivor) {
node.intrinsic_size = size;
}
self.request_toplevel_resize(
survivor,
size.x.round() as i32,
size.y.round() as i32,
);
}
let _ = self.model.field.touch(survivor, now_ms);
}
}
Some(RemoveNodeClusterEffect::RemovedCore(cid)) => {
self.model.monitor_state.node_monitor.remove(&id);
let _ = self.sync_cluster_monitor(cid, None);
}
None => {}
}
true
}
pub fn detach_member_from_cluster(
&mut self,
cid: halley_core::cluster::ClusterId,
member_id: NodeId,
world_pos: Vec2,
now: Instant,
) -> bool {
let now_ms = self.now_ms(now);
let Some(outcome) = self
.cluster_mutation_controller()
.detach_member_from_cluster(cid, member_id, world_pos, now_ms)
else {
return false;
};
match outcome {
ClusterRemoveMemberOutcome::Removed => {
if let Some(cluster_monitor) = self.preferred_monitor_for_cluster(cid, None)
&& self.active_cluster_workspace_for_monitor(cluster_monitor.as_str())
== Some(cid)
{
self.layout_active_cluster_workspace_for_monitor(
cluster_monitor.as_str(),
now_ms,
);
}
}
ClusterRemoveMemberOutcome::RequiresDissolve => {
if !self.dissolve_cluster(cid) {
return false;
}
}
}
true
}
pub fn absorb_node_into_cluster(
&mut self,
cid: halley_core::cluster::ClusterId,
node_id: NodeId,
now: Instant,
) -> bool {
let previous_overflow_len = self.cluster_overflow_len(cid);
let stack_insert_transition = self
.preferred_monitor_for_cluster(cid, None)
.filter(|monitor| {
self.active_cluster_workspace_for_monitor(monitor.as_str()) == Some(cid)
})
.filter(|_| {
matches!(
self.active_cluster_layout_kind(),
ClusterWorkspaceLayoutKind::Stacking
)
})
.map(|monitor| {
let old_visible =
crate::compositor::surface::active_stacking_visible_members_for_monitor(
self,
monitor.as_str(),
);
(monitor, old_visible)
});
if !self
.cluster_mutation_controller()
.absorb_node_into_cluster(cid, node_id)
{
return false;
}
if let Some(cluster_monitor) = self.preferred_monitor_for_cluster(cid, None) {
self.assign_node_to_monitor(node_id, cluster_monitor.as_str());
if self.active_cluster_workspace_for_monitor(cluster_monitor.as_str()) == Some(cid) {
if let Some(node) = self.model.field.node_mut(node_id) {
node.visibility.set(Visibility::HIDDEN_BY_CLUSTER, false);
}
let now_ms = self.now_ms(now);
self.layout_active_cluster_workspace_for_monitor(cluster_monitor.as_str(), now_ms);
if matches!(
self.active_cluster_layout_kind(),
ClusterWorkspaceLayoutKind::Stacking
) {
if let Some((transition_monitor, old_visible)) =
stack_insert_transition.as_ref()
&& transition_monitor == &cluster_monitor
{
let duration_ms = self.runtime.tuning.stack_animation_duration_ms();
let new_visible =
crate::compositor::surface::active_stacking_visible_members_for_monitor(
self,
cluster_monitor.as_str(),
);
if self.runtime.tuning.stack_animation_enabled() {
self.ui.render_state.start_stack_cycle_transition(
cluster_monitor.as_str(),
ClusterCycleDirection::Prev,
old_visible.clone(),
new_visible,
now,
duration_ms,
);
self.request_maintenance();
}
}
self.set_recent_top_node(node_id, now + std::time::Duration::from_millis(1200));
self.set_interaction_focus(Some(node_id), 30_000, now);
self.update_focus_tracking_for_surface(node_id, now_ms);
}
let overflow_len = self.cluster_overflow_len(cid);
if overflow_len > previous_overflow_len {
self.reveal_cluster_overflow_for_monitor(cluster_monitor.as_str(), now_ms);
}
}
}
if let Some(core_id) = self
.model
.field
.cluster(cid)
.and_then(|cluster| cluster.core)
{
let now_ms = self.now_ms(now);
let _ = self.model.field.touch(core_id, now_ms);
}
true
}
pub(crate) fn commit_ready_cluster_join_for_node(
&mut self,
node_id: NodeId,
now: Instant,
) -> bool {
let Some(candidate) = self
.input
.interaction_state
.cluster_join_candidate
.clone()
.filter(|candidate| candidate.node_id == node_id && candidate.ready)
else {
return false;
};
self.input.interaction_state.cluster_join_candidate = None;
self.absorb_node_into_cluster(candidate.cluster_id, node_id, now)
}
}