Skip to main content

jag_ui/elements/
text.rs

1//! Simple text element.
2
3use jag_draw::{Color, Rect};
4use jag_surface::Canvas;
5
6use crate::event::{
7    EventHandler, EventResult, KeyboardEvent, MouseClickEvent, MouseMoveEvent, ScrollEvent,
8};
9use crate::focus::FocusId;
10
11use super::Element;
12
13/// A single run of text rendered at a given position, size, and color.
14pub struct Text {
15    pub content: String,
16    pub pos: [f32; 2],
17    pub size: f32,
18    pub color: Color,
19}
20
21impl Text {
22    /// Create a new text element with the given content and font size.
23    ///
24    /// Position defaults to `[0, 0]` and color to white.
25    pub fn new(content: impl Into<String>, size: f32) -> Self {
26        Self {
27            content: content.into(),
28            pos: [0.0, 0.0],
29            size,
30            color: Color::from_srgba_u8([255, 255, 255, 255]),
31        }
32    }
33}
34
35// ---------------------------------------------------------------------------
36// Element trait
37// ---------------------------------------------------------------------------
38
39impl Element for Text {
40    fn rect(&self) -> Rect {
41        // Approximate width based on character count; real measurement
42        // requires a text provider which is available at render time.
43        let approx_w = self.content.len() as f32 * self.size * 0.5;
44        Rect {
45            x: self.pos[0],
46            y: self.pos[1] - self.size,
47            w: approx_w,
48            h: self.size,
49        }
50    }
51
52    fn set_rect(&mut self, rect: Rect) {
53        self.pos = [rect.x, rect.y + rect.h];
54    }
55
56    fn render(&self, canvas: &mut Canvas, z: i32) {
57        canvas.draw_text_run_weighted(
58            self.pos,
59            self.content.clone(),
60            self.size,
61            400.0,
62            self.color,
63            z,
64        );
65    }
66
67    fn focus_id(&self) -> Option<FocusId> {
68        None
69    }
70}
71
72// ---------------------------------------------------------------------------
73// EventHandler trait (text is non-interactive by default)
74// ---------------------------------------------------------------------------
75
76impl EventHandler for Text {
77    fn handle_mouse_click(&mut self, _event: &MouseClickEvent) -> EventResult {
78        EventResult::Ignored
79    }
80
81    fn handle_keyboard(&mut self, _event: &KeyboardEvent) -> EventResult {
82        EventResult::Ignored
83    }
84
85    fn handle_mouse_move(&mut self, _event: &MouseMoveEvent) -> EventResult {
86        EventResult::Ignored
87    }
88
89    fn handle_scroll(&mut self, _event: &ScrollEvent) -> EventResult {
90        EventResult::Ignored
91    }
92
93    fn is_focused(&self) -> bool {
94        false
95    }
96
97    fn set_focused(&mut self, _focused: bool) {
98        // Text elements are not focusable.
99    }
100
101    fn contains_point(&self, x: f32, y: f32) -> bool {
102        let r = self.rect();
103        x >= r.x && x <= r.x + r.w && y >= r.y && y <= r.y + r.h
104    }
105}
106
107// ---------------------------------------------------------------------------
108// Tests
109// ---------------------------------------------------------------------------
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn text_new() {
117        let t = Text::new("Hello", 16.0);
118        assert_eq!(t.content, "Hello");
119        assert_eq!(t.size, 16.0);
120        assert_eq!(t.pos, [0.0, 0.0]);
121    }
122
123    #[test]
124    fn text_is_not_focusable() {
125        let t = Text::new("No focus", 12.0);
126        assert!(t.focus_id().is_none());
127        assert!(!t.is_focused());
128    }
129
130    #[test]
131    fn text_set_rect_updates_pos() {
132        let mut t = Text::new("Pos", 14.0);
133        t.set_rect(Rect {
134            x: 10.0,
135            y: 20.0,
136            w: 100.0,
137            h: 14.0,
138        });
139        assert_eq!(t.pos[0], 10.0);
140        // pos[1] = rect.y + rect.h = 20 + 14 = 34
141        assert!((t.pos[1] - 34.0).abs() < f32::EPSILON);
142    }
143}