Skip to main content

taskers_control/
controller.rs

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}