1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//! Streaming Output Demo
//!
//! Demonstrates rnk's support for streaming/incremental output:
//!
//! 1. **Line-level diff rendering**: Only changed lines are redrawn (like Ink/Bubbletea)
//! 2. **Static component**: Permanent output that persists above dynamic UI
//! 3. **rnk::println()**: Print messages that persist (like Bubbletea's tea.Println())
//!
//! Run with: cargo run --example streaming_demo
use std::time::Duration;
use rnk::hooks::use_signal;
use rnk::prelude::*;
fn main() -> std::io::Result<()> {
println!("Streaming Output Demo\n");
println!("This demo shows:");
println!(" 1. Static component - logs that persist above the UI");
println!(" 2. Line-level diff rendering - only changed lines update");
println!(" 3. Simulated streaming - progressive text updates\n");
println!("Press Ctrl+C to exit.\n");
// Start background thread before entering the render loop
std::thread::spawn(|| {
let mut tick = 0u32;
loop {
std::thread::sleep(Duration::from_millis(100));
tick += 1;
rnk::request_render();
// Print permanent log every 20 ticks
if tick % 20 == 0 {
rnk::println(format!("[LOG] Tick {} completed", tick));
}
}
});
render(app).run()
}
fn app() -> Element {
// Counter for the dynamic UI
let counter = use_signal(|| 0);
// Streaming text simulation
let stream_text = use_signal(String::new);
let full_text = "Hello! This is simulated streaming text that appears progressively...";
// Update state on each render
let current = counter.get();
counter.set(current + 1);
// Simulate streaming text
let char_index = (current as usize) % (full_text.len() + 10);
if char_index < full_text.len() {
let current_text: String = full_text.chars().take(char_index + 1).collect();
stream_text.set(current_text);
} else if char_index == full_text.len() {
stream_text.set(full_text.to_string());
}
Box::new()
.flex_direction(FlexDirection::Column)
// Separator
.child(
Text::new("─".repeat(50))
.color(Color::Ansi256(240))
.into_element(),
)
// Dynamic UI - this part updates with line-level diff
.child(
Box::new()
.flex_direction(FlexDirection::Column)
.padding(1)
.child(
Text::new("Dynamic UI (line-level diff rendering)")
.color(Color::Cyan)
.bold()
.into_element(),
)
.child(Newline::new().into_element())
.child(
Box::new()
.flex_direction(FlexDirection::Row)
.child(Text::new("Counter: ").color(Color::White).into_element())
.child(
Text::new(format!("{}", counter.get()))
.color(Color::Green)
.bold()
.into_element(),
)
.into_element(),
)
.child(Newline::new().into_element())
.child(
Box::new()
.flex_direction(FlexDirection::Column)
.child(
Text::new("Streaming text:")
.color(Color::Yellow)
.into_element(),
)
.child(
Text::new(format!("{}_", stream_text.get()))
.color(Color::White)
.into_element(),
)
.into_element(),
)
.child(Newline::new().into_element())
.child(
Text::new("Note: Only changed lines are redrawn! Logs above persist.")
.dim()
.into_element(),
)
.into_element(),
)
.into_element()
}