1use super::*;
2use crate::KeyMap;
3
4impl Context {
5 pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
16 let content = s.into();
17 let default_fg = self.inherited_text_fg();
18 self.commands.push(Command::Text {
19 content,
20 cursor_offset: None,
21 style: Style::new().fg(default_fg),
22 grow: 0,
23 align: Align::Start,
24 wrap: false,
25 truncate: false,
26 margin: Margin::default(),
27 constraints: Constraints::default(),
28 });
29 self.rollback.last_text_idx = Some(self.commands.len() - 1);
30 self
31 }
32
33 #[allow(clippy::print_stderr)]
39 pub fn link(&mut self, text: impl Into<String>, url: impl Into<String>) -> &mut Self {
40 let url_str = url.into();
41 let focused = self.register_focusable();
42 let (_interaction_id, response) = self.begin_widget_interaction(focused);
43
44 let activated = response.clicked || self.consume_activation_keys(focused);
45
46 if activated && let Err(e) = open_url(&url_str) {
47 eprintln!("[slt] failed to open URL: {e}");
48 }
49
50 let style = if focused {
51 Style::new()
52 .fg(self.theme.primary)
53 .bg(self.theme.surface_hover)
54 .underline()
55 .bold()
56 } else if response.hovered {
57 Style::new()
58 .fg(self.theme.accent)
59 .bg(self.theme.surface_hover)
60 .underline()
61 } else {
62 Style::new().fg(self.theme.primary).underline()
63 };
64
65 self.commands.push(Command::Link {
66 text: text.into(),
67 url: url_str,
68 style,
69 wrap: false,
70 margin: Margin::default(),
71 constraints: Constraints::default(),
72 });
73 self.rollback.last_text_idx = Some(self.commands.len() - 1);
74 self
75 }
76
77 pub fn timer_display(&mut self, elapsed: std::time::Duration) -> &mut Self {
81 let total_centis = elapsed.as_millis() / 10;
82 let centis = total_centis % 100;
83 let total_seconds = total_centis / 100;
84 let seconds = total_seconds % 60;
85 let minutes = (total_seconds / 60) % 60;
86 let hours = total_seconds / 3600;
87
88 let content = if hours > 0 {
89 format!("{hours:02}:{minutes:02}:{seconds:02}.{centis:02}")
90 } else {
91 format!("{minutes:02}:{seconds:02}.{centis:02}")
92 };
93
94 self.commands.push(Command::Text {
95 content,
96 cursor_offset: None,
97 style: Style::new().fg(self.theme.text),
98 grow: 0,
99 align: Align::Start,
100 wrap: false,
101 truncate: false,
102 margin: Margin::default(),
103 constraints: Constraints::default(),
104 });
105 self.rollback.last_text_idx = Some(self.commands.len() - 1);
106 self
107 }
108
109 pub fn help_from_keymap(&mut self, keymap: &KeyMap) -> Response {
111 let pairs: Vec<(&str, &str)> = keymap
112 .visible_bindings()
113 .map(|binding| (binding.display.as_str(), binding.description.as_str()))
114 .collect();
115 self.help(&pairs)
116 }
117
118 pub fn bold(&mut self) -> &mut Self {
122 self.modify_last_style(|s| s.modifiers |= Modifiers::BOLD);
123 self
124 }
125
126 pub fn dim(&mut self) -> &mut Self {
131 let text_dim = self.theme.text_dim;
132 let inherited_fg = self.inherited_text_fg();
133 if let Some(idx) = self.rollback.last_text_idx {
134 match &mut self.commands[idx] {
135 Command::Text { style, .. } => {
136 style.modifiers |= Modifiers::DIM;
137 if style.fg.is_none() || style.fg == Some(inherited_fg) {
138 style.fg = Some(text_dim);
139 }
140 }
141 Command::Link { style, .. } => {
142 style.modifiers |= Modifiers::DIM;
143 }
144 Command::RichText { segments, .. } => {
145 let all_inherited = segments
146 .iter()
147 .all(|(_, style)| style.fg.is_none() || style.fg == Some(inherited_fg));
148 for (_, style) in segments {
149 style.modifiers |= Modifiers::DIM;
150 if all_inherited {
151 style.fg = Some(text_dim);
152 }
153 }
154 }
155 _ => {}
156 }
157 }
158 self
159 }
160
161 pub fn italic(&mut self) -> &mut Self {
163 self.modify_last_style(|s| s.modifiers |= Modifiers::ITALIC);
164 self
165 }
166
167 pub fn underline(&mut self) -> &mut Self {
169 self.modify_last_style(|s| s.modifiers |= Modifiers::UNDERLINE);
170 self
171 }
172
173 pub fn reversed(&mut self) -> &mut Self {
175 self.modify_last_style(|s| s.modifiers |= Modifiers::REVERSED);
176 self
177 }
178
179 pub fn strikethrough(&mut self) -> &mut Self {
181 self.modify_last_style(|s| s.modifiers |= Modifiers::STRIKETHROUGH);
182 self
183 }
184
185 pub fn fg(&mut self, color: Color) -> &mut Self {
187 self.modify_last_style(|s| s.fg = Some(color));
188 self
189 }
190
191 pub fn bg(&mut self, color: Color) -> &mut Self {
193 self.modify_last_style(|s| s.bg = Some(color));
194 self
195 }
196
197 pub fn gradient(&mut self, from: Color, to: Color) -> &mut Self {
199 self.apply_char_gradient(false, |t| to.blend_f64(from, t));
200 self
201 }
202
203 pub fn gradient_stops_f64(&mut self, stops: &[(f64, Color)]) -> &mut Self {
228 if stops.is_empty() {
229 return self;
230 }
231 let sorted = Self::sorted_gradient_stops(stops);
232 self.apply_char_gradient(false, |t| Self::sample_gradient_stops(&sorted, t));
233 self
234 }
235
236 #[deprecated(
238 since = "0.22.2",
239 note = "use Context::gradient_stops_f64() to keep public float APIs on f64"
240 )]
241 pub fn gradient_stops(&mut self, stops: &[(f32, Color)]) -> &mut Self {
242 let stops: Vec<(f64, Color)> = stops
243 .iter()
244 .map(|(pos, color)| (f64::from(*pos), *color))
245 .collect();
246 self.gradient_stops_f64(&stops)
247 }
248
249 pub fn bg_gradient(&mut self, from: Color, to: Color) -> &mut Self {
266 self.apply_char_gradient(true, |t| to.blend_f64(from, t));
267 self
268 }
269
270 pub fn bg_gradient_stops_f64(&mut self, stops: &[(f64, Color)]) -> &mut Self {
290 if stops.is_empty() {
291 return self;
292 }
293 let sorted = Self::sorted_gradient_stops(stops);
294 self.apply_char_gradient(true, |t| Self::sample_gradient_stops(&sorted, t));
295 self
296 }
297
298 #[deprecated(
300 since = "0.22.2",
301 note = "use Context::bg_gradient_stops_f64() to keep public float APIs on f64"
302 )]
303 pub fn bg_gradient_stops(&mut self, stops: &[(f32, Color)]) -> &mut Self {
304 let stops: Vec<(f64, Color)> = stops
305 .iter()
306 .map(|(pos, color)| (f64::from(*pos), *color))
307 .collect();
308 self.bg_gradient_stops_f64(&stops)
309 }
310
311 fn sorted_gradient_stops(stops: &[(f64, Color)]) -> Vec<(f64, Color)> {
314 let mut sorted: Vec<(f64, Color)> = stops
315 .iter()
316 .map(|(pos, color)| {
317 let pos = if pos.is_finite() {
318 pos.clamp(0.0, 1.0)
319 } else {
320 0.0
321 };
322 (pos, *color)
323 })
324 .collect();
325 sorted.sort_by(|a, b| a.0.total_cmp(&b.0));
326 sorted
327 }
328
329 fn sample_gradient_stops(stops: &[(f64, Color)], t: f64) -> Color {
332 let t = if t.is_finite() {
333 t.clamp(0.0, 1.0)
334 } else {
335 0.0
336 };
337 let first = match stops.first() {
339 Some(stop) => *stop,
340 None => return Color::Rgb(0, 0, 0),
341 };
342 let last = *stops.last().unwrap_or(&first);
343 if t <= first.0 {
344 return first.1;
345 }
346 if t >= last.0 {
347 return last.1;
348 }
349 for window in stops.windows(2) {
350 let (p0, c0) = window[0];
351 let (p1, c1) = window[1];
352 if t >= p0 && t <= p1 {
353 let span = p1 - p0;
354 if span <= f64::EPSILON {
355 return c1;
356 }
357 let local = (t - p0) / span;
358 return c1.blend_f64(c0, local);
359 }
360 }
361 last.1
362 }
363
364 fn apply_char_gradient(&mut self, is_bg: bool, color_at: impl Fn(f64) -> Color) {
368 if let Some(idx) = self.rollback.last_text_idx {
369 let replacement = match &self.commands[idx] {
370 Command::Text {
371 content,
372 style,
373 wrap,
374 align,
375 margin,
376 constraints,
377 ..
378 } => {
379 let graphemes: Vec<&str> = content.graphemes(true).collect();
380 let last_start = graphemes
381 .iter()
382 .take(graphemes.len().saturating_sub(1))
383 .map(|grapheme| UnicodeWidthStr::width(*grapheme))
384 .sum::<usize>();
385 let denom = last_start.max(1) as f64;
386 let mut cell = 0usize;
387 let segments = graphemes
388 .into_iter()
389 .map(|grapheme| {
390 let mut seg_style = *style;
391 let color = color_at(cell as f64 / denom);
392 if is_bg {
393 seg_style.bg = Some(color);
394 } else {
395 seg_style.fg = Some(color);
396 }
397 cell = cell.saturating_add(UnicodeWidthStr::width(grapheme));
398 (grapheme.to_string(), seg_style)
399 })
400 .collect();
401
402 Some(Command::RichText {
403 segments,
404 wrap: *wrap,
405 align: *align,
406 margin: *margin,
407 constraints: *constraints,
408 })
409 }
410 _ => None,
411 };
412
413 if let Some(command) = replacement {
414 self.commands[idx] = command;
415 }
416 }
417 }
418
419 pub fn group_hover_fg(&mut self, color: Color) -> &mut Self {
421 let apply_group_style = self
422 .rollback
423 .group_stack
424 .last()
425 .map(|name| self.is_group_hovered(name) || self.is_group_focused(name))
426 .unwrap_or(false);
427 if apply_group_style {
428 self.modify_last_style(|s| s.fg = Some(color));
429 }
430 self
431 }
432
433 pub fn group_hover_bg(&mut self, color: Color) -> &mut Self {
435 let apply_group_style = self
436 .rollback
437 .group_stack
438 .last()
439 .map(|name| self.is_group_hovered(name) || self.is_group_focused(name))
440 .unwrap_or(false);
441 if apply_group_style {
442 self.modify_last_style(|s| s.bg = Some(color));
443 }
444 self
445 }
446
447 pub fn styled(&mut self, s: impl Into<String>, style: Style) -> &mut Self {
452 self.styled_with_cursor(s, style, None)
453 }
454
455 pub(crate) fn styled_with_cursor(
456 &mut self,
457 s: impl Into<String>,
458 style: Style,
459 cursor_offset: Option<usize>,
460 ) -> &mut Self {
461 self.commands.push(Command::Text {
462 content: s.into(),
463 cursor_offset,
464 style,
465 grow: 0,
466 align: Align::Start,
467 wrap: false,
468 truncate: false,
469 margin: Margin::default(),
470 constraints: Constraints::default(),
471 });
472 self.rollback.last_text_idx = Some(self.commands.len() - 1);
473 self
474 }
475
476 pub fn wrap(&mut self) -> &mut Self {
478 if let Some(idx) = self.rollback.last_text_idx {
479 match &mut self.commands[idx] {
480 Command::Text { wrap, .. }
481 | Command::Link { wrap, .. }
482 | Command::RichText { wrap, .. } => *wrap = true,
483 _ => {}
484 }
485 }
486 self
487 }
488
489 pub fn truncate(&mut self) -> &mut Self {
492 if let Some(idx) = self.rollback.last_text_idx
493 && let Command::Text { truncate, .. } = &mut self.commands[idx]
494 {
495 *truncate = true;
496 }
497 self
498 }
499
500 fn modify_last_style(&mut self, mut f: impl FnMut(&mut Style)) {
501 if let Some(idx) = self.rollback.last_text_idx {
502 match &mut self.commands[idx] {
503 Command::Text { style, .. } | Command::Link { style, .. } => f(style),
504 Command::RichText { segments, .. } => {
505 for (_, style) in segments {
506 f(style);
507 }
508 }
509 _ => {}
510 }
511 }
512 }
513
514 fn modify_last_constraints(&mut self, f: impl FnOnce(&mut Constraints)) {
515 if let Some(idx) = self.rollback.last_text_idx {
516 match &mut self.commands[idx] {
517 Command::Text { constraints, .. } | Command::Link { constraints, .. } => {
518 f(constraints)
519 }
520 Command::RichText { constraints, .. } => f(constraints),
521 _ => {}
522 }
523 }
524 }
525
526 fn modify_last_margin(&mut self, f: impl FnOnce(&mut Margin)) {
527 if let Some(idx) = self.rollback.last_text_idx {
528 match &mut self.commands[idx] {
529 Command::Text { margin, .. } | Command::Link { margin, .. } => f(margin),
530 Command::RichText { margin, .. } => f(margin),
531 _ => {}
532 }
533 }
534 }
535
536 pub fn grow(&mut self, value: u16) -> &mut Self {
543 if let Some(idx) = self.rollback.last_text_idx
544 && let Command::Text { grow, .. } = &mut self.commands[idx]
545 {
546 *grow = value;
547 }
548 self
549 }
550
551 pub fn align(&mut self, align: Align) -> &mut Self {
553 if let Some(idx) = self.rollback.last_text_idx {
554 match &mut self.commands[idx] {
555 Command::Text {
556 align: text_align, ..
557 }
558 | Command::RichText {
559 align: text_align, ..
560 } => *text_align = align,
561 _ => {}
562 }
563 }
564 self
565 }
566
567 pub fn text_center(&mut self) -> &mut Self {
571 self.align(Align::Center)
572 }
573
574 pub fn text_right(&mut self) -> &mut Self {
577 self.align(Align::End)
578 }
579
580 pub fn w(&mut self, value: u32) -> &mut Self {
588 self.modify_last_constraints(|c| {
589 *c = c.w(value);
590 });
591 self
592 }
593
594 pub fn h(&mut self, value: u32) -> &mut Self {
598 self.modify_last_constraints(|c| {
599 *c = c.h(value);
600 });
601 self
602 }
603
604 pub fn min_w(&mut self, value: u32) -> &mut Self {
606 self.modify_last_constraints(|c| c.set_min_width(Some(value)));
607 self
608 }
609
610 pub fn max_w(&mut self, value: u32) -> &mut Self {
612 self.modify_last_constraints(|c| c.set_max_width(Some(value)));
613 self
614 }
615
616 pub fn min_h(&mut self, value: u32) -> &mut Self {
618 self.modify_last_constraints(|c| c.set_min_height(Some(value)));
619 self
620 }
621
622 pub fn max_h(&mut self, value: u32) -> &mut Self {
624 self.modify_last_constraints(|c| c.set_max_height(Some(value)));
625 self
626 }
627
628 pub fn m(&mut self, value: u32) -> &mut Self {
632 self.modify_last_margin(|m| *m = Margin::all(value));
633 self
634 }
635
636 pub fn mx(&mut self, value: u32) -> &mut Self {
638 self.modify_last_margin(|m| {
639 m.left = value;
640 m.right = value;
641 });
642 self
643 }
644
645 pub fn my(&mut self, value: u32) -> &mut Self {
647 self.modify_last_margin(|m| {
648 m.top = value;
649 m.bottom = value;
650 });
651 self
652 }
653
654 pub fn mt(&mut self, value: u32) -> &mut Self {
656 self.modify_last_margin(|m| m.top = value);
657 self
658 }
659
660 pub fn mr(&mut self, value: u32) -> &mut Self {
662 self.modify_last_margin(|m| m.right = value);
663 self
664 }
665
666 pub fn mb(&mut self, value: u32) -> &mut Self {
668 self.modify_last_margin(|m| m.bottom = value);
669 self
670 }
671
672 pub fn ml(&mut self, value: u32) -> &mut Self {
674 self.modify_last_margin(|m| m.left = value);
675 self
676 }
677
678 pub fn spacer(&mut self) -> &mut Self {
682 self.commands.push(Command::Spacer { grow: 1 });
683 self.rollback.last_text_idx = None;
684 self
685 }
686
687 pub fn with_if(&mut self, cond: bool, f: impl FnOnce(&mut Self)) -> &mut Self {
716 if cond {
717 f(self);
718 }
719 self
720 }
721
722 pub fn with(&mut self, f: impl FnOnce(&mut Self)) -> &mut Self {
736 f(self);
737 self
738 }
739
740 fn inherited_text_fg(&self) -> Color {
741 self.rollback
742 .text_color_stack
743 .iter()
744 .rev()
745 .find_map(|color| *color)
746 .unwrap_or(self.theme.text)
747 }
748}
749
750#[cfg(test)]
751mod gradient_tests {
752 use super::*;
753 use crate::TestBackend;
754
755 #[test]
756 fn gradient_stops_interpolates_fg_across_columns() {
757 let red = Color::Rgb(255, 0, 0);
758 let blue = Color::Rgb(0, 0, 255);
759 let mut backend = TestBackend::new(20, 4);
760 backend.render(|ui| {
761 ui.text("ABC")
762 .gradient_stops_f64(&[(0.0, red), (1.0, blue)]);
763 });
764
765 let buf = backend.buffer();
766 assert_eq!(
769 buf.get(0, 0).style.fg,
770 Some(red),
771 "first column should be red"
772 );
773 assert_eq!(
774 buf.get(1, 0).style.fg,
775 Some(Color::Rgb(128, 0, 128)),
776 "middle column should be the halfway blend"
777 );
778 assert_eq!(
779 buf.get(2, 0).style.fg,
780 Some(blue),
781 "last column should be blue"
782 );
783 }
784
785 #[test]
786 fn two_stop_gradient_uses_documented_endpoint_order() {
787 let red = Color::Rgb(255, 0, 0);
788 let blue = Color::Rgb(0, 0, 255);
789 let mut backend = TestBackend::new(20, 4);
790 backend.render(|ui| {
791 ui.text("ABC").gradient(red, blue);
792 });
793
794 let buf = backend.buffer();
795 assert_eq!(buf.get(0, 0).style.fg, Some(red));
796 assert_eq!(buf.get(1, 0).style.fg, Some(Color::Rgb(128, 0, 128)));
797 assert_eq!(buf.get(2, 0).style.fg, Some(blue));
798 }
799
800 #[test]
801 fn gradient_stops_unsorted_input_is_sorted() {
802 let red = Color::Rgb(255, 0, 0);
803 let blue = Color::Rgb(0, 0, 255);
804 let mut backend = TestBackend::new(20, 4);
805 backend.render(|ui| {
806 ui.text("ABC")
808 .gradient_stops_f64(&[(1.0, blue), (0.0, red)]);
809 });
810
811 let buf = backend.buffer();
812 assert_eq!(buf.get(0, 0).style.fg, Some(red));
813 assert_eq!(buf.get(2, 0).style.fg, Some(blue));
814 }
815
816 #[test]
817 fn gradient_stops_multi_stop_hits_middle_stop_exactly() {
818 let red = Color::Rgb(255, 0, 0);
819 let green = Color::Rgb(0, 255, 0);
820 let blue = Color::Rgb(0, 0, 255);
821 let mut backend = TestBackend::new(20, 4);
822 backend.render(|ui| {
823 ui.text("ABC")
825 .gradient_stops_f64(&[(0.0, red), (0.5, green), (1.0, blue)]);
826 });
827
828 let buf = backend.buffer();
829 assert_eq!(buf.get(0, 0).style.fg, Some(red), "t=0 → first stop");
830 assert_eq!(
831 buf.get(1, 0).style.fg,
832 Some(green),
833 "t=0.5 → middle stop exactly"
834 );
835 assert_eq!(buf.get(2, 0).style.fg, Some(blue), "t=1 → last stop");
836 }
837
838 #[test]
839 fn gradient_stops_single_stop_is_solid() {
840 let cyan = Color::Rgb(0, 200, 200);
841 let mut backend = TestBackend::new(20, 4);
842 backend.render(|ui| {
843 ui.text("ABCD").gradient_stops_f64(&[(0.0, cyan)]);
844 });
845
846 let buf = backend.buffer();
847 for x in 0..4 {
848 assert_eq!(
849 buf.get(x, 0).style.fg,
850 Some(cyan),
851 "every column should be the single solid stop"
852 );
853 }
854 }
855
856 #[test]
857 fn gradient_stops_empty_is_noop() {
858 let mut backend = TestBackend::new(20, 4);
859 backend.render(|ui| {
860 ui.text("HELLO").gradient_stops_f64(&[]);
862 });
863
864 backend.assert_contains("HELLO");
865 }
866
867 #[test]
868 fn bg_gradient_applies_to_background() {
869 let red = Color::Rgb(255, 0, 0);
870 let blue = Color::Rgb(0, 0, 255);
871 let mut backend = TestBackend::new(20, 4);
872 backend.render(|ui| {
873 ui.text("ABC").bg_gradient(red, blue);
874 });
875
876 let buf = backend.buffer();
877 assert_eq!(buf.get(0, 0).style.bg, Some(red), "first column bg = from");
878 assert_eq!(buf.get(2, 0).style.bg, Some(blue), "last column bg = to");
879 assert_eq!(
880 buf.get(1, 0).style.bg,
881 Some(Color::Rgb(128, 0, 128)),
882 "middle column bg = halfway blend"
883 );
884 }
885
886 #[test]
887 fn bg_gradient_stops_interpolates_background() {
888 let red = Color::Rgb(255, 0, 0);
889 let blue = Color::Rgb(0, 0, 255);
890 let mut backend = TestBackend::new(20, 4);
891 backend.render(|ui| {
892 ui.text("ABC")
893 .bg_gradient_stops_f64(&[(0.0, red), (1.0, blue)]);
894 });
895
896 let buf = backend.buffer();
897 assert_eq!(buf.get(0, 0).style.bg, Some(red), "first column bg = red");
898 assert_eq!(
899 buf.get(1, 0).style.bg,
900 Some(Color::Rgb(128, 0, 128)),
901 "middle column bg = halfway blend"
902 );
903 assert_eq!(buf.get(2, 0).style.bg, Some(blue), "last column bg = blue");
904 }
905
906 #[test]
907 fn bg_gradient_stops_empty_is_noop() {
908 let mut backend = TestBackend::new(20, 4);
909 backend.render(|ui| {
910 ui.text("WORLD").bg_gradient_stops_f64(&[]);
911 });
912
913 backend.assert_contains("WORLD");
914 }
915
916 #[test]
917 fn style_and_constraints_after_gradient_update_rich_text() {
918 let mut backend = TestBackend::new(20, 4);
919 backend.render(|ui| {
920 ui.text("ABC")
921 .gradient(Color::Red, Color::Blue)
922 .bold()
923 .fg(Color::Green)
924 .bg(Color::Black)
925 .w(8)
926 .m(1)
927 .align(Align::End);
928 });
929
930 let buf = backend.buffer();
931 let (start_x, y) = backend.find_text("ABC").expect("rendered gradient text");
932 for x in start_x..start_x + 3 {
933 let cell = buf.get(x, y);
934 assert_eq!(cell.style.fg, Some(Color::Green));
935 assert_eq!(cell.style.bg, Some(Color::Black));
936 assert!(cell.style.modifiers.contains(Modifiers::BOLD));
937 }
938 }
939
940 #[test]
941 fn dim_uses_theme_color_only_for_inherited_foreground() {
942 let theme = Theme::dark();
943 let mut backend = TestBackend::new(20, 4);
944 backend.render(|ui| {
945 ui.set_theme(theme);
946 ui.text("inherited").dim();
947 ui.text("explicit").fg(Color::Red).dim();
948 });
949
950 let buf = backend.buffer();
951 assert_eq!(buf.get(0, 0).style.fg, Some(theme.text_dim));
952 assert_eq!(buf.get(0, 1).style.fg, Some(Color::Red));
953 assert!(buf.get(0, 0).style.modifiers.contains(Modifiers::DIM));
954 assert!(buf.get(0, 1).style.modifiers.contains(Modifiers::DIM));
955 }
956
957 #[test]
958 fn gradient_positions_follow_grapheme_cell_width() {
959 let mut backend = TestBackend::new(20, 4);
960 backend.render(|ui| {
961 ui.text("界A").gradient(Color::Red, Color::Blue);
962 });
963
964 let buf = backend.buffer();
965 assert_eq!(buf.get(0, 0).style.fg, Some(Color::Red));
966 assert_eq!(buf.get(2, 0).style.fg, Some(Color::Blue));
967 }
968}