Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/examples/frame-capture-test/root_view.rs
1use image::ImageEncoder;
2use pathfinder_color::ColorU;
3use std::sync::{Arc, Mutex};
4use std::time::{SystemTime, UNIX_EPOCH};
5use strato_ui::SingletonEntity as _;
6use strato_ui::{
7 elements::{
8 Align, ConstrainedBox, Container, DispatchEventResult, EventHandler, Padding,
9 ParentElement, Rect, Stack, Text,
10 },
11 fonts::{Cache as FontCache, FamilyId},
12 platform::CapturedFrame,
13 AppContext, Element, Entity, TypedActionView, View, ViewContext,
14};
15 
16#[derive(Clone, Debug)]
17pub enum RootViewAction {
18 CaptureFrame,
19}
20 
21pub struct RootView {
22 window_id: strato_ui::WindowId,
23 font_family: FamilyId,
24 last_capture_msg: Arc<Mutex<Option<String>>>,
25}
26 
27impl RootView {
28 pub fn new(ctx: &mut ViewContext<Self>) -> Self {
29 let window_id = ctx.window_id();
30 let font_family = FontCache::handle(ctx)
31 .update(ctx, |cache: &mut FontCache, _| {
32 cache.load_system_font("Arial").ok()
33 })
34 .unwrap_or(FamilyId(0));
35 log::info!("Frame capture demo initialized. Click the button to capture!");
36 println!("\n📸 Click the blue button to capture the frame!\n");
37 Self {
38 window_id,
39 font_family,
40 last_capture_msg: Arc::new(Mutex::new(None)),
41 }
42 }
43 
44 fn request_capture(&mut self, ctx: &mut ViewContext<Self>) {
45 let timestamp = SystemTime::now()
46 .duration_since(UNIX_EPOCH)
47 .unwrap_or_default()
48 .as_secs();
49 let filename = format!("frame_capture_{}.png", timestamp);
50 
51 log::info!("Requesting frame capture to: {}", filename);
52 println!("\n📸 Requesting frame capture to: {}\n", filename);
53 
54 *self.last_capture_msg.lock().unwrap() = Some("Capture requested...".to_string());
55 ctx.notify();
56 
57 if let Some(window) = ctx.windows().platform_window(self.window_id) {
58 let msg_handle = Arc::clone(&self.last_capture_msg);
59 window
60 .as_ctx()
61 .request_frame_capture(Box::new(move |frame| {
62 log::info!("Frame captured, saving to file");
63 match save_frame_as_png(&frame, &filename) {
64 Ok(()) => {
65 log::info!("Frame saved to: {}", filename);
66 println!("\n✅ Frame saved to: {}\n", filename);
67 *msg_handle.lock().unwrap() =
68 Some(format!("Frame written to {}", filename));
69 }
70 Err(e) => {
71 log::error!("Failed to save frame: {}", e);
72 *msg_handle.lock().unwrap() = Some(format!("Error: {}", e));
73 }
74 }
75 }));
76 }
77 }
78}
79 
80fn save_frame_as_png(frame: &CapturedFrame, path: &str) -> Result<(), Box<dyn std::error::Error>> {
81 let file = std::fs::File::create(path)?;
82 let mut writer = std::io::BufWriter::new(file);
83 
84 let encoder = image::codecs::png::PngEncoder::new_with_quality(
85 &mut writer,
86 image::codecs::png::CompressionType::Fast,
87 image::codecs::png::FilterType::NoFilter,
88 );
89 
90 encoder.write_image(
91 &frame.data,
92 frame.width,
93 frame.height,
94 image::ColorType::Rgba8.into(),
95 )?;
96 
97 Ok(())
98}
99 
100impl Entity for RootView {
101 type Event = ();
102}
103 
104impl View for RootView {
105 fn ui_name() -> &'static str {
106 "RootView"
107 }
108 
109 fn render(&self, _: &AppContext) -> Box<dyn Element> {
110 let button_color = ColorU::new(70, 120, 200, 255);
111 let status_msg = self
112 .last_capture_msg
113 .lock()
114 .ok()
115 .and_then(|guard| guard.clone())
116 .unwrap_or_else(|| "Click the button below to write a frame".to_string());
117 
118 Stack::new()
119 // Dark background
120 .with_child(
121 Rect::new()
122 .with_background_color(ColorU::new(40, 40, 45, 255))
123 .finish(),
124 )
125 // Content: Colorful squares arranged vertically
126 .with_child(
127 Align::new(
128 Container::new(
129 Stack::new()
130 // Title text
131 .with_child(
132 Container::new(
133 Text::new_inline(
134 "Frame Capture Test".to_string(),
135 self.font_family,
136 28.0,
137 )
138 .with_color(ColorU::white())
139 .finish(),
140 )
141 .with_padding(Padding::uniform(16.0))
142 .finish(),
143 )
144 // Subtitle
145 .with_child(
146 Container::new(
147 Text::new_inline(
148 "WarpUI rendering sample with clickable capture button"
149 .to_string(),
150 self.font_family,
151 16.0,
152 )
153 .with_color(ColorU::new(200, 200, 200, 255))
154 .finish(),
155 )
156 .with_padding(Padding::uniform(12.0))
157 .finish(),
158 )
159 // Toast / status line
160 .with_child(
161 Container::new(
162 Text::new_inline(status_msg, self.font_family, 14.0)
163 .with_color(ColorU::new(180, 220, 180, 255))
164 .finish(),
165 )
166 .with_padding(Padding::uniform(12.0))
167 .finish(),
168 )
169 // Red square
170 .with_child(
171 Container::new(
172 ConstrainedBox::new(
173 Rect::new()
174 .with_background_color(ColorU::new(255, 100, 100, 255))
175 .finish(),
176 )
177 .with_width(150.0)
178 .with_height(150.0)
179 .finish(),
180 )
181 .with_padding(Padding::uniform(15.0))
182 .finish(),
183 )
184 // Green square
185 .with_child(
186 Container::new(
187 ConstrainedBox::new(
188 Rect::new()
189 .with_background_color(ColorU::new(100, 255, 100, 255))
190 .finish(),
191 )
192 .with_width(150.0)
193 .with_height(150.0)
194 .finish(),
195 )
196 .with_padding(Padding::uniform(15.0))
197 .finish(),
198 )
199 // Blue square
200 .with_child(
201 Container::new(
202 ConstrainedBox::new(
203 Rect::new()
204 .with_background_color(ColorU::new(100, 100, 255, 255))
205 .finish(),
206 )
207 .with_width(150.0)
208 .with_height(150.0)
209 .finish(),
210 )
211 .with_padding(Padding::uniform(15.0))
212 .finish(),
213 )
214 // Orange square
215 .with_child(
216 Container::new(
217 ConstrainedBox::new(
218 Rect::new()
219 .with_background_color(ColorU::new(255, 165, 0, 255))
220 .finish(),
221 )
222 .with_width(150.0)
223 .with_height(150.0)
224 .finish(),
225 )
226 .with_padding(Padding::uniform(15.0))
227 .finish(),
228 )
229 // Capture button (purple square)
230 .with_child(
231 Container::new(
232 EventHandler::new(
233 ConstrainedBox::new(
234 Stack::new()
235 // Button background
236 .with_child(
237 Rect::new()
238 .with_background_color(button_color)
239 .finish(),
240 )
241 // Button label
242 .with_child(
243 Align::new(
244 Text::new_inline(
245 "Write Frame to File System"
246 .to_string(),
247 self.font_family,
248 16.0,
249 )
250 .with_color(ColorU::white())
251 .finish(),
252 )
253 .finish(),
254 )
255 .finish(),
256 )
257 .with_width(200.0)
258 .with_height(80.0)
259 .finish(),
260 )
261 .on_left_mouse_down(|ctx, _, _| {
262 ctx.dispatch_typed_action(RootViewAction::CaptureFrame);
263 DispatchEventResult::StopPropagation
264 })
265 .finish(),
266 )
267 .with_padding(Padding::uniform(15.0))
268 .finish(),
269 )
270 .finish(),
271 )
272 .with_padding(Padding::uniform(40.0))
273 .finish(),
274 )
275 .finish(),
276 )
277 .finish()
278 }
279}
280 
281impl TypedActionView for RootView {
282 type Action = RootViewAction;
283 
284 fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
285 match action {
286 RootViewAction::CaptureFrame => self.request_capture(ctx),
287 }
288 }
289}
290