1#![doc = include_str!("../README.md")]
2
3use fltk::{enums::*, prelude::*, *};
4use std::sync::{Arc, Mutex};
5
6#[derive(Copy, Clone, Debug)]
7pub struct Style {
8 pub color: Color,
9 pub font: Font,
10 pub size: i32,
11 pub attr: text::TextAttr,
12 pub bgcolor: Color,
13}
14
15impl Default for Style {
16 fn default() -> Self {
17 Self {
18 color: Color::Foreground,
19 font: Font::Helvetica,
20 size: app::font_size(),
21 attr: text::TextAttr::None,
22 bgcolor: Color::Background2,
23 }
24 }
25}
26
27#[derive(Debug, Clone)]
28pub struct RichTextBuilder {
29 buf: text::TextBuffer,
30 sbuf: text::TextBuffer,
31 data: Arc<Mutex<Vec<text::StyleTableEntryExt>>>,
32}
33
34impl Default for RichTextBuilder {
35 fn default() -> Self {
36 Self::new()
37 }
38}
39
40impl RichTextBuilder {
41 pub fn new() -> Self {
42 let buf = text::TextBuffer::default();
43 let sbuf = text::TextBuffer::default();
44 let style = Style::default();
45 let data = Arc::new(Mutex::new(vec![text::StyleTableEntryExt {
46 color: style.color,
47 font: style.font,
48 size: style.size,
49 attr: style.attr,
50 bgcolor: style.bgcolor,
51 }]));
52 Self { buf, sbuf, data }
53 }
54 pub fn append<T: Into<Option<Style>>>(&mut self, txt: &str, style: T) {
55 self.buf.append(txt);
56 if let Some(style) = style.into() {
57 let mut data = self.data.lock().unwrap();
58 let se = text::StyleTableEntryExt {
59 color: style.color,
60 font: style.font,
61 size: style.size,
62 attr: style.attr,
63 bgcolor: style.bgcolor,
64 };
65 let idx = data.iter().position(|&i| i == se).unwrap_or((*data).len());
66 self.sbuf
67 .append(&((b'A' + idx as u8) as char).to_string().repeat(txt.len()));
68 if idx == (*data).len() {
69 (*data).push(se);
70 }
71 } else {
72 self.sbuf
73 .append(&(b'A' as char).to_string().repeat(txt.len()));
74 }
75 }
76 pub fn clear(&mut self) {
77 self.buf.set_text("");
78 self.sbuf.set_text("");
79 self.data.lock().unwrap().clear();
80 }
81 pub fn replace_first<T: Into<Option<Style>>>(&mut self, old: &str, new: &str, style: T) {
82 let mut buf = self.buf.text();
83 let mut sbuf = self.sbuf.text();
84 if let Some(find) = buf.find(old) {
85 if let Some(style) = style.into() {
86 let mut data = self.data.lock().unwrap();
87 let se = text::StyleTableEntryExt {
88 color: style.color,
89 font: style.font,
90 size: style.size,
91 attr: style.attr,
92 bgcolor: style.bgcolor,
93 };
94 let idx = data.iter().position(|&i| i == se).unwrap_or((*data).len());
95 let range = find..find + old.len();
96 sbuf.replace_range(
97 range.clone(),
98 &((b'A' + idx as u8) as char).to_string().repeat(new.len()),
99 );
100 buf.replace_range(range, new);
101 if idx == (*data).len() {
102 (*data).push(se);
103 }
104 } else {
105 let range = find..find + old.len();
106 sbuf.replace_range(range.clone(), &(b'A' as char).to_string().repeat(new.len()));
107 buf.replace_range(range, new);
108 }
109 }
110 self.buf.set_text(&buf);
111 self.sbuf.set_text(&sbuf);
112 }
113 pub fn replace_all<T: Into<Option<Style>> + Clone>(&mut self, old: &str, new: &str, style: T) {
114 let mut idx = 0;
115 while let Some(find) = &self.buf.text()[idx..].find(old) {
116 self.replace_first(old, new, style.clone());
117 idx = *find + 1;
118 }
119 }
120}
121
122pub trait RichTextDisplay {
123 fn set_rich_text(&mut self, buf: RichTextBuilder);
124}
125
126impl<T: DisplayExt> RichTextDisplay for T {
127 fn set_rich_text(&mut self, buf: RichTextBuilder) {
128 self.set_buffer(buf.buf);
129 self.set_highlight_data_ext(buf.sbuf, (*buf.data.lock().unwrap()).clone());
130 }
131}