1use crossterm::{
2 event::{self, Event as CEvent, KeyCode, KeyEvent},
3 terminal::{disable_raw_mode, enable_raw_mode},
4};
5use log::{info, warn};
6#[allow(unused_imports)]
7use std::borrow::Borrow;
8use std::{
9 collections::HashMap,
10 fmt,
11 fs::File,
12 io::BufReader,
13 panic,
14 path::Path,
15 rc::Rc,
16 sync::mpsc,
17 thread,
18 time::{Duration, Instant},
19 vec::Vec,
20 {borrow::BorrowMut, cell::RefCell},
21};
22use tui::{
23 backend::Backend,
24 layout::{Alignment, Constraint, Direction, Layout, Rect},
25 style::{Modifier, Style},
26 text::{Span, Spans},
27 widgets::{Block, BorderType, Borders, Clear, Paragraph, Wrap},
28 Frame, Terminal,
29};
30use xml::reader::{EventReader, XmlEvent};
31
32use crate::{
33 actions::{ActionsStorage, IActionsStorage},
34 event_response::EventResponse,
35 markup_element::MarkupElement,
36 storage::{IRendererStorage, RendererStorage},
37 styles::{IStylesStorage, StylesStorage},
38 utils::{color_from_str, extract_attribute, modifier_from_str, modifiers_from_str},
39};
40
41type ActionCallback = fn(HashMap<String, String>, Option<MarkupElement>) -> EventResponse;
44
45pub enum Event<I> {
46 Input(I),
47 Tick,
48}
49
50const WIDGET_NAMES: &[&str] = &["p", "button"];
51
52pub struct MarkupParser<B: Backend> {
64 pub path: String,
65 pub failed: bool,
66 pub error: Option<String>,
67 pub root: Option<Rc<RefCell<MarkupElement>>>,
68 pub storage: Option<Rc<RefCell<RendererStorage<B>>>>,
69 pub current: i32,
70 pub indexed_elements: Vec<MarkupElement>,
71 pub contexts: Vec<(String, Vec<MarkupElement>)>,
72 pub state: HashMap<String, String>,
73 pub actions: ActionsStorage,
74 pub global_styles: StylesStorage,
75 fingerprint: String,
76}
77
78impl<B: Backend> fmt::Debug for MarkupParser<B> {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 let mut r = f.debug_struct("MarkupParser");
81 r.field("failed", &self.failed);
82 r.field("root", &self.root);
83 r.finish()
84 }
85}
86
87impl<B: Backend> MarkupParser<B> {
88 pub fn new(
90 path: String,
91 optional_storage: Option<RendererStorage<B>>,
92 initial_state: Option<HashMap<String, String>>,
93 ) -> MarkupParser<B> {
94 if !Path::new(&path).exists() {
96 panic!("Markup file does not exist at {}", &path);
97 }
98 let file = File::open(&path).unwrap();
99 let buffer = BufReader::new(file);
100 let parser = EventReader::new(buffer);
101 let storage = optional_storage.unwrap_or(RendererStorage::new());
102 let mut root_node: Option<Rc<RefCell<MarkupElement>>> = None;
103 let mut current_node: Option<Rc<RefCell<MarkupElement>>> = None;
104 let mut parent_node: Option<Rc<RefCell<MarkupElement>>> = None;
105 let mut global_styles = StylesStorage::new();
106 let mut indexed_elements = vec![];
107 let mut cntr = 0;
108 let mut parent_count = 0;
109 let mut actions = ActionsStorage::new();
110 for e in parser {
111 cntr += 1;
112 match e {
113 Ok(XmlEvent::StartElement {
114 name, attributes, ..
115 }) => {
116 let valid_name = name.local_name.clone();
117 let mut attrs = HashMap::new();
118 for attr in attributes {
119 attrs.insert(attr.name.local_name, attr.value);
120 }
121
122 if valid_name.eq("tab-item") {
124 if !attrs.contains_key("action") {
125 attrs.insert("action".to_string(), "__change_tab".to_string());
126 }
127 if !attrs.contains_key("index") {
128 attrs.insert("index".to_string(), format!("{}", cntr));
129 }
130 if !attrs.contains_key("tabs-id") && parent_node.is_some() {
131 let pn = MarkupParser::<B>::get_element(parent_node.clone());
132 let gpn = MarkupParser::<B>::get_element(pn.parent_node);
133 attrs.insert("tabs-id".to_string(), gpn.id);
134 }
135 }
136 if valid_name.eq("tab-content")
137 && !attrs.contains_key("tabs-id")
138 && parent_node.is_some()
139 {
140 let pn = MarkupParser::<B>::get_element(parent_node.clone());
141 let gpn = MarkupParser::<B>::get_element(pn.parent_node);
142 attrs.insert("tabs-id".to_string(), gpn.id);
143 }
144
145 let unknown_id = format!("unknown_elm_{}", cntr);
146 let _id = attrs.get("id").unwrap_or(&unknown_id);
147 let unknown_idx = "-1".to_owned();
148
149 let posible_elm_idx = if valid_name.eq(&"tab-item") {
150 parent_count * 10 + cntr
151 } else {
152 -1
153 };
154 let elm_idx = if posible_elm_idx > -1 {
155 posible_elm_idx
156 } else {
157 attrs
158 .get("index")
159 .unwrap_or(&unknown_idx)
160 .parse::<i32>()
161 .unwrap_or(posible_elm_idx)
162 };
163
164 let partial = MarkupElement {
165 deep: if parent_node.is_some() {
166 MarkupParser::<B>::get_element(parent_node.clone()).deep + 1
167 } else {
168 0
169 },
170 id: String::from(_id),
171 text: None,
172 order: elm_idx,
173 name: name.local_name.to_lowercase(),
174 attributes: attrs,
175 children: vec![],
176 parent_node: parent_node.clone(),
177 dependencies: vec![],
178 };
179
180 current_node = Some(Rc::new(RefCell::new(partial.clone())));
181
182 let is_root_defined = root_node.clone().as_ref().is_some();
183 if !is_root_defined {
184 root_node = current_node.clone();
185 }
186
187 if parent_node.is_some() {
188 let parent = parent_node.clone();
189 let parent = parent.unwrap();
190 let parent = parent.as_ref();
191 let mut parent = parent.borrow_mut();
192 let son = current_node.clone().unwrap();
193 parent.children.push(son);
194 }
195
196 if elm_idx != -1 {
197 indexed_elements.push(partial);
198 }
199
200 parent_node = current_node.clone();
201 parent_count = elm_idx;
202 }
203 Ok(XmlEvent::Characters(ref r)) => {
204 let node = current_node.clone();
205 let node = node.unwrap();
206 let node = node.as_ref();
207 let mut node = node.borrow_mut();
208 node.text = Some(String::from(r.trim()));
209 }
210 Ok(XmlEvent::EndElement { .. }) => {
211 let p = MarkupParser::<B>::get_element(parent_node.clone());
212 let q = p.clone();
213 if q.name.eq("styles") {
214 global_styles = MarkupParser::<B>::process_styles(q);
215 }
216 parent_node = p.parent_node;
217 }
218 Ok(XmlEvent::EndDocument { .. }) => {}
219 Err(e) => {
220 return MarkupParser {
221 path,
222 failed: true,
223 error: Some(e.msg().to_string()),
224 root: None,
225 storage: None,
226 current: -1,
227 indexed_elements: vec![],
228 contexts: vec![],
229 actions: ActionsStorage::new(),
230 state: HashMap::new(),
231 global_styles: StylesStorage::new(),
232 fingerprint: String::from("<empty>"),
233 };
234 }
235 _ => {}
236 };
237 }
238 indexed_elements.sort_by(|e1, e2| e1.order.cmp(&e2.order));
239 let state = initial_state.unwrap_or(HashMap::new());
240 actions.add_action("__change_tab".to_string(), |old_state, node_wrapper| {
241 let mut state = old_state;
242 if let Some(node) = node_wrapper {
243 let key = node.attributes.get("tabs-id").unwrap();
244 state.insert(format!("{}:index", key), node.id.clone());
245 }
246 EventResponse::CLEANFOCUS(state)
247 });
248 MarkupParser {
249 path,
250 failed: false,
251 error: None,
252 root: root_node,
253 storage: Some(Rc::new(RefCell::new(storage))),
254 current: -1,
255 indexed_elements,
256 contexts: vec![],
257 actions,
258 state,
259 global_styles,
260 fingerprint: String::from("<empty>"),
261 }
262 }
263
264 fn draw_block(
266 &self,
267 child: &MarkupElement,
268 _area: Rect,
269 focus: bool,
270 active: bool,
271 base_styles: Style,
272 ) -> Block {
273 let styles = MarkupParser::<B>::get_styles(&child.clone(), focus, active);
274 let styles = base_styles.patch(styles);
275 let title = extract_attribute(child.attributes.clone(), "title");
276 let border = extract_attribute(child.attributes.clone(), "border");
277 let border = MarkupParser::<B>::get_border(border.as_str());
278 let block = Block::default().title(title).style(styles).borders(border);
279 block
280 }
281
282 fn draw_paragraph(
283 &self,
284 child: &MarkupElement,
285 area: Rect,
286 focus: bool,
287 active: bool,
288 base_styles: Style,
289 ) -> Paragraph {
290 let styles = MarkupParser::<B>::get_styles(&child.clone(), focus, active);
291 let styles = base_styles.patch(styles);
292 let alignment = MarkupParser::<B>::get_alignment(&child.clone());
293 let block = self.draw_block(&child.clone(), area, focus, active, base_styles);
294 let p = Paragraph::new(child.text.clone().unwrap_or(String::from("")))
295 .style(styles)
296 .alignment(alignment)
297 .wrap(Wrap { trim: true })
298 .block(block);
299 p
300 }
301
302 fn draw_button(
303 &self,
304 child: &MarkupElement,
305 area: Rect,
306 focus: bool,
307 active: bool,
308 base_styles: Style,
309 ) -> Paragraph {
310 let styles = MarkupParser::<B>::get_styles(&child.clone(), focus, active);
311 let styles = base_styles.patch(styles);
312 let mut elcnt = usize::from(area.height);
313 if area.height > 0 {
314 elcnt = usize::from(area.height / 2 - 1);
315 }
316 let text = child.text.clone().unwrap_or(String::from(""));
317 let mut lns_cntt = vec![];
318 for _i in 0..elcnt {
319 lns_cntt.push(Spans::from(""));
320 }
321 lns_cntt.push(Spans::from(Span::styled(
322 text,
323 if focus {
324 styles.add_modifier(Modifier::UNDERLINED)
325 } else {
326 styles
327 },
328 )));
329 let block = Block::default()
330 .style(styles)
331 .borders(Borders::ALL)
332 .border_type(BorderType::Rounded);
333 let p = Paragraph::new(lns_cntt)
334 .style(styles)
335 .alignment(Alignment::Center)
336 .block(block);
337 p
338 }
339
340 fn draw_dialog(
341 &self,
342 child: &MarkupElement,
343 _area: Rect,
344 focus: bool,
345 active: bool,
346 base_styles: Style,
347 ) -> Block {
348 let styles = MarkupParser::<B>::get_styles(&child.clone(), focus, active);
349 let styles = base_styles.patch(styles);
350 let block = Block::default()
351 .style(styles)
352 .borders(Borders::ALL)
353 .border_type(BorderType::Double);
354 block
355 }
356
357 fn draw_tab_borders(
358 &self,
359 _child: &MarkupElement,
360 _area: Rect,
361 _focus: bool,
362 _active: bool,
363 _base_styles: Style,
364 ) -> Block {
365 let block = Block::default()
366 .borders(Borders::BOTTOM)
367 .border_type(BorderType::Rounded);
368 block
369 }
370
371 fn draw_tab_item(
372 &self,
373 child: &MarkupElement,
374 _area: Rect,
375 _focus: bool,
376 _active: bool,
377 base_styles: Style,
378 ) -> Paragraph {
379 let styles = base_styles;
397 let text = child.text.clone();
398 let text = text.unwrap_or("Tab".to_string());
399 let block = Block::default()
400 .style(styles)
401 .borders(Borders::TOP | Borders::RIGHT | Borders::LEFT)
402 .border_type(BorderType::Rounded);
403 let p = Paragraph::new(text)
404 .style(styles)
405 .alignment(Alignment::Center)
406 .block(block);
407 p
408 }
409
410 fn go_next(&mut self) -> i32 {
411 let size = i32::try_from(self.indexed_elements.len()).unwrap() - 2;
412 if self.current > size {
413 self.current = -1;
414 } else {
415 self.current += 1;
416 }
417 self.current
418 }
419
420 fn go_prev(&mut self) -> i32 {
421 let size = i32::try_from(self.indexed_elements.len()).unwrap() - 1;
422 if self.current < 0 {
423 self.current = size;
424 } else {
425 self.current -= 1;
426 }
427 self.current
428 }
429
430 fn do_action(&mut self) -> EventResponse {
431 if self.current > -1 {
432 let current = self.indexed_elements[self.current as usize].clone();
433 let action = extract_attribute(current.attributes.clone(), "action");
434 if self.actions.has_action(action.clone()) {
435 info!("Executing {}", action);
436 let new_state = self
437 .actions
438 .execute(action, self.state.clone(), Some(current));
439 if let Some(event_response) = new_state {
440 return event_response;
441 }
442 }
443 }
444 EventResponse::NOOP
445 }
446
447 fn get_element_styles(&self, node: &MarkupElement, focus: bool, active: bool) -> Style {
448 let name = node.name.clone();
449 let parent = node.parent_node.clone();
450 let parent_styles = if let Some(nref) = parent {
451 let parent = MarkupParser::<B>::extract_element(&nref);
452 self.get_element_styles(&parent, focus, active)
453 } else {
454 Style::default()
455 };
456 let rulename = if focus {
457 format!("{}{}", name, if focus { ":focus" } else { "" })
458 } else if active {
459 format!("{}{}", name, if active { ":active" } else { "" })
460 } else {
461 name
462 };
463 let base_styles = parent_styles.patch(self.global_styles.get_rule(rulename));
464 let rulename = format!("#{}", node.id);
465 let elm_styles = self.global_styles.get_rule(rulename);
466
467 base_styles.patch(elm_styles)
468 }
469
470 fn draw_element(&mut self, frame: &mut Frame<B>, area: Rect, node: &MarkupElement) -> bool {
471 let name = node.name.clone();
472 let name = name.as_str();
473 let storage = self.storage.clone();
474 let storage = storage.unwrap();
475 let storage = storage.as_ref();
476 let storage = storage.borrow_mut();
477 if storage.has_component(name) {
478 storage.render(name, frame);
479 true
480 } else {
481 let mut cid = "".to_owned();
482 if self.current > -1 {
483 cid = self.indexed_elements[self.current as usize].id.clone();
484 }
485 let is_focused_node = node.id.eq(&cid);
486 let is_active_tab = if node.parent_node.is_some() {
487 let parent_node: MarkupElement =
488 node.parent_node.clone().unwrap().as_ref().borrow().clone();
489 let parent_id = parent_node.id;
490 let state_elm = format!("{}:index", parent_id);
491 let current = self.state.get(&state_elm);
492 if let Some(current) = current {
493 let currval = current;
494 currval.eq(&node.id)
495 } else {
496 false
497 }
498 } else {
499 false
500 };
501 let base_styles = self.get_element_styles(node, is_focused_node, is_active_tab);
502 match name {
503 "container" | "block" => {
504 let widget = self.draw_block(node, area, is_focused_node, false, base_styles);
505 frame.render_widget(Clear, area);
506 frame.render_widget(widget, area);
507 true
508 }
509 "tabs-borders" => {
510 let widget =
511 self.draw_tab_borders(node, area, is_focused_node, false, base_styles);
512 frame.render_widget(widget, area);
513 true
514 }
515 "p" => {
516 let widget = self.draw_paragraph(node, area, is_focused_node, false, base_styles);
517 frame.render_widget(Clear, area);
518 frame.render_widget(widget, area);
519 true
520 }
521 "tabs" => {
522 let id = format!("{}:index", node.id.clone());
523 if self.state.get(&id).is_none() {
524 let mut state = self.state.clone();
525 let thdr = node.children.first();
526 if let Some(wrapped_value) = thdr {
527 let plain_elm = MarkupParser::<B>::extract_element(wrapped_value);
528 let frst = plain_elm.children.first();
529 if let Some(first) = frst {
530 let chld = MarkupParser::<B>::extract_element(first);
531 state.insert(id, chld.id);
532 }
533 }
534 self.state = state;
535 }
536 true
537 }
538 "tab-item" => {
539 let widget =
540 self.draw_tab_item(node, area, is_focused_node, is_active_tab, base_styles);
541 frame.render_widget(Clear, area);
542 frame.render_widget(widget, area);
543 true
544 }
545 "tab-content" => {
546 let default_val = "unknown".to_string();
547 let show_flag = node.attributes.get("tabs-id").unwrap_or(&default_val);
548 let show_flag = format!("{}:index", show_flag);
549 let state_value = self.state.get(&show_flag).unwrap_or(&default_val);
550 let me = node.attributes.get("for").unwrap_or(&default_val);
551 if state_value.eq(me) {
552 let widget = self.draw_block(node, area, is_focused_node, false, base_styles);
553 frame.render_widget(Clear, area);
554 frame.render_widget(widget, area);
555 return true;
556 }
557 false
558 }
559 "dialog" => {
560 let new_node = node.clone();
561 let show_flag = extract_attribute(new_node.clone().attributes, "show");
562 let default_val = "false".to_string();
563 let state_value = self.state.get(&show_flag).unwrap_or(&default_val);
564 if state_value.eq(&"true".to_string()) {
565 self.add_context(node);
566 let widget =
567 self.draw_dialog(&new_node, area, is_focused_node, false, base_styles);
568 frame.render_widget(Clear, area);
569 frame.render_widget(widget, area);
570 return true;
571 } else {
572 self.remove_context(node);
573 }
574 false
575 }
576 "button" => {
577 let mut new_area = area;
578 new_area.height = if new_area.height > 3 {
579 3
580 } else {
581 new_area.height
582 };
583 let widget = self.draw_button(node, new_area, is_focused_node, false, base_styles);
584 frame.render_widget(Clear, area);
585 frame.render_widget(widget, new_area);
586 true
587 }
588 _ => {
589 let widget = Block::default();
590 frame.render_widget(Clear, area);
591 frame.render_widget(widget, area);
592 true
593 }
594 }
595 }
596 }
597
598 fn process_block(
599 &self,
600 frame: &mut Frame<B>,
601 node: &MarkupElement,
602 dependency: Option<MarkupElement>,
603 place: Option<Rect>,
604 _margin: Option<u16>, count: usize,
606 ) -> Vec<(Rect, MarkupElement)> {
607 let current = node.clone();
608 let split_space = place.unwrap_or(frame.size());
609 let border_value = extract_attribute(current.attributes.clone(), "border");
610 let mut res: Vec<(Rect, MarkupElement)> = vec![];
611 let mut constraints: Vec<Constraint> = vec![];
612 let id = extract_attribute(current.attributes.clone(), "id");
613 let mut widgets_info: Vec<(usize, MarkupElement)> = vec![];
614 let mut children_nodes: Vec<(usize, MarkupElement)> = vec![];
615 res.push((place.unwrap_or(frame.size()), current));
616
617 info!(target: "MarkupParser",
618 "{}Container #{}[[{:?}]]",
619 "".repeat(count * 2),
620 id,
621 split_space.clone(),
622 );
623
624 for (position, base_child) in node.children.iter().enumerate() {
627 let child = base_child.as_ref().borrow();
628 let constraint = extract_attribute(child.clone().attributes, "constraint");
629 constraints.push(MarkupParser::<B>::get_constraint(constraint));
630 let child_name = child.clone().name;
631
632 if MarkupParser::<B>::is_widget(child_name.as_str()) {
633 widgets_info.push((position, child.clone()));
634 } else {
635 children_nodes.push((position, child.clone()));
636 }
637 }
638
639 let new_margin = if border_value.eq("") || border_value.eq("none") {
640 0 } else {
642 1
643 };
644 let layout = Layout::default()
645 .direction(Direction::Horizontal)
646 .margin(new_margin)
647 .constraints(constraints.clone().as_ref());
648 let chunks = layout.split(split_space);
649
650 for (cntr, base_child) in children_nodes.iter() {
651 let counter = *cntr;
652 let mut child = base_child.clone();
653 if dependency.is_some() {
654 child.dependencies.push(dependency.clone().unwrap().id);
655 }
656 let partial_res = self.process_node(
657 frame,
658 &child,
659 dependency.clone(),
660 Some(chunks[counter]),
661 None,
662 count + 1,
663 );
664 for pair in partial_res.iter() {
665 res.push((pair.0, pair.1.clone()));
666 }
667 }
668
669 for (cntr, widget_info) in widgets_info.iter() {
670 let counter = *cntr;
671 let mut mkp_elm = widget_info.clone();
672 if dependency.is_some() {
673 let did = dependency.clone().unwrap().id;
674 if !mkp_elm.dependencies.contains(&did) {
675 mkp_elm.dependencies.push(did);
676 }
677 }
678 res.push((chunks[counter], mkp_elm));
679 }
680
681 res
682 }
683
684 fn process_layout(
685 &self,
686 frame: &mut Frame<B>,
687 node: &MarkupElement,
688 dependency: Option<MarkupElement>,
689 place: Option<Rect>,
690 margin: Option<u16>,
691 count: usize,
692 ) -> Vec<(Rect, MarkupElement)> {
693 let current = node.clone();
694 let split_space = place.unwrap_or(frame.size());
695 let direction = MarkupParser::<B>::get_direction(node);
696 let id = extract_attribute(current.attributes.clone(), "id");
697 info!(target: "MarkupParser",
698 "{}Layout #{}[{}]({} children) [[{:?}]]",
699 " ".repeat(count * 2),
700 id,
701 current.attributes.get("direction").unwrap(),
702 node.children.len(),
703 split_space.clone(),
704 );
705 let mut res: Vec<(Rect, MarkupElement)> = vec![];
706 let constraints: Vec<Constraint> = MarkupParser::<B>::get_constraints(node.clone());
707 info!(target: "MarkupParser", "{} ::>{:?}", "".repeat(count * 2), constraints);
708
709 let layout = Layout::default()
710 .direction(direction)
711 .margin(margin.unwrap_or(0))
712 .constraints(constraints.as_ref());
713
714 let chunks = layout.split(split_space);
715
716 for (position, base_child) in node.children.iter().enumerate() {
717 let mut child = base_child.as_ref().borrow().clone();
718 if dependency.is_some() {
719 child.dependencies.push(dependency.clone().unwrap().id);
720 }
721 let partial_res = self.process_node(
722 frame,
723 &child,
724 dependency.clone(),
725 Some(chunks[position]),
726 Some(1),
727 count + 1,
728 );
729 for pair in partial_res.iter() {
730 let mut mkp_elm = pair.1.clone();
731 if dependency.is_some() {
732 let did = dependency.clone().unwrap().id;
733 if !mkp_elm.dependencies.contains(&did) {
734 mkp_elm.dependencies.push(did);
735 }
736 }
737 res.push((pair.0, mkp_elm));
738 }
739 }
740
741 res
742 }
743
744 fn process_other(
745 &self,
746 frame: &mut Frame<B>,
747 node: &MarkupElement,
748 depends_on: Option<MarkupElement>,
749 place: Option<Rect>,
750 margin: Option<u16>,
751 count: usize,
752 ) -> Option<Vec<(Rect, MarkupElement)>> {
753 let mut current = node.clone();
754 let id = extract_attribute(current.attributes.clone(), "id");
768 let mut split_space = place.unwrap_or(frame.size());
769 let mut child_space = split_space;
770 let mut res: Vec<(Rect, MarkupElement)> = vec![];
771 let mut subsequents: Vec<(Rect, MarkupElement)> = vec![];
772 let mut dependency = depends_on;
773 let mut process_children = true;
774
775 info!(target: "MarkupParser",
776 "{}Other #{}[[{:?}]]",
777 "".repeat(count * 2),
778 id,
779 split_space.clone(),
780 );
781
782 let cname = node.name.as_str();
783 match cname {
784 "tabs" => {
785 let header_size = 3;
786 let vertical_layout = Layout::default()
787 .direction(Direction::Vertical)
788 .margin(margin.unwrap_or(0))
789 .constraints(
790 vec![
791 Constraint::Length(header_size),
792 Constraint::Length(split_space.height - header_size),
793 ]
794 .as_ref(),
795 );
796 let vertical_chunks = vertical_layout.split(split_space);
797 for (pos, chld) in node.children.iter().enumerate() {
798 let elm = chld.as_ref().borrow().clone();
799 let child_space = vertical_chunks[pos];
800 if pos > 0 {
801 let partial_res = self.process_node(
802 frame,
803 &elm,
804 None,
805 Some(child_space),
806 Some(1),
807 count + 1,
808 );
809 for pair in partial_res.iter() {
810 subsequents.push(pair.clone());
811 }
812 } else {
813 let elm = chld.as_ref().borrow().clone();
814 let start_x = vertical_chunks[0].x + 1;
815 let start_y = vertical_chunks[0].y;
816 let line = MarkupElement {
817 id: "line_unk".to_string(),
818 attributes: HashMap::new(),
819 parent_node: None,
820 children: vec![],
821 name: "tabs-borders".to_string(),
822 text: None,
823 deep: 0,
824 dependencies: vec![],
825 order: -1,
826 };
827 let tab_width: u16 = 8;
828 subsequents.push((vertical_chunks[0], line));
829 for (_idx, chld) in elm.children.iter().enumerate() {
830 let idx: u16 = _idx as u16;
831 let chldelm = chld.as_ref().clone().into_inner();
832 let order = 10 + (idx as i32);
833 let btn = MarkupElement {
834 id: chldelm.id.clone(),
835 attributes: chldelm.attributes.clone(),
836 parent_node: elm.parent_node.clone(),
837 children: vec![],
838 name: chldelm.name,
839 text: chldelm.text.clone(),
840 deep: chldelm.deep + 1,
841 dependencies: vec![],
842 order,
843 };
844 let place = Rect::new(
845 start_x + (idx * tab_width) + (idx),
846 start_y,
847 tab_width + 1,
848 2,
849 );
850 subsequents.push((place, btn));
851 }
852 }
853 }
854 process_children = false;
855 }
856 "tab-content" => {
857 let vertical_layout = Layout::default()
858 .direction(Direction::Vertical)
859 .margin(margin.unwrap_or(0))
860 .constraints(
861 vec![Constraint::Percentage(10), Constraint::Percentage(90)].as_ref(),
862 );
863 let vertical_chunks = vertical_layout.split(split_space);
864 split_space = vertical_chunks[1];
865 dependency = Some(node.clone());
866 }
867 "dialog" => {
868 let horizontal_layout = Layout::default()
869 .direction(Direction::Horizontal)
870 .margin(margin.unwrap_or(0))
871 .constraints(
872 vec![
873 Constraint::Percentage(34),
874 Constraint::Percentage(32),
875 Constraint::Percentage(34),
876 ]
877 .as_ref(),
878 );
879 let horizontal_chunks = horizontal_layout.split(frame.size());
880
881 let vertical_layout = Layout::default()
882 .direction(Direction::Vertical)
883 .margin(margin.unwrap_or(0))
884 .constraints(
885 vec![
886 Constraint::Percentage(31),
887 Constraint::Percentage(34),
888 Constraint::Percentage(31),
889 ]
890 .as_ref(),
891 );
892 let vertical_chunks = vertical_layout.split(horizontal_chunks[1]);
893
894 split_space = vertical_chunks[1];
895 let dialog_space = vertical_chunks[1];
896
897 let dialog_parts = Layout::default()
898 .direction(Direction::Vertical)
899 .margin(1)
900 .constraints(
901 vec![Constraint::Percentage(80), Constraint::Percentage(20)].as_ref(),
902 );
903 let dialog_chunks = dialog_parts.split(dialog_space);
904
905 let action = extract_attribute(node.attributes.clone(), "action");
906 let btns = extract_attribute(node.attributes.clone(), "buttons");
907 let btns: Vec<String> = btns.split('|').map(String::from).collect();
908 let btn_constraints: Vec<Constraint> = btns
909 .clone()
910 .iter()
911 .map(|_| Constraint::Percentage((100 / btns.len()) as u16))
912 .collect();
913
914 let buttons_layout = Layout::default()
915 .direction(Direction::Horizontal)
916 .constraints(btn_constraints.as_ref());
917 child_space = dialog_chunks[0];
918 let button_chunks = buttons_layout.split(dialog_chunks[1]);
919
920 for (elm_idx, btn) in btns.iter().enumerate() {
921 let btn_id = format!("{}_btn_{}", node.id, btn);
922 let btn_action = if !action.is_empty() {
923 action.clone()
924 } else {
925 format!("on_{}", btn_id)
926 };
927 let btn_elm = MarkupElement {
928 deep: node.deep + 1,
929 id: btn_id.clone(),
930 text: Some(String::from(btn)),
931 order: elm_idx as i32,
932 name: String::from("button"),
933 attributes: HashMap::from([
934 ("id".to_string(), btn_id.clone()),
935 ("action".to_string(), btn_action),
936 ("index".to_string(), format!("{}", elm_idx)),
937 ]),
938 children: vec![],
939 parent_node: Some(Rc::new(RefCell::new(node.clone()))),
940 dependencies: vec![node.id.clone()],
941 };
942 let btn_desc = Rc::new(RefCell::new(btn_elm.clone()));
943 current.children.push(btn_desc);
944 subsequents.push((button_chunks[elm_idx], btn_elm));
945 }
946 dependency = Some(node.clone());
947 }
948 _ => {
949 let layout = Layout::default()
950 .direction(Direction::Horizontal)
951 .margin(margin.unwrap_or(0))
952 .constraints(vec![Constraint::Percentage(100)].as_ref());
953 split_space = layout.split(place.unwrap_or(frame.size()))[0];
954 }
955 }
956 res.push((split_space, current));
957
958 if process_children {
959 for base_child in node.children.iter() {
960 let mut child = base_child.as_ref().borrow().clone();
961 if dependency.is_some() {
962 child.dependencies.push(dependency.clone().unwrap().id);
963 }
964 let partial_res = self.process_node(
965 frame,
966 &child,
967 dependency.clone(),
968 Some(child_space),
969 Some(1),
970 count + 1,
971 );
972 for pair in partial_res.iter() {
973 let mut mkp_elm = pair.1.clone();
974 if dependency.is_some() {
975 let did = dependency.clone().unwrap().id;
976 if !mkp_elm.dependencies.contains(&did) {
977 mkp_elm.dependencies.push(did);
978 }
979 }
980 res.push((pair.0, mkp_elm));
981 }
982 }
983 }
984
985 for shld in subsequents {
986 res.push(shld);
987 }
988
989 Some(res)
990 }
991
992 fn process_node(
993 &self,
994 frame: &mut Frame<B>,
995 node: &MarkupElement,
996 depends_on: Option<MarkupElement>,
997 place: Option<Rect>,
998 margin: Option<u16>,
999 count: usize,
1000 ) -> Vec<(Rect, MarkupElement)> {
1001 let name = node.name.clone();
1002 let name = name.as_str();
1003 let values: Vec<(Rect, MarkupElement)> = match name {
1004 "styles" => vec![],
1005 "layout" => {
1006 self.process_layout(frame.borrow_mut(), node, depends_on, place, margin, count)
1007 }
1008 "container" => {
1009 self.process_block(frame.borrow_mut(), node, depends_on, place, margin, count)
1010 }
1011 "block" => {
1012 self.process_block(frame.borrow_mut(), node, depends_on, place, margin, count)
1013 }
1014 _ => {
1015 let res =
1016 self.process_other(frame.borrow_mut(), node, depends_on, place, margin, count);
1017 if let Some(value) = res {
1018 value
1019 } else {
1020 warn!("Unknown node type \"{}\"", name);
1021 vec![]
1022 }
1023 }
1024 };
1025 values
1026 }
1027
1028 pub fn add_action(&mut self, name: &str, action: ActionCallback) -> &mut Self {
1029 self.actions.add_action(String::from(name), action);
1030 self
1031 }
1032
1033 fn can_be_drawn(&self, node: MarkupElement, drawn: &[String]) -> bool {
1034 let others = node.dependencies;
1035 if others.is_empty() {
1036 return true;
1037 }
1038 let mut res = false;
1039 for eid in others {
1040 if drawn.contains(&eid) {
1041 res = true;
1042 }
1043 }
1044 res
1045 }
1046
1047 fn get_fingerprint(&self) -> String {
1048 let idxd: Vec<String> = self.indexed_elements.iter().map(|x| x.id.clone()).collect();
1049 let mut state_fngrprnt = format!(
1050 "{}:{}:{}:",
1051 self.current,
1052 self.contexts.len(),
1053 idxd.join("~")
1054 );
1055 for (key, value) in self.state.clone().iter() {
1056 state_fngrprnt = format!("{}-{}_{}", state_fngrprnt, key, value);
1057 }
1058 state_fngrprnt
1059 }
1060
1061 fn update_fingerprint(&mut self) {
1062 let state_fngrprnt = self.get_fingerprint();
1063 self.fingerprint = state_fngrprnt;
1064 }
1065
1066 pub fn render_ui(&mut self, frame: &mut Frame<B>) -> Result<bool, String> {
1069 let elm = self.root.clone();
1070 if elm.is_some() {
1071 let root = MarkupParser::<B>::get_element(elm);
1072 let drawables = self.process_node(frame.borrow_mut(), &root, None, None, None, 0);
1073 let mut drawn: Vec<String> = vec![];
1074 drawables.iter().for_each(|pair| {
1075 let area = pair.0;
1076 let node = pair.1.clone();
1077 if self.can_be_drawn(node.clone(), &drawn) {
1078 let done = self.draw_element(frame, area, &node);
1080 if done {
1081 drawn.push(node.id);
1082 }
1083 } else {
1084 }
1086 });
1087 Ok(true)
1088 } else {
1089 let err = "Critical error on render process.".to_string();
1090 Err(err)
1091 }
1092 }
1093
1094 pub fn add_context(&mut self, node: &MarkupElement) {
1095 let loc = self.contexts.len();
1096 let current = self.contexts.get(loc);
1097 let must_insert = current.is_some() && !current.unwrap().0.eq(&node.id);
1098 if loc == 0 || must_insert {
1099 self.contexts
1100 .push((node.id.clone(), self.indexed_elements.clone()));
1101 let chld: Vec<MarkupElement> = node
1102 .clone()
1103 .children
1104 .iter()
1105 .map(|x| x.as_ref().borrow().clone())
1106 .filter(|x| x.order > -1)
1107 .collect();
1108 self.indexed_elements = chld;
1109 self.current = -1;
1110 }
1111 self.fingerprint = String::from("<>");
1112 }
1113
1114 pub fn remove_context(&mut self, node: &MarkupElement) {
1115 let loc = self.contexts.len();
1116 if loc > 0 {
1117 let partial = self.contexts[loc - 1].clone();
1118 if partial.0.eq(&node.id) {
1119 self.indexed_elements = partial.1;
1120 self.contexts.pop();
1121 self.current = -1;
1122 }
1123 }
1124 self.fingerprint = String::from("<>");
1125 }
1126
1127 pub fn test_check(&self, backend: B) -> Result<(), Box<dyn std::error::Error>> {
1128 let elm = self.root.clone();
1129 if elm.is_some() {
1130 let mut terminal = Terminal::new(backend)?;
1131 let root = MarkupParser::<B>::get_element(elm);
1132 terminal.draw(|frame| {
1133 let drawables = self.process_node(frame.borrow_mut(), &root, None, None, None, 0);
1134 let ids: Vec<String> = drawables
1135 .iter()
1136 .map(|x| format!("{}#{}", x.1.name, x.1.id))
1137 .collect();
1138 println!("{:#?}", drawables);
1139 println!("{:#?}", ids);
1140 })?;
1141 }
1142 println!("{:#?}", self.global_styles);
1143 Ok(())
1144 }
1145
1146 pub fn ui_loop(
1152 &mut self,
1153 backend: B,
1154 on_event: impl Fn(crossterm::event::KeyEvent, HashMap<String, String>) -> EventResponse,
1155 ) -> Result<(), Box<dyn std::error::Error>>
1157{
1163 if self.error.is_some() {
1164 panic!("{}", self.error.clone().unwrap());
1165 }
1166
1167 let mut terminal = Terminal::new(backend)?;
1168
1169 enable_raw_mode().expect("Can't run in raw mode.");
1170 terminal.clear()?;
1171
1172 let (tx, rx) = mpsc::channel::<Event<KeyEvent>>();
1173 let tick_rate = Duration::from_millis(200);
1174
1175 thread::spawn(move || {
1176 let mut last_tick = Instant::now();
1177 loop {
1178 let timeout = tick_rate
1179 .checked_sub(last_tick.elapsed())
1180 .unwrap_or_else(|| Duration::from_secs(0));
1181
1182 if event::poll(timeout).expect("poll works") {
1183 if let CEvent::Key(key) = event::read().expect("can read events") {
1184 tx.send(Event::Input(key)).expect("can send events");
1185 }
1186 }
1187
1188 if last_tick.elapsed() >= tick_rate && tx.send(Event::Tick).is_ok() {
1189 last_tick = Instant::now();
1190 }
1191 }
1192 });
1193 let mut error_info: Option<String> = None;
1194 let mut should_quit: bool = false;
1195 loop {
1196 let new_fprnt = self.get_fingerprint();
1197 if !new_fprnt.eq(&self.fingerprint) {
1198 terminal.draw(|frame| {
1199 let res = self.render_ui(frame);
1200 if res.is_ok() {
1201 self.update_fingerprint();
1202 } else {
1203 error_info = res.err();
1204 should_quit = true;
1205 }
1206 })?;
1207 }
1208 let evt: Event<crossterm::event::KeyEvent> = rx.recv()?;
1209 if let Event::Input(key_event) = evt {
1210 let event = key_event;
1211 match event.code {
1212 KeyCode::Tab => {
1213 self.go_next();
1214 }
1215 KeyCode::BackTab => {
1216 self.go_prev();
1217 }
1218 KeyCode::Enter => {
1219 let res = self.do_action();
1220 match res {
1221 EventResponse::QUIT => {
1222 should_quit = true;
1223 }
1224 EventResponse::STATE(state) => {
1225 self.state = state;
1226 }
1227 EventResponse::CLEANFOCUS(state) => {
1228 self.state = state;
1229 self.current = -1;
1230 }
1231 _ => {}
1232 }
1233 }
1234 _ => {
1235 info!("{:?}", key_event);
1236 }
1237 }
1238 let response =
1239 on_event(key_event as crossterm::event::KeyEvent, self.state.clone());
1240 match response {
1241 EventResponse::QUIT => {
1242 should_quit = true;
1243 }
1244 EventResponse::STATE(new_state) => {
1245 self.state = new_state;
1246 }
1247 EventResponse::CLEANFOCUS(new_state) => {
1248 self.state = new_state;
1249 self.current = -1;
1250 }
1251 EventResponse::NOOP => {}
1252 }
1253 if should_quit {
1254 break;
1255 }
1256 }
1257 }
1258
1259 disable_raw_mode()?;
1260 terminal.show_cursor()?;
1261 terminal.clear()?;
1262 if error_info.is_some() {
1263 panic!("{}", error_info.unwrap());
1264 }
1265 Ok(())
1266 }
1267
1268 fn get_constraints(node: MarkupElement) -> Vec<Constraint> {
1271 let mut constraints: Vec<Constraint> = vec![];
1272 if !node.children.is_empty() {
1273 for (_position, base_child) in node.children.iter().enumerate() {
1274 let child = base_child.as_ref().borrow().clone();
1275 let constraint = extract_attribute(child.attributes.clone(), "constraint");
1276 constraints.push(MarkupParser::<B>::get_constraint(constraint));
1277 }
1278 }
1279 constraints
1280 }
1281
1282 pub fn get_element(node: Option<Rc<RefCell<MarkupElement>>>) -> MarkupElement {
1283 let r = node.unwrap();
1284 let r = r.as_ref().borrow().to_owned();
1285 r
1286 }
1287
1288 pub fn extract_element(node: &Rc<RefCell<MarkupElement>>) -> MarkupElement {
1289 let r = node.as_ref().borrow().to_owned();
1290 r
1291 }
1292
1293 pub fn is_widget(node_name: &str) -> bool {
1294 WIDGET_NAMES.contains(&node_name)
1295 }
1296
1297 pub fn is_layout(node_name: &str) -> bool {
1298 node_name.eq("layout")
1299 }
1300
1301 pub fn get_border(border_value: &str) -> Borders {
1302 let border = String::from(border_value);
1303 if border.contains('|') {
1304 let borders = border
1305 .split('|')
1306 .map(String::from)
1307 .map(|s| MarkupParser::<B>::get_border(&s))
1308 .collect::<Vec<Borders>>();
1309 let size = borders.len();
1310 let mut res = borders[0];
1311 for border in borders.iter().take(size).skip(1) {
1314 res |= *border;
1315 }
1316 return res;
1317 }
1318 let border = match border.to_lowercase().as_str() {
1319 "all" => Borders::ALL,
1320 "bottom" => Borders::BOTTOM,
1321 "top" => Borders::TOP,
1322 "left" => Borders::LEFT,
1323 "right" => Borders::RIGHT,
1324 _ => Borders::NONE,
1325 };
1326 border
1327 }
1328
1329 pub fn get_constraint(constraint: String) -> Constraint {
1330 let res = if constraint.ends_with('%') {
1331 let constraint_value = constraint.replace('%', "");
1332 let constraint_value = constraint_value.parse::<u16>().unwrap_or(1);
1333 Constraint::Percentage(constraint_value)
1334 } else if constraint.ends_with("min") {
1335 let constraint_value = constraint.replace("min", "");
1336 let constraint_value = constraint_value.parse::<u16>().unwrap_or(1);
1337 Constraint::Min(constraint_value)
1338 } else if constraint.ends_with("max") {
1339 let constraint_value = constraint.replace("max", "");
1340 let constraint_value = constraint_value.parse::<u16>().unwrap_or(1);
1341 Constraint::Max(constraint_value)
1342 } else if constraint.contains(':') {
1343 let parts = constraint.split(':');
1344 let parts: Vec<&str> = parts.collect();
1345 let x = String::from(parts[0]).parse::<u32>().unwrap_or(1);
1346 let y = String::from(parts[1]).parse::<u32>().unwrap_or(1);
1347 Constraint::Ratio(x, y)
1348 } else {
1349 let constraint_value = constraint.parse::<u16>().unwrap_or(1);
1350 Constraint::Length(constraint_value)
1351 };
1352 res
1353 }
1354
1355 pub fn get_direction(node: &MarkupElement) -> Direction {
1356 let direction = extract_attribute(node.attributes.clone(), "direction");
1357 if direction.eq("vertical") {
1358 Direction::Vertical
1359 } else {
1360 Direction::Horizontal
1361 }
1362 }
1363
1364 pub fn get_alignment(node: &MarkupElement) -> Alignment {
1365 let align_text = extract_attribute(node.attributes.clone(), "align");
1366 match align_text.as_str() {
1367 "center" => Alignment::Center,
1368 "left" => Alignment::Left,
1369 "right" => Alignment::Right,
1370 _ => Alignment::Left,
1371 }
1372 }
1373
1374 pub fn process_styles(node: MarkupElement) -> StylesStorage {
1375 let mut global_styles = StylesStorage::new();
1376 if node.text.is_some() {
1377 let text = node.text.unwrap();
1378 let text = text
1379 .replace(['\n', '\r', ' '], "")
1380 .replace('{', " {")
1381 .replace('}', "}\n");
1382 let rules: Vec<_> = text
1383 .split('\n')
1384 .filter(|x| !x.is_empty())
1385 .map(|text| {
1386 let nt = String::from(text);
1387 let rule_info = nt.replace('}', "");
1388 let rule_info: Vec<String> = rule_info.split(" {").map(String::from).collect();
1389 let rules = rule_info;
1390 let rulename: String = rules.get(0).unwrap().to_string();
1391 let properties: String = rules.get(1).unwrap().to_string();
1392 (rulename, MarkupParser::<B>::generate_styles(properties))
1393 })
1394 .collect();
1395 for (rulename, styles) in rules.iter() {
1396 global_styles.add_rule(rulename.clone(), *styles);
1397 }
1398 }
1399 global_styles
1400 }
1401
1402 fn generate_styles(styles_text: String) -> Style {
1403 let mut res = Style::default();
1404 if styles_text.len() < 3 {
1405 return res;
1406 }
1407 let styles_vec = styles_text
1408 .split(';')
1409 .filter(|x| !x.is_empty())
1410 .map(|style| style.split(':').map(|word| word.trim()).collect())
1411 .map(|data: Vec<&str>| (data[0], data[1]))
1412 .collect::<Vec<(&str, &str)>>();
1413 let styles: HashMap<&str, &str> = styles_vec.into_iter().collect();
1414 if styles.contains_key("bg") {
1415 let color = styles.get("bg").unwrap();
1416 let color = color_from_str(color);
1417 res = res.bg(color);
1418 }
1419 if styles.contains_key("fg") {
1420 let color = styles.get("fg").unwrap();
1421 let color = color_from_str(color);
1422 res = res.fg(color);
1423 }
1424 if styles.contains_key("weight") {
1425 let weight = modifier_from_str(styles.get("weight").unwrap());
1426 res = res.add_modifier(weight);
1427 }
1428 if styles.contains_key("font-decoration") {
1429 let decorations = modifiers_from_str(styles.get("font-decoration").unwrap());
1430 res = res.patch(decorations);
1431 }
1432 res
1434 }
1435
1436 pub fn get_styles(node: &MarkupElement, focus: bool, active: bool) -> Style {
1437 let key = if focus { "focus_styles" } else { "styles" };
1438 let key = if active { "active_styles" } else { key };
1439 let styles_text = extract_attribute(node.attributes.clone(), key);
1440 MarkupParser::<B>::generate_styles(styles_text)
1441 }
1442}