use crate::_private::NonExhaustive;
use crate::clipboard::Clipboard;
use crate::event::{ReadOnly, TextOutcome};
use crate::text_input_mask::{MaskedInput, MaskedInputState};
use crate::undo_buffer::{UndoBuffer, UndoEntry};
use crate::{upos_type, HasScreenCursor, TextError, TextStyle};
use format_num_pattern::{NumberFmtError, NumberFormat, NumberSymbols};
use rat_event::{HandleEvent, MouseOnly, Regular};
use rat_focus::{FocusFlag, HasFocus, Navigation};
use rat_reloc::RelocatableState;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::prelude::{StatefulWidget, Style};
use ratatui::widgets::Block;
#[cfg(feature = "unstable-widget-ref")]
use ratatui::widgets::StatefulWidgetRef;
use std::fmt::{Debug, Display, LowerExp};
use std::ops::Range;
use std::str::FromStr;
#[derive(Debug, Default, Clone)]
pub struct NumberInput<'a> {
widget: MaskedInput<'a>,
}
#[derive(Debug, Clone)]
pub struct NumberInputState {
pub widget: MaskedInputState,
pattern: String,
locale: format_num_pattern::Locale,
format: NumberFormat,
pub non_exhaustive: NonExhaustive,
}
impl<'a> NumberInput<'a> {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn compact(mut self, compact: bool) -> Self {
self.widget = self.widget.compact(compact);
self
}
#[inline]
pub fn styles(mut self, style: TextStyle) -> Self {
self.widget = self.widget.styles(style);
self
}
#[inline]
pub fn style(mut self, style: impl Into<Style>) -> Self {
self.widget = self.widget.style(style);
self
}
#[inline]
pub fn focus_style(mut self, style: impl Into<Style>) -> Self {
self.widget = self.widget.focus_style(style);
self
}
#[inline]
pub fn select_style(mut self, style: impl Into<Style>) -> Self {
self.widget = self.widget.select_style(style);
self
}
#[inline]
pub fn invalid_style(mut self, style: impl Into<Style>) -> Self {
self.widget = self.widget.invalid_style(style);
self
}
#[inline]
pub fn block(mut self, block: Block<'a>) -> Self {
self.widget = self.widget.block(block);
self
}
}
#[cfg(feature = "unstable-widget-ref")]
impl<'a> StatefulWidgetRef for NumberInput<'a> {
type State = NumberInputState;
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
self.widget.render_ref(area, buf, &mut state.widget);
}
}
impl<'a> StatefulWidget for NumberInput<'a> {
type State = NumberInputState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
self.widget.render(area, buf, &mut state.widget);
}
}
impl Default for NumberInputState {
fn default() -> Self {
Self {
widget: Default::default(),
pattern: "#####".to_string(),
locale: Default::default(),
format: NumberFormat::new("#####").expect("valid_pattern"),
non_exhaustive: NonExhaustive,
}
}
}
impl HasFocus for NumberInputState {
#[inline]
fn focus(&self) -> FocusFlag {
self.widget.focus.clone()
}
#[inline]
fn area(&self) -> Rect {
self.widget.area
}
fn navigable(&self) -> Navigation {
self.widget.navigable()
}
}
impl NumberInputState {
pub fn new() -> Self {
Self::default()
}
pub fn named(name: &str) -> Self {
Self {
widget: MaskedInputState::named(name),
..Default::default()
}
}
pub fn with_pattern<S: AsRef<str>>(mut self, pattern: S) -> Result<Self, NumberFmtError> {
self.set_format(pattern)?;
Ok(self)
}
pub fn with_loc_pattern<S: AsRef<str>>(
mut self,
pattern: S,
locale: format_num_pattern::Locale,
) -> Result<Self, NumberFmtError> {
self.set_format_loc(pattern.as_ref(), locale)?;
Ok(self)
}
#[inline]
pub fn format(&self) -> &str {
self.pattern.as_str()
}
#[inline]
pub fn locale(&self) -> chrono::Locale {
self.locale
}
pub fn set_format<S: AsRef<str>>(&mut self, pattern: S) -> Result<(), NumberFmtError> {
self.set_format_loc(pattern, format_num_pattern::Locale::default())
}
pub fn set_format_loc<S: AsRef<str>>(
&mut self,
pattern: S,
locale: format_num_pattern::Locale,
) -> Result<(), NumberFmtError> {
let sym = NumberSymbols::monetary(locale);
self.format = NumberFormat::new(pattern.as_ref())?;
self.widget.set_mask(pattern.as_ref())?;
self.widget.set_num_symbols(sym);
Ok(())
}
#[inline]
pub fn set_invalid(&mut self, invalid: bool) {
self.widget.invalid = invalid;
}
#[inline]
pub fn get_invalid(&self) -> bool {
self.widget.invalid
}
}
impl NumberInputState {
#[inline]
pub fn set_clipboard(&mut self, clip: Option<impl Clipboard + 'static>) {
self.widget.set_clipboard(clip);
}
#[inline]
pub fn clipboard(&self) -> Option<&dyn Clipboard> {
self.widget.clipboard()
}
#[inline]
pub fn copy_to_clip(&mut self) -> bool {
self.widget.copy_to_clip()
}
#[inline]
pub fn cut_to_clip(&mut self) -> bool {
self.widget.cut_to_clip()
}
#[inline]
pub fn paste_from_clip(&mut self) -> bool {
self.widget.paste_from_clip()
}
}
impl NumberInputState {
#[inline]
pub fn set_undo_buffer(&mut self, undo: Option<impl UndoBuffer + 'static>) {
self.widget.set_undo_buffer(undo);
}
#[inline]
pub fn undo_buffer(&self) -> Option<&dyn UndoBuffer> {
self.widget.undo_buffer()
}
#[inline]
pub fn undo_buffer_mut(&mut self) -> Option<&mut dyn UndoBuffer> {
self.widget.undo_buffer_mut()
}
#[inline]
pub fn recent_replay_log(&mut self) -> Vec<UndoEntry> {
self.widget.recent_replay_log()
}
#[inline]
pub fn replay_log(&mut self, replay: &[UndoEntry]) {
self.widget.replay_log(replay)
}
#[inline]
pub fn undo(&mut self) -> bool {
self.widget.undo()
}
#[inline]
pub fn redo(&mut self) -> bool {
self.widget.redo()
}
}
impl NumberInputState {
#[inline]
pub fn set_styles(&mut self, styles: Vec<(Range<usize>, usize)>) {
self.widget.set_styles(styles);
}
#[inline]
pub fn add_style(&mut self, range: Range<usize>, style: usize) {
self.widget.add_style(range, style);
}
#[inline]
pub fn add_range_style(
&mut self,
range: Range<upos_type>,
style: usize,
) -> Result<(), TextError> {
self.widget.add_range_style(range, style)
}
#[inline]
pub fn remove_style(&mut self, range: Range<usize>, style: usize) {
self.widget.remove_style(range, style);
}
#[inline]
pub fn remove_range_style(
&mut self,
range: Range<upos_type>,
style: usize,
) -> Result<(), TextError> {
self.widget.remove_range_style(range, style)
}
pub fn styles_in(&self, range: Range<usize>, buf: &mut Vec<(Range<usize>, usize)>) {
self.widget.styles_in(range, buf)
}
#[inline]
pub fn styles_at(&self, byte_pos: usize, buf: &mut Vec<(Range<usize>, usize)>) {
self.widget.styles_at(byte_pos, buf)
}
#[inline]
pub fn style_match(&self, byte_pos: usize, style: usize) -> Option<Range<usize>> {
self.widget.style_match(byte_pos, style)
}
#[inline]
pub fn styles(&self) -> Option<impl Iterator<Item = (Range<usize>, usize)> + '_> {
self.widget.styles()
}
}
impl NumberInputState {
#[inline]
pub fn offset(&self) -> upos_type {
self.widget.offset()
}
#[inline]
pub fn set_offset(&mut self, offset: upos_type) {
self.widget.set_offset(offset)
}
#[inline]
pub fn cursor(&self) -> upos_type {
self.widget.cursor()
}
#[inline]
pub fn set_cursor(&mut self, cursor: upos_type, extend_selection: bool) -> bool {
self.widget.set_cursor(cursor, extend_selection)
}
#[inline]
pub fn set_default_cursor(&mut self) {
self.widget.set_default_cursor()
}
#[inline]
pub fn anchor(&self) -> upos_type {
self.widget.anchor()
}
#[inline]
pub fn has_selection(&self) -> bool {
self.widget.has_selection()
}
#[inline]
pub fn selection(&self) -> Range<upos_type> {
self.widget.selection()
}
#[inline]
pub fn set_selection(&mut self, anchor: upos_type, cursor: upos_type) -> bool {
self.widget.set_selection(anchor, cursor)
}
#[inline]
pub fn select_all(&mut self) {
self.widget.select_all();
}
#[inline]
pub fn selected_text(&self) -> &str {
self.widget.selected_text()
}
}
impl NumberInputState {
#[inline]
pub fn is_empty(&self) -> bool {
self.widget.is_empty()
}
pub fn value<T: FromStr>(&self) -> Result<T, NumberFmtError> {
let s = self.widget.text();
self.format.parse(s)
}
#[inline]
pub fn len(&self) -> upos_type {
self.widget.len()
}
#[inline]
pub fn line_width(&self) -> upos_type {
self.widget.line_width()
}
}
impl NumberInputState {
#[inline]
pub fn clear(&mut self) {
self.widget.clear();
}
pub fn set_value<T: LowerExp + Display + Debug>(
&mut self,
number: T,
) -> Result<(), NumberFmtError> {
let s = self.format.fmt(number)?;
self.widget.set_text(s);
Ok(())
}
#[inline]
pub fn insert_char(&mut self, c: char) -> bool {
self.widget.insert_char(c)
}
#[inline]
pub fn delete_range(&mut self, range: Range<upos_type>) -> bool {
self.widget.delete_range(range)
}
#[inline]
pub fn try_delete_range(&mut self, range: Range<upos_type>) -> Result<bool, TextError> {
self.widget.try_delete_range(range)
}
}
impl NumberInputState {
#[inline]
pub fn delete_next_char(&mut self) -> bool {
self.widget.delete_next_char()
}
#[inline]
pub fn delete_prev_char(&mut self) -> bool {
self.widget.delete_prev_char()
}
#[inline]
pub fn move_right(&mut self, extend_selection: bool) -> bool {
self.widget.move_right(extend_selection)
}
#[inline]
pub fn move_left(&mut self, extend_selection: bool) -> bool {
self.widget.move_left(extend_selection)
}
#[inline]
pub fn move_to_line_start(&mut self, extend_selection: bool) -> bool {
self.widget.move_to_line_start(extend_selection)
}
#[inline]
pub fn move_to_line_end(&mut self, extend_selection: bool) -> bool {
self.widget.move_to_line_end(extend_selection)
}
}
impl HasScreenCursor for NumberInputState {
#[inline]
fn screen_cursor(&self) -> Option<(u16, u16)> {
self.widget.screen_cursor()
}
}
impl RelocatableState for NumberInputState {
fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
self.widget.relocate(shift, clip);
}
}
impl NumberInputState {
#[inline]
pub fn col_to_screen(&self, pos: upos_type) -> Option<u16> {
self.widget.col_to_screen(pos)
}
#[inline]
pub fn screen_to_col(&self, scx: i16) -> upos_type {
self.widget.screen_to_col(scx)
}
#[inline]
pub fn set_screen_cursor(&mut self, cursor: i16, extend_selection: bool) -> bool {
self.widget.set_screen_cursor(cursor, extend_selection)
}
}
impl HandleEvent<crossterm::event::Event, Regular, TextOutcome> for NumberInputState {
fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> TextOutcome {
self.widget.handle(event, Regular)
}
}
impl HandleEvent<crossterm::event::Event, ReadOnly, TextOutcome> for NumberInputState {
fn handle(&mut self, event: &crossterm::event::Event, _keymap: ReadOnly) -> TextOutcome {
self.widget.handle(event, ReadOnly)
}
}
impl HandleEvent<crossterm::event::Event, MouseOnly, TextOutcome> for NumberInputState {
fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> TextOutcome {
self.widget.handle(event, MouseOnly)
}
}
pub fn handle_events(
state: &mut NumberInputState,
focus: bool,
event: &crossterm::event::Event,
) -> TextOutcome {
state.widget.focus.set(focus);
HandleEvent::handle(state, event, Regular)
}
pub fn handle_readonly_events(
state: &mut NumberInputState,
focus: bool,
event: &crossterm::event::Event,
) -> TextOutcome {
state.widget.focus.set(focus);
state.handle(event, ReadOnly)
}
pub fn handle_mouse_events(
state: &mut NumberInputState,
event: &crossterm::event::Event,
) -> TextOutcome {
HandleEvent::handle(state, event, MouseOnly)
}