tui_lipan/widgets/text_area/
sentinel.rs1use std::any::Any;
2use std::fmt;
3use std::hash::{Hash, Hasher};
4use std::sync::Arc;
5use std::sync::atomic::{AtomicU64, Ordering};
6
7use crate::clipboard::ImageContent;
8use crate::core::event::MouseEvent;
9
10static NEXT_SENTINEL_ID: AtomicU64 = AtomicU64::new(1);
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub struct SentinelId(u64);
15
16impl SentinelId {
17 pub const UNKNOWN: Self = Self(0);
19
20 pub(crate) fn next() -> Self {
21 Self(NEXT_SENTINEL_ID.fetch_add(1, Ordering::Relaxed))
22 }
23}
24
25pub const SENTINEL_BASE: char = '\u{F000}';
31#[derive(Clone)]
37pub struct TextAreaSentinel {
38 pub label: std::sync::Arc<str>,
40 pub style: crate::style::Style,
42 pub focus_style: Option<crate::style::Style>,
44 pub hover_style: Option<crate::style::Style>,
46 payload: Option<Arc<dyn Any + Send + Sync>>,
47 id: Option<SentinelId>,
48}
49
50impl PartialEq for TextAreaSentinel {
51 fn eq(&self, other: &Self) -> bool {
52 self.label == other.label
53 && self.style == other.style
54 && self.focus_style == other.focus_style
55 && self.hover_style == other.hover_style
56 && self.id == other.id
57 }
58}
59
60impl Eq for TextAreaSentinel {}
61
62impl Hash for TextAreaSentinel {
63 fn hash<H: Hasher>(&self, state: &mut H) {
64 self.label.hash(state);
65 self.style.hash(state);
66 self.focus_style.hash(state);
67 self.hover_style.hash(state);
68 self.id.hash(state);
69 }
70}
71
72impl fmt::Debug for TextAreaSentinel {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 f.debug_struct("TextAreaSentinel")
75 .field("label", &self.label)
76 .field("style", &self.style)
77 .field("focus_style", &self.focus_style)
78 .field("hover_style", &self.hover_style)
79 .field("id", &self.id)
80 .field(
81 "payload_type_id",
82 &self.payload.as_ref().map(|p| Any::type_id(p.as_ref())),
83 )
84 .finish()
85 }
86}
87
88impl TextAreaSentinel {
89 pub fn new(label: impl Into<std::sync::Arc<str>>) -> Self {
91 Self {
92 label: label.into(),
93 style: crate::style::Style::default(),
94 focus_style: None,
95 hover_style: None,
96 payload: None,
97 id: None,
98 }
99 }
100
101 pub fn payload<T: Send + Sync + 'static>(mut self, data: T) -> Self {
103 self.payload = Some(Arc::new(data));
104 self
105 }
106
107 pub fn get_payload<T: Send + Sync + 'static>(&self) -> Option<&T> {
109 self.payload.as_ref()?.downcast_ref::<T>()
110 }
111
112 pub fn raw_payload(&self) -> Option<&Arc<dyn Any + Send + Sync>> {
114 self.payload.as_ref()
115 }
116
117 pub fn id(mut self, id: SentinelId) -> Self {
119 self.id = Some(id);
120 self
121 }
122
123 pub fn sentinel_id(&self) -> Option<SentinelId> {
125 self.id
126 }
127
128 pub fn style(mut self, style: crate::style::Style) -> Self {
130 self.style = style;
131 self
132 }
133
134 pub fn focus_style(mut self, style: crate::style::Style) -> Self {
136 self.focus_style = Some(style);
137 self
138 }
139
140 pub fn hover_style(mut self, style: crate::style::Style) -> Self {
142 self.hover_style = Some(style);
143 self
144 }
145}
146
147#[derive(Clone, Debug)]
149pub enum SentinelEvent {
150 Deleted {
152 id: SentinelId,
154 sentinel: TextAreaSentinel,
156 },
157}
158
159#[derive(Clone, Debug, PartialEq, Eq)]
161pub enum TextAreaSentinelClickKind {
162 Image {
164 index: usize,
166 image: ImageContent,
168 },
169 Custom {
171 index: usize,
173 id: SentinelId,
175 sentinel: TextAreaSentinel,
177 },
178}
179
180#[derive(Clone, Debug, PartialEq, Eq)]
182pub struct TextAreaSentinelClickEvent {
183 pub kind: TextAreaSentinelClickKind,
185 pub byte_range: (usize, usize),
187 pub mouse: MouseEvent,
189}
190
191pub fn insert_sentinel(
196 value: &str,
197 cursor: usize,
198 sentinels: &mut Vec<TextAreaSentinel>,
199 mut sentinel: TextAreaSentinel,
200) -> (String, usize) {
201 if sentinel.id.is_none() {
202 sentinel.id = Some(SentinelId::next());
203 }
204 let idx = sentinels.len();
205 sentinels.push(sentinel);
206 let ch = char::from_u32(SENTINEL_BASE as u32 + idx as u32).unwrap_or(SENTINEL_BASE);
207 let mut new_value = String::with_capacity(value.len() + ch.len_utf8());
208 let cursor = crate::utils::text::clamp_cursor(value, cursor);
209 new_value.push_str(&value[..cursor]);
210 new_value.push(ch);
211 new_value.push_str(&value[cursor..]);
212 let new_cursor = cursor + ch.len_utf8();
213 (new_value, new_cursor)
214}
215
216pub const IMAGE_SENTINEL_BASE: char = '\u{E000}';
220
221#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
224pub enum TextAreaImageMode {
225 #[default]
228 Inline,
229 Attachment,
232}