1use std::collections::BTreeMap;
8use std::time::Instant;
9
10use a7n_proto::v1::{Focus as WireFocus, InputEvent, Landing, envelope::Body};
11use a7n_proto::{MachineId, Route};
12
13pub mod edge;
14pub mod held;
15mod owner;
16mod responder;
17
18use held::HeldInputs;
19
20#[derive(Debug, Clone, PartialEq)]
22pub struct Take {
23 pub target: MachineId,
24 pub landing: Option<Landing>,
25 pub resistance_px: u32,
26 pub reach: Vec<MachineId>,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum Cursor {
32 Home,
33 Driving(MachineId),
34}
35
36#[derive(Debug, Clone, PartialEq)]
38pub enum Action {
39 Send {
40 peer: MachineId,
41 body: Body,
42 },
43 Release(Vec<InputEvent>),
44 EndedDriven,
45 Cursor(Cursor),
46 Driven {
48 owner: MachineId,
49 landing: Option<Landing>,
50 resistance_px: u32,
52 },
53}
54
55#[derive(Debug, Clone)]
56struct Session {
57 take: Take,
58 route: Route,
59}
60
61#[derive(Debug)]
62enum Owner {
63 Home,
64 Driving(Session),
65 Taking {
66 session: Session,
67 previous: Option<Session>,
68 deadline: Instant,
69 },
70}
71
72#[derive(Debug)]
73struct Driven {
74 owner: MachineId,
75 route: Route,
76 last_input: Instant,
77 reach: Vec<MachineId>,
78}
79
80#[derive(Debug)]
82pub struct Focus {
83 own_id: MachineId,
84 last_route: Route,
85 owner: Owner,
86 driven: Option<Driven>,
87 accepted: BTreeMap<MachineId, Route>,
88 last_local_input: Option<Instant>,
89 held: HeldInputs,
90 locked: bool,
91}
92
93impl Focus {
94 #[must_use]
95 pub fn new(own_id: MachineId) -> Self {
96 Self {
97 own_id,
98 last_route: Route(0),
99 owner: Owner::Home,
100 driven: None,
101 accepted: BTreeMap::new(),
102 last_local_input: None,
103 held: HeldInputs::default(),
104 locked: false,
105 }
106 }
107
108 pub fn set_locked(&mut self, locked: bool) {
110 self.locked = locked;
111 }
112
113 pub fn toggle_lock(&mut self) {
114 self.locked = !self.locked;
115 }
116
117 #[must_use]
118 pub fn is_locked(&self) -> bool {
119 self.locked
120 }
121
122 #[must_use]
123 pub fn cursor(&self) -> Cursor {
124 match &self.owner {
125 Owner::Home => Cursor::Home,
126 Owner::Driving(session) => Cursor::Driving(session.take.target),
127 Owner::Taking { previous, .. } => previous
128 .as_ref()
129 .map_or(Cursor::Home, |s| Cursor::Driving(s.take.target)),
130 }
131 }
132
133 #[must_use]
135 pub fn driving_route(&self) -> Option<Route> {
136 match &self.owner {
137 Owner::Home => None,
138 Owner::Driving(session) => Some(session.route),
139 Owner::Taking { previous, .. } => previous.as_ref().map(|s| s.route),
140 }
141 }
142
143 #[must_use]
145 pub fn is_driven(&self) -> bool {
146 self.driven.is_some()
147 }
148
149 #[must_use]
151 pub fn driven_by(&self) -> Option<MachineId> {
152 self.driven.as_ref().map(|driven| driven.owner)
153 }
154
155 #[must_use]
157 pub fn next_deadline(&self) -> Option<Instant> {
158 match self.owner {
159 Owner::Taking { deadline, .. } => Some(deadline),
160 _ => None,
161 }
162 }
163
164 fn next_route(&mut self) -> Option<Route> {
165 self.last_route = Route(self.last_route.0.checked_add(1)?);
167 Some(self.last_route)
168 }
169
170 fn send(peer: MachineId, body: Body) -> Action {
171 Action::Send { peer, body }
172 }
173
174 fn focus_message(session: &Session, target: MachineId, stay: bool) -> WireFocus {
175 WireFocus {
176 route: session.route.0,
177 target: target.as_bytes().to_vec(),
178 landing: if stay {
179 None
180 } else {
181 session.take.landing.clone()
182 },
183 resistance_px: session.take.resistance_px,
184 reach: session
185 .take
186 .reach
187 .iter()
188 .map(|id| id.as_bytes().to_vec())
189 .collect(),
190 stay,
191 }
192 }
193}
194
195#[cfg(test)]
196mod tests;