use web_time::Instant;
use crate::event::{Event, EventResult};
use crate::geometry::Point;
use crate::widgets::text_context_menu::TextMenuAction;
use super::super::model::{DocPos, InlineStyle};
use super::RichTextEdit;
impl RichTextEdit {
pub(super) fn menu_copy(&mut self) {
self.copy_selection();
}
pub(super) fn menu_cut(&mut self) {
if self.copy_selection() {
self.core.borrow_mut().backspace();
}
}
pub(super) fn menu_paste(&mut self) {
self.paste_clipboard();
}
pub(super) fn menu_select_all(&mut self) {
self.core.borrow_mut().select_all();
}
fn has_selection(&self) -> bool {
!self.core.borrow().selection().is_empty()
}
fn selection_contains(&self, pos: DocPos) -> bool {
let sel = self.core.borrow().selection();
let (lo, hi) = sel.ordered();
!sel.is_empty() && pos >= lo && pos <= hi
}
pub(super) fn open_context_menu(&mut self, pos: Point) {
let target = self.hit_test_pos(pos);
if !self.selection_contains(target) {
self.core.borrow_mut().set_caret(target, false);
}
let has_sel = self.has_selection();
self.context_menu.open(pos, has_sel, true);
self.focus_time = Some(Instant::now());
crate::animation::request_draw();
}
pub(super) fn apply_text_menu_action(&mut self, action: TextMenuAction) {
match action {
TextMenuAction::Cut => self.menu_cut(),
TextMenuAction::Copy => self.menu_copy(),
TextMenuAction::Paste => self.menu_paste(),
TextMenuAction::SelectAll => self.menu_select_all(),
}
let caret = self.core.borrow().caret();
self.ensure_pos_visible(caret);
crate::animation::request_draw();
}
pub(super) fn route_context_menu(&mut self, event: &Event) -> Option<EventResult> {
if !self.context_menu.is_open() {
return None;
}
let (result, action) = self.context_menu.handle_event(event);
if let Some(action) = action {
self.apply_text_menu_action(action);
}
if result.is_consumed() {
crate::animation::request_draw();
Some(result)
} else {
None
}
}
pub(super) fn context_menu_font(&self) -> std::sync::Arc<crate::text::Font> {
crate::font_settings::current_system_font()
.unwrap_or_else(|| (self.resolver)(&InlineStyle::default()))
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::event::{Event, EventResult, Modifiers, MouseButton};
use crate::geometry::{Point, Size};
use crate::text::Font;
use crate::widget::Widget;
use crate::widgets::rich_text::model::{Block, InlineStyle, RichDoc};
use crate::widgets::rich_text::view::SharedResolver;
use crate::widgets::text_context_menu::TextMenuAction;
use super::RichTextEdit;
const FONT_BYTES: &[u8] = include_bytes!("../../../../../demo/assets/Arial-Regular.ttf");
fn resolver() -> SharedResolver {
let f = Arc::new(Font::from_slice(FONT_BYTES).expect("test font"));
std::rc::Rc::new(move |_: &InlineStyle| Arc::clone(&f))
}
fn laid_out(text: &str) -> RichTextEdit {
crate::widget::set_current_viewport(Size::new(800.0, 600.0));
let doc = RichDoc::from_blocks(vec![Block::plain(text)]);
let mut ed = RichTextEdit::new(doc, resolver()).with_font_size(16.0);
ed.layout(Size::new(400.0, 120.0));
ed.on_event(&Event::FocusGained);
ed
}
fn right_click(ed: &mut RichTextEdit, x: f64, y: f64) -> EventResult {
ed.on_event(&Event::MouseDown {
pos: Point::new(x, y),
button: MouseButton::Right,
modifiers: Modifiers::default(),
})
}
#[test]
fn right_click_opens_menu_through_event_routing() {
let mut ed = laid_out("hello world");
assert_eq!(right_click(&mut ed, 20.0, 60.0), EventResult::Consumed);
assert!(ed.has_active_modal());
assert!(ed.context_menu.is_open());
}
#[test]
fn context_menu_opt_out() {
crate::widget::set_current_viewport(Size::new(800.0, 600.0));
let doc = RichDoc::from_blocks(vec![Block::plain("hello")]);
let mut ed = RichTextEdit::new(doc, resolver())
.with_font_size(16.0)
.with_context_menu(false);
ed.layout(Size::new(400.0, 120.0));
ed.on_event(&Event::FocusGained);
assert_eq!(right_click(&mut ed, 20.0, 60.0), EventResult::Ignored);
assert!(!ed.has_active_modal());
}
#[test]
fn copy_item_copies_selection_via_clipboard() {
let mut ed = laid_out("hello world");
ed.menu_select_all();
right_click(&mut ed, 20.0, 60.0);
assert!(ed.context_menu.is_open());
crate::clipboard::set_text("SENTINEL");
let center = ed
.context_menu
.action_row_center(TextMenuAction::Copy)
.expect("Copy row is laid out");
let r = ed.on_event(&Event::MouseDown {
pos: center,
button: MouseButton::Left,
modifiers: Modifiers::default(),
});
assert_eq!(r, EventResult::Consumed);
assert_eq!(crate::clipboard::get_text().as_deref(), Some("hello world"));
assert!(!ed.context_menu.is_open());
}
}