Skip to main content

29_canvas/
29_canvas.rs

1//! Example 29: Canvas (Kitty Graphics)
2//!
3//! Demonstrates the canvas widget for pixel-level drawing using the Kitty
4//! graphics protocol.
5//!
6//! Features:
7//! - Drawing primitives (lines, rectangles, circles)
8//! - Bar chart visualization
9//! - Animation support
10//!
11//! NOTE: Requires a Kitty-protocol compatible terminal:
12//! - Kitty
13//! - Ghostty
14//! - WezTerm
15//!
16//! Run with: cargo run -p telex-tui --example 29_canvas
17
18use crossterm::event::KeyCode;
19use telex::prelude::*;
20use telex::Color;
21
22telex::require_api!(0, 2);
23
24fn main() {
25    telex::run_with_theme(App, telex::theme::Theme::nord()).unwrap();
26}
27
28struct App;
29
30impl Component for App {
31    fn render(&self, cx: Scope) -> View {
32        let show_help = state!(cx, || false);
33
34        // F1 toggles help
35        cx.use_command(
36            KeyBinding::key(KeyCode::F(1)),
37            with!(show_help => move || show_help.update(|v| *v = !*v)),
38        );
39
40        // Animated value for the bar chart using stream macro
41        let frame_stream = stream!(cx, || {
42            (0u32..).inspect(|&i| {
43                if i > 0 {
44                    std::thread::sleep(std::time::Duration::from_millis(100));
45                }
46            })
47        });
48
49        let current_frame = frame_stream.get();
50
51        View::vstack()
52            .spacing(1)
53            .child(
54                View::styled_text("Canvas Examples (Kitty Graphics)")
55                    .bold()
56                    .build(),
57            )
58            .child(View::text("Requires Kitty, Ghostty, or WezTerm terminal"))
59            .child(View::text(""))
60            // Basic shapes demo
61            .child(View::text("Basic Shapes:"))
62            .child(
63                View::canvas()
64                    .width(200)
65                    .height(80)
66                    .on_draw(|ctx| {
67                        // Clear to dark background
68                        ctx.clear(Color::Rgb {
69                            r: 30,
70                            g: 30,
71                            b: 40,
72                        });
73
74                        // Draw some lines
75                        ctx.line(10, 10, 190, 10, Color::Red);
76                        ctx.line(10, 10, 10, 70, Color::Green);
77                        ctx.line(10, 70, 190, 70, Color::Blue);
78                        ctx.line(190, 10, 190, 70, Color::Yellow);
79
80                        // Draw diagonal lines
81                        ctx.line(10, 10, 190, 70, Color::Cyan);
82                        ctx.line(10, 70, 190, 10, Color::Magenta);
83
84                        // Draw filled rectangles
85                        ctx.fill_rect(30, 25, 30, 20, Color::Red);
86                        ctx.fill_rect(80, 25, 30, 20, Color::Green);
87                        ctx.fill_rect(130, 25, 30, 20, Color::Blue);
88
89                        // Draw stroked rectangles
90                        ctx.stroke_rect(30, 50, 30, 15, Color::Yellow);
91                        ctx.stroke_rect(80, 50, 30, 15, Color::Cyan);
92                        ctx.stroke_rect(130, 50, 30, 15, Color::Magenta);
93                    })
94                    .build(),
95            )
96            .child(View::text(""))
97            // Circles demo
98            .child(View::text("Circles:"))
99            .child(
100                View::canvas()
101                    .width(200)
102                    .height(60)
103                    .on_draw(|ctx| {
104                        ctx.clear(Color::Rgb {
105                            r: 20,
106                            g: 25,
107                            b: 35,
108                        });
109
110                        // Filled circles
111                        ctx.fill_circle(30, 30, 20, Color::Red);
112                        ctx.fill_circle(80, 30, 15, Color::Green);
113                        ctx.fill_circle(120, 30, 10, Color::Blue);
114
115                        // Stroked circles
116                        ctx.circle(160, 30, 20, Color::Yellow);
117                        ctx.circle(160, 30, 15, Color::Cyan);
118                        ctx.circle(160, 30, 10, Color::Magenta);
119                    })
120                    .build(),
121            )
122            .child(View::text(""))
123            // Animated bar chart
124            .child(View::text("Animated Bar Chart:"))
125            .child(
126                View::canvas()
127                    .width(200)
128                    .height(80)
129                    .on_draw({
130                        move |ctx| {
131                            ctx.clear(Color::Rgb {
132                                r: 25,
133                                g: 25,
134                                b: 30,
135                            });
136
137                            // Generate animated data
138                            let data: Vec<f32> = (0..8)
139                                .map(|i| {
140                                    let phase = (current_frame as f32 * 0.1) + (i as f32 * 0.5);
141                                    0.3 + 0.7 * ((phase.sin() + 1.0) / 2.0)
142                                })
143                                .collect();
144
145                            let bar_width = 20u16;
146                            let gap = 5u16;
147                            let max_height = 60u16;
148                            let start_x = 10u16;
149                            let baseline = 75u16;
150
151                            // Draw baseline
152                            ctx.line(5, baseline as i32, 195, baseline as i32, Color::Grey);
153
154                            // Draw bars
155                            let colors = [
156                                Color::Red,
157                                Color::Green,
158                                Color::Blue,
159                                Color::Yellow,
160                                Color::Cyan,
161                                Color::Magenta,
162                                Color::Rgb {
163                                    r: 255,
164                                    g: 128,
165                                    b: 0,
166                                },
167                                Color::Rgb {
168                                    r: 128,
169                                    g: 255,
170                                    b: 128,
171                                },
172                            ];
173
174                            for (i, &value) in data.iter().enumerate() {
175                                let x = start_x + (i as u16) * (bar_width + gap);
176                                let height = (value * max_height as f32) as u16;
177                                let y = baseline - height;
178                                ctx.fill_rect(x, y, bar_width, height, colors[i % colors.len()]);
179                            }
180                        }
181                    })
182                    .build(),
183            )
184            .child(View::text(""))
185            .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
186            .child(
187                View::modal()
188                    .visible(show_help.get())
189                    .title("Example 29: Canvas")
190                    .on_dismiss(with!(show_help => move || show_help.set(false)))
191                    .child(
192                        View::vstack()
193                            .child(View::styled_text("What you're seeing").bold().build())
194                            .child(View::text("• Pixel graphics via Kitty protocol"))
195                            .child(View::text("• Lines, rectangles, circles"))
196                            .child(View::text("• Animated bar chart"))
197                            .child(View::gap(1))
198                            .child(View::styled_text("Key concepts").bold().build())
199                            .child(View::text("• View::canvas() creates drawing area"))
200                            .child(View::text("• .on_draw(|ctx| { ... }) draws pixels"))
201                            .child(View::text("• ctx.line(), ctx.fill_rect(), etc."))
202                            .child(View::text("• Works in Kitty/Ghostty/WezTerm"))
203                            .child(View::gap(1))
204                            .child(View::styled_text("Try this").bold().build())
205                            .child(View::text("• Watch the animated bar chart"))
206                            .child(View::text("• Run in compatible terminal"))
207                            .child(View::gap(1))
208                            .child(View::styled_text("Next up").bold().build())
209                            .child(View::text("→ 30_image: image display"))
210                            .child(View::gap(1))
211                            .child(View::styled_text("Press Escape to close").dim().build())
212                            .build(),
213                    )
214                    .build(),
215            )
216            .build()
217    }
218}