lingxia_surface/
manager.rs1use crate::arbitrate::{Decision, Policy, arbitrate};
10use crate::graph::SurfaceGraph;
11use crate::layout::{DEFAULT_HYSTERESIS, DerivedLayout, LayoutPresentationPlan, SizeClass};
12use crate::model::{Surface, SurfaceId};
13
14#[derive(Debug, Clone)]
16pub struct SurfaceManager {
17 graph: SurfaceGraph,
18 policy: Policy,
19 width: f64,
20 hysteresis: f64,
21 size_class: SizeClass,
22}
23
24impl SurfaceManager {
25 pub fn new(width: f64) -> Self {
27 Self::with_policy(width, Policy::default())
28 }
29
30 pub fn with_policy(width: f64, policy: Policy) -> Self {
31 Self {
32 graph: SurfaceGraph::new(),
33 policy,
34 width,
35 hysteresis: DEFAULT_HYSTERESIS,
36 size_class: SizeClass::from_width(width),
37 }
38 }
39
40 pub fn graph(&self) -> &SurfaceGraph {
41 &self.graph
42 }
43 pub fn size_class(&self) -> SizeClass {
44 self.size_class
45 }
46 pub fn width(&self) -> f64 {
47 self.width
48 }
49
50 pub fn set_width(&mut self, width: f64) -> bool {
53 self.width = width;
54 let next = SizeClass::resolve(Some(self.size_class), width, self.hysteresis);
55 let changed = next != self.size_class;
56 self.size_class = next;
57 changed
58 }
59
60 pub fn open(&mut self, request: Surface) -> Decision {
63 let (next, decision) = arbitrate(&self.graph, request, &self.policy, self.size_class);
64 self.graph = next;
65 decision
66 }
67
68 pub fn close(&mut self, id: &str) -> Vec<SurfaceId> {
70 self.graph.remove(id)
71 }
72
73 pub fn set_active_main(&mut self, id: &str) -> bool {
74 self.graph.set_active_main(id)
75 }
76 pub fn set_focus(&mut self, id: &str) -> bool {
77 self.graph.set_focus(id)
78 }
79
80 pub fn derive(&self) -> DerivedLayout {
82 self.graph.derive_layout(self.size_class)
83 }
84
85 pub fn presentation_plan(&self) -> LayoutPresentationPlan {
88 self.graph.presentation_plan(self.size_class)
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::layout::{SplitForm, SwitcherForm};
96 use crate::model::{Edge, Role, Surface};
97
98 fn main_s(id: &str) -> Surface {
99 Surface::entry(id, Role::Main, id)
100 }
101 fn aside_s(id: &str, edge: Edge) -> Surface {
102 let mut s = Surface::entry(id, Role::Aside, id);
103 s.placement.edge = Some(edge);
104 s
105 }
106
107 #[test]
108 fn open_then_derive_on_expanded() {
109 let mut m = SurfaceManager::new(1200.0);
110 assert_eq!(m.size_class(), SizeClass::Expanded);
111 assert_eq!(m.open(main_s("home")), Decision::Accepted);
112 assert_eq!(
113 m.open(aside_s("assistant", Edge::Right)),
114 Decision::Accepted
115 );
116 let d = m.derive();
117 assert_eq!(d.split_form, SplitForm::Split);
118 assert!(m.graph().is_valid());
119 }
120
121 #[test]
122 fn aside_on_compact_promotes_without_host_switcher() {
123 let mut m = SurfaceManager::new(390.0); assert_eq!(m.size_class(), SizeClass::Compact);
125 m.open(main_s("home"));
126 assert_eq!(
128 m.open(aside_s("assistant", Edge::Right)),
129 Decision::FullScreenFallback
130 );
131 let d = m.derive();
132 assert_eq!(d.switcher_form, SwitcherForm::None);
133 assert_eq!(d.bottom_owner, crate::BottomOwner::App);
134 assert!(m.graph().is_valid());
135 }
136
137 #[test]
138 fn width_change_reports_sizeclass_flip_with_hysteresis() {
139 let mut m = SurfaceManager::new(1200.0);
140 assert!(!m.set_width(900.0));
142 assert_eq!(m.size_class(), SizeClass::Expanded);
143 assert!(m.set_width(390.0));
145 assert_eq!(m.size_class(), SizeClass::Compact);
146 assert!(!m.set_width(590.0));
148 assert_eq!(m.size_class(), SizeClass::Compact);
149 }
150
151 #[test]
152 fn resize_reflows_existing_aside_without_mutating_roles() {
153 let mut m = SurfaceManager::new(1200.0);
154 m.open(main_s("home"));
155 m.open(aside_s("assistant", Edge::Right));
156 assert_eq!(m.derive().split_form, SplitForm::Split);
158 assert_eq!(m.graph().role_of("assistant"), Some(Role::Aside));
159 m.set_width(390.0);
161 let d = m.derive();
162 assert_eq!(d.split_form, SplitForm::FullScreen);
163 assert_eq!(d.switcher_form, SwitcherForm::None);
164 assert_eq!(m.graph().role_of("assistant"), Some(Role::Aside));
166 m.set_width(1200.0);
167 assert_eq!(m.derive().split_form, SplitForm::Split);
168 }
169}