use crate::event::Modifiers;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldRef {
pub name: String,
pub index: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Keystroke {
pub change: String,
pub value: String,
pub selection_start: i32,
pub selection_end: i32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KeystrokeOutcome {
Accept(Keystroke),
Reject,
}
#[derive(Debug, Clone, Default)]
pub struct FieldWrites {
writes: Vec<(u32, String)>,
depth: u32,
max_depth: u32,
}
impl FieldWrites {
#[must_use]
pub fn with_max_depth(max_depth: u32) -> FieldWrites {
FieldWrites {
writes: Vec::new(),
depth: 0,
max_depth,
}
}
pub fn set(&mut self, field: u32, value: impl Into<String>) {
if self.depth <= self.max_depth {
self.writes.push((field, value.into()));
}
}
pub fn writes(&self) -> impl Iterator<Item = (u32, &str)> {
self.writes.iter().map(|(f, v)| (*f, v.as_str()))
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.writes.is_empty()
}
#[must_use]
pub fn depth(&self) -> u32 {
self.depth
}
#[must_use]
pub fn can_recurse(&self) -> bool {
self.depth < self.max_depth
}
pub fn enter(&mut self) -> bool {
if !self.can_recurse() {
return false;
}
self.depth += 1;
true
}
pub fn leave(&mut self) {
self.depth = self.depth.saturating_sub(1);
}
}
pub trait Cascade {
fn keystroke(&mut self, _field: &FieldRef, change: Keystroke) -> KeystrokeOutcome {
KeystrokeOutcome::Accept(change)
}
fn keystroke_commit(&mut self, _field: &FieldRef, _value: &str) -> bool {
true
}
fn validate(&mut self, _field: &FieldRef, _value: &str) -> bool {
true
}
fn calculate(&mut self, _writes: &mut FieldWrites, _trigger: &FieldRef) {}
fn format(&mut self, _field: &FieldRef, _value: &str) -> Option<String> {
None
}
fn pointer(&mut self, _field: &FieldRef, _trigger: PointerTrigger, _held: Modifiers) {}
fn take_focus_request(&mut self) -> Option<u32> {
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PointerTrigger {
Enter,
Exit,
Down,
Up,
Focus,
Blur,
}
impl Keystroke {
#[must_use]
pub fn of(edit: &crate::edit::TextEdit, change: impl Into<String>) -> Keystroke {
let (start, end) = edit.selection_indices();
Keystroke {
change: change.into(),
value: edit.text.clone(),
selection_start: i32::try_from(start).unwrap_or(i32::MAX),
selection_end: i32::try_from(end).unwrap_or(i32::MAX),
}
}
#[must_use]
pub fn applied(&self) -> String {
let chars: Vec<char> = self.value.chars().collect();
let len = chars.len();
let prefix: String = usize::try_from(self.selection_start)
.ok()
.filter(|start| *start <= len)
.and_then(|start| chars.get(..start))
.unwrap_or_default()
.iter()
.collect();
let suffix: String = usize::try_from(self.selection_end)
.ok()
.filter(|end| *end < len)
.and_then(|end| chars.get(end..))
.unwrap_or_default()
.iter()
.collect();
let mut out = prefix;
out.push_str(&self.change);
out.push_str(&suffix);
out
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct NoScripts;
impl Cascade for NoScripts {}
#[cfg(test)]
mod tests {
use super::*;
fn field() -> FieldRef {
FieldRef {
name: "Text Box".to_string(),
index: Some(0),
}
}
fn keystroke(change: &str) -> Keystroke {
Keystroke {
change: change.to_string(),
value: String::new(),
selection_start: 0,
selection_end: 0,
}
}
#[test]
fn the_script_free_cascade_accepts_everything_unchanged() {
let mut cascade = NoScripts;
assert_eq!(
cascade.keystroke(&field(), keystroke("A")),
KeystrokeOutcome::Accept(keystroke("A"))
);
assert!(cascade.keystroke_commit(&field(), "anything"));
assert!(cascade.validate(&field(), "anything"));
}
#[test]
fn the_script_free_cascade_writes_no_other_field() {
let mut cascade = NoScripts;
let mut writes = FieldWrites::with_max_depth(4);
cascade.calculate(&mut writes, &field());
assert!(writes.is_empty());
}
#[test]
fn the_script_free_cascade_formats_nothing() {
let mut cascade = NoScripts;
assert_eq!(cascade.format(&field(), "1234"), None);
}
#[test]
fn writes_are_recorded_in_order() {
let mut writes = FieldWrites::with_max_depth(4);
writes.set(2, "two");
writes.set(1, "one");
let seen: Vec<_> = writes.writes().collect();
assert_eq!(seen, vec![(2, "two"), (1, "one")]);
}
#[test]
fn nesting_stops_at_the_budget() {
let mut writes = FieldWrites::with_max_depth(2);
assert!(writes.enter());
assert!(writes.enter());
assert!(!writes.enter(), "budget should be exhausted");
assert_eq!(writes.depth(), 2);
writes.leave();
assert!(writes.can_recurse());
}
#[test]
fn the_seam_is_object_safe() {
let mut owned = NoScripts;
let cascade: &mut dyn Cascade = &mut owned;
assert!(cascade.validate(&field(), "x"));
}
}