use std::any::Any;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::clipboard::ImageContent;
use crate::core::event::MouseEvent;
static NEXT_SENTINEL_ID: AtomicU64 = AtomicU64::new(1);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SentinelId(u64);
impl SentinelId {
pub const UNKNOWN: Self = Self(0);
pub(crate) fn next() -> Self {
Self(NEXT_SENTINEL_ID.fetch_add(1, Ordering::Relaxed))
}
}
pub const SENTINEL_BASE: char = '\u{F000}';
#[derive(Clone)]
pub struct TextAreaSentinel {
pub label: std::sync::Arc<str>,
pub style: crate::style::Style,
pub focus_style: Option<crate::style::Style>,
pub hover_style: Option<crate::style::Style>,
payload: Option<Arc<dyn Any + Send + Sync>>,
id: Option<SentinelId>,
}
impl PartialEq for TextAreaSentinel {
fn eq(&self, other: &Self) -> bool {
self.label == other.label
&& self.style == other.style
&& self.focus_style == other.focus_style
&& self.hover_style == other.hover_style
&& self.id == other.id
}
}
impl Eq for TextAreaSentinel {}
impl Hash for TextAreaSentinel {
fn hash<H: Hasher>(&self, state: &mut H) {
self.label.hash(state);
self.style.hash(state);
self.focus_style.hash(state);
self.hover_style.hash(state);
self.id.hash(state);
}
}
impl fmt::Debug for TextAreaSentinel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TextAreaSentinel")
.field("label", &self.label)
.field("style", &self.style)
.field("focus_style", &self.focus_style)
.field("hover_style", &self.hover_style)
.field("id", &self.id)
.field(
"payload_type_id",
&self.payload.as_ref().map(|p| Any::type_id(p.as_ref())),
)
.finish()
}
}
impl TextAreaSentinel {
pub fn new(label: impl Into<std::sync::Arc<str>>) -> Self {
Self {
label: label.into(),
style: crate::style::Style::default(),
focus_style: None,
hover_style: None,
payload: None,
id: None,
}
}
pub fn payload<T: Send + Sync + 'static>(mut self, data: T) -> Self {
self.payload = Some(Arc::new(data));
self
}
pub fn get_payload<T: Send + Sync + 'static>(&self) -> Option<&T> {
self.payload.as_ref()?.downcast_ref::<T>()
}
pub fn raw_payload(&self) -> Option<&Arc<dyn Any + Send + Sync>> {
self.payload.as_ref()
}
pub fn id(mut self, id: SentinelId) -> Self {
self.id = Some(id);
self
}
pub fn sentinel_id(&self) -> Option<SentinelId> {
self.id
}
pub fn style(mut self, style: crate::style::Style) -> Self {
self.style = style;
self
}
pub fn focus_style(mut self, style: crate::style::Style) -> Self {
self.focus_style = Some(style);
self
}
pub fn hover_style(mut self, style: crate::style::Style) -> Self {
self.hover_style = Some(style);
self
}
}
#[derive(Clone, Debug)]
pub enum SentinelEvent {
Deleted {
id: SentinelId,
sentinel: TextAreaSentinel,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TextAreaSentinelClickKind {
Image {
index: usize,
image: ImageContent,
},
Custom {
index: usize,
id: SentinelId,
sentinel: TextAreaSentinel,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TextAreaSentinelClickEvent {
pub kind: TextAreaSentinelClickKind,
pub byte_range: (usize, usize),
pub mouse: MouseEvent,
}
pub fn insert_sentinel(
value: &str,
cursor: usize,
sentinels: &mut Vec<TextAreaSentinel>,
mut sentinel: TextAreaSentinel,
) -> (String, usize) {
if sentinel.id.is_none() {
sentinel.id = Some(SentinelId::next());
}
let idx = sentinels.len();
sentinels.push(sentinel);
let ch = char::from_u32(SENTINEL_BASE as u32 + idx as u32).unwrap_or(SENTINEL_BASE);
let mut new_value = String::with_capacity(value.len() + ch.len_utf8());
let cursor = crate::utils::text::clamp_cursor(value, cursor);
new_value.push_str(&value[..cursor]);
new_value.push(ch);
new_value.push_str(&value[cursor..]);
let new_cursor = cursor + ch.len_utf8();
(new_value, new_cursor)
}
pub const IMAGE_SENTINEL_BASE: char = '\u{E000}';
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum TextAreaImageMode {
#[default]
Inline,
Attachment,
}