fltk_richtext/
lib.rs

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}