1use std::sync::{Arc, Mutex};
2
3use taskers_domain::{AppModel, DomainError, WindowId, WorkspaceId};
4
5use crate::protocol::{ControlCommand, ControlQuery, ControlResponse};
6
7#[derive(Debug, Clone)]
8pub struct InMemoryController {
9 state: Arc<Mutex<AppModel>>,
10}
11
12#[derive(Debug, Clone)]
13pub struct ControllerSnapshot {
14 pub model: AppModel,
15}
16
17impl InMemoryController {
18 pub fn new(state: AppModel) -> Self {
19 Self {
20 state: Arc::new(Mutex::new(state)),
21 }
22 }
23
24 pub fn snapshot(&self) -> ControllerSnapshot {
25 let state = self.state.lock().expect("state mutex poisoned").clone();
26 ControllerSnapshot { model: state }
27 }
28
29 pub fn handle(&self, command: ControlCommand) -> Result<ControlResponse, DomainError> {
30 let mut model = self.state.lock().expect("state mutex poisoned");
31
32 match command {
33 ControlCommand::CreateWorkspace { label } => {
34 let workspace_id = model.create_workspace(label);
35 Ok(ControlResponse::WorkspaceCreated { workspace_id })
36 }
37 ControlCommand::RenameWorkspace {
38 workspace_id,
39 label,
40 } => {
41 model.rename_workspace(workspace_id, label)?;
42 Ok(ControlResponse::Ack {
43 message: "workspace renamed".into(),
44 })
45 }
46 ControlCommand::SwitchWorkspace {
47 window_id,
48 workspace_id,
49 } => {
50 let target_window = window_id.unwrap_or(model.active_window);
51 model.switch_workspace(target_window, workspace_id)?;
52 Ok(ControlResponse::Ack {
53 message: "workspace switched".into(),
54 })
55 }
56 ControlCommand::SplitPane {
57 workspace_id,
58 pane_id,
59 axis,
60 } => {
61 let new_pane_id = model.split_pane(workspace_id, pane_id, axis)?;
62 Ok(ControlResponse::PaneSplit {
63 pane_id: new_pane_id,
64 })
65 }
66 ControlCommand::CreateWorkspaceWindow {
67 workspace_id,
68 direction,
69 } => {
70 let new_pane_id = model.create_workspace_window(workspace_id, direction)?;
71 Ok(ControlResponse::WorkspaceWindowCreated {
72 pane_id: new_pane_id,
73 })
74 }
75 ControlCommand::FocusWorkspaceWindow {
76 workspace_id,
77 workspace_window_id,
78 } => {
79 model.focus_workspace_window(workspace_id, workspace_window_id)?;
80 Ok(ControlResponse::Ack {
81 message: "workspace window focused".into(),
82 })
83 }
84 ControlCommand::FocusPane {
85 workspace_id,
86 pane_id,
87 } => {
88 model.focus_pane(workspace_id, pane_id)?;
89 Ok(ControlResponse::Ack {
90 message: "pane focused".into(),
91 })
92 }
93 ControlCommand::FocusPaneDirection {
94 workspace_id,
95 direction,
96 } => {
97 model.focus_pane_direction(workspace_id, direction)?;
98 Ok(ControlResponse::Ack {
99 message: "pane focus moved".into(),
100 })
101 }
102 ControlCommand::ResizeActiveWindow {
103 workspace_id,
104 direction,
105 amount,
106 } => {
107 model.resize_active_window(workspace_id, direction, amount)?;
108 Ok(ControlResponse::Ack {
109 message: "workspace window resized".into(),
110 })
111 }
112 ControlCommand::ResizeActivePaneSplit {
113 workspace_id,
114 direction,
115 amount,
116 } => {
117 model.resize_active_pane_split(workspace_id, direction, amount)?;
118 Ok(ControlResponse::Ack {
119 message: "pane split resized".into(),
120 })
121 }
122 ControlCommand::SetWorkspaceWindowFrame {
123 workspace_id,
124 workspace_window_id,
125 frame,
126 } => {
127 model.set_workspace_window_frame(workspace_id, workspace_window_id, frame)?;
128 Ok(ControlResponse::Ack {
129 message: "workspace window frame updated".into(),
130 })
131 }
132 ControlCommand::SetWindowSplitRatio {
133 workspace_id,
134 workspace_window_id,
135 path,
136 ratio,
137 } => {
138 model.set_window_split_ratio(workspace_id, workspace_window_id, &path, ratio)?;
139 Ok(ControlResponse::Ack {
140 message: "window split ratio updated".into(),
141 })
142 }
143 ControlCommand::UpdatePaneMetadata { pane_id, patch } => {
144 model.update_pane_metadata(pane_id, patch)?;
145 Ok(ControlResponse::Ack {
146 message: "pane metadata updated".into(),
147 })
148 }
149 ControlCommand::UpdateSurfaceMetadata { surface_id, patch } => {
150 model.update_surface_metadata(surface_id, patch)?;
151 Ok(ControlResponse::Ack {
152 message: "surface metadata updated".into(),
153 })
154 }
155 ControlCommand::CreateSurface {
156 workspace_id,
157 pane_id,
158 kind,
159 } => {
160 let surface_id = model.create_surface(workspace_id, pane_id, kind)?;
161 Ok(ControlResponse::SurfaceCreated { surface_id })
162 }
163 ControlCommand::FocusSurface {
164 workspace_id,
165 pane_id,
166 surface_id,
167 } => {
168 model.focus_surface(workspace_id, pane_id, surface_id)?;
169 Ok(ControlResponse::Ack {
170 message: "surface focused".into(),
171 })
172 }
173 ControlCommand::MarkSurfaceCompleted {
174 workspace_id,
175 pane_id,
176 surface_id,
177 } => {
178 model.mark_surface_completed(workspace_id, pane_id, surface_id)?;
179 Ok(ControlResponse::Ack {
180 message: "surface marked completed".into(),
181 })
182 }
183 ControlCommand::CloseSurface {
184 workspace_id,
185 pane_id,
186 surface_id,
187 } => {
188 model.close_surface(workspace_id, pane_id, surface_id)?;
189 Ok(ControlResponse::Ack {
190 message: "surface closed".into(),
191 })
192 }
193 ControlCommand::SetWorkspaceViewport {
194 workspace_id,
195 viewport,
196 } => {
197 model.set_workspace_viewport(workspace_id, viewport)?;
198 Ok(ControlResponse::Ack {
199 message: "workspace viewport updated".into(),
200 })
201 }
202 ControlCommand::ClosePane {
203 workspace_id,
204 pane_id,
205 } => {
206 model.close_pane(workspace_id, pane_id)?;
207 Ok(ControlResponse::Ack {
208 message: "pane closed".into(),
209 })
210 }
211 ControlCommand::CloseWorkspace { workspace_id } => {
212 model.close_workspace(workspace_id)?;
213 Ok(ControlResponse::Ack {
214 message: "workspace closed".into(),
215 })
216 }
217 ControlCommand::EmitSignal {
218 workspace_id,
219 pane_id,
220 surface_id,
221 event,
222 } => {
223 if let Some(surface_id) = surface_id {
224 model.apply_surface_signal(workspace_id, pane_id, surface_id, event)?;
225 } else {
226 model.apply_signal(workspace_id, pane_id, event)?;
227 }
228 Ok(ControlResponse::Ack {
229 message: "signal applied".into(),
230 })
231 }
232 ControlCommand::QueryStatus { query } => match query {
233 ControlQuery::ActiveWindow | ControlQuery::All => Ok(ControlResponse::Status {
234 session: model.snapshot(),
235 }),
236 ControlQuery::Window { window_id } => window_snapshot(&model, window_id),
237 ControlQuery::Workspace { workspace_id } => {
238 workspace_snapshot(&model, workspace_id)
239 }
240 },
241 }
242 }
243}
244
245fn window_snapshot(model: &AppModel, window_id: WindowId) -> Result<ControlResponse, DomainError> {
246 let _ = model
247 .windows
248 .get(&window_id)
249 .ok_or(DomainError::MissingWindow(window_id))?;
250 Ok(ControlResponse::Status {
251 session: model.snapshot(),
252 })
253}
254
255fn workspace_snapshot(
256 model: &AppModel,
257 workspace_id: WorkspaceId,
258) -> Result<ControlResponse, DomainError> {
259 let _ = model
260 .workspaces
261 .get(&workspace_id)
262 .ok_or(DomainError::MissingWorkspace(workspace_id))?;
263 Ok(ControlResponse::WorkspaceState {
264 workspace_id,
265 session: model.snapshot(),
266 })
267}