use crate::Color;
use std::rc::Rc;
use std::sync::Arc;
pub trait VisualTransformation: std::fmt::Debug + Send + Sync + 'static {
fn filter(&self, text: &str) -> TransformedText;
}
pub struct TransformedText {
pub text: String,
pub offset_map: Rc<dyn Fn(usize) -> usize>,
}
impl Clone for TransformedText {
fn clone(&self) -> Self {
Self {
text: self.text.clone(),
offset_map: self.offset_map.clone(),
}
}
}
impl std::fmt::Debug for TransformedText {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TransformedText")
.field("text", &self.text)
.finish()
}
}
#[derive(Clone, Copy, Debug)]
pub struct NoVisualTransformation;
impl VisualTransformation for NoVisualTransformation {
fn filter(&self, text: &str) -> TransformedText {
let len = text.len();
TransformedText {
text: text.to_string(),
offset_map: Rc::new(move |offset| offset.min(len)),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct PasswordVisualTransformation {
pub mask_char: char,
}
impl Default for PasswordVisualTransformation {
fn default() -> Self {
Self { mask_char: '*' }
}
}
impl VisualTransformation for PasswordVisualTransformation {
fn filter(&self, text: &str) -> TransformedText {
let masked: String = text.chars().map(|_| self.mask_char).collect();
let len = text.len();
TransformedText {
text: masked,
offset_map: Rc::new(move |offset| offset.min(len)),
}
}
}
pub fn original_offset_to_display(original: &str, display: &str, original_byte: usize) -> usize {
let char_idx = original[..original_byte.min(original.len())]
.chars()
.count();
display
.char_indices()
.nth(char_idx)
.map(|(i, _)| i)
.unwrap_or(display.len())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Default)]
pub enum KeyboardType {
#[default]
Text,
Ascii,
Number,
Phone,
Email,
Uri,
Decimal,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Default)]
pub enum ImeAction {
#[default]
Unspecified,
None,
Go,
Search,
Send,
Next,
Done,
Previous,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpanStyle {
pub color: Option<Color>,
pub font_size: Option<f32>,
}
impl SpanStyle {
pub const fn default() -> Self {
Self {
color: None,
font_size: None,
}
}
pub fn color(mut self, c: Color) -> Self {
self.color = Some(c);
self
}
pub fn font_size(mut self, px: f32) -> Self {
self.font_size = Some(px);
self
}
}
impl Default for SpanStyle {
fn default() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TextSpan {
pub start: usize,
pub end: usize,
pub style: SpanStyle,
}
#[derive(Debug, Clone)]
pub struct AnnotatedString {
pub text: String,
pub spans: Arc<[TextSpan]>,
}
impl AnnotatedString {
pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
let text = text.into();
Self {
text,
spans: spans.into(),
}
}
pub fn as_str(&self) -> &str {
&self.text
}
}
impl From<String> for AnnotatedString {
fn from(text: String) -> Self {
Self {
text,
spans: Arc::from([]),
}
}
}
impl From<&str> for AnnotatedString {
fn from(text: &str) -> Self {
Self {
text: text.to_string(),
spans: Arc::from([]),
}
}
}
#[derive(Default)]
pub struct AnnotatedStringBuilder {
text: String,
spans: Vec<TextSpan>,
}
impl AnnotatedStringBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, text: &str) -> &mut Self {
self.text.push_str(text);
self
}
pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
let start = self.text.len();
self.text.push_str(text);
let end = self.text.len();
if start < end {
self.spans.push(TextSpan { start, end, style });
}
self
}
pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
self.push_with_style(text, SpanStyle::default().color(color))
}
pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
if start < end && end <= self.text.len() {
self.spans.push(TextSpan { start, end, style });
}
self
}
pub fn build(&mut self) -> AnnotatedString {
let text = std::mem::take(&mut self.text);
self.spans.sort_by_key(|s| s.start);
let mut merged: Vec<TextSpan> = Vec::new();
for span in std::mem::take(&mut self.spans) {
if let Some(last) = merged.last_mut()
&& last.end == span.start && last.style == span.style {
last.end = span.end;
continue;
}
merged.push(span);
}
AnnotatedString {
text,
spans: merged.into(),
}
}
}
pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
let mut builder = AnnotatedStringBuilder::new();
b(&mut builder);
builder.build()
}