use std::sync::Arc;
use crate::event::{Event, Modifiers, MouseButton};
use crate::geometry::{Point, Size};
use crate::text::Font;
use crate::widget::Widget;
use crate::widgets::rich_text::model::{Block, InlineStyle, RichDoc, TextRun};
use crate::widgets::rich_text::view::SharedResolver;
use crate::widgets::rich_text::{RichEditHandle, RichTextEdit};
use super::RichTextToolbar;
const FONT_BYTES: &[u8] = include_bytes!("../../../../../demo/assets/CascadiaCode.ttf");
fn font() -> Arc<Font> {
Arc::new(Font::from_slice(FONT_BYTES).expect("test font loads"))
}
fn resolver(font: &Arc<Font>) -> SharedResolver {
let f = Arc::clone(font);
std::rc::Rc::new(move |_: &InlineStyle| Arc::clone(&f))
}
fn toolbar_over(doc: RichDoc) -> (RichTextToolbar, RichEditHandle) {
let font = font();
let editor = RichTextEdit::new(doc, resolver(&font));
let handle = editor.handle();
(RichTextToolbar::new(handle.clone(), font), handle)
}
fn child_type_names(w: &dyn Widget) -> Vec<&'static str> {
w.children().iter().map(|c| c.type_name()).collect()
}
fn rows(toolbar: &RichTextToolbar) -> Vec<&dyn Widget> {
let col = toolbar.children()[0].as_ref();
col.children().iter().map(|c| c.as_ref()).collect()
}
#[test]
fn full_roster_with_families() {
let (toolbar, _h) = toolbar_over(RichDoc::from_blocks(vec![Block::plain("hi")]));
let toolbar = toolbar.with_families(vec!["Sans".into(), "Serif".into()], None);
let rows = rows(&toolbar);
assert_eq!(rows.len(), 2, "two rows");
assert_eq!(
child_type_names(rows[0]),
["Button", "Button", "Button", "Button", "ComboBox", "ComboBox", "Button", "Button"],
"row 1: B/I/U/S, family combo, size combo, text-colour, highlight"
);
assert_eq!(child_type_names(rows[1]), vec!["Button"; 9], "row 2 roster");
}
#[test]
fn config_disables_controls_and_empty_rows() {
let (toolbar, _h) = toolbar_over(RichDoc::from_blocks(vec![Block::plain("hi")]));
let toolbar = toolbar
.with_bold(false)
.with_colors(false)
.with_history(false)
.with_alignment(false)
.with_lists(false)
.with_indent(false);
let rows = rows(&toolbar);
assert_eq!(rows.len(), 1, "empty second row omitted");
assert_eq!(
child_type_names(rows[0]),
["Button", "Button", "Button", "ComboBox"],
"row 1 lost bold + colours, kept I/U/S + size combo"
);
}
#[test]
fn family_omitted_when_no_families() {
let (toolbar, _h) = toolbar_over(RichDoc::from_blocks(vec![Block::plain("hi")]));
let rows = rows(&toolbar);
assert_eq!(
child_type_names(rows[0]),
["Button", "Button", "Button", "Button", "ComboBox", "Button", "Button"],
"row 1 without a family combo: B/I/U/S, size combo, two colour swatches"
);
let combo_count = child_type_names(rows[0]).iter().filter(|t| **t == "ComboBox").count();
assert_eq!(combo_count, 1, "only the size ComboBox, no family ComboBox");
}
fn bold_button(toolbar: &mut RichTextToolbar) -> &mut Box<dyn Widget> {
let col = &mut toolbar.children_mut()[0];
let row1 = &mut col.children_mut()[0];
&mut row1.children_mut()[0]
}
#[test]
fn click_bold_bolds_selection_through_handle() {
let (mut toolbar, handle) = toolbar_over(RichDoc::from_blocks(vec![Block::plain("hello")]));
handle.select_all();
assert_ne!(handle.common_style_of_selection().bold, Some(true), "not bold yet");
toolbar.layout(Size::new(600.0, 100.0));
let button = bold_button(&mut toolbar);
let b = button.bounds();
let pos = Point::new(b.width * 0.5, b.height * 0.5);
let down = Event::MouseDown { pos, button: MouseButton::Left, modifiers: Modifiers::default() };
let up = Event::MouseUp { pos, button: MouseButton::Left, modifiers: Modifiers::default() };
button.on_event(&down);
button.on_event(&up);
assert_eq!(
handle.common_style_of_selection().bold,
Some(true),
"clicking Bold bolded the whole selection through the handle"
);
assert_eq!(handle.plain_text(), "hello", "text unchanged by formatting");
}
#[test]
fn bold_toggle_reflects_tri_state() {
let bold = InlineStyle { bold: true, ..Default::default() };
let (toolbar, handle) = toolbar_over(RichDoc::from_blocks(vec![Block::from_run(
TextRun::new("bold", bold.clone()),
)]));
handle.select_all();
let cs = toolbar.common_style_of_selection();
assert_eq!(cs.bold, Some(true));
assert!(cs.bold == Some(true), "uniform bold ⇒ toggle active");
let (toolbar, handle) = toolbar_over(RichDoc::from_blocks(vec![Block {
runs: vec![TextRun::new("B", bold), TextRun::plain("p")],
..Block::new()
}]));
handle.select_all();
let cs = toolbar.common_style_of_selection();
assert_eq!(cs.bold, None, "mixed selection reports None");
assert!(cs.bold != Some(true), "mixed ⇒ toggle inactive");
let (toolbar, handle) = toolbar_over(RichDoc::from_blocks(vec![Block::plain("plain")]));
handle.select_all();
assert_eq!(toolbar.common_style_of_selection().bold, Some(false));
}
#[test]
fn drop_mid_preview_cancels_session() {
let (mut toolbar, handle) = toolbar_over(RichDoc::from_blocks(vec![Block::plain("hi")]));
handle.select_all();
toolbar.layout(Size::new(600.0, 100.0));
{
let col = &mut toolbar.children_mut()[0];
let row1 = &mut col.children_mut()[0];
let swatch = &mut row1.children_mut()[5];
let b = swatch.bounds();
let pos = Point::new(b.width * 0.5, b.height * 0.5);
swatch.on_event(&Event::MouseDown { pos, button: MouseButton::Left, modifiers: Modifiers::default() });
swatch.on_event(&Event::MouseUp { pos, button: MouseButton::Left, modifiers: Modifiers::default() });
}
toolbar.layout(Size::new(600.0, 100.0));
assert!(handle.is_previewing(), "opening the swatch begins a preview session");
drop(toolbar);
assert!(
!handle.is_previewing(),
"dropping the toolbar mid-preview must cancel the session"
);
}