use crate::_private::NonExhaustive;
use crate::clipboard::Clipboard;
use crate::event::{ReadOnly, TextOutcome};
use crate::text_input::{TextInputState, TextInputStyle};
use crate::text_mask_core::MaskedCore;
use crate::undo_buffer::{UndoBuffer, UndoEntry};
use crate::{ipos_type, upos_type, Cursor, Glyph, Grapheme, HasScreenCursor, TextError};
use crossterm::event::KeyModifiers;
use format_num_pattern::NumberSymbols;
use rat_event::util::MouseFlags;
use rat_event::{ct_event, HandleEvent, MouseOnly, Regular};
use rat_focus::{FocusFlag, HasFocusFlag, Navigation};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::prelude::BlockExt;
use ratatui::style::{Style, Stylize};
#[cfg(feature = "unstable-widget-ref")]
use ratatui::widgets::StatefulWidgetRef;
use ratatui::widgets::{Block, StatefulWidget, Widget};
use std::borrow::Cow;
use std::cmp::min;
use std::fmt;
use std::ops::Range;
#[derive(Debug, Default, Clone)]
pub struct MaskedInput<'a> {
compact: bool,
block: Option<Block<'a>>,
style: Style,
focus_style: Option<Style>,
select_style: Option<Style>,
invalid_style: Option<Style>,
text_style: Vec<Style>,
}
#[derive(Debug, Clone)]
pub struct MaskedInputState {
pub focus: FocusFlag,
pub invalid: bool,
pub area: Rect,
pub inner: Rect,
pub offset: upos_type,
pub value: MaskedCore,
pub mouse: MouseFlags,
pub non_exhaustive: NonExhaustive,
}
impl<'a> MaskedInput<'a> {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn compact(mut self, show_compact: bool) -> Self {
self.compact = show_compact;
self
}
#[inline]
pub fn styles(mut self, style: TextInputStyle) -> Self {
self.style = style.style;
self.focus_style = style.focus;
self.select_style = style.select;
self.invalid_style = style.invalid;
self
}
#[inline]
pub fn style(mut self, style: impl Into<Style>) -> Self {
self.style = style.into();
self
}
#[inline]
pub fn focus_style(mut self, style: impl Into<Style>) -> Self {
self.focus_style = Some(style.into());
self
}
#[inline]
pub fn select_style(mut self, style: impl Into<Style>) -> Self {
self.select_style = Some(style.into());
self
}
#[inline]
pub fn invalid_style(mut self, style: impl Into<Style>) -> Self {
self.invalid_style = Some(style.into());
self
}
pub fn text_style<T: IntoIterator<Item = Style>>(mut self, styles: T) -> Self {
self.text_style = styles.into_iter().collect();
self
}
#[inline]
pub fn block(mut self, block: Block<'a>) -> Self {
self.block = Some(block);
self
}
}
#[cfg(feature = "unstable-widget-ref")]
impl<'a> StatefulWidgetRef for MaskedInput<'a> {
type State = MaskedInputState;
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
render_ref(self, area, buf, state);
}
}
impl<'a> StatefulWidget for MaskedInput<'a> {
type State = MaskedInputState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
render_ref(&self, area, buf, state);
}
}
fn render_ref(
widget: &MaskedInput<'_>,
area: Rect,
buf: &mut Buffer,
state: &mut MaskedInputState,
) {
state.area = area;
state.inner = widget.block.inner_if_some(area);
widget.block.render(area, buf);
let inner = state.inner;
let focus_style = if let Some(focus_style) = widget.focus_style {
focus_style
} else {
widget.style
};
let select_style = if let Some(select_style) = widget.select_style {
select_style
} else {
Style::default().on_yellow()
};
let invalid_style = if let Some(invalid_style) = widget.invalid_style {
invalid_style
} else {
Style::default().red()
};
let (style, select_style) = if state.focus.get() {
if state.invalid {
(
focus_style.patch(invalid_style),
select_style.patch(invalid_style),
)
} else {
(focus_style, select_style)
}
} else {
if state.invalid {
(
widget.style.patch(invalid_style),
widget.style.patch(invalid_style),
)
} else {
(widget.style, widget.style)
}
};
for y in inner.top()..inner.bottom() {
for x in inner.left()..inner.right() {
if let Some(cell) = buf.cell_mut((x, y)) {
cell.reset();
cell.set_style(style);
}
}
}
let ox = state.offset() as u16;
let show_range = {
let start = ox as upos_type;
let end = min(start + inner.width as upos_type, state.len());
state.bytes_at_range(start..end)
};
let selection = state.selection();
let mut styles = Vec::new();
let mut glyph_iter_regular;
let mut glyph_iter_cond;
let glyph_iter: &mut dyn Iterator<Item = Glyph<'_>>;
if state.is_focused() || !widget.compact {
glyph_iter_regular = state
.value
.glyphs(0..1, ox, inner.width)
.expect("valid_offset");
glyph_iter = &mut glyph_iter_regular;
} else {
glyph_iter_cond = state
.value
.condensed_glyphs(0..1, ox, inner.width)
.expect("valid_offset");
glyph_iter = &mut glyph_iter_cond;
}
for g in glyph_iter {
if g.screen_width() > 0 {
let mut style = style;
styles.clear();
state
.value
.styles_at_page(show_range.clone(), g.text_bytes().start, &mut styles);
for style_nr in &styles {
if let Some(s) = widget.text_style.get(*style_nr) {
style = style.patch(*s);
}
}
if selection.contains(&g.pos().x) {
style = style.patch(select_style);
};
let screen_pos = g.screen_pos();
if let Some(cell) = buf.cell_mut((inner.x + screen_pos.0, inner.y + screen_pos.1)) {
cell.set_symbol(g.glyph());
cell.set_style(style);
}
for d in 1..g.screen_width() {
if let Some(cell) =
buf.cell_mut((inner.x + screen_pos.0 + d, inner.y + screen_pos.1))
{
cell.reset();
cell.set_style(style);
}
}
}
}
}
impl Default for MaskedInputState {
fn default() -> Self {
Self {
focus: Default::default(),
invalid: false,
area: Default::default(),
inner: Default::default(),
mouse: Default::default(),
value: Default::default(),
non_exhaustive: NonExhaustive,
offset: 0,
}
}
}
impl HasFocusFlag for MaskedInputState {
fn focus(&self) -> FocusFlag {
self.focus.clone()
}
fn area(&self) -> Rect {
self.area
}
fn navigable(&self) -> Navigation {
let sel = self.selection();
let has_next = self
.value
.next_section_range(sel.end)
.map(|v| !v.is_empty())
.is_some();
let has_prev = self
.value
.prev_section_range(sel.start.saturating_sub(1))
.map(|v| !v.is_empty())
.is_some();
if has_next {
if has_prev {
Navigation::Reach
} else {
Navigation::ReachLeaveFront
}
} else {
if has_prev {
Navigation::ReachLeaveBack
} else {
Navigation::Regular
}
}
}
}
impl MaskedInputState {
pub fn new() -> Self {
Self::default()
}
pub fn named(name: &str) -> Self {
Self {
focus: FocusFlag::named(name),
..MaskedInputState::default()
}
}
#[inline]
pub fn with_symbols(mut self, sym: NumberSymbols) -> Self {
self.set_num_symbols(sym);
self
}
pub fn with_mask<S: AsRef<str>>(mut self, mask: S) -> Result<Self, fmt::Error> {
self.value.set_mask(mask.as_ref())?;
Ok(self)
}
#[inline]
pub fn set_num_symbols(&mut self, sym: NumberSymbols) {
self.value.set_num_symbols(sym);
}
#[inline]
pub fn set_mask<S: AsRef<str>>(&mut self, s: S) -> Result<(), fmt::Error> {
self.value.set_mask(s)
}
#[inline]
pub fn mask(&self) -> String {
self.value.mask()
}
#[inline]
pub fn set_invalid(&mut self, invalid: bool) {
self.invalid = invalid;
}
#[inline]
pub fn get_invalid(&self) -> bool {
self.invalid
}
}
impl MaskedInputState {
#[inline]
pub fn set_clipboard(&mut self, clip: Option<impl Clipboard + 'static>) {
match clip {
None => self.value.set_clipboard(None),
Some(v) => self.value.set_clipboard(Some(Box::new(v))),
}
}
#[inline]
pub fn clipboard(&self) -> Option<&dyn Clipboard> {
self.value.clipboard()
}
#[inline]
pub fn copy_to_clip(&mut self) -> bool {
let Some(clip) = self.value.clipboard() else {
return false;
};
_ = clip.set_string(self.selected_text().as_ref());
true
}
#[inline]
pub fn cut_to_clip(&mut self) -> bool {
let Some(clip) = self.value.clipboard() else {
return false;
};
match clip.set_string(self.selected_text().as_ref()) {
Ok(_) => self.delete_range(self.selection()),
Err(_) => true,
}
}
#[inline]
pub fn paste_from_clip(&mut self) -> bool {
let Some(clip) = self.value.clipboard() else {
return false;
};
if let Ok(text) = clip.get_string() {
for c in text.chars() {
self.insert_char(c);
}
true
} else {
false
}
}
}
impl MaskedInputState {
#[inline]
pub fn set_undo_buffer(&mut self, undo: Option<impl UndoBuffer + 'static>) {
match undo {
None => self.value.set_undo_buffer(None),
Some(v) => self.value.set_undo_buffer(Some(Box::new(v))),
}
}
#[inline]
pub fn undo_buffer(&self) -> Option<&dyn UndoBuffer> {
self.value.undo_buffer()
}
#[inline]
pub fn undo_buffer_mut(&mut self) -> Option<&mut dyn UndoBuffer> {
self.value.undo_buffer_mut()
}
#[inline]
pub fn recent_replay_log(&mut self) -> Vec<UndoEntry> {
self.value.recent_replay_log()
}
#[inline]
pub fn replay_log(&mut self, replay: &[UndoEntry]) {
self.value.replay_log(replay)
}
#[inline]
pub fn undo(&mut self) -> bool {
self.value.undo()
}
#[inline]
pub fn redo(&mut self) -> bool {
self.value.redo()
}
}
impl MaskedInputState {
#[inline]
pub fn set_styles(&mut self, styles: Vec<(Range<usize>, usize)>) {
self.value.set_styles(styles);
}
#[inline]
pub fn add_style(&mut self, range: Range<usize>, style: usize) {
self.value.add_style(range.into(), style);
}
#[inline]
pub fn add_range_style(
&mut self,
range: Range<upos_type>,
style: usize,
) -> Result<(), TextError> {
let r = self.value.bytes_at_range(range)?;
self.value.add_style(r, style);
Ok(())
}
#[inline]
pub fn remove_style(&mut self, range: Range<usize>, style: usize) {
self.value.remove_style(range.into(), style);
}
#[inline]
pub fn remove_range_style(
&mut self,
range: Range<upos_type>,
style: usize,
) -> Result<(), TextError> {
let r = self.value.bytes_at_range(range)?;
self.value.remove_style(r, style);
Ok(())
}
pub fn styles_in(&self, range: Range<usize>, buf: &mut Vec<(Range<usize>, usize)>) {
self.value.styles_in(range, buf)
}
#[inline]
pub fn styles_at(&self, byte_pos: usize, buf: &mut Vec<(Range<usize>, usize)>) {
self.value.styles_at(byte_pos, buf)
}
#[inline]
pub fn style_match(&self, byte_pos: usize, style: usize) -> Option<Range<usize>> {
self.value.style_match(byte_pos, style.into())
}
#[inline]
pub fn styles(&self) -> Option<impl Iterator<Item = (Range<usize>, usize)> + '_> {
self.value.styles()
}
}
impl MaskedInputState {
#[inline]
pub fn offset(&self) -> upos_type {
self.offset
}
#[inline]
pub fn set_offset(&mut self, offset: upos_type) {
self.offset = offset;
}
#[inline]
pub fn cursor(&self) -> upos_type {
self.value.cursor()
}
#[inline]
pub fn set_cursor(&mut self, cursor: upos_type, extend_selection: bool) -> bool {
self.value.set_cursor(cursor, extend_selection)
}
#[inline]
pub fn set_default_cursor(&mut self) {
self.value.set_default_cursor();
}
#[inline]
pub fn anchor(&self) -> upos_type {
self.value.anchor()
}
#[inline]
pub fn has_selection(&self) -> bool {
self.value.has_selection()
}
#[inline]
pub fn selection(&self) -> Range<upos_type> {
self.value.selection()
}
#[inline]
pub fn set_selection(&mut self, anchor: upos_type, cursor: upos_type) -> bool {
self.value.set_selection(anchor, cursor)
}
#[inline]
pub fn select_all(&mut self) -> bool {
if let Some(section) = self.value.section_range(self.cursor()) {
if self.selection() == section {
self.value.select_all()
} else {
self.value.set_selection(section.start, section.end)
}
} else {
self.value.select_all()
}
}
#[inline]
pub fn selected_text(&self) -> &str {
self.value.selected_text()
}
}
impl MaskedInputState {
#[inline]
pub fn is_empty(&self) -> bool {
self.value.is_empty()
}
#[inline]
pub fn text(&self) -> &str {
self.value.text()
}
#[inline]
pub fn str_slice_byte(&self, range: Range<usize>) -> Cow<'_, str> {
self.value.str_slice_byte(range).expect("valid_range")
}
#[inline]
pub fn try_str_slice_byte(&self, range: Range<usize>) -> Result<Cow<'_, str>, TextError> {
self.value.str_slice_byte(range)
}
#[inline]
pub fn str_slice(&self, range: Range<upos_type>) -> Cow<'_, str> {
self.value.str_slice(range).expect("valid_range")
}
#[inline]
pub fn try_str_slice(&self, range: Range<upos_type>) -> Result<Cow<'_, str>, TextError> {
self.value.str_slice(range)
}
#[inline]
pub fn len(&self) -> upos_type {
self.value.line_width()
}
#[inline]
pub fn line_width(&self) -> upos_type {
self.value.line_width()
}
#[inline]
pub fn glyphs(&self, screen_offset: u16, screen_width: u16) -> impl Iterator<Item = Glyph<'_>> {
self.value
.glyphs(0..1, screen_offset, screen_width)
.expect("valid_row")
}
#[inline]
pub fn condensed_glyphs(
&self,
screen_offset: u16,
screen_width: u16,
) -> impl Iterator<Item = Glyph<'_>> {
self.value
.condensed_glyphs(0..1, screen_offset, screen_width)
.expect("valid_row")
}
#[inline]
pub fn text_graphemes(&self, pos: upos_type) -> impl Iterator<Item = Grapheme<'_>> + Cursor {
self.value.text_graphemes(pos).expect("valid_pos")
}
#[inline]
pub fn try_text_graphemes(
&self,
pos: upos_type,
) -> Result<impl Iterator<Item = Grapheme<'_>> + Cursor, TextError> {
self.value.text_graphemes(pos)
}
#[inline]
pub fn graphemes(
&self,
range: Range<upos_type>,
pos: upos_type,
) -> impl Iterator<Item = Grapheme<'_>> + Cursor {
self.value.graphemes(range, pos).expect("valid_args")
}
#[inline]
pub fn try_graphemes(
&self,
range: Range<upos_type>,
pos: upos_type,
) -> Result<impl Iterator<Item = Grapheme<'_>> + Cursor, TextError> {
self.value.graphemes(range, pos)
}
#[inline]
pub fn byte_at(&self, pos: upos_type) -> Range<usize> {
self.value.byte_at(pos).expect("valid_pos")
}
#[inline]
pub fn try_byte_at(&self, pos: upos_type) -> Result<Range<usize>, TextError> {
self.value.byte_at(pos)
}
#[inline]
pub fn bytes_at_range(&self, range: Range<upos_type>) -> Range<usize> {
self.value.bytes_at_range(range).expect("valid_range")
}
#[inline]
pub fn try_bytes_at_range(&self, range: Range<upos_type>) -> Result<Range<usize>, TextError> {
self.value.bytes_at_range(range)
}
#[inline]
pub fn byte_pos(&self, byte: usize) -> upos_type {
self.value.byte_pos(byte).expect("valid_pos")
}
#[inline]
pub fn try_byte_pos(&self, byte: usize) -> Result<upos_type, TextError> {
self.value.byte_pos(byte)
}
#[inline]
pub fn byte_range(&self, bytes: Range<usize>) -> Range<upos_type> {
self.value.byte_range(bytes).expect("valid_range")
}
#[inline]
pub fn try_byte_range(&self, bytes: Range<usize>) -> Result<Range<upos_type>, TextError> {
self.value.byte_range(bytes)
}
}
impl MaskedInputState {
#[inline]
pub fn clear(&mut self) -> bool {
if self.is_empty() {
false
} else {
self.offset = 0;
self.value.clear();
true
}
}
#[inline]
pub fn set_text<S: Into<String>>(&mut self, s: S) {
self.offset = 0;
self.value.set_text(s);
}
#[inline]
pub fn insert_char(&mut self, c: char) -> bool {
self.value.begin_undo_seq();
if self.value.has_selection() {
let sel = self.value.selection();
self.value
.remove_range(sel.clone())
.expect("valid_selection");
self.value.set_cursor(sel.start, false);
}
let c0 = self.value.advance_cursor(c);
let c1 = self.value.insert_char(c);
self.value.end_undo_seq();
self.scroll_cursor_to_visible();
c0 || c1
}
#[inline]
pub fn delete_range(&mut self, range: Range<upos_type>) -> bool {
self.try_delete_range(range).expect("valid_range")
}
#[inline]
pub fn try_delete_range(&mut self, range: Range<upos_type>) -> Result<bool, TextError> {
self.value.begin_undo_seq();
let r = self.value.remove_range(range.clone())?;
if let Some(pos) = self.value.section_cursor(range.start) {
self.value.set_cursor(pos, false);
}
self.value.end_undo_seq();
self.scroll_cursor_to_visible();
Ok(r)
}
}
impl MaskedInputState {
#[inline]
pub fn delete_next_char(&mut self) -> bool {
if self.has_selection() {
self.delete_range(self.selection())
} else if self.cursor() == self.len() {
false
} else {
self.value.remove_next();
self.scroll_cursor_to_visible();
true
}
}
#[inline]
pub fn delete_prev_char(&mut self) -> bool {
if self.has_selection() {
self.delete_range(self.selection())
} else if self.cursor() == 0 {
false
} else {
self.value.remove_prev();
self.scroll_cursor_to_visible();
true
}
}
#[inline]
pub fn delete_prev_section(&mut self) -> bool {
if self.has_selection() {
self.delete_range(self.selection())
} else {
if let Some(range) = self.value.prev_section_range(self.cursor()) {
self.delete_range(range)
} else {
false
}
}
}
#[inline]
pub fn delete_next_section(&mut self) -> bool {
if self.has_selection() {
self.delete_range(self.selection())
} else {
if let Some(range) = self.value.next_section_range(self.cursor()) {
self.delete_range(range)
} else {
false
}
}
}
#[inline]
pub fn move_right(&mut self, extend_selection: bool) -> bool {
let c = min(self.cursor() + 1, self.len());
let c = self.set_cursor(c, extend_selection);
let s = self.scroll_cursor_to_visible();
c || s
}
#[inline]
pub fn move_left(&mut self, extend_selection: bool) -> bool {
let c = self.cursor().saturating_sub(1);
let c = self.set_cursor(c, extend_selection);
let s = self.scroll_cursor_to_visible();
c || s
}
#[inline]
pub fn move_to_line_start(&mut self, extend_selection: bool) -> bool {
let c = if let Some(c) = self.value.section_cursor(self.cursor()) {
if c != self.cursor() {
self.set_cursor(c, extend_selection)
} else {
self.set_cursor(0, extend_selection)
}
} else {
self.set_cursor(0, extend_selection)
};
let s = self.scroll_cursor_to_visible();
c || s
}
#[inline]
pub fn move_to_line_end(&mut self, extend_selection: bool) -> bool {
let c = self.len();
let c = self.set_cursor(c, extend_selection);
let s = self.scroll_cursor_to_visible();
c || s
}
#[inline]
pub fn move_to_prev_section(&mut self, extend_selection: bool) -> bool {
if let Some(curr) = self.value.section_range(self.cursor()) {
if self.value.cursor() != curr.start {
return self.value.set_cursor(curr.start, extend_selection);
}
}
if let Some(range) = self.value.prev_section_range(self.cursor()) {
self.value.set_cursor(range.start, extend_selection)
} else {
false
}
}
#[inline]
pub fn move_to_next_section(&mut self, extend_selection: bool) -> bool {
if let Some(curr) = self.value.section_range(self.cursor()) {
if self.value.cursor() != curr.end {
return self.value.set_cursor(curr.end, extend_selection);
}
}
if let Some(range) = self.value.next_section_range(self.cursor()) {
self.value.set_cursor(range.end, extend_selection)
} else {
false
}
}
#[inline]
pub fn select_current_section(&mut self) -> bool {
let selection = self.selection();
if let Some(next) = self.value.section_range(selection.start.saturating_sub(1)) {
if !next.is_empty() {
self.set_selection(next.start, next.end)
} else {
false
}
} else {
false
}
}
#[inline]
pub fn select_next_section(&mut self) -> bool {
let selection = self.selection();
if let Some(next) = self.value.next_section_range(selection.start) {
if !next.is_empty() {
self.set_selection(next.start, next.end)
} else {
false
}
} else {
false
}
}
#[inline]
pub fn select_prev_section(&mut self) -> bool {
let selection = self.selection();
if let Some(next) = self
.value
.prev_section_range(selection.start.saturating_sub(1))
{
if !next.is_empty() {
self.set_selection(next.start, next.end)
} else {
false
}
} else {
false
}
}
}
impl HasScreenCursor for MaskedInputState {
#[inline]
fn screen_cursor(&self) -> Option<(u16, u16)> {
if self.is_focused() {
let cx = self.cursor();
let ox = self.offset();
if cx < ox {
None
} else if cx > ox + self.inner.width as upos_type {
None
} else {
let sc = self.col_to_screen(cx);
Some((self.inner.x + sc, self.inner.y))
}
} else {
None
}
}
}
impl MaskedInputState {
pub fn col_to_screen(&self, pos: upos_type) -> u16 {
let ox = self.offset();
if pos < ox {
return 0;
}
let line = self.glyphs(ox as u16, self.inner.width);
let mut screen_x = 0;
for g in line {
if g.pos().x >= pos {
break;
}
screen_x = g.screen_pos().0 + g.screen_width();
}
screen_x
}
pub fn screen_to_col(&self, scx: i16) -> upos_type {
let ox = self.offset();
if scx < 0 {
ox.saturating_sub((scx as ipos_type).abs() as upos_type)
} else if scx as u16 >= self.inner.width {
min(ox + scx as upos_type, self.len())
} else {
let scx = scx as u16;
let line = self.glyphs(ox as u16, self.inner.width);
let mut col = ox;
for g in line {
if scx < g.screen_pos().0 + g.screen_width() {
break;
}
col = g.pos().x + 1;
}
col
}
}
#[inline]
pub fn set_screen_cursor(&mut self, cursor: i16, extend_selection: bool) -> bool {
let scx = cursor;
let cx = self.screen_to_col(scx);
let c = self.set_cursor(cx, extend_selection);
let s = self.scroll_cursor_to_visible();
c || s
}
pub fn set_screen_cursor_sections(
&mut self,
screen_cursor: i16,
extend_selection: bool,
) -> bool {
let anchor = self.anchor();
let cursor = self.screen_to_col(screen_cursor);
let Some(range) = self.value.section_range(cursor) else {
return false;
};
let cursor = if cursor < anchor {
range.start
} else {
range.end
};
if !self.value.is_section_boundary(anchor) {
if let Some(range) = self.value.section_range(anchor) {
if cursor < anchor {
self.set_cursor(range.end, false);
} else {
self.set_cursor(range.start, false);
}
};
}
let c = self.set_cursor(cursor, extend_selection);
let s = self.scroll_cursor_to_visible();
c || s
}
pub fn scroll_left(&mut self, delta: upos_type) -> bool {
self.set_offset(self.offset.saturating_sub(delta));
true
}
pub fn scroll_right(&mut self, delta: upos_type) -> bool {
self.set_offset(self.offset + delta);
true
}
pub fn scroll_cursor_to_visible(&mut self) -> bool {
let old_offset = self.offset();
let c = self.cursor();
let o = self.offset();
let no = if c < o {
c
} else if c >= o + self.inner.width as upos_type {
c.saturating_sub(self.inner.width as upos_type)
} else {
o
};
self.set_offset(no);
self.offset() != old_offset
}
}
impl HandleEvent<crossterm::event::Event, Regular, TextOutcome> for MaskedInputState {
fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> TextOutcome {
fn tc(r: bool) -> TextOutcome {
if r {
TextOutcome::TextChanged
} else {
TextOutcome::Unchanged
}
}
let mut r = if self.is_focused() {
match event {
ct_event!(key press c)
| ct_event!(key press SHIFT-c)
| ct_event!(key press CONTROL_ALT-c) => tc(self.insert_char(*c)),
ct_event!(keycode press Backspace) => tc(self.delete_prev_char()),
ct_event!(keycode press Delete) => tc(self.delete_next_char()),
ct_event!(keycode press CONTROL-Backspace)
| ct_event!(keycode press ALT-Backspace) => tc(self.delete_prev_section()),
ct_event!(keycode press CONTROL-Delete) => tc(self.delete_next_section()),
ct_event!(key press CONTROL-'x') => tc(self.cut_to_clip()),
ct_event!(key press CONTROL-'v') => tc(self.paste_from_clip()),
ct_event!(key press CONTROL-'d') => tc(self.clear()),
ct_event!(key press CONTROL-'z') => tc(self.undo()),
ct_event!(key press CONTROL_SHIFT-'Z') => tc(self.redo()),
ct_event!(key release _)
| ct_event!(key release SHIFT-_)
| ct_event!(key release CONTROL_ALT-_)
| ct_event!(keycode release Backspace)
| ct_event!(keycode release Delete)
| ct_event!(keycode release CONTROL-Backspace)
| ct_event!(keycode release ALT-Backspace)
| ct_event!(keycode release CONTROL-Delete)
| ct_event!(key release CONTROL-'x')
| ct_event!(key release CONTROL-'v')
| ct_event!(key release CONTROL-'d')
| ct_event!(key release CONTROL-'z')
| ct_event!(key release CONTROL_SHIFT-'Z') => TextOutcome::Unchanged,
_ => TextOutcome::Continue,
}
} else {
TextOutcome::Continue
};
if r == TextOutcome::Changed {
r = TextOutcome::TextChanged;
}
if r == TextOutcome::Continue {
r = self.handle(event, ReadOnly);
}
r
}
}
impl HandleEvent<crossterm::event::Event, ReadOnly, TextOutcome> for MaskedInputState {
fn handle(&mut self, event: &crossterm::event::Event, _keymap: ReadOnly) -> TextOutcome {
let mut r = if self.is_focused() {
if self.focus.gained() {
self.set_default_cursor();
self.select_current_section();
};
match event {
ct_event!(keycode press Left) => self.move_left(false).into(),
ct_event!(keycode press Right) => self.move_right(false).into(),
ct_event!(keycode press CONTROL-Left) => self.move_to_prev_section(false).into(),
ct_event!(keycode press CONTROL-Right) => self.move_to_next_section(false).into(),
ct_event!(keycode press Home) => self.move_to_line_start(false).into(),
ct_event!(keycode press End) => self.move_to_line_end(false).into(),
ct_event!(keycode press SHIFT-Left) => self.move_left(true).into(),
ct_event!(keycode press SHIFT-Right) => self.move_right(true).into(),
ct_event!(keycode press CONTROL_SHIFT-Left) => {
self.move_to_prev_section(true).into()
}
ct_event!(keycode press CONTROL_SHIFT-Right) => {
self.move_to_next_section(true).into()
}
ct_event!(keycode press SHIFT-Home) => self.move_to_line_start(true).into(),
ct_event!(keycode press SHIFT-End) => self.move_to_line_end(true).into(),
ct_event!(keycode press Tab) => {
if !self.focus.gained() {
self.select_next_section().into()
} else {
TextOutcome::Unchanged
}
}
ct_event!(keycode press SHIFT-BackTab) => {
if !self.focus.gained() {
self.select_prev_section().into()
} else {
TextOutcome::Unchanged
}
}
ct_event!(key press CONTROL-'a') => self.select_all().into(),
ct_event!(key press CONTROL-'c') => self.copy_to_clip().into(),
ct_event!(keycode release Left)
| ct_event!(keycode release Right)
| ct_event!(keycode release CONTROL-Left)
| ct_event!(keycode release CONTROL-Right)
| ct_event!(keycode release Home)
| ct_event!(keycode release End)
| ct_event!(keycode release SHIFT-Left)
| ct_event!(keycode release SHIFT-Right)
| ct_event!(keycode release CONTROL_SHIFT-Left)
| ct_event!(keycode release CONTROL_SHIFT-Right)
| ct_event!(keycode release SHIFT-Home)
| ct_event!(keycode release SHIFT-End)
| ct_event!(key release CONTROL-'a')
| ct_event!(key release CONTROL-'c') => TextOutcome::Unchanged,
_ => TextOutcome::Continue,
}
} else {
TextOutcome::Continue
};
if r == TextOutcome::Continue {
r = self.handle(event, MouseOnly);
}
r
}
}
impl HandleEvent<crossterm::event::Event, MouseOnly, TextOutcome> for MaskedInputState {
fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> TextOutcome {
match event {
ct_event!(mouse any for m) if self.mouse.drag(self.inner, m) => {
let c = (m.column as i16) - (self.inner.x as i16);
self.set_screen_cursor(c, true).into()
}
ct_event!(mouse any for m) if self.mouse.drag2(self.inner, m, KeyModifiers::ALT) => {
let cx = m.column as i16 - self.inner.x as i16;
self.set_screen_cursor_sections(cx, true).into()
}
ct_event!(mouse any for m) if self.mouse.doubleclick(self.inner, m) => {
let tx = self.screen_to_col(m.column as i16 - self.inner.x as i16);
if let Some(range) = self.value.section_range(tx) {
self.set_selection(range.start, range.end).into()
} else {
TextOutcome::Unchanged
}
}
ct_event!(mouse down Left for column,row) => {
if self.gained_focus() {
TextOutcome::Unchanged
} else if self.inner.contains((*column, *row).into()) {
let c = (column - self.inner.x) as i16;
self.set_screen_cursor(c, false).into()
} else {
TextOutcome::Continue
}
}
ct_event!(mouse down CONTROL-Left for column,row) => {
if self.inner.contains((*column, *row).into()) {
let cx = (column - self.inner.x) as i16;
self.set_screen_cursor(cx, true).into()
} else {
TextOutcome::Continue
}
}
ct_event!(mouse down ALT-Left for column,row) => {
if self.inner.contains((*column, *row).into()) {
let cx = (column - self.inner.x) as i16;
self.set_screen_cursor_sections(cx, true).into()
} else {
TextOutcome::Continue
}
}
_ => TextOutcome::Continue,
}
}
}
pub fn handle_events(
state: &mut MaskedInputState,
focus: bool,
event: &crossterm::event::Event,
) -> TextOutcome {
state.focus.set(focus);
state.handle(event, Regular)
}
pub fn handle_readonly_events(
state: &mut TextInputState,
focus: bool,
event: &crossterm::event::Event,
) -> TextOutcome {
state.focus.set(focus);
state.handle(event, ReadOnly)
}
pub fn handle_mouse_events(
state: &mut MaskedInputState,
event: &crossterm::event::Event,
) -> TextOutcome {
state.handle(event, MouseOnly)
}