1use fltk::{
4 app::{App, Scheme},
5 button::Button,
6 enums::{Color, Event, Font},
7 group::*,
8 menu::Choice,
9 misc::Spinner,
10 prelude::*,
11 text::{StyleTableEntry, TextAttr, TextBuffer, TextEditor},
12 window::Window,
13};
14use std::{cell::RefCell, char, rc::Rc};
15
16struct Style {
17 style_table: Vec<StyleTableEntry>,
18}
19
20impl Style {
21 fn new() -> Style {
22 Style {
23 style_table: Vec::<StyleTableEntry>::new(),
24 }
25 }
26
27 #[allow(clippy::too_many_arguments)]
31 fn apply_style(
32 &mut self,
33 pos: Option<i32>,
34 ins_items: Option<i32>,
35 del_items: Option<i32>,
36 repl_start: Option<i32>,
37 repl_end: Option<i32>,
38 font: Font,
39 size: i32,
40 color: Color,
41 attr: TextAttr,
42 text_editor: &mut TextEditor,
43 ) {
44 let mut style_buffer = text_editor.style_buffer().unwrap_or_default();
45
46 let style_char =
48 match self.style_table.iter().position(|s| {
49 s.font == font && s.size == size && s.color == color && s.attr == attr
50 }) {
51 Some(i) => ((i + 65) as u8 as char).to_string(),
52 None => {
53 self.style_table.push(StyleTableEntry {
54 color,
55 font,
56 size,
57 attr,
58 bgcolor: Color::Black,
59 });
60 ((self.style_table.len() + 64) as u8 as char).to_string()
61 }
62 };
63
64 match ins_items {
66 Some(n) if n > 0 => {
67 style_buffer.insert(pos.unwrap(), style_char.repeat(n as usize).as_str());
69 }
70 _ => match del_items {
71 Some(n) if n > 0 => {
72 style_buffer.remove(pos.unwrap(), pos.unwrap() + n);
74 }
75 _ => match repl_end {
76 Some(n) if n > 0 => {
77 style_buffer.replace(
79 repl_start.unwrap(),
80 repl_end.unwrap(),
81 style_char
82 .repeat((repl_end.unwrap() - repl_start.unwrap()) as usize)
83 .as_str(),
84 );
85 }
86 _ => {}
87 },
88 },
89 }
90
91 let mut style_index = style_buffer
93 .text()
94 .chars()
95 .map(|c| (c as usize) - 65)
96 .collect::<Vec<usize>>();
97 style_index.sort_unstable();
98 style_index.dedup();
99 for (i, &v) in style_index.iter().enumerate() {
100 self.style_table.swap(i, v);
101 style_buffer.set_text(
102 style_buffer
103 .text()
104 .replace(
105 (v + 65) as u8 as char,
106 ((i + 65) as u8 as char).to_string().as_str(),
107 )
108 .as_str(),
109 );
110 }
111
112 self.style_table.truncate(style_index.len());
115 text_editor.set_highlight_data(style_buffer, self.style_table.to_owned());
116
117 }
120}
121
122fn main() {
123 let style = Rc::from(RefCell::from(Style::new()));
124
125 let app = App::default().with_scheme(Scheme::Gleam);
126 let mut wind = Window::default()
127 .with_size(500, 200)
128 .with_label("Highlight");
129 let mut vpack = Pack::new(4, 4, 492, 192, "");
130 vpack.set_spacing(4);
131 let mut text_editor = TextEditor::default().with_size(492, 163);
132
133 let mut hpack = Pack::new(4, 4, 492, 25, "").with_type(PackType::Horizontal);
134 hpack.set_spacing(8);
135 let mut font = Choice::default().with_size(130, 25);
136 let mut choice = Choice::default().with_size(130, 25);
137 let mut size = Spinner::default().with_size(60, 25);
138
139 let mut color = Choice::default().with_size(100, 25);
140 let mut btn_clear = Button::default().with_size(40, 25).with_label("X");
141 hpack.end();
142
143 vpack.end();
144 wind.end();
145 wind.show();
146
147 text_editor.wrap_mode(fltk::text::WrapMode::AtBounds, 0);
148 text_editor.set_buffer(TextBuffer::default());
149
150 font.add_choice("Courier|Helvetica|Times");
151 font.set_value(0);
152 font.set_tooltip("Font");
153
154 choice.add_choice("Normal|Underline|Strike");
155 choice.set_value(0);
156
157 size.set_value(18.0);
158 size.set_step(1.0);
159 size.set_range(12.0, 28.0);
160 size.set_tooltip("Size");
161
162 color.set_tooltip("Color");
163 color.add_choice("#000000|#ff0000|#00ff00|#0000ff|#ffff00|#00ffff");
164 color.set_value(0);
165
166 btn_clear.set_label_color(Color::Red);
167 btn_clear.set_tooltip("Clear style");
168
169 for mut item in color.clone() {
171 if let Some(lbl) = item.label() {
172 item.set_label_color(Color::from_u32(
173 u32::from_str_radix(lbl.trim().strip_prefix('#').unwrap(), 16)
174 .ok()
175 .unwrap(),
176 ));
177 }
178 }
179
180 let style_rc1 = Rc::clone(&style);
181
182 text_editor.buffer().unwrap().add_modify_callback({
183 let mut text_editor1 = text_editor.clone();
184 let font1 = font.clone();
185 let size1 = size.clone();
186 let color1 = color.clone();
187 let choice1 = choice.clone();
188 move |_buf, pos: i32, ins_items: i32, del_items: i32, _: i32, _: Option<&str>| {
189 let attr = if choice1.value() == 1 {
190 TextAttr::Underline
191 } else if choice1.value() == 2 {
192 TextAttr::StrikeThrough
193 } else {
194 TextAttr::None
195 };
196 if ins_items > 0 || del_items > 0 {
197 let mut style = style_rc1.borrow_mut();
198 let color = Color::from_u32(
199 u32::from_str_radix(
200 color1
201 .text(color1.value())
202 .unwrap()
203 .trim()
204 .strip_prefix('#')
205 .unwrap(),
206 16,
207 )
208 .ok()
209 .unwrap(),
210 );
211 style.apply_style(
212 Some(pos),
213 Some(ins_items),
214 Some(del_items),
215 None,
216 None,
217 Font::by_name(font1.text(font1.value()).unwrap().trim()),
218 size1.value() as i32,
219 color,
220 attr,
221 &mut text_editor1,
222 );
223 }
224 }
225 });
226
227 color.set_callback({
228 let size = size.clone();
229 let font = font.clone();
230 let choice = choice.clone();
231 let mut text_editor = text_editor.clone();
232 let style_rc1 = Rc::clone(&style);
233 move |color| {
234 let attr = match choice.value() {
235 0 => TextAttr::None,
236 1 => TextAttr::Underline,
237 2 => TextAttr::StrikeThrough,
238 _ => unreachable!(),
239 };
240 if let Some(buf) = text_editor.buffer() {
241 if let Some((s, e)) = buf.selection_position() {
242 let mut style = style_rc1.borrow_mut();
243 let color = Color::from_u32(
244 u32::from_str_radix(
245 color
246 .text(color.value())
247 .unwrap()
248 .trim()
249 .strip_prefix('#')
250 .unwrap(),
251 16,
252 )
253 .ok()
254 .unwrap(),
255 );
256 style.apply_style(
257 None,
258 None,
259 None,
260 Some(s),
261 Some(e),
262 Font::by_name(font.text(font.value()).unwrap().trim()),
263 size.value() as i32,
264 color,
265 attr,
266 &mut text_editor,
267 );
268 }
269 }
270 }
271 });
272
273 text_editor.handle({
275 let style_rc1 = Rc::clone(&style);
276 let mut font1 = font.clone();
277 let mut size1 = size.clone();
278 let mut color1 = color.clone();
279 move |te, e| match e {
280 Event::KeyUp | Event::Released => {
281 if let Some(buff) = te.style_buffer() {
282 let i = te.insert_position();
283 if let Some(t) = buff.text_range(i, i + 1) {
284 if !t.is_empty() {
285 let style = style_rc1.borrow_mut();
286 if let Some(i) = t.chars().next().map(|c| (c as usize - 65)) {
287 if let Some(style) = style.style_table.get(i) {
288 if let Some(mn) = font1.find_item(&format!("{:?}", style.font))
289 {
290 font1.set_item(&mn);
291 }
292 size1.set_value(style.size as f64);
293 let (r, g, b) = style.color.to_rgb();
294 if let Some(mn) =
295 color1.find_item(format!("{r:02x}{g:02x}{b:02x}").as_str())
296 {
297 color1.set_item(&mn);
298 }
299 }
300 }
301 }
302 }
303 }
304 true
305 }
306 _ => false,
307 }
308 });
309
310 choice.set_callback({
311 let mut color1 = color.clone();
312 move |_| color1.do_callback()
313 });
314
315 font.set_callback({
316 let mut color1 = color.clone();
317 move |_| color1.do_callback()
318 });
319
320 size.set_callback({
321 let mut color1 = color.clone();
322 move |_| color1.do_callback()
323 });
324
325 btn_clear.set_callback({
327 let style_rc1 = Rc::clone(&style);
328 let text_editor1 = text_editor.clone();
329 move |_| {
330 match text_editor1.buffer().unwrap().selection_position() {
331 Some((_, _)) => {
332 font.set_value(0);
333 size.set_value(18.0);
334 color.set_value(0);
335 choice.set_value(0);
336 color.do_callback();
337 }
338 None => {
339 font.set_value(0);
340 size.set_value(18.0);
341 color.set_value(0);
342 style_rc1.borrow_mut().apply_style(
343 None,
344 None,
345 None,
346 Some(0),
347 Some(text_editor1.buffer().unwrap().length()),
348 Font::Courier,
349 16,
350 Color::Black,
351 TextAttr::None,
352 &mut text_editor,
353 );
354 }
355 };
356 }
357 });
358
359 app.run().unwrap();
360}