1use crate::*;
2use serde::Deserialize;
3use serde_json::{json, Value};
4use softbuffer::{Context, Surface};
5use std::{
6 error::Error,
7 io::{BufRead, Read},
8 num::NonZeroU32,
9 rc::Rc,
10};
11use winit::{
12 application::ApplicationHandler,
13 dpi::LogicalSize,
14 event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent},
15 event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
16 keyboard::{Key, ModifiersState, NamedKey},
17 window::{Window, WindowId},
18};
19
20#[derive(Clone, Copy, Debug, serde::Serialize)]
21#[serde(rename_all = "snake_case")]
22pub enum Source {
23 Human,
24 Agent,
25}
26pub trait Application: 'static {
28 fn view(&self) -> Node;
29 fn update(&mut self, action: Action);
30 fn update_from(&mut self, action: Action, _source: Source) {
31 self.update(action);
32 }
33 fn theme(&self) -> Theme {
34 Theme::DARK
35 }
36 fn debug_bounds(&self) -> bool {
37 false
38 }
39 fn inspect(&self) -> Value {
41 Value::Null
42 }
43}
44#[derive(Clone, Debug)]
45pub struct WindowOptions {
46 pub title: String,
47 pub width: f64,
48 pub height: f64,
49 pub agent: bool,
50}
51impl Default for WindowOptions {
52 fn default() -> Self {
53 Self {
54 title: "Forge UI".into(),
55 width: 1120.0,
56 height: 820.0,
57 agent: false,
58 }
59 }
60}
61#[derive(Debug, Deserialize)]
62#[serde(deny_unknown_fields)]
63struct Request {
64 #[serde(default)]
65 request_id: Option<String>,
66 op: String,
67 #[serde(default)]
68 id: String,
69 text: Option<String>,
70 value: Option<f32>,
71 key: Option<String>,
72 #[serde(default)]
73 shift: bool,
74 #[serde(default)]
75 command: bool,
76 path: Option<String>,
77}
78enum UserEvent {
79 Agent(Result<Request, String>),
80}
81struct Host<A: Application> {
82 app: A,
83 ui: Ui,
84 options: WindowOptions,
85 window: Option<Rc<Window>>,
86 surface: Option<Surface<Rc<Window>, Rc<Window>>>,
87 mods: ModifiersState,
88 error: Option<String>,
89}
90impl<A: Application> Host<A> {
91 fn rebuild(&mut self) -> Result<(), String> {
92 self.ui.theme = self.app.theme();
93 self.ui.debug_bounds = self.app.debug_bounds();
94 self.ui.set_root(self.app.view())
95 }
96 fn apply(&mut self, actions: Vec<Action>, source: Source) -> Result<(), String> {
97 for a in actions {
98 self.app.update_from(a, source);
99 }
100 self.rebuild()?;
101 if let Some(w) = &self.window {
102 w.request_redraw();
103 }
104 Ok(())
105 }
106 fn input(&mut self, input: Input) -> Result<(), String> {
107 let a = self.ui.event(input);
108 self.apply(a, Source::Human)
109 }
110 fn fail(&mut self, e: impl ToString, ev: &ActiveEventLoop) {
111 self.error = Some(e.to_string());
112 ev.exit();
113 }
114 fn draw(&mut self) -> Result<(), String> {
115 let Some(w) = &self.window else { return Ok(()) };
116 let size = w.inner_size();
117 if size.width == 0 || size.height == 0 {
118 return Ok(());
119 }
120 self.ui
121 .resize(size.width, size.height, w.scale_factor() as f32);
122 self.ui.paint();
123 let surface = self.surface.as_mut().ok_or("surface not initialized")?;
124 surface
125 .resize(
126 NonZeroU32::new(size.width).unwrap(),
127 NonZeroU32::new(size.height).unwrap(),
128 )
129 .map_err(|e| e.to_string())?;
130 let mut buffer = surface.buffer_mut().map_err(|e| e.to_string())?;
131 buffer.copy_from_slice(&self.ui.painter.pixels);
132 buffer.present().map_err(|e| e.to_string())
133 }
134 fn agent(&mut self, r: &Request, ev: &ActiveEventLoop) -> Result<Value, String> {
135 match r.op.as_str() {
136 "inspect" => {}
137 "click" | "set_text" | "set_value" => {
138 let actions = self
139 .ui
140 .agent_action(&r.op, &r.id, r.text.as_deref(), r.value)?;
141 self.apply(actions, Source::Agent)?;
142 }
143 "key" => {
144 let key = r.key.as_deref().ok_or("key is required")?;
145 if ![
146 "Tab",
147 "Enter",
148 "Space",
149 "ArrowLeft",
150 "ArrowRight",
151 "ArrowUp",
152 "ArrowDown",
153 "Home",
154 "End",
155 "Backspace",
156 "Delete",
157 "a",
158 "A",
159 ]
160 .contains(&key)
161 {
162 return Err("unsupported key".into());
163 }
164 let actions = self.ui.event(Input::Key {
165 key: key.into(),
166 shift: r.shift,
167 command: r.command,
168 });
169 self.apply(actions, Source::Agent)?;
170 }
171 "screenshot" => {
172 self.draw()?;
173 let path = r.path.as_ref().ok_or("path is required")?;
174 self.ui
175 .painter
176 .save_ppm(std::path::Path::new(path))
177 .map_err(|e| e.to_string())?;
178 }
179 "quit" => ev.exit(),
180 _ => return Err(format!("unknown operation: {}", r.op)),
181 }
182 if r.op != "quit" {
184 self.draw()?;
185 }
186 Ok(json!({"tree":self.ui.inspect(),"app":self.app.inspect()}))
187 }
188}
189impl<A: Application> ApplicationHandler<UserEvent> for Host<A> {
190 fn resumed(&mut self, ev: &ActiveEventLoop) {
191 if self.window.is_some() {
192 return;
193 }
194 let result = (|| -> Result<(), String> {
195 let w = Rc::new(
196 ev.create_window(
197 Window::default_attributes()
198 .with_title(&self.options.title)
199 .with_inner_size(LogicalSize::new(self.options.width, self.options.height))
200 .with_min_inner_size(LogicalSize::new(760.0, 580.0)),
201 )
202 .map_err(|e| e.to_string())?,
203 );
204 let ctx = Context::new(w.clone()).map_err(|e| e.to_string())?;
205 let surface = Surface::new(&ctx, w.clone()).map_err(|e| e.to_string())?;
206 self.surface = Some(surface);
207 self.window = Some(w);
208 self.rebuild()?;
209 self.draw()?;
210 Ok(())
211 })();
212 if let Err(e) = result {
213 self.fail(e, ev);
214 } else if self.options.agent {
215 println!("{}", json!({"event":"ready","protocol":"forge-ui/1"}));
216 }
217 }
218 fn suspended(&mut self, _ev: &ActiveEventLoop) {
219 self.surface = None;
220 self.window = None;
221 }
222 fn user_event(&mut self, ev: &ActiveEventLoop, event: UserEvent) {
223 let UserEvent::Agent(request) = event;
224 match request {
225 Ok(r) => {
226 let result = self.agent(&r, ev);
227 let response = match result {
228 Ok(v) => json!({"request_id":r.request_id,"ok":true,"result":v}),
229 Err(e) => json!({"request_id":r.request_id,"ok":false,"error":e}),
230 };
231 println!("{response}");
232 }
233 Err(e) => println!("{}", json!({"ok":false,"error":e})),
234 }
235 }
236 fn window_event(&mut self, ev: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
237 let result = match event {
238 WindowEvent::CloseRequested => {
239 ev.exit();
240 Ok(())
241 }
242 WindowEvent::RedrawRequested => self.draw(),
243 WindowEvent::Resized(_) | WindowEvent::ScaleFactorChanged { .. } => {
244 if let Some(w) = &self.window {
245 w.request_redraw();
246 }
247 Ok(())
248 }
249 WindowEvent::ModifiersChanged(m) => {
250 self.mods = m.state();
251 Ok(())
252 }
253 WindowEvent::Focused(false) => self.input(Input::Cancel),
254 WindowEvent::CursorLeft { .. } => self.input(Input::Move(-1.0, -1.0)),
255 WindowEvent::CursorMoved { position, .. } => {
256 let s = self
257 .window
258 .as_ref()
259 .map(|w| w.scale_factor())
260 .unwrap_or(1.0);
261 self.input(Input::Move(
262 (position.x / s) as f32,
263 (position.y / s) as f32,
264 ))
265 }
266 WindowEvent::MouseInput {
267 state,
268 button: MouseButton::Left,
269 ..
270 } => self.input(if state == ElementState::Pressed {
271 Input::Down
272 } else {
273 Input::Up
274 }),
275 WindowEvent::MouseWheel { delta, .. } => {
276 let s = self
277 .window
278 .as_ref()
279 .map(|w| w.scale_factor())
280 .unwrap_or(1.0);
281 self.input(Input::Wheel(match delta {
282 MouseScrollDelta::LineDelta(_, y) => -y * 36.0,
283 MouseScrollDelta::PixelDelta(p) => -p.y as f32 / s as f32,
284 }))
285 }
286 WindowEvent::KeyboardInput { event, .. } if event.state == ElementState::Pressed => {
287 let command = self.mods.control_key() || self.mods.super_key();
288 let key = match &event.logical_key {
289 Key::Named(NamedKey::Space) => "Space".into(),
290 Key::Named(k) => format!("{k:?}"),
291 Key::Character(c) => c.to_string(),
292 _ => String::new(),
293 };
294 let named = matches!(event.logical_key, Key::Named(_));
295 let r = self.input(Input::Key {
296 key,
297 shift: self.mods.shift_key(),
298 command,
299 });
300 if r.is_ok() && !command {
301 if let Some(text) = event.text {
302 if !named || matches!(event.logical_key, Key::Named(NamedKey::Space)) {
303 return if let Err(e) = self.input(Input::Text(text.to_string())) {
304 self.fail(e, ev);
305 };
306 }
307 }
308 }
309 r
310 }
311 _ => Ok(()),
312 };
313 if let Err(e) = result {
314 self.fail(e, ev);
315 }
316 }
317}
318pub fn run<A: Application>(app: A, options: WindowOptions) -> Result<(), Box<dyn Error>> {
321 let ui = Ui::new(app.view()).map_err(std::io::Error::other)?;
322 let event_loop = EventLoop::<UserEvent>::with_user_event().build()?;
323 event_loop.set_control_flow(ControlFlow::Wait);
324 if options.agent {
325 let proxy = event_loop.create_proxy();
326 std::thread::spawn(move || {
327 let stdin = std::io::stdin();
328 let mut input = stdin.lock();
329 loop {
330 let mut line = String::new();
331 let result = (&mut input).take(65537).read_line(&mut line);
332 let request = match result {
333 Ok(0) => break,
334 Ok(_) if line.len() > 65536 => Err("request exceeds 64 KiB".into()),
335 Ok(_) => serde_json::from_str(&line).map_err(|e| e.to_string()),
336 Err(e) => Err(e.to_string()),
337 };
338 let stop = line.len() > 65536;
339 if proxy.send_event(UserEvent::Agent(request)).is_err() || stop {
340 break;
341 }
342 }
343 });
344 }
345 let mut host = Host {
346 app,
347 ui,
348 options,
349 window: None,
350 surface: None,
351 mods: ModifiersState::empty(),
352 error: None,
353 };
354 event_loop.run_app(&mut host)?;
355 if let Some(e) = host.error {
356 return Err(std::io::Error::other(e).into());
357 }
358 Ok(())
359}