text_input_example/
text_input_example.rs

1use spottedcat::{Context, Key, Spot, Text, DrawOption};
2
3struct TextInputExample {
4    committed: String,
5    preedit: String,
6    font_data: Vec<u8>,
7    capture_enabled: bool,
8}
9
10impl Spot for TextInputExample {
11    fn initialize(_: &mut Context) -> Self {
12        // Prefer a system font that contains CJK glyphs.
13        // Fallback to the bundled DejaVuSans.ttf.
14        const FALLBACK_FONT: &[u8] = include_bytes!("../assets/DejaVuSans.ttf");
15
16        let font_candidates = [
17            "/System/Library/Fonts/Supplemental/Arial Unicode.ttf",
18            "/System/Library/Fonts/PingFang.ttc",
19            "/System/Library/Fonts/Supplemental/Songti.ttc",
20            "/System/Library/Fonts/Supplemental/Heiti TC.ttc",
21            "/System/Library/Fonts/Supplemental/STHeiti Medium.ttc",
22        ];
23
24        let mut font_data = None;
25        for path in font_candidates {
26            if let Ok(data) = spottedcat::load_font_from_file(path) {
27                font_data = Some(data);
28                break;
29            }
30        }
31
32        let font_data = font_data.unwrap_or_else(|| spottedcat::load_font_from_bytes(FALLBACK_FONT));
33        Self {
34            committed: String::new(),
35            preedit: String::new(),
36            font_data,
37            capture_enabled: false,
38        }
39    }
40
41    fn update(&mut self, ctx: &mut Context, _dt: std::time::Duration) {
42        if spottedcat::key_pressed(ctx, Key::F1) {
43            self.capture_enabled = !self.capture_enabled;
44            spottedcat::set_text_input_enabled(ctx, self.capture_enabled);
45        }
46
47        // Append characters entered this frame.
48        self.committed.push_str(spottedcat::text_input(ctx));
49
50        // Cache IME preedit so draw() doesn't need to query input state.
51        self.preedit = spottedcat::ime_preedit(ctx).unwrap_or("").to_string();
52
53        // Simple editing: Backspace deletes one Unicode scalar value.
54        if spottedcat::key_pressed(ctx, Key::Backspace) {
55            self.committed.pop();
56        }
57
58        // Clear input.
59        if spottedcat::key_pressed(ctx, Key::Escape) {
60            self.committed.clear();
61            self.preedit.clear();
62        }
63    }
64
65    fn draw(&mut self, ctx: &mut Context) {
66        let mut title_opts = DrawOption::new();
67        title_opts.position = [spottedcat::Pt::from(20.0), spottedcat::Pt::from(40.0)];
68        let status = if self.capture_enabled { "ON" } else { "OFF" };
69        Text::new(
70            format!(
71            "Text Input Example (capture: {}, F1 toggle, Backspace delete, Esc clear)",
72            status
73            ),
74            self.font_data.clone(),
75        )
76        .with_font_size(spottedcat::Pt::from(22.0))
77        .with_color([1.0, 1.0, 1.0, 1.0])
78        .draw(ctx, title_opts);
79
80        let mut input_opts = DrawOption::new();
81        input_opts.position = [spottedcat::Pt::from(20.0), spottedcat::Pt::from(90.0)];
82
83        let mut composed = self.committed.clone();
84        if !self.preedit.is_empty() {
85            composed.push_str(&self.preedit);
86        }
87        Text::new(composed, self.font_data.clone())
88            .with_font_size(spottedcat::Pt::from(28.0))
89            .with_color([0.9, 0.9, 0.9, 1.0])
90            .draw(ctx, input_opts);
91
92        if !self.preedit.is_empty() {
93            let mut ime_opts = DrawOption::new();
94            ime_opts.position = [spottedcat::Pt::from(20.0), spottedcat::Pt::from(130.0)];
95            Text::new(
96                format!("IME preedit: {}", self.preedit),
97                self.font_data.clone(),
98            )
99            .with_font_size(spottedcat::Pt::from(16.0))
100            .with_color([0.6, 0.8, 1.0, 1.0])
101            .draw(ctx, ime_opts);
102        }
103    }
104
105    fn remove(&self) {}
106}
107
108fn main() {
109    spottedcat::run::<TextInputExample>(spottedcat::WindowConfig::default());
110}