lingxia_surface/
arbitrate.rs1use serde::{Deserialize, Serialize};
10
11use crate::graph::SurfaceGraph;
12use crate::layout::SizeClass;
13use crate::model::{Edge, Role, Surface, SurfaceContent};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub enum Decision {
20 Accepted,
21 DowngradedRole,
22 ReplacedExisting,
23 FullScreenFallback,
24}
25
26#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct Policy {
30 pub max_asides_expanded: usize,
31 pub max_asides_medium: usize,
32 pub max_asides_compact: usize,
33}
34
35impl Default for Policy {
36 fn default() -> Self {
37 Self {
38 max_asides_expanded: 2,
39 max_asides_medium: 1,
40 max_asides_compact: 0,
41 }
42 }
43}
44
45impl Policy {
46 pub fn max_asides(&self, size_class: SizeClass) -> usize {
47 match size_class {
48 SizeClass::Expanded => self.max_asides_expanded,
49 SizeClass::Medium => self.max_asides_medium,
50 SizeClass::Compact => self.max_asides_compact,
51 }
52 }
53}
54
55pub fn arbitrate(
58 graph: &SurfaceGraph,
59 request: Surface,
60 policy: &Policy,
61 size_class: SizeClass,
62) -> (SurfaceGraph, Decision) {
63 let mut next = graph.clone();
64
65 match request.role {
66 Role::Main | Role::Float => {
68 next.insert(request);
69 (next, Decision::Accepted)
70 }
71 Role::Aside => {
72 let max = policy.max_asides(size_class);
73 let has_main = !next.mains().is_empty();
74
75 if !has_main || max == 0 {
78 let decision = if max == 0 {
79 Decision::FullScreenFallback
80 } else {
81 Decision::DowngradedRole
82 };
83 let promoted_id = request.id.clone();
84 next.insert(promote_to_main(request));
85 next.set_active_main(&promoted_id);
86 next.set_focus(&promoted_id);
87 return (next, decision);
88 }
89
90 if is_web(&request)
95 && let Some(victim) = existing_web_aside_id(&next, &request.id)
96 {
97 next.remove(&victim);
98 next.insert(request);
99 return (next, Decision::ReplacedExisting);
100 }
101
102 if next.asides().len() < max {
104 next.insert(request);
105 return (next, Decision::Accepted);
106 }
107
108 if let Some(victim) = oldest_aside_id(&next, request.placement.edge) {
110 next.remove(&victim);
111 next.insert(request);
112 (next, Decision::ReplacedExisting)
113 } else {
114 next.insert(promote_to_main(request));
115 (next, Decision::DowngradedRole)
116 }
117 }
118 }
119}
120
121fn promote_to_main(mut request: Surface) -> Surface {
122 request.role = Role::Main;
123 request.placement.edge = None;
124 request
125}
126
127fn is_web(surface: &Surface) -> bool {
128 matches!(surface.content, SurfaceContent::Web { .. })
129}
130
131fn existing_web_aside_id(graph: &SurfaceGraph, exclude_id: &str) -> Option<String> {
133 graph
134 .surfaces()
135 .iter()
136 .find(|s| s.id != exclude_id && s.role == Role::Aside && is_web(s))
137 .map(|s| s.id.clone())
138}
139
140fn oldest_aside_id(graph: &SurfaceGraph, edge: Option<Edge>) -> Option<String> {
142 if let Some(edge) = edge
143 && let Some(s) = graph
144 .surfaces()
145 .iter()
146 .find(|s| s.role == Role::Aside && s.placement.edge == Some(edge))
147 {
148 return Some(s.id.clone());
149 }
150 graph
151 .surfaces()
152 .iter()
153 .find(|s| s.role == Role::Aside)
154 .map(|s| s.id.clone())
155}