use super::*;
use crate::widgets::text_context_menu::TextMenuAction;
impl TextArea {
pub fn with_context_menu(mut self, v: bool) -> Self {
self.context_menu_enabled = v;
self
}
pub(super) fn clipboard_copy(&mut self) {
let sel = self.selected_text();
if !sel.is_empty() {
clipboard_set(&sel);
}
}
pub(super) fn clipboard_cut(&mut self) {
let sel = self.selected_text();
if !sel.is_empty() {
clipboard_set(&sel);
self.delete(0);
}
}
pub(super) fn clipboard_paste(&mut self) {
if let Some(t) = clipboard_get() {
self.insert_str(&t);
}
}
pub(super) fn select_all_text(&mut self) {
let len = self.edit.borrow().text.len();
self.move_cursor_to(0, false);
self.move_cursor_to(len, true);
}
fn selection_contains(&self, offset: usize) -> bool {
match self.selection() {
Some((lo, hi)) => lo != hi && offset >= lo && offset <= hi,
None => false,
}
}
pub(super) fn open_context_menu(&mut self, pos: Point) {
let off = self.byte_offset_at(pos);
if !self.selection_contains(off) {
self.move_cursor_to(off, false);
}
let has_sel = self.selection().map(|(l, h)| l != h).unwrap_or(false);
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.clipboard_cut(),
TextMenuAction::Copy => self.clipboard_copy(),
TextMenuAction::Paste => self.clipboard_paste(),
TextMenuAction::SelectAll => self.select_all_text(),
}
self.ensure_cursor_visible();
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
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::{Event, EventResult, MouseButton};
use crate::widget::Widget;
use crate::widgets::text_context_menu::TextMenuAction;
use std::sync::Arc;
const FONT_BYTES: &[u8] = include_bytes!("../../../../demo/assets/CascadiaCode.ttf");
fn font() -> Arc<Font> {
Arc::new(Font::from_slice(FONT_BYTES).expect("font"))
}
fn laid_out(text: &str) -> TextArea {
crate::widget::set_current_viewport(Size::new(800.0, 600.0));
let mut ta = TextArea::new(font()).with_text(text);
ta.layout(Size::new(200.0, 100.0));
ta.on_event(&Event::FocusGained);
ta
}
fn right_click(ta: &mut TextArea, x: f64, y: f64) -> EventResult {
ta.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 ta = laid_out("hello world");
assert_eq!(right_click(&mut ta, 20.0, 50.0), EventResult::Consumed);
assert!(ta.has_active_modal());
assert!(ta.context_menu.is_open());
}
#[test]
fn context_menu_opt_out() {
crate::widget::set_current_viewport(Size::new(800.0, 600.0));
let mut ta = TextArea::new(font())
.with_text("hello")
.with_context_menu(false);
ta.layout(Size::new(200.0, 100.0));
ta.on_event(&Event::FocusGained);
assert_eq!(right_click(&mut ta, 20.0, 50.0), EventResult::Ignored);
assert!(!ta.has_active_modal());
}
#[test]
fn copy_item_copies_selection_via_clipboard() {
let mut ta = laid_out("hello world");
ta.select_all_text();
right_click(&mut ta, 20.0, 50.0);
assert!(ta.context_menu.is_open());
crate::clipboard::set_text("SENTINEL");
let center = ta
.context_menu
.action_row_center(TextMenuAction::Copy)
.expect("Copy row is laid out");
let r = ta.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!(!ta.context_menu.is_open(), "activating Copy closes the menu");
}
}