use crate::keyboard::{KbKey, KeyEvent};
use crate::kurbo::{Point, Rect};
use crate::piet::HitTestPoint;
use crate::window::{TextFieldToken, WinHandler};
use std::borrow::Cow;
use std::ops::Range;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Event {
SelectionChanged,
LayoutChanged,
Reset,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[non_exhaustive]
pub struct Selection {
pub anchor: usize,
pub active: usize,
pub h_pos: Option<f64>,
}
#[allow(clippy::len_without_is_empty)]
impl Selection {
pub fn new(anchor: usize, active: usize) -> Selection {
Selection {
anchor,
active,
h_pos: None,
}
}
pub fn caret(index: usize) -> Selection {
Selection {
anchor: index,
active: index,
h_pos: None,
}
}
pub fn with_h_pos(mut self, h_pos: Option<f64>) -> Self {
self.h_pos = h_pos;
self
}
#[must_use = "constrained constructs a new Selection"]
pub fn constrained(mut self, s: &str) -> Self {
let s_len = s.len();
self.anchor = self.anchor.min(s_len);
self.active = self.active.min(s_len);
while !s.is_char_boundary(self.anchor) {
self.anchor += 1;
}
while !s.is_char_boundary(self.active) {
self.active += 1;
}
self
}
pub fn min(&self) -> usize {
usize::min(self.anchor, self.active)
}
pub fn max(&self) -> usize {
usize::max(self.anchor, self.active)
}
pub fn range(&self) -> Range<usize> {
self.min()..self.max()
}
pub fn len(&self) -> usize {
if self.anchor > self.active {
self.anchor - self.active
} else {
self.active - self.anchor
}
}
pub fn is_caret(&self) -> bool {
self.len() == 0
}
}
pub trait InputHandler {
fn selection(&self) -> Selection;
fn set_selection(&mut self, selection: Selection);
fn composition_range(&self) -> Option<Range<usize>>;
fn set_composition_range(&mut self, range: Option<Range<usize>>);
fn is_char_boundary(&self, i: usize) -> bool;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn slice(&self, range: Range<usize>) -> Cow<str>;
fn utf8_to_utf16(&self, utf8_range: Range<usize>) -> usize {
self.slice(utf8_range).encode_utf16().count()
}
fn utf16_to_utf8(&self, utf16_range: Range<usize>) -> usize {
if utf16_range.is_empty() {
return 0;
}
let doc_range = 0..self.len();
let text = self.slice(doc_range);
let utf16: Vec<u16> = text
.encode_utf16()
.skip(utf16_range.start)
.take(utf16_range.end)
.collect();
String::from_utf16_lossy(&utf16).len()
}
fn replace_range(&mut self, range: Range<usize>, text: &str);
fn hit_test_point(&self, point: Point) -> HitTestPoint;
fn line_range(&self, index: usize, affinity: Affinity) -> Range<usize>;
fn bounding_box(&self) -> Option<Rect>;
fn slice_bounding_box(&self, range: Range<usize>) -> Option<Rect>;
fn handle_action(&mut self, action: Action);
}
#[allow(dead_code)]
pub fn simulate_input<H: WinHandler + ?Sized>(
handler: &mut H,
token: Option<TextFieldToken>,
event: KeyEvent,
) -> bool {
if handler.key_down(event.clone()) {
return true;
}
let token = match token {
Some(v) => v,
None => return false,
};
let mut input_handler = handler.acquire_input_lock(token, true);
match event.key {
KbKey::Character(c) if !event.mods.ctrl() && !event.mods.meta() && !event.mods.alt() => {
let selection = input_handler.selection();
input_handler.replace_range(selection.range(), &c);
let new_caret_index = selection.min() + c.len();
input_handler.set_selection(Selection::caret(new_caret_index));
}
KbKey::ArrowLeft => {
let movement = if event.mods.ctrl() {
Movement::Word(Direction::Left)
} else {
Movement::Grapheme(Direction::Left)
};
if event.mods.shift() {
input_handler.handle_action(Action::MoveSelecting(movement));
} else {
input_handler.handle_action(Action::Move(movement));
}
}
KbKey::ArrowRight => {
let movement = if event.mods.ctrl() {
Movement::Word(Direction::Right)
} else {
Movement::Grapheme(Direction::Right)
};
if event.mods.shift() {
input_handler.handle_action(Action::MoveSelecting(movement));
} else {
input_handler.handle_action(Action::Move(movement));
}
}
KbKey::ArrowUp => {
let movement = Movement::Vertical(VerticalMovement::LineUp);
if event.mods.shift() {
input_handler.handle_action(Action::MoveSelecting(movement));
} else {
input_handler.handle_action(Action::Move(movement));
}
}
KbKey::ArrowDown => {
let movement = Movement::Vertical(VerticalMovement::LineDown);
if event.mods.shift() {
input_handler.handle_action(Action::MoveSelecting(movement));
} else {
input_handler.handle_action(Action::Move(movement));
}
}
KbKey::Backspace => {
let movement = if event.mods.ctrl() {
Movement::Word(Direction::Upstream)
} else {
Movement::Grapheme(Direction::Upstream)
};
input_handler.handle_action(Action::Delete(movement));
}
KbKey::Delete => {
let movement = if event.mods.ctrl() {
Movement::Word(Direction::Downstream)
} else {
Movement::Grapheme(Direction::Downstream)
};
input_handler.handle_action(Action::Delete(movement));
}
KbKey::Enter => {
input_handler.handle_action(Action::InsertNewLine {
ignore_hotkey: false,
newline_type: '\n',
});
}
KbKey::Tab => {
let action = if event.mods.shift() {
Action::InsertBacktab
} else {
Action::InsertTab {
ignore_hotkey: false,
}
};
input_handler.handle_action(action);
}
KbKey::Home => {
let movement = if event.mods.ctrl() {
Movement::Vertical(VerticalMovement::DocumentStart)
} else {
Movement::Line(Direction::Upstream)
};
if event.mods.shift() {
input_handler.handle_action(Action::MoveSelecting(movement));
} else {
input_handler.handle_action(Action::Move(movement));
}
}
KbKey::End => {
let movement = if event.mods.ctrl() {
Movement::Vertical(VerticalMovement::DocumentEnd)
} else {
Movement::Line(Direction::Downstream)
};
if event.mods.shift() {
input_handler.handle_action(Action::MoveSelecting(movement));
} else {
input_handler.handle_action(Action::Move(movement));
}
}
KbKey::PageUp => {
let movement = Movement::Vertical(VerticalMovement::PageUp);
if event.mods.shift() {
input_handler.handle_action(Action::MoveSelecting(movement));
} else {
input_handler.handle_action(Action::Move(movement));
}
}
KbKey::PageDown => {
let movement = Movement::Vertical(VerticalMovement::PageDown);
if event.mods.shift() {
input_handler.handle_action(Action::MoveSelecting(movement));
} else {
input_handler.handle_action(Action::Move(movement));
}
}
_ => {
handler.release_input_lock(token);
return false;
}
};
handler.release_input_lock(token);
true
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Movement {
Grapheme(Direction),
Word(Direction),
Line(Direction),
ParagraphStart,
ParagraphEnd,
Vertical(VerticalMovement),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Direction {
Left,
Right,
Upstream,
Downstream,
}
impl Direction {
pub fn is_upstream_for_direction(self, direction: WritingDirection) -> bool {
assert!(
!matches!(direction, WritingDirection::Natural),
"writing direction must be resolved"
);
match self {
Direction::Upstream => true,
Direction::Downstream => false,
Direction::Left => matches!(direction, WritingDirection::LeftToRight),
Direction::Right => matches!(direction, WritingDirection::RightToLeft),
}
}
}
pub enum Affinity {
Upstream,
Downstream,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum WritingDirection {
LeftToRight,
RightToLeft,
Natural,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum VerticalMovement {
LineUp,
LineDown,
PageUp,
PageDown,
DocumentStart,
DocumentEnd,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Action {
Move(Movement),
MoveSelecting(Movement),
SelectAll,
SelectLine,
SelectParagraph,
SelectWord,
Delete(Movement),
DecomposingBackspace,
UppercaseSelection,
LowercaseSelection,
TitlecaseSelection,
InsertNewLine {
ignore_hotkey: bool,
newline_type: char,
},
InsertTab {
ignore_hotkey: bool,
},
InsertBacktab,
InsertSingleQuoteIgnoringSmartQuotes,
InsertDoubleQuoteIgnoringSmartQuotes,
Scroll(VerticalMovement),
ScrollToSelection,
SetSelectionWritingDirection(WritingDirection),
SetParagraphWritingDirection(WritingDirection),
Cancel,
}