1use crate::core::tui::{ExitReason, Tui};
2use crate::inputs::legend::Legend;
3use crate::types::args::EzArgs;
4use crate::types::cpt_ids::{EzCptId, EzCptIds};
5use crate::types::event::EzMsg;
6use crate::types::ship_ids::{EzShipId, EzShipIds};
7use crate::types::state::EzState;
8use crate::utils::logs::LogSwitcher;
9use crate::{
10 AppArguments, EventClause, EzEvent, GlobalHotKeys, IoContext, NoClientState, Port, Result,
11 Sender, State, SubClause, SubId, Subscription, View,
12};
13use clap::Parser;
14use ratatui::layout::Rect;
15use std::collections::VecDeque;
16use tokio::sync::Mutex;
17use tokio::time::Instant;
18use tracing::{debug, error, info, trace, warn};
19
20#[doc = include_str!("../../examples/basic/main.rs")]
33#[allow(missing_debug_implementations)]
35pub struct Application<CID, SID, CA, CS, CM>
36where
37 CID: EzCptIds,
38 SID: EzShipIds,
39 CA: EzArgs,
40 CS: EzState,
41 CM: EzMsg,
42{
43 args: AppArguments<CA>,
44 view: Option<View<CID, CA, CS, CM>>,
45 hot_keys: GlobalHotKeys,
46 port: Option<Port<CID, SID, CA, CS, CM>>,
47 subs: Vec<Subscription<CID, SID, CM>>,
48 tui: Tui<CID, CM>,
49 tx: Sender<EzEvent<CID, CM>>,
50 state: Mutex<State<CS>>,
51}
52
53impl<CID, SID, CA, CM> Application<CID, SID, CA, NoClientState, CM>
55where
56 CID: EzCptIds,
57 SID: EzShipIds,
58 CA: EzArgs,
59 CM: EzMsg,
60{
61 pub fn init(args: AppArguments<CA>, logger: LogSwitcher) -> Self {
65 Self::init_with_state(args, logger, NoClientState)
66 }
67}
68
69impl<CID, SID, CA, CS, CM> Application<CID, SID, CA, CS, CM>
71where
72 CID: EzCptIds,
73 SID: EzShipIds,
74 CA: EzArgs,
75 CS: EzState,
76 CM: EzMsg,
77{
78 #[must_use]
80 pub fn parse() -> (AppArguments<CA>, LogSwitcher) {
81 let args = AppArguments::parse();
82 let logger = LogSwitcher::initialize(args.lib.log_level);
83 (args, logger)
84 }
85 #[must_use]
87 pub fn with_view(mut self, view: View<CID, CA, CS, CM>) -> Self {
88 self.view = Some(view);
89 self
90 }
91
92 #[must_use]
94 pub fn with_port(mut self, port: Port<CID, SID, CA, CS, CM>) -> Self {
95 self.port = Some(port);
96 self
97 }
98 #[must_use]
100 pub fn with_global_key_codes(mut self, hk: GlobalHotKeys) -> Self {
101 self.hot_keys = hk;
102 self
103 }
104
105 pub fn init_with_state(args: AppArguments<CA>, logger: LogSwitcher, client_state: CS) -> Self {
107 let tui = Tui::new(logger, args.lib.frame_rate, args.lib.tick_rate);
108 Self {
109 args,
110 tx: tui.get_tx(),
111 hot_keys: GlobalHotKeys::default(),
112 port: None,
113 view: None,
114 subs: vec![
115 Subscription::new(
116 SubId::CPt(EzCptId::TickIndicator),
117 EventClause::<CID, CM>::Lifecycle,
118 SubClause::Always,
119 ),
120 Subscription::new(
121 SubId::CPt(EzCptId::FrameIndicator),
122 EventClause::<CID, CM>::Lifecycle,
123 SubClause::Always,
124 ),
125 ],
126 state: Mutex::new(State::with_client(client_state)),
127 tui,
128 }
129 }
130
131 pub fn get_args(&self) -> AppArguments<CA> {
133 self.args.clone()
134 }
135
136 pub fn clone_tx(&mut self) -> Sender<EzEvent<CID, CM>> {
138 self.tx.clone()
139 }
140
141 pub async fn start(&mut self) {
148 self.tui.enter().expect("Failed to initialize TUI");
149 info!("TUI entered");
150
151 self.tx.send(EzEvent::Init).expect("Channel already closed");
152 if let Some(ref mut p) = self.port {
153 p.init(&self.tui.get_tx());
154 }
155
156 info!("Starting the application");
157 match self.run().await {
158 Ok(()) => {}
159 Err(err) => {
160 error!("Error during application lifecycle: {}", err);
161 }
162 }
163
164 self.tui.exit(&ExitReason::LifecycleEnded);
165 }
166
167 #[must_use]
170 pub fn subscribe_cpt(
171 mut self,
172 id: &EzCptId<CID>,
173 subs: Vec<(EventClause<CID, CM>, SubClause<CID, SID>)>,
174 ) -> Self {
175 for (ev, sub) in subs {
176 let subscription = Subscription::new(SubId::CPt(id.clone()), ev, sub);
177 if !self.cpt_subscribed(id, subscription.event()) {
178 self.subs.push(subscription);
179 }
180 }
181 self
182 }
183 #[must_use]
186 #[allow(clippy::needless_pass_by_value)]
187 pub fn subscribe_ship(
188 mut self,
189 id: EzShipId<SID>,
190 subs: Vec<(EventClause<CID, CM>, SubClause<CID, SID>)>,
191 ) -> Self {
192 for (ev, sub) in subs {
193 let subscription = Subscription::new(SubId::Ship(id.clone()), ev, sub);
194 if !self.port_subscribed(&id, subscription.event()) {
195 self.subs.push(subscription);
196 }
197 }
198 self
199 }
200}
201
202impl<CID, SID, CA, CS, CM> Application<CID, SID, CA, CS, CM>
204where
205 CID: EzCptIds,
206 SID: EzShipIds,
207 CA: EzArgs,
208 CS: EzState,
209 CM: EzMsg,
210{
211 async fn run(&mut self) -> Result<()> {
212 debug!("Initial drawing");
213 self.draw_if_needed()?;
214
215 loop {
216 trace!("[ LOOP # {} ]", self.state.get_mut().loop_cnt());
217 let event = self.tui.next().await?;
218
219 let start_time = Instant::now(); let mut event_stack = VecDeque::new();
222 event_stack.push_back(event);
223 event_stack.extend(self.handle_init());
224 while let Some(event) = event_stack.pop_front() {
225 let new = self.handle_event(&event)?;
226 event_stack.extend(new);
227 }
228
229 if self.state.get_mut().should_quit() {
230 self.tui.exit(&ExitReason::UserRequested);
231 break;
232 }
233 if self.state.get_mut().should_redraw() {
234 self.draw_if_needed()?;
235 }
236 self.state.get_mut().log_loop(start_time);
237 }
238 Ok(())
239 }
240
241 fn handle_init(&mut self) -> VecDeque<EzEvent<CID, CM>> {
242 let mut event_stack = VecDeque::new();
243 if let Some(v) = &mut self.view {
244 event_stack.extend(v.init_cpts(self.state.get_mut()));
245 }
246 if let Some(p) = &mut self.port {
247 event_stack.extend(p.init_ships(self.state.get_mut()));
248 }
249 event_stack
250 }
251
252 fn draw_if_needed(&mut self) -> Result<()> {
253 self.state.get_mut().unflag_draw();
254 if let Some(ref mut v) = self.view {
255 let start_time = Instant::now();
256 let mut draw_error = None;
257 let draw_result = self.tui.draw(|f| {
258 v.draw(f).unwrap_or_else(|err| {
259 draw_error = Some(err.clone());
260 });
261 });
262
263 self.state.get_mut().log_frame(start_time);
264 match (draw_result, draw_error) {
265 (Ok(_), None) => Ok(()),
266 (Ok(_), Some(original_error)) => {
267 warn!("Frame rendered but error happened: {}", original_error);
268 Ok(())
269 }
270 (Err(io_error), None) => Err(IoContext::Drawing.wrap_io(&io_error)),
271 (Err(io_error), Some(original_error)) => {
272 error!("Next error is probably caused by {}", io_error);
273 Err(original_error)
274 }
275 }
276 } else {
277 Ok(())
278 }
279 }
280
281 fn tick_if_needed(&mut self) -> Vec<EzEvent<CID, CM>> {
282 let mut ticked = false;
283 let mut messages = vec![];
284 let start_time = Instant::now();
285 if let Some(ref mut v) = self.view {
286 ticked = true;
287 messages.extend(v.tick_components(self.state.get_mut()));
288 }
289
290 if let Some(ref mut p) = self.port {
291 ticked = true;
292 messages.extend(p.tick(self.state.get_mut()));
293 }
294
295 if ticked {
296 self.state.get_mut().log_tick(start_time);
297 }
298
299 messages
300 }
301
302 fn handle_event(&mut self, event: &EzEvent<CID, CM>) -> Result<Vec<EzEvent<CID, CM>>> {
303 let mut messages = vec![];
304 if event == &EzEvent::None {
305 return Ok(vec![]);
306 } else if event == &EzEvent::Tick {
307 trace!("[ TICK # {} ]", self.state.get_mut().tick_cnt());
308 messages.extend(self.tick_if_needed());
309 } else if event == &EzEvent::Render {
310 trace!("[ FRAME # {} ]", self.state.get_mut().frame_cnt());
311 self.state.get_mut().initiate_draw();
312 } else if let EzEvent::Error(err) = event {
313 error!("Received error: '{}'", err);
314 } else if let EzEvent::Client(sub) = event {
315 debug!("Client event: {:?}", sub);
316 } else if let EzEvent::Quit = event {
317 warn!("Received quit event");
318 self.state.get_mut().quit();
319 }
320
321 if let Some(ref mut v) = self.view {
323 if let EzEvent::UpdateLegend(name, matchers) = event {
324 v.set_legend(Some(Legend::new(name.clone(), matchers.clone())));
325 } else if let EzEvent::ForceFocus(cid) = event {
326 v.focus(cid, true)?;
327 } else if let EzEvent::ForceBlur() = event {
328 v.blur()?;
329 }
330
331 for msg in v.forward_event_to_focused_component(event.clone(), self.state.get_mut()) {
333 messages.push(msg);
334 }
335 }
336
337 for msg in self.forward_to_subscriptions(event) {
339 messages.push(msg);
340 }
341
342 if let EzEvent::WindowResize(w, h) = event {
344 let rect: Rect = Rect::new(0, 0, *w, *h);
345 self.tui
346 .resize(rect)
347 .map_err(|err| IoContext::TerminalInit.wrap_io(&err))?;
348 }
349
350 if messages.is_empty() {
351 if let EzEvent::Keyboard(key_pressed) = event {
352 if let Some(quit_hk) = &self.hot_keys.quit
353 && quit_hk.matches(key_pressed)
354 {
355 messages.push(EzEvent::Quit);
356 }
357 if let Some(ref mut v) = self.view {
358 if let Some(nfocus_hk) = &self.hot_keys.next_focus
359 && nfocus_hk.matches(key_pressed)
360 {
361 v.cycle_focus()?;
362 } else if let Some(pfocus_hk) = &self.hot_keys.previous_focus
363 && pfocus_hk.matches(key_pressed)
364 {
365 v.blur()?;
366 }
367 }
368 }
369 }
370
371 Ok(messages)
372 }
373
374 fn forward_to_subscriptions(&mut self, ev: &EzEvent<CID, CM>) -> Vec<EzEvent<CID, CM>> {
375 let mut messages: Vec<EzEvent<CID, CM>> = Vec::new();
376
377 for sub in &self.subs {
378 if let SubId::CPt(cid) = sub.target()
380 && let Some(ref mut v) = self.view
381 && v.focused() != Some(cid)
382 {
383 if !sub.forward(
384 ev,
385 |id, q| {
386 if let SubId::CPt(cid) = id {
387 v.query_cpt(cid, q).ok().flatten()
388 } else {
389 None
390 }
391 },
392 |id| {
393 if let SubId::CPt(cid) = id {
394 v.mounted_internal(cid)
395 } else {
396 false
397 }
398 },
399 ) {
400 continue;
401 }
402 messages.extend(v.forward_event_to_component(
403 cid,
404 ev.clone(),
405 self.state.get_mut(),
406 ));
407 }
408
409 if let SubId::Ship(sid) = sub.target()
411 && let Some(ref mut p) = self.port
412 {
413 if !sub.forward(
414 ev,
415 |id, q| {
416 if let SubId::Ship(sid) = id {
417 p.query_ship(sid, q).ok().flatten()
418 } else {
419 None
420 }
421 },
422 |id| {
423 if let SubId::Ship(sid) = id {
424 p.registered_internal(sid)
425 } else {
426 false
427 }
428 },
429 ) {
430 continue;
431 }
432 messages.extend(p.forward(sid, ev.clone(), self.state.get_mut()));
433 }
434 }
435 messages
436 }
437}
438
439impl<CID, SID, CA, CS, CM> Application<CID, SID, CA, CS, CM>
441where
442 CID: EzCptIds,
443 SID: EzShipIds,
444 CA: EzArgs,
445 CS: EzState,
446 CM: EzMsg,
447{
448 pub fn set_state(&mut self, state: CS) {
450 self.state.get_mut().set_client(state);
451 }
452
453 pub fn unsubscribe_component(&mut self, id: &EzCptId<CID>) {
455 self.subs
456 .retain(|x| !matches!(x.target(), SubId::CPt(cid) if cid == id));
457 }
458
459 pub fn unsubscribe_ship(&mut self, id: &EzShipId<SID>) {
461 self.subs
462 .retain(|x| !matches!(x.target(), SubId::Ship(sid) if sid == id));
463 }
464
465 pub fn cpt_subscribed(&self, id: &EzCptId<CID>, clause: &EventClause<CID, CM>) -> bool {
467 self.subs
468 .iter()
469 .any(|s| matches!(s.target(), SubId::CPt(cid) if cid == id) && s.event() == clause)
470 }
471
472 pub fn port_subscribed(&self, id: &EzShipId<SID>, clause: &EventClause<CID, CM>) -> bool {
474 self.subs
475 .iter()
476 .any(|s| matches!(s.target(), SubId::Ship(sid) if sid == id) && s.event() == clause)
477 }
478}