Files
galaxy/crates/galaxyui/examples/frame-capture-test/root_view.rs
T

290 lines
12 KiB
Rust

use image::ImageEncoder;
use pathfinder_color::ColorU;
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
use galaxyui::SingletonEntity as _;
use galaxyui::{
elements::{
Align, ConstrainedBox, Container, DispatchEventResult, EventHandler, Padding,
ParentElement, Rect, Stack, Text,
},
fonts::{Cache as FontCache, FamilyId},
platform::CapturedFrame,
AppContext, Element, Entity, TypedActionView, View, ViewContext,
};
#[derive(Clone, Debug)]
pub enum RootViewAction {
CaptureFrame,
}
pub struct RootView {
window_id: galaxyui::WindowId,
font_family: FamilyId,
last_capture_msg: Arc<Mutex<Option<String>>>,
}
impl RootView {
pub fn new(ctx: &mut ViewContext<Self>) -> Self {
let window_id = ctx.window_id();
let font_family = FontCache::handle(ctx)
.update(ctx, |cache: &mut FontCache, _| {
cache.load_system_font("Arial").ok()
})
.unwrap_or(FamilyId(0));
log::info!("Frame capture demo initialized. Click the button to capture!");
println!("\n📸 Click the blue button to capture the frame!\n");
Self {
window_id,
font_family,
last_capture_msg: Arc::new(Mutex::new(None)),
}
}
fn request_capture(&mut self, ctx: &mut ViewContext<Self>) {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let filename = format!("frame_capture_{}.png", timestamp);
log::info!("Requesting frame capture to: {}", filename);
println!("\n📸 Requesting frame capture to: {}\n", filename);
*self.last_capture_msg.lock().unwrap() = Some("Capture requested...".to_string());
ctx.notify();
if let Some(window) = ctx.windows().platform_window(self.window_id) {
let msg_handle = Arc::clone(&self.last_capture_msg);
window
.as_ctx()
.request_frame_capture(Box::new(move |frame| {
log::info!("Frame captured, saving to file");
match save_frame_as_png(&frame, &filename) {
Ok(()) => {
log::info!("Frame saved to: {}", filename);
println!("\n✅ Frame saved to: {}\n", filename);
*msg_handle.lock().unwrap() =
Some(format!("Frame written to {}", filename));
}
Err(e) => {
log::error!("Failed to save frame: {}", e);
*msg_handle.lock().unwrap() = Some(format!("Error: {}", e));
}
}
}));
}
}
}
fn save_frame_as_png(frame: &CapturedFrame, path: &str) -> Result<(), Box<dyn std::error::Error>> {
let file = std::fs::File::create(path)?;
let mut writer = std::io::BufWriter::new(file);
let encoder = image::codecs::png::PngEncoder::new_with_quality(
&mut writer,
image::codecs::png::CompressionType::Fast,
image::codecs::png::FilterType::NoFilter,
);
encoder.write_image(
&frame.data,
frame.width,
frame.height,
image::ColorType::Rgba8.into(),
)?;
Ok(())
}
impl Entity for RootView {
type Event = ();
}
impl View for RootView {
fn ui_name() -> &'static str {
"RootView"
}
fn render(&self, _: &AppContext) -> Box<dyn Element> {
let button_color = ColorU::new(70, 120, 200, 255);
let status_msg = self
.last_capture_msg
.lock()
.ok()
.and_then(|guard| guard.clone())
.unwrap_or_else(|| "Click the button below to write a frame".to_string());
Stack::new()
// Dark background
.with_child(
Rect::new()
.with_background_color(ColorU::new(40, 40, 45, 255))
.finish(),
)
// Content: Colorful squares arranged vertically
.with_child(
Align::new(
Container::new(
Stack::new()
// Title text
.with_child(
Container::new(
Text::new_inline(
"Frame Capture Test".to_string(),
self.font_family,
28.0,
)
.with_color(ColorU::white())
.finish(),
)
.with_padding(Padding::uniform(16.0))
.finish(),
)
// Subtitle
.with_child(
Container::new(
Text::new_inline(
"WarpUI rendering sample with clickable capture button"
.to_string(),
self.font_family,
16.0,
)
.with_color(ColorU::new(200, 200, 200, 255))
.finish(),
)
.with_padding(Padding::uniform(12.0))
.finish(),
)
// Toast / status line
.with_child(
Container::new(
Text::new_inline(status_msg, self.font_family, 14.0)
.with_color(ColorU::new(180, 220, 180, 255))
.finish(),
)
.with_padding(Padding::uniform(12.0))
.finish(),
)
// Red square
.with_child(
Container::new(
ConstrainedBox::new(
Rect::new()
.with_background_color(ColorU::new(255, 100, 100, 255))
.finish(),
)
.with_width(150.0)
.with_height(150.0)
.finish(),
)
.with_padding(Padding::uniform(15.0))
.finish(),
)
// Green square
.with_child(
Container::new(
ConstrainedBox::new(
Rect::new()
.with_background_color(ColorU::new(100, 255, 100, 255))
.finish(),
)
.with_width(150.0)
.with_height(150.0)
.finish(),
)
.with_padding(Padding::uniform(15.0))
.finish(),
)
// Blue square
.with_child(
Container::new(
ConstrainedBox::new(
Rect::new()
.with_background_color(ColorU::new(100, 100, 255, 255))
.finish(),
)
.with_width(150.0)
.with_height(150.0)
.finish(),
)
.with_padding(Padding::uniform(15.0))
.finish(),
)
// Orange square
.with_child(
Container::new(
ConstrainedBox::new(
Rect::new()
.with_background_color(ColorU::new(255, 165, 0, 255))
.finish(),
)
.with_width(150.0)
.with_height(150.0)
.finish(),
)
.with_padding(Padding::uniform(15.0))
.finish(),
)
// Capture button (purple square)
.with_child(
Container::new(
EventHandler::new(
ConstrainedBox::new(
Stack::new()
// Button background
.with_child(
Rect::new()
.with_background_color(button_color)
.finish(),
)
// Button label
.with_child(
Align::new(
Text::new_inline(
"Write Frame to File System"
.to_string(),
self.font_family,
16.0,
)
.with_color(ColorU::white())
.finish(),
)
.finish(),
)
.finish(),
)
.with_width(200.0)
.with_height(80.0)
.finish(),
)
.on_left_mouse_down(|ctx, _, _| {
ctx.dispatch_typed_action(RootViewAction::CaptureFrame);
DispatchEventResult::StopPropagation
})
.finish(),
)
.with_padding(Padding::uniform(15.0))
.finish(),
)
.finish(),
)
.with_padding(Padding::uniform(40.0))
.finish(),
)
.finish(),
)
.finish()
}
}
impl TypedActionView for RootView {
type Action = RootViewAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action {
RootViewAction::CaptureFrame => self.request_capture(ctx),
}
}
}