713 lines
26 KiB
Rust
713 lines
26 KiB
Rust
// examples/validation_3.rs
|
|
//! Comprehensive Display Mask Features Demo
|
|
//!
|
|
//! This example showcases the full power of the display mask system (Feature 3)
|
|
//! demonstrating visual formatting that keeps business logic clean.
|
|
//!
|
|
//! Key Features Demonstrated:
|
|
//! - Dynamic vs Template display modes
|
|
//! - Custom patterns for different data types
|
|
//! - Custom input characters and separators
|
|
//! - Custom placeholder characters
|
|
//! - Real-time visual formatting with clean raw data
|
|
//! - Cursor movement through formatted displays
|
|
//! - 🔥 CRITICAL: Perfect mask/character-limit coordination to prevent invisible character bugs
|
|
//!
|
|
//! ⚠️ IMPORTANT BUG PREVENTION:
|
|
//! This example demonstrates the CORRECT way to configure masks with character limits.
|
|
//! Each mask's input position count EXACTLY matches its character limit to prevent
|
|
//! the critical bug where users can type more characters than they can see.
|
|
//!
|
|
//! Run with: cargo run --example validation_3 --features "gui,validation"
|
|
|
|
// REQUIRE validation and gui features for mask functionality
|
|
#[cfg(not(all(feature = "validation", feature = "gui")))]
|
|
compile_error!(
|
|
"This example requires the 'validation' and 'gui' features. \
|
|
Run with: cargo run --example validation_3 --features \"gui,validation\""
|
|
);
|
|
|
|
use std::io;
|
|
use crossterm::{
|
|
event::{
|
|
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers,
|
|
},
|
|
execute,
|
|
terminal::{
|
|
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
|
},
|
|
};
|
|
use ratatui::{
|
|
backend::{Backend, CrosstermBackend},
|
|
layout::{Constraint, Direction, Layout, Rect},
|
|
style::{Color, Style},
|
|
text::{Line, Span},
|
|
widgets::{Block, Borders, Paragraph, Wrap},
|
|
Frame, Terminal,
|
|
};
|
|
|
|
use canvas::{
|
|
canvas::{
|
|
gui::render_canvas_default,
|
|
modes::AppMode,
|
|
},
|
|
DataProvider, FormEditor,
|
|
ValidationConfig, ValidationConfigBuilder, DisplayMask,
|
|
validation::mask::MaskDisplayMode,
|
|
};
|
|
|
|
// Enhanced FormEditor wrapper for mask demonstration
|
|
struct MaskDemoFormEditor<D: DataProvider> {
|
|
editor: FormEditor<D>,
|
|
debug_message: String,
|
|
command_buffer: String,
|
|
validation_enabled: bool,
|
|
show_raw_data: bool,
|
|
}
|
|
|
|
impl<D: DataProvider> MaskDemoFormEditor<D> {
|
|
fn new(data_provider: D) -> Self {
|
|
let mut editor = FormEditor::new(data_provider);
|
|
editor.set_validation_enabled(true);
|
|
|
|
Self {
|
|
editor,
|
|
debug_message: "🎭 Display Mask Demo - Visual formatting with clean business logic!".to_string(),
|
|
command_buffer: String::new(),
|
|
validation_enabled: true,
|
|
show_raw_data: false,
|
|
}
|
|
}
|
|
|
|
// === COMMAND BUFFER HANDLING ===
|
|
fn clear_command_buffer(&mut self) { self.command_buffer.clear(); }
|
|
fn add_to_command_buffer(&mut self, ch: char) { self.command_buffer.push(ch); }
|
|
fn get_command_buffer(&self) -> &str { &self.command_buffer }
|
|
fn has_pending_command(&self) -> bool { !self.command_buffer.is_empty() }
|
|
|
|
// === MASK CONTROL ===
|
|
fn toggle_validation(&mut self) {
|
|
self.validation_enabled = !self.validation_enabled;
|
|
self.editor.set_validation_enabled(self.validation_enabled);
|
|
if self.validation_enabled {
|
|
self.debug_message = "✅ Display Masks ENABLED - See visual formatting in action!".to_string();
|
|
} else {
|
|
self.debug_message = "❌ Display Masks DISABLED - Raw text only".to_string();
|
|
}
|
|
}
|
|
|
|
fn toggle_raw_data_view(&mut self) {
|
|
self.show_raw_data = !self.show_raw_data;
|
|
if self.show_raw_data {
|
|
self.debug_message = "👁️ Showing RAW business data (what's actually stored)".to_string();
|
|
} else {
|
|
self.debug_message = "🎭 Showing FORMATTED display (what users see)".to_string();
|
|
}
|
|
}
|
|
|
|
fn get_current_field_info(&self) -> (String, String, String) {
|
|
let field_index = self.editor.current_field();
|
|
let raw_data = self.editor.current_text();
|
|
let display_data = if self.validation_enabled {
|
|
self.editor.current_display_text()
|
|
} else {
|
|
raw_data.to_string()
|
|
};
|
|
|
|
let mask_info = if let Some(config) = self.editor.validation_state().get_field_config(field_index) {
|
|
if let Some(mask) = &config.display_mask {
|
|
format!("Pattern: '{}', Mode: {:?}",
|
|
mask.pattern(),
|
|
mask.display_mode())
|
|
} else {
|
|
"No mask configured".to_string()
|
|
}
|
|
} else {
|
|
"No validation config".to_string()
|
|
};
|
|
|
|
(raw_data.to_string(), display_data, mask_info)
|
|
}
|
|
|
|
// === ENHANCED MOVEMENT WITH MASK AWARENESS ===
|
|
fn move_left(&mut self) {
|
|
self.editor.move_left();
|
|
self.update_cursor_info();
|
|
}
|
|
|
|
fn move_right(&mut self) {
|
|
self.editor.move_right();
|
|
self.update_cursor_info();
|
|
}
|
|
|
|
fn move_up(&mut self) {
|
|
match self.editor.move_up() {
|
|
Ok(()) => { self.update_field_info(); }
|
|
Err(e) => { self.debug_message = format!("🚫 Field switch blocked: {}", e); }
|
|
}
|
|
}
|
|
|
|
fn move_down(&mut self) {
|
|
match self.editor.move_down() {
|
|
Ok(()) => { self.update_field_info(); }
|
|
Err(e) => { self.debug_message = format!("🚫 Field switch blocked: {}", e); }
|
|
}
|
|
}
|
|
|
|
fn move_line_start(&mut self) {
|
|
self.editor.move_line_start();
|
|
self.update_cursor_info();
|
|
}
|
|
|
|
fn move_line_end(&mut self) {
|
|
self.editor.move_line_end();
|
|
self.update_cursor_info();
|
|
}
|
|
|
|
fn update_cursor_info(&mut self) {
|
|
if self.validation_enabled {
|
|
let raw_pos = self.editor.cursor_position();
|
|
let display_pos = self.editor.display_cursor_position();
|
|
if raw_pos != display_pos {
|
|
self.debug_message = format!("📍 Cursor: Raw pos {} → Display pos {} (mask active)", raw_pos, display_pos);
|
|
} else {
|
|
self.debug_message = format!("📍 Cursor at position {} (no mask offset)", raw_pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn update_field_info(&mut self) {
|
|
let field_name = self.editor.data_provider().field_name(self.editor.current_field());
|
|
self.debug_message = format!("📝 Switched to: {}", field_name);
|
|
}
|
|
|
|
// === MODE TRANSITIONS ===
|
|
fn enter_edit_mode(&mut self) {
|
|
self.editor.enter_edit_mode();
|
|
self.debug_message = "✏️ INSERT MODE - Type to see mask formatting in real-time".to_string();
|
|
}
|
|
|
|
fn enter_append_mode(&mut self) {
|
|
self.editor.enter_append_mode();
|
|
self.debug_message = "✏️ INSERT (append) - Mask formatting active".to_string();
|
|
}
|
|
|
|
fn exit_edit_mode(&mut self) {
|
|
self.editor.exit_edit_mode();
|
|
self.debug_message = "🔒 NORMAL MODE - Press 'r' to see raw data, 'm' for mask info".to_string();
|
|
}
|
|
|
|
fn insert_char(&mut self, ch: char) -> anyhow::Result<()> {
|
|
let result = self.editor.insert_char(ch);
|
|
if result.is_ok() {
|
|
let (raw, display, _) = self.get_current_field_info();
|
|
if raw != display {
|
|
self.debug_message = format!("✏️ Added '{}': Raw='{}' Display='{}'", ch, raw, display);
|
|
} else {
|
|
self.debug_message = format!("✏️ Added '{}': '{}'", ch, raw);
|
|
}
|
|
}
|
|
Ok(result?)
|
|
}
|
|
|
|
// === DELETE OPERATIONS ===
|
|
fn delete_backward(&mut self) -> anyhow::Result<()> {
|
|
let result = self.editor.delete_backward();
|
|
if result.is_ok() {
|
|
self.debug_message = "⌫ Character deleted".to_string();
|
|
self.update_cursor_info();
|
|
}
|
|
Ok(result?)
|
|
}
|
|
|
|
fn delete_forward(&mut self) -> anyhow::Result<()> {
|
|
let result = self.editor.delete_forward();
|
|
if result.is_ok() {
|
|
self.debug_message = "⌦ Character deleted".to_string();
|
|
self.update_cursor_info();
|
|
}
|
|
Ok(result?)
|
|
}
|
|
|
|
// === DELEGATE TO ORIGINAL EDITOR ===
|
|
fn current_field(&self) -> usize { self.editor.current_field() }
|
|
fn cursor_position(&self) -> usize { self.editor.cursor_position() }
|
|
fn mode(&self) -> AppMode { self.editor.mode() }
|
|
fn current_text(&self) -> &str { self.editor.current_text() }
|
|
fn data_provider(&self) -> &D { self.editor.data_provider() }
|
|
fn ui_state(&self) -> &canvas::EditorState { self.editor.ui_state() }
|
|
fn set_mode(&mut self, mode: AppMode) { self.editor.set_mode(mode); }
|
|
|
|
fn next_field(&mut self) {
|
|
match self.editor.next_field() {
|
|
Ok(()) => { self.update_field_info(); }
|
|
Err(e) => { self.debug_message = format!("🚫 Cannot move to next field: {}", e); }
|
|
}
|
|
}
|
|
|
|
fn prev_field(&mut self) {
|
|
match self.editor.prev_field() {
|
|
Ok(()) => { self.update_field_info(); }
|
|
Err(e) => { self.debug_message = format!("🚫 Cannot move to previous field: {}", e); }
|
|
}
|
|
}
|
|
|
|
// === STATUS AND DEBUG ===
|
|
fn set_debug_message(&mut self, msg: String) { self.debug_message = msg; }
|
|
fn debug_message(&self) -> &str { &self.debug_message }
|
|
|
|
fn show_mask_details(&mut self) {
|
|
let (raw, display, mask_info) = self.get_current_field_info();
|
|
self.debug_message = format!("🔍 Field {}: {} | Raw: '{}' Display: '{}'",
|
|
self.current_field() + 1, mask_info, raw, display);
|
|
}
|
|
|
|
fn get_mask_status(&self) -> String {
|
|
if !self.validation_enabled {
|
|
return "❌ DISABLED".to_string();
|
|
}
|
|
|
|
let field_count = self.editor.data_provider().field_count();
|
|
let mut mask_count = 0;
|
|
for i in 0..field_count {
|
|
if let Some(config) = self.editor.validation_state().get_field_config(i) {
|
|
if config.display_mask.is_some() {
|
|
mask_count += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
format!("🎭 {} MASKS", mask_count)
|
|
}
|
|
}
|
|
|
|
// Demo data with comprehensive mask examples
|
|
struct MaskDemoData {
|
|
fields: Vec<(String, String)>,
|
|
}
|
|
|
|
impl MaskDemoData {
|
|
fn new() -> Self {
|
|
Self {
|
|
fields: vec![
|
|
("📞 Phone (Dynamic)".to_string(), "".to_string()),
|
|
("📞 Phone (Template)".to_string(), "".to_string()),
|
|
("📅 Date US (MM/DD/YYYY)".to_string(), "".to_string()),
|
|
("📅 Date EU (DD.MM.YYYY)".to_string(), "".to_string()),
|
|
("📅 Date ISO (YYYY-MM-DD)".to_string(), "".to_string()),
|
|
("🏛️ SSN (XXX-XX-XXXX)".to_string(), "".to_string()),
|
|
("💳 Credit Card".to_string(), "".to_string()),
|
|
("🏢 Employee ID (EMP-####)".to_string(), "".to_string()),
|
|
("📦 Product Code (ABC###XYZ)".to_string(), "".to_string()),
|
|
("🌈 Custom Separators".to_string(), "".to_string()),
|
|
("⭐ Custom Placeholders".to_string(), "".to_string()),
|
|
("🎯 Mixed Input Chars".to_string(), "".to_string()),
|
|
],
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DataProvider for MaskDemoData {
|
|
fn field_count(&self) -> usize { self.fields.len() }
|
|
fn field_name(&self, index: usize) -> &str { &self.fields[index].0 }
|
|
fn field_value(&self, index: usize) -> &str { &self.fields[index].1 }
|
|
fn set_field_value(&mut self, index: usize, value: String) { self.fields[index].1 = value; }
|
|
|
|
fn validation_config(&self, field_index: usize) -> Option<ValidationConfig> {
|
|
match field_index {
|
|
0 => {
|
|
// 📞 Phone (Dynamic) - FIXED: Perfect mask/limit coordination
|
|
let phone_mask = DisplayMask::new("(###) ###-####", '#');
|
|
Some(ValidationConfigBuilder::new()
|
|
.with_display_mask(phone_mask)
|
|
.with_max_length(10) // ✅ CRITICAL: Exactly matches 10 input positions
|
|
.build())
|
|
}
|
|
1 => {
|
|
// 📞 Phone (Template) - FIXED: Perfect mask/limit coordination
|
|
let phone_template = DisplayMask::new("(###) ###-####", '#')
|
|
.with_template('_');
|
|
Some(ValidationConfigBuilder::new()
|
|
.with_display_mask(phone_template)
|
|
.with_max_length(10) // ✅ CRITICAL: Exactly matches 10 input positions
|
|
.build())
|
|
}
|
|
2 => {
|
|
// 📅 Date US (MM/DD/YYYY) - American date format
|
|
let us_date = DisplayMask::new("##/##/####", '#');
|
|
Some(ValidationConfig::with_mask(us_date))
|
|
}
|
|
3 => {
|
|
// 📅 Date EU (DD.MM.YYYY) - European date format with dots
|
|
let eu_date = DisplayMask::new("##.##.####", '#')
|
|
.with_template('•');
|
|
Some(ValidationConfig::with_mask(eu_date))
|
|
}
|
|
4 => {
|
|
// 📅 Date ISO (YYYY-MM-DD) - ISO date format
|
|
let iso_date = DisplayMask::new("####-##-##", '#')
|
|
.with_template('-');
|
|
Some(ValidationConfig::with_mask(iso_date))
|
|
}
|
|
5 => {
|
|
// 🏛️ SSN using custom input character 'X' - FIXED: Perfect coordination
|
|
let ssn_mask = DisplayMask::new("XXX-XX-XXXX", 'X');
|
|
Some(ValidationConfigBuilder::new()
|
|
.with_display_mask(ssn_mask)
|
|
.with_max_length(9) // ✅ CRITICAL: Exactly matches 9 input positions
|
|
.build())
|
|
}
|
|
6 => {
|
|
// 💳 Credit Card (16 digits with spaces) - FIXED: Perfect coordination
|
|
let cc_mask = DisplayMask::new("#### #### #### ####", '#')
|
|
.with_template('•');
|
|
Some(ValidationConfigBuilder::new()
|
|
.with_display_mask(cc_mask)
|
|
.with_max_length(16) // ✅ CRITICAL: Exactly matches 16 input positions
|
|
.build())
|
|
}
|
|
7 => {
|
|
// 🏢 Employee ID with business prefix
|
|
let emp_id = DisplayMask::new("EMP-####", '#');
|
|
Some(ValidationConfig::with_mask(emp_id))
|
|
}
|
|
8 => {
|
|
// 📦 Product Code with mixed letters and numbers
|
|
let product_code = DisplayMask::new("ABC###XYZ", '#');
|
|
Some(ValidationConfig::with_mask(product_code))
|
|
}
|
|
9 => {
|
|
// 🌈 Custom Separators - Using | and ~ as separators
|
|
let custom_sep = DisplayMask::new("##|##~####", '#')
|
|
.with_template('?');
|
|
Some(ValidationConfig::with_mask(custom_sep))
|
|
}
|
|
10 => {
|
|
// ⭐ Custom Placeholders - Using different placeholder characters
|
|
let custom_placeholder = DisplayMask::new("##-##-##", '#')
|
|
.with_template('★');
|
|
Some(ValidationConfig::with_mask(custom_placeholder))
|
|
}
|
|
11 => {
|
|
// 🎯 Mixed Input Characters - Using 'N' for numbers
|
|
let mixed_input = DisplayMask::new("ID:NNN-NNN", 'N');
|
|
Some(ValidationConfig::with_mask(mixed_input))
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enhanced key handling with mask-specific commands
|
|
fn handle_key_press(
|
|
key: KeyCode,
|
|
modifiers: KeyModifiers,
|
|
editor: &mut MaskDemoFormEditor<MaskDemoData>,
|
|
) -> anyhow::Result<bool> {
|
|
let mode = editor.mode();
|
|
|
|
// Quit handling
|
|
if (key == KeyCode::Char('q') && modifiers.contains(KeyModifiers::CONTROL))
|
|
|| (key == KeyCode::Char('c') && modifiers.contains(KeyModifiers::CONTROL))
|
|
|| key == KeyCode::F(10)
|
|
{
|
|
return Ok(false);
|
|
}
|
|
|
|
match (mode, key, modifiers) {
|
|
// === MODE TRANSITIONS ===
|
|
(AppMode::ReadOnly, KeyCode::Char('i'), _) => {
|
|
editor.enter_edit_mode();
|
|
editor.clear_command_buffer();
|
|
}
|
|
(AppMode::ReadOnly, KeyCode::Char('a'), _) => {
|
|
editor.enter_append_mode();
|
|
editor.clear_command_buffer();
|
|
}
|
|
(AppMode::ReadOnly, KeyCode::Char('A'), _) => {
|
|
editor.move_line_end();
|
|
editor.enter_edit_mode();
|
|
editor.clear_command_buffer();
|
|
}
|
|
|
|
// Escape: Exit edit mode
|
|
(_, KeyCode::Esc, _) => {
|
|
if mode == AppMode::Edit {
|
|
editor.exit_edit_mode();
|
|
} else {
|
|
editor.clear_command_buffer();
|
|
}
|
|
}
|
|
|
|
// === MASK SPECIFIC COMMANDS ===
|
|
(AppMode::ReadOnly, KeyCode::Char('m'), _) => {
|
|
editor.show_mask_details();
|
|
editor.clear_command_buffer();
|
|
}
|
|
(AppMode::ReadOnly, KeyCode::Char('r'), _) => {
|
|
editor.toggle_raw_data_view();
|
|
editor.clear_command_buffer();
|
|
}
|
|
(AppMode::ReadOnly, KeyCode::F(1), _) => {
|
|
editor.toggle_validation();
|
|
}
|
|
|
|
// === MOVEMENT ===
|
|
(AppMode::ReadOnly, KeyCode::Char('h'), _) | (AppMode::ReadOnly, KeyCode::Left, _) => {
|
|
editor.move_left();
|
|
editor.clear_command_buffer();
|
|
}
|
|
(AppMode::ReadOnly, KeyCode::Char('l'), _) | (AppMode::ReadOnly, KeyCode::Right, _) => {
|
|
editor.move_right();
|
|
editor.clear_command_buffer();
|
|
}
|
|
(AppMode::ReadOnly, KeyCode::Char('j'), _) | (AppMode::ReadOnly, KeyCode::Down, _) => {
|
|
editor.move_down();
|
|
editor.clear_command_buffer();
|
|
}
|
|
(AppMode::ReadOnly, KeyCode::Char('k'), _) | (AppMode::ReadOnly, KeyCode::Up, _) => {
|
|
editor.move_up();
|
|
editor.clear_command_buffer();
|
|
}
|
|
|
|
// Line movement
|
|
(AppMode::ReadOnly, KeyCode::Char('0'), _) => {
|
|
editor.move_line_start();
|
|
editor.clear_command_buffer();
|
|
}
|
|
(AppMode::ReadOnly, KeyCode::Char('$'), _) => {
|
|
editor.move_line_end();
|
|
editor.clear_command_buffer();
|
|
}
|
|
|
|
// === EDIT MODE MOVEMENT ===
|
|
(AppMode::Edit, KeyCode::Left, _) => { editor.move_left(); }
|
|
(AppMode::Edit, KeyCode::Right, _) => { editor.move_right(); }
|
|
(AppMode::Edit, KeyCode::Up, _) => { editor.move_up(); }
|
|
(AppMode::Edit, KeyCode::Down, _) => { editor.move_down(); }
|
|
|
|
// === DELETE OPERATIONS ===
|
|
(AppMode::Edit, KeyCode::Backspace, _) => { editor.delete_backward()?; }
|
|
(AppMode::Edit, KeyCode::Delete, _) => { editor.delete_forward()?; }
|
|
|
|
// === TAB NAVIGATION ===
|
|
(_, KeyCode::Tab, _) => { editor.next_field(); }
|
|
(_, KeyCode::BackTab, _) => { editor.prev_field(); }
|
|
|
|
// === CHARACTER INPUT ===
|
|
(AppMode::Edit, KeyCode::Char(c), m) if !m.contains(KeyModifiers::CONTROL) => {
|
|
editor.insert_char(c)?;
|
|
}
|
|
|
|
// === DEBUG/INFO COMMANDS ===
|
|
(AppMode::ReadOnly, KeyCode::Char('?'), _) => {
|
|
let (raw, display, mask_info) = editor.get_current_field_info();
|
|
editor.set_debug_message(format!(
|
|
"Field {}/{}, Cursor {}, {}, Raw: '{}', Display: '{}'",
|
|
editor.current_field() + 1,
|
|
editor.data_provider().field_count(),
|
|
editor.cursor_position(),
|
|
mask_info,
|
|
raw,
|
|
display
|
|
));
|
|
}
|
|
|
|
_ => {
|
|
if editor.has_pending_command() {
|
|
editor.clear_command_buffer();
|
|
editor.set_debug_message("Invalid command sequence".to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
fn run_app<B: Backend>(
|
|
terminal: &mut Terminal<B>,
|
|
mut editor: MaskDemoFormEditor<MaskDemoData>,
|
|
) -> io::Result<()> {
|
|
loop {
|
|
terminal.draw(|f| ui(f, &editor))?;
|
|
|
|
if let Event::Key(key) = event::read()? {
|
|
match handle_key_press(key.code, key.modifiers, &mut editor) {
|
|
Ok(should_continue) => {
|
|
if !should_continue {
|
|
break;
|
|
}
|
|
}
|
|
Err(e) => {
|
|
editor.set_debug_message(format!("Error: {}", e));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn ui(f: &mut Frame, editor: &MaskDemoFormEditor<MaskDemoData>) {
|
|
let chunks = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints([Constraint::Min(8), Constraint::Length(16)])
|
|
.split(f.area());
|
|
|
|
render_enhanced_canvas(f, chunks[0], editor);
|
|
render_mask_status(f, chunks[1], editor);
|
|
}
|
|
|
|
fn render_enhanced_canvas(
|
|
f: &mut Frame,
|
|
area: Rect,
|
|
editor: &MaskDemoFormEditor<MaskDemoData>,
|
|
) {
|
|
render_canvas_default(f, area, &editor.editor);
|
|
}
|
|
|
|
fn render_mask_status(
|
|
f: &mut Frame,
|
|
area: Rect,
|
|
editor: &MaskDemoFormEditor<MaskDemoData>,
|
|
) {
|
|
let chunks = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints([
|
|
Constraint::Length(3), // Status bar
|
|
Constraint::Length(6), // Data comparison
|
|
Constraint::Length(7), // Help
|
|
])
|
|
.split(area);
|
|
|
|
// Status bar with mask information
|
|
let mode_text = match editor.mode() {
|
|
AppMode::Edit => "INSERT",
|
|
AppMode::ReadOnly => "NORMAL",
|
|
_ => "OTHER",
|
|
};
|
|
|
|
let mask_status = editor.get_mask_status();
|
|
let status_text = format!("-- {} -- {} | Masks: {} | View: {}",
|
|
mode_text,
|
|
editor.debug_message(),
|
|
mask_status,
|
|
if editor.show_raw_data { "RAW" } else { "FORMATTED" });
|
|
|
|
let status = Paragraph::new(Line::from(Span::raw(status_text)))
|
|
.block(Block::default().borders(Borders::ALL).title("🎭 Display Mask Demo"));
|
|
|
|
f.render_widget(status, chunks[0]);
|
|
|
|
// Data comparison showing raw vs display
|
|
let (raw_data, display_data, mask_info) = editor.get_current_field_info();
|
|
let field_name = editor.data_provider().field_name(editor.current_field());
|
|
|
|
let comparison_text = format!(
|
|
"📝 Current Field: {}\n\
|
|
🔧 Mask Config: {}\n\
|
|
\n\
|
|
💾 Raw Business Data: '{}' ← What's actually stored in your database\n\
|
|
🎭 Formatted Display: '{}' ← What users see in the interface\n\
|
|
📍 Cursor: Raw pos {} → Display pos {}",
|
|
field_name,
|
|
mask_info,
|
|
raw_data,
|
|
display_data,
|
|
editor.cursor_position(),
|
|
editor.editor.display_cursor_position()
|
|
);
|
|
|
|
let comparison_style = if raw_data != display_data {
|
|
Style::default().fg(Color::Green) // Green when mask is active
|
|
} else {
|
|
Style::default().fg(Color::Gray) // Gray when no formatting
|
|
};
|
|
|
|
let data_comparison = Paragraph::new(comparison_text)
|
|
.block(Block::default().borders(Borders::ALL).title("📊 Raw Data vs Display Formatting"))
|
|
.style(comparison_style)
|
|
.wrap(Wrap { trim: true });
|
|
|
|
f.render_widget(data_comparison, chunks[1]);
|
|
|
|
// Enhanced help text
|
|
let help_text = match editor.mode() {
|
|
AppMode::ReadOnly => {
|
|
"🎭 MASK DEMO: See how visual formatting keeps business logic clean!\n\
|
|
\n\
|
|
📱 Try different fields to see various mask patterns:\n\
|
|
• Dynamic vs Template modes • Custom separators • Different input chars\n\
|
|
\n\
|
|
Commands: i/a=insert, m=mask details, r=toggle raw/display view\n\
|
|
Movement: hjkl/arrows=move, 0=$=line start/end, Tab=next field, F1=toggle masks\n\
|
|
?=detailed info, Ctrl+C=quit"
|
|
}
|
|
AppMode::Edit => {
|
|
"✏️ INSERT MODE - Type to see real-time mask formatting!\n\
|
|
\n\
|
|
🔥 Key Features in Action:\n\
|
|
• Separators auto-appear as you type • Cursor skips over separators\n\
|
|
• Template fields show placeholders • Raw data stays clean for business logic\n\
|
|
\n\
|
|
arrows=move through mask, Backspace/Del=delete, Esc=normal, Tab=next field\n\
|
|
Notice how cursor position maps between raw data and display!"
|
|
}
|
|
_ => "🎭 Display Mask Demo Active!"
|
|
};
|
|
|
|
let help = Paragraph::new(help_text)
|
|
.block(Block::default().borders(Borders::ALL).title("🚀 Mask Features & Commands"))
|
|
.style(Style::default().fg(Color::Gray))
|
|
.wrap(Wrap { trim: true });
|
|
|
|
f.render_widget(help, chunks[2]);
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Print feature status
|
|
println!("🎭 Canvas Display Mask Demo (Feature 3)");
|
|
println!("✅ validation feature: ENABLED");
|
|
println!("✅ gui feature: ENABLED");
|
|
println!("🎭 Display masks: ACTIVE");
|
|
println!("🔥 Key Benefits Demonstrated:");
|
|
println!(" • Clean separation: Visual formatting ≠ Business logic");
|
|
println!(" • User-friendly: Pretty displays with automatic cursor handling");
|
|
println!(" • Flexible: Custom patterns, separators, and placeholders");
|
|
println!(" • Transparent: Library handles all complexity, API stays simple");
|
|
println!();
|
|
println!("💡 Try typing in different fields to see mask magic!");
|
|
println!(" 📞 Phone fields show dynamic vs template modes");
|
|
println!(" 📅 Date fields show different regional formats");
|
|
println!(" 💳 Credit card shows spaced formatting");
|
|
println!(" ⭐ Custom fields show advanced separator/placeholder options");
|
|
println!();
|
|
|
|
enable_raw_mode()?;
|
|
let mut stdout = io::stdout();
|
|
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
|
let backend = CrosstermBackend::new(stdout);
|
|
let mut terminal = Terminal::new(backend)?;
|
|
|
|
let data = MaskDemoData::new();
|
|
let editor = MaskDemoFormEditor::new(data);
|
|
|
|
let res = run_app(&mut terminal, editor);
|
|
|
|
disable_raw_mode()?;
|
|
execute!(
|
|
terminal.backend_mut(),
|
|
LeaveAlternateScreen,
|
|
DisableMouseCapture
|
|
)?;
|
|
terminal.show_cursor()?;
|
|
|
|
if let Err(err) = res {
|
|
println!("{:?}", err);
|
|
}
|
|
|
|
println!("🎭 Display mask demo completed!");
|
|
println!("🏆 You've seen how masks provide beautiful UX while keeping business logic clean!");
|
|
Ok(())
|
|
}
|