ailloli_ui_editor/paint/
caret_painter.rs

1//! Caret paint-item construction and deterministic blink phase.
2
3use crate::input::caret::caret_rect_for_run;
4use crate::layout::EditorTextRun;
5use crate::paint::EditorPaintItem;
6use crate::EditorStyle;
7
8/// Converts run-local caret geometry into a neutral paint item.
9///
10/// `content_x` and `content_y` are screen-space text origins in logical pixels;
11/// `local_byte` is clamped by the underlying text layout.
12///
13/// # Examples
14///
15/// ```
16/// use ailloli_ui_core::{Color, FontId, TextStyle};
17/// use ailloli_ui_editor::{layout::EditorTextRun, paint::{caret_painter::caret_item, EditorPaintItem}, EditorStyle};
18/// use ailloli_ui_text::{TextLayoutParams, TextSystem};
19/// let mut system = TextSystem::new();
20/// let layout = system.layout_cached(TextLayoutParams::new("x", TextStyle::new(FontId::Mono, 13, Color::WHITE)));
21/// let run = EditorTextRun { index: 0, byte_range: 0..1, baseline_y: 12.0, layout };
22/// assert!(matches!(caret_item(10.0, 10.0, &run, 1, EditorStyle::default()), EditorPaintItem::Caret { .. }));
23/// ```
24pub fn caret_item(
25    content_x: f32,
26    content_y: f32,
27    run: &EditorTextRun,
28    local_byte: usize,
29    style: EditorStyle,
30) -> EditorPaintItem {
31    EditorPaintItem::Caret {
32        rect: caret_rect_for_run(content_x, content_y, run, local_byte, style),
33        color: style.caret,
34    }
35}
36
37/// Returns caret visibility for focus and a millisecond blink phase.
38///
39/// An unfocused caret is always hidden. A non-positive interval disables
40/// blinking and keeps a focused caret visible. Otherwise visibility alternates
41/// every `blink_ms`, starting visible at time zero.
42///
43/// # Examples
44///
45/// ```
46/// use ailloli_ui_editor::paint::caret_painter::caret_visible;
47/// assert!(caret_visible(true, 0, 500));
48/// assert!(!caret_visible(true, 500, 500));
49/// assert!(caret_visible(true, 9_999, 0));
50/// assert!(!caret_visible(false, 0, 500));
51/// ```
52pub fn caret_visible(focused: bool, frame_time_ms: u128, blink_ms: i64) -> bool {
53    if !focused {
54        return false;
55    }
56    if blink_ms <= 0 {
57        return true;
58    }
59    (frame_time_ms / blink_ms as u128).is_multiple_of(2)
60}