cursive_cached_text_view/
lib.rs1use std::ops::Deref;
2use std::sync::{Mutex, MutexGuard};
3use std::sync::Arc;
4
5use cursive::{Printer, Vec2, With, XY};
6use cursive::align::*;
7use cursive::theme::StyleType;
8use cursive::utils::lines::spans::{LinesIterator, Row};
9use cursive::utils::markup::StyledString;
10use cursive::view::{SizeCache, View};
11use owning_ref::{ArcRef, OwningHandle};
12use unicode_width::UnicodeWidthStr;
13
14type InnerContentType = Arc<StyledString>;
16
17#[derive(Clone)]
35pub struct TextContent {
36 content: Arc<Mutex<TextContentInner>>,
37}
38
39impl TextContent {
40 pub fn new<S>(content: S) -> Self
44 where
45 S: Into<StyledString>,
46 {
47 let content = Arc::new(content.into());
48
49 TextContent {
50 content: Arc::new(Mutex::new(TextContentInner {
51 content_value: content,
52 content_cache: Arc::new(StyledString::default()),
53 size_cache: None,
54 })),
55 }
56 }
57}
58
59pub struct TextContentRef {
67 _handle: OwningHandle<
68 ArcRef<Mutex<TextContentInner>>,
69 MutexGuard<'static, TextContentInner>,
70 >,
71 data: Arc<StyledString>,
74}
75
76impl Deref for TextContentRef {
77 type Target = StyledString;
78
79 fn deref(&self) -> &StyledString {
80 self.data.as_ref()
81 }
82}
83
84impl TextContent {
85 pub fn set_content<S>(&self, content: S)
87 where
88 S: Into<StyledString>,
89 {
90 self.with_content(|c| {
91 *c = content.into();
92 });
93 }
94
95 pub fn append<S>(&self, content: S)
97 where
98 S: Into<StyledString>,
99 {
100 self.with_content(|c| {
101 c.append(content);
104 })
105 }
106
107 pub fn get_content(&self) -> TextContentRef {
112 TextContentInner::get_content(&self.content)
113 }
114
115 pub fn with_content<F, O>(&self, f: F) -> O
117 where
118 F: FnOnce(&mut StyledString) -> O,
119 {
120 self.with_content_inner(|c| f(Arc::make_mut(&mut c.content_value)))
121 }
122
123 fn with_content_inner<F, O>(&self, f: F) -> O
125 where
126 F: FnOnce(&mut TextContentInner) -> O,
127 {
128 let mut content = self.content.lock().unwrap();
129
130 let out = f(&mut content);
131
132 content.size_cache = None;
133
134 out
135 }
136}
137
138struct TextContentInner {
144 content_value: InnerContentType,
146 content_cache: InnerContentType,
147
148 size_cache: Option<XY<SizeCache>>,
150}
151
152impl TextContentInner {
153 fn get_content(content: &Arc<Mutex<TextContentInner>>) -> TextContentRef {
155 let arc_ref: ArcRef<Mutex<TextContentInner>> =
156 ArcRef::new(Arc::clone(content));
157
158 let _handle = OwningHandle::new_with_fn(arc_ref, |mutex| unsafe {
159 (*mutex).lock().unwrap()
160 });
161
162 let data = Arc::clone(&_handle.content_value);
163
164 TextContentRef { _handle, data }
165 }
166
167 fn is_cache_valid(&self, size: Vec2) -> bool {
168 match self.size_cache {
169 None => false,
170 Some(ref last) => last.x.accept(size.x) && last.y.accept(size.y),
171 }
172 }
173
174 fn get_cache(&self) -> &InnerContentType {
175 &self.content_cache
176 }
177}
178
179pub struct CachedTextView {
191 cache: TinyCache<usize, Vec<Row>>,
192 content: TextContent,
193
194 align: Align,
195
196 style: StyleType,
197
198 wrap: bool,
200
201 width: Option<usize>,
203}
204
205impl CachedTextView {
206 pub fn new<S>(content: S, cache_size: usize) -> Self
208 where
209 S: Into<StyledString>,
210 {
211 Self::new_with_content(TextContent::new(content), cache_size)
212 }
213
214 pub fn new_with_content(content: TextContent, cache_size: usize) -> Self {
231 CachedTextView {
232 cache: TinyCache::new(cache_size),
233 content,
234 style: StyleType::default(),
235 wrap: true,
236 align: Align::top_left(),
237 width: None,
238 }
239 }
240
241 pub fn empty() -> Self {
243 CachedTextView::new("", 5)
244 }
245
246 pub fn set_style<S: Into<StyleType>>(&mut self, style: S) {
248 self.cache.clear();
249 self.style = style.into();
250 }
251
252 #[must_use]
256 pub fn style<S: Into<StyleType>>(self, style: S) -> Self {
257 self.with(|s| s.set_style(style))
258 }
259
260 #[must_use]
264 pub fn no_wrap(self) -> Self {
265 self.with(|s| s.set_content_wrap(false))
266 }
267
268 pub fn set_content_wrap(&mut self, wrap: bool) {
272 self.cache.clear();
273 self.wrap = wrap;
274 }
275
276 #[must_use]
278 pub fn h_align(mut self, h: HAlign) -> Self {
279 self.align.h = h;
280
281 self
282 }
283
284 #[must_use]
286 pub fn v_align(mut self, v: VAlign) -> Self {
287 self.align.v = v;
288
289 self
290 }
291
292 #[must_use]
294 pub fn align(mut self, a: Align) -> Self {
295 self.align = a;
296
297 self
298 }
299
300 #[must_use]
302 pub fn center(mut self) -> Self {
303 self.align = Align::center();
304 self
305 }
306
307 #[must_use]
311 pub fn content<S>(self, content: S) -> Self
312 where
313 S: Into<StyledString>,
314 {
315 self.with(|s| s.set_content(content))
316 }
317
318 pub fn set_content<S>(&mut self, content: S)
320 where
321 S: Into<StyledString>,
322 {
323 self.cache.clear();
324 self.content.set_content(content);
325 }
326
327 pub fn append<S>(&mut self, content: S)
329 where
330 S: Into<StyledString>,
331 {
332 self.cache.clear();
333 self.content.append(content);
334 }
335
336 pub fn get_content(&self) -> TextContentRef {
338 TextContentInner::get_content(&self.content.content)
339 }
340
341 pub fn get_shared_content(&mut self) -> TextContent {
343 TextContent {
346 content: Arc::clone(&self.content.content),
347 }
348 }
349
350 fn compute_rows(&mut self, size: Vec2) {
353 let size = if self.wrap { size } else { Vec2::max_value() };
354
355 let mut content = self.content.content.lock().unwrap();
356 if content.is_cache_valid(size) {
357 return;
358 }
359
360 content.size_cache = None;
363 content.content_cache = Arc::clone(&content.content_value);
364
365 let rows = self.cache.compute(size.x, || {
366 LinesIterator::new(content.get_cache().as_ref(), size.x).collect()
367 });
368
369 self.width = if rows.iter().any(|row| row.is_wrapped) {
371 Some(size.x)
373 } else {
374 rows.iter().map(|row| row.width).max()
375 }
376 }
377}
378
379impl View for CachedTextView {
380 fn draw(&self, printer: &Printer) {
381 let rows = if let Some(rows) = self.cache.last() {
382 rows
383 } else {
384 return;
385 };
386 let h = rows.len();
387 let offset = self.align.v.get_offset(h, printer.size.y);
389 let printer = &printer.offset((0, offset));
390
391 let content = self.content.content.lock().unwrap();
392
393 printer.with_style(self.style, |printer| {
394 for (y, row) in rows
395 .iter()
396 .enumerate()
397 .skip(printer.content_offset.y)
398 .take(printer.output_size.y)
399 {
400 let l = row.width;
401 let mut x = self.align.h.get_offset(l, printer.size.x);
402
403 for span in row.resolve_stream(content.get_cache().as_ref()) {
404 printer.with_style(*span.attr, |printer| {
405 printer.print((x, y), span.content);
406 x += span.content.width();
407 });
408 }
409 }
410 });
411 }
412
413 fn layout(&mut self, size: Vec2) {
414 self.compute_rows(size);
416
417 let num_rows = self.cache.last().map(|rows| rows.len()).unwrap_or(0);
418
419 let my_size = Vec2::new(self.width.unwrap_or(0), num_rows);
421
422 let mut content = self.content.content.lock().unwrap();
424 content.size_cache = Some(SizeCache::build(my_size, size));
425 }
426
427 fn needs_relayout(&self) -> bool {
428 let content = self.content.content.lock().unwrap();
429 content.size_cache.is_none()
430 }
431
432 fn required_size(&mut self, size: Vec2) -> Vec2 {
433 self.compute_rows(size);
434
435 let num_rows = self.cache.last().map(|rows| rows.len()).unwrap_or(0);
436
437 Vec2::new(self.width.unwrap_or(0), num_rows)
438 }
439}
440
441
442struct TinyCache<K, V> {
443 size: usize,
444 data: Vec<(usize, K, V)>,
445}
446
447impl<K, V> TinyCache<K, V> {
448 fn new(size: usize) -> Self {
449 TinyCache {
450 size,
451 data: Vec::with_capacity(size),
452 }
453 }
454
455 fn get_key_index(&self, key: &K) -> Option<usize> where K: Eq {
456 self.data.iter().rposition(|(_, k, _)| k == key)
457 }
458
459 fn compute(&mut self, key: K, f: impl FnOnce() -> V) -> &V where K: Eq {
460 if let Some(index) = self.get_key_index(&key) {
461 self.data[index].0 += 1;
462 return &self.data[index].2;
463 }
464
465 let v = f();
466 self.clean();
467 self.data.push((0, key, v));
468 &self.data.last().as_ref().unwrap().2
469 }
470
471 fn clean(&mut self) {
472 if self.data.len() < self.size {
473 return;
474 }
475 let index = self.data.iter().enumerate()
476 .min_by_key(|(_, (count, _, _))| *count)
477 .map(|(i, _)| i);
478
479 if let Some(index) = index {
480 self.data.swap_remove(index);
481 }
482 }
483
484 fn clear(&mut self) {
485 self.data.clear();
486 }
487
488 fn last(&self) -> Option<&V> {
489 self.data.last().map(|(_, _, v)| v)
490 }
491
492 #[cfg(test)]
493 fn len(&self) -> usize {
494 self.data.len()
495 }
496
497 #[cfg(test)]
498 fn is_empty(&self) -> bool {
499 self.data.is_empty()
500 }
501
502 #[cfg(test)]
503 fn keys(&self) -> Vec<(&K, usize)> {
504 self.data.iter().map(|(count, key, _)| (key, *count)).collect()
505 }
506}
507
508#[cfg(test)]
509mod tests {
510 use cursive::theme::Style;
511 use cursive::Vec2;
512
513 use super::*;
514
515 #[test]
516 fn sanity() {
517 let text_view = CachedTextView::new("", 5);
518 assert_eq!(text_view.get_content().data.spans().len(), 1);
519 }
520
521 #[test]
522 fn test_cache() {
523 let mut text_view = CachedTextView::new("sample", 3);
524 assert!(text_view.cache.is_empty());
525
526 text_view.compute_rows(Vec2::new(0, 0));
527 assert_eq!(text_view.cache.len(), 1);
528 text_view.compute_rows(Vec2::new(0, 0));
529 assert_eq!(text_view.cache.len(), 1);
530
531 text_view.compute_rows(Vec2::new(1, 0));
532 assert_eq!(text_view.cache.len(), 2);
533
534 text_view.compute_rows(Vec2::new(2, 0));
535 assert_eq!(text_view.cache.len(), 3);
536
537 text_view.compute_rows(Vec2::new(3, 0));
538 assert_eq!(text_view.cache.len(), 3);
539
540 assert_eq!(text_view.cache.keys(), [(&0, 1), (&2, 0), (&3, 0)]);
541
542 text_view.set_content("");
543 assert_eq!(text_view.cache.len(), 0);
544 text_view.compute_rows(Vec2::new(0, 0));
545
546 text_view.append("sample");
547 assert_eq!(text_view.cache.len(), 0);
548 text_view.compute_rows(Vec2::new(0, 0));
549
550 text_view.set_content_wrap(false);
551 assert_eq!(text_view.cache.len(), 0);
552 text_view.compute_rows(Vec2::new(0, 0));
553
554 text_view.set_style(Style::view());
555 assert_eq!(text_view.cache.len(), 0);
556 }
557}