Skip to main content

w_gui/
context.rs

1use std::thread::JoinHandle;
2
3use crate::element::{ElementDecl, Value};
4use crate::protocol::ServerMsg;
5use crate::server;
6use crate::state::{self, Shared};
7use crate::window::Window;
8
9pub struct Context {
10    shared: Shared,
11    prev_frame: Vec<ElementDecl>,
12    current_frame: Vec<ElementDecl>,
13    http_port: u16,
14    ws_port: u16,
15    _http_handle: JoinHandle<()>,
16    _ws_handle: JoinHandle<()>,
17}
18
19impl Context {
20    /// Create a new wgui context. Starts HTTP + WS servers on localhost.
21    /// Prints the URL to stdout and logs via `log` crate.
22    pub fn new() -> Self {
23        Self::with_port(9080)
24    }
25
26    /// Create a new wgui context starting port search from `start_port`.
27    pub fn with_port(start_port: u16) -> Self {
28        let (http_port, ws_port) = server::find_port_pair(start_port);
29        let shared = state::new_shared();
30
31        let http_handle = server::spawn_http(shared.clone(), http_port);
32        let ws_handle = server::spawn_ws(shared.clone(), ws_port);
33
34        println!("wgui: UI available at http://127.0.0.1:{http_port}");
35
36        Self {
37            shared,
38            prev_frame: Vec::new(),
39            current_frame: Vec::new(),
40            http_port,
41            ws_port,
42            _http_handle: http_handle,
43            _ws_handle: ws_handle,
44        }
45    }
46
47    /// Returns the HTTP port the UI is served on.
48    pub fn http_port(&self) -> u16 {
49        self.http_port
50    }
51
52    /// Returns the WebSocket port.
53    pub fn ws_port(&self) -> u16 {
54        self.ws_port
55    }
56
57    /// Get or create a named window. Call widget methods on the returned `Window`.
58    pub fn window(&mut self, name: &str) -> Window<'_> {
59        Window::new(name.to_string(), self)
60    }
61
62    /// Consume a pending browser edit for the given element id, if any.
63    pub(crate) fn consume_edit(&mut self, id: &str) -> Option<Value> {
64        let mut state = self.shared.lock().unwrap();
65        state.incoming_edits.remove(id)
66    }
67
68    /// Record an element declaration for the current frame.
69    pub(crate) fn declare(&mut self, decl: ElementDecl) {
70        self.current_frame.push(decl);
71    }
72
73    /// Number of elements declared so far this frame (for generating unique separator ids).
74    pub(crate) fn current_frame_len(&self) -> usize {
75        self.current_frame.len()
76    }
77
78    /// Finish the current frame: reconcile with previous frame, send diffs over WS.
79    pub fn end_frame(&mut self) {
80        let mut outgoing = Vec::new();
81
82        // Detect added and updated elements
83        for decl in &self.current_frame {
84            let prev = self.prev_frame.iter().find(|p| p.id == decl.id);
85            match prev {
86                None => {
87                    // New element
88                    outgoing.push(ServerMsg::Add {
89                        element: decl.clone(),
90                    });
91                }
92                Some(prev_decl) => {
93                    // Check if value changed from Rust side
94                    let value_changed = prev_decl.value != decl.value || prev_decl.kind != decl.kind;
95                    let meta_changed = prev_decl.meta != decl.meta;
96                    if value_changed || meta_changed {
97                        outgoing.push(ServerMsg::Update {
98                            id: decl.id.clone(),
99                            value: decl.value.clone(),
100                            meta: if meta_changed { Some(decl.meta.clone()) } else { None },
101                        });
102                    }
103                }
104            }
105        }
106
107        // Detect removed elements
108        for prev_decl in &self.prev_frame {
109            if !self.current_frame.iter().any(|d| d.id == prev_decl.id) {
110                outgoing.push(ServerMsg::Remove {
111                    id: prev_decl.id.clone(),
112                });
113            }
114        }
115
116        // Push to shared state
117        {
118            let mut state = self.shared.lock().unwrap();
119            state.outgoing_msgs.extend(outgoing);
120            state.current_elements = self.current_frame.clone();
121        }
122
123        // Swap frames
124        self.prev_frame = std::mem::take(&mut self.current_frame);
125    }
126}
127
128impl Drop for Context {
129    fn drop(&mut self) {
130        let mut state = self.shared.lock().unwrap();
131        state.shutdown = true;
132    }
133}