use crate::_private::NonExhaustive;
use crate::clipboard::Clipboard;
use crate::event::{ReadOnly, TextOutcome};
use crate::text_input::TextInputState;
use crate::text_mask_core::MaskedCore;
use crate::undo_buffer::{UndoBuffer, UndoEntry};
use crate::{
ipos_type, upos_type, Cursor, Glyph, Grapheme, HasScreenCursor, TextError, TextFocusGained,
TextFocusLost, TextStyle,
};
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::{FocusBuilder, FocusFlag, HasFocus, Navigation};
use rat_reloc::{relocate_area, relocate_dark_offset, RelocatableState};
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>,
on_focus_gained: TextFocusGained,
on_focus_lost: TextFocusLost,
}
#[derive(Debug)]
pub struct MaskedInputState {
pub area: Rect,
pub inner: Rect,
pub offset: upos_type,
pub dark_offset: (u16, u16),
pub value: MaskedCore,
pub invalid: bool,
pub overwrite: bool,
pub on_focus_gained: TextFocusGained,
pub on_focus_lost: TextFocusLost,
pub focus: FocusFlag,
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_opt(self, styles: Option<TextStyle>) -> Self {
if let Some(styles) = styles {
self.styles(styles)
} else {
self
}
}
#[inline]
pub fn styles(mut self, styles: TextStyle) -> Self {
self.style = styles.style;
if styles.focus.is_some() {
self.focus_style = styles.focus;
}
if styles.select.is_some() {
self.select_style = styles.select;
}
if styles.invalid.is_some() {
self.invalid_style = styles.invalid;
}
if let Some(of) = styles.on_focus_gained {
self.on_focus_gained = of;
}
if let Some(of) = styles.on_focus_lost {
self.on_focus_lost = of;
}
if let Some(border_style) = styles.border_style {
self.block = self.block.map(|v| v.border_style(border_style));
}
self.block = self.block.map(|v| v.style(self.style));
if styles.block.is_some() {
self.block = styles.block;
}
self.block = self.block.map(|v| v.style(self.style));
self
}
#[inline]
pub fn style(mut self, style: impl Into<Style>) -> Self {
self.style = style.into();
self.block = self.block.map(|v| v.style(self.style));
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.block = self.block.map(|v| v.style(self.style));
self
}
#[inline]
pub fn on_focus_gained(mut self, of: TextFocusGained) -> Self {
self.on_focus_gained = of;
self
}
#[inline]
pub fn on_focus_lost(mut self, of: TextFocusLost) -> Self {
self.on_focus_lost = of;
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 StatefulWidget for MaskedInput<'_> {
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);
state.on_focus_gained = widget.on_focus_gained;
state.on_focus_lost = widget.on_focus_lost;
let inner = state.inner;
let style = widget.style;
let focus_style = if let Some(focus_style) = widget.focus_style {
focus_style
} else {
style
};
let select_style = if let Some(select_style) = widget.select_style {
select_style
} else {
Style::default().black().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 {
(
style.patch(focus_style).patch(invalid_style),
style
.patch(focus_style)
.patch(select_style)
.patch(invalid_style),
)
} else {
(
style.patch(focus_style),
style.patch(focus_style).patch(select_style),
)
}
} else {
if state.invalid {
(style.patch(invalid_style), style.patch(invalid_style))
} else {
(style, style)
}
};
if widget.block.is_some() {
widget.block.render(area, buf);
} else {
buf.set_style(area, style);
}
if inner.width == 0 || inner.height == 0 {
return;
}
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 Clone for MaskedInputState {
fn clone(&self) -> Self {
Self {
area: self.area,
inner: self.inner,
offset: self.offset,
dark_offset: self.dark_offset,
value: self.value.clone(),
invalid: self.invalid,
overwrite: Default::default(),
on_focus_gained: Default::default(),
on_focus_lost: Default::default(),
focus: FocusFlag::named(self.focus.name()),
mouse: Default::default(),
non_exhaustive: NonExhaustive,
}
}
}
impl Default for MaskedInputState {
fn default() -> Self {
Self {
area: Default::default(),
inner: Default::default(),
offset: Default::default(),
dark_offset: Default::default(),
value: Default::default(),
invalid: Default::default(),
overwrite: Default::default(),
on_focus_gained: Default::default(),
on_focus_lost: Default::default(),
focus: Default::default(),
mouse: Default::default(),
non_exhaustive: NonExhaustive,
}
}
}
impl HasFocus for MaskedInputState {
fn build(&self, builder: &mut FocusBuilder) {
builder.leaf_widget(self);
}
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
}
#[inline]
pub fn set_overwrite(&mut self, overwrite: bool) {
self.overwrite = overwrite;
}
#[inline]
pub fn overwrite(&self) -> bool {
self.overwrite
}
}
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, 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, 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)
}
#[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 Cursor<Item = Grapheme<'_>> {
self.value.text_graphemes(pos).expect("valid_pos")
}
#[inline]
pub fn try_text_graphemes(
&self,
pos: upos_type,
) -> Result<impl Cursor<Item = Grapheme<'_>>, TextError> {
self.value.text_graphemes(pos)
}
#[inline]
pub fn graphemes(
&self,
range: Range<upos_type>,
pos: upos_type,
) -> impl Cursor<Item = Grapheme<'_>> {
self.value.graphemes(range, pos).expect("valid_args")
}
#[inline]
pub fn try_graphemes(
&self,
range: Range<upos_type>,
pos: upos_type,
) -> Result<impl Cursor<Item = Grapheme<'_>>, 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);
self.value.set_default_cursor();
}
#[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 + self.dark_offset.0) as upos_type {
None
} else {
self.col_to_screen(cx)
.map(|sc| (self.inner.x + sc, self.inner.y))
}
} else {
None
}
}
}
impl RelocatableState for MaskedInputState {
fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
self.dark_offset = relocate_dark_offset(self.inner, shift, clip);
self.area = relocate_area(self.area, shift, clip);
self.inner = relocate_area(self.inner, shift, clip);
}
}
impl MaskedInputState {
pub fn screen_to_col(&self, scx: i16) -> upos_type {
let ox = self.offset();
let scx = scx + self.dark_offset.0 as i16;
if scx < 0 {
ox.saturating_sub((scx as ipos_type).unsigned_abs())
} else if scx as u16 >= (self.inner.width + self.dark_offset.0) {
min(ox + scx as upos_type, self.len())
} else {
let scx = scx as u16;
let line = self.glyphs(ox as u16, self.inner.width + self.dark_offset.0);
let mut col = ox;
for g in line {
if scx < g.screen_pos().0 + g.screen_width() {
break;
}
col = g.pos().x + 1;
}
col
}
}
pub fn col_to_screen(&self, pos: upos_type) -> Option<u16> {
let ox = self.offset();
if pos < ox {
return None;
}
let line = self.glyphs(ox as u16, self.inner.width + self.dark_offset.0);
let mut screen_x = 0;
for g in line {
if g.pos().x == pos {
break;
}
screen_x = g.screen_pos().0 + g.screen_width();
}
if screen_x >= self.dark_offset.0 {
Some(screen_x - self.dark_offset.0)
} else {
None
}
}
#[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 + self.dark_offset.0) as upos_type {
c.saturating_sub((self.inner.width + self.dark_offset.0) 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
}
}
fn overwrite(state: &mut MaskedInputState) {
if state.overwrite {
state.overwrite = false;
state.clear();
}
}
fn clear_overwrite(state: &mut MaskedInputState) {
state.overwrite = false;
}
if self.lost_focus() {
match self.on_focus_lost {
TextFocusLost::None => {}
TextFocusLost::Position0 => {
self.set_default_cursor();
self.scroll_cursor_to_visible();
}
}
}
if self.gained_focus() {
match self.on_focus_gained {
TextFocusGained::None => {}
TextFocusGained::Overwrite => {
self.overwrite = true;
}
TextFocusGained::SelectAll => {
self.select_all();
}
}
}
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) => {
overwrite(self);
tc(self.insert_char(*c))
}
ct_event!(keycode press Backspace) => {
clear_overwrite(self);
tc(self.delete_prev_char())
}
ct_event!(keycode press Delete) => {
clear_overwrite(self);
tc(self.delete_next_char())
}
ct_event!(keycode press CONTROL-Backspace)
| ct_event!(keycode press ALT-Backspace) => {
clear_overwrite(self);
tc(self.delete_prev_section())
}
ct_event!(keycode press CONTROL-Delete) => {
clear_overwrite(self);
tc(self.delete_next_section())
}
ct_event!(key press CONTROL-'x') => {
clear_overwrite(self);
tc(self.cut_to_clip())
}
ct_event!(key press CONTROL-'v') => {
clear_overwrite(self);
tc(self.paste_from_clip())
}
ct_event!(key press CONTROL-'d') => {
clear_overwrite(self);
tc(self.clear())
}
ct_event!(key press CONTROL-'z') => {
clear_overwrite(self);
tc(self.undo())
}
ct_event!(key press CONTROL_SHIFT-'Z') => {
clear_overwrite(self);
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::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 {
fn clear_overwrite(state: &mut MaskedInputState) {
state.overwrite = false;
}
let mut r = if self.is_focused() {
match event {
ct_event!(keycode press Left) => {
clear_overwrite(self);
self.move_left(false).into()
}
ct_event!(keycode press Right) => {
clear_overwrite(self);
self.move_right(false).into()
}
ct_event!(keycode press CONTROL-Left) => {
clear_overwrite(self);
self.move_to_prev_section(false).into()
}
ct_event!(keycode press CONTROL-Right) => {
clear_overwrite(self);
self.move_to_next_section(false).into()
}
ct_event!(keycode press Home) => {
clear_overwrite(self);
self.move_to_line_start(false).into()
}
ct_event!(keycode press End) => {
clear_overwrite(self);
self.move_to_line_end(false).into()
}
ct_event!(keycode press SHIFT-Left) => {
clear_overwrite(self);
self.move_left(true).into()
}
ct_event!(keycode press SHIFT-Right) => {
clear_overwrite(self);
self.move_right(true).into()
}
ct_event!(keycode press CONTROL_SHIFT-Left) => {
clear_overwrite(self);
self.move_to_prev_section(true).into()
}
ct_event!(keycode press CONTROL_SHIFT-Right) => {
clear_overwrite(self);
self.move_to_next_section(true).into()
}
ct_event!(keycode press SHIFT-Home) => {
clear_overwrite(self);
self.move_to_line_start(true).into()
}
ct_event!(keycode press SHIFT-End) => {
clear_overwrite(self);
self.move_to_line_end(true).into()
}
ct_event!(keycode press Tab) => {
if !self.focus.gained() {
clear_overwrite(self);
self.select_next_section().into()
} else {
TextOutcome::Unchanged
}
}
ct_event!(keycode press SHIFT-BackTab) => {
if !self.focus.gained() {
clear_overwrite(self);
self.select_prev_section().into()
} else {
TextOutcome::Unchanged
}
}
ct_event!(key press CONTROL-'a') => {
clear_overwrite(self);
self.select_all().into()
}
ct_event!(key press CONTROL-'c') => {
clear_overwrite(self);
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 {
fn clear_overwrite(state: &mut MaskedInputState) {
state.overwrite = false;
}
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);
clear_overwrite(self);
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;
clear_overwrite(self);
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);
clear_overwrite(self);
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;
clear_overwrite(self);
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;
clear_overwrite(self);
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;
clear_overwrite(self);
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)
}