//! A viewer demo: a mock playback clock drives the transport bar and (on //! macOS) a generated test frame fills the picture area every tick. //! //! Run with `cargo run -p gpui_widgets --example viewer`. use gpui::timeline::{Frame, FrameRate}; use gpui::{ App, Bounds, Context, Entity, Render, Window, WindowBounds, WindowOptions, div, prelude::*, px, size, }; use gpui_widgets::viewer::{PlaybackClock, ViewerEvent, ViewerWidget}; #[cfg(target_os = "macos")] mod frame_gen { use core_video::pixel_buffer::CVPixelBuffer; use std::ffi::c_void; const WIDTH: u32 = 1280; const HEIGHT: u32 = 720; /// Generates cheap test frames (a moving hue band) into CVPixelBuffers. pub struct FrameGen { frame: u64, } impl FrameGen { pub fn new() -> Self { Self { frame: 0 } } pub fn next(&mut self) -> CVPixelBuffer { let mut bytes = vec![0u8; (WIDTH * HEIGHT * 4) as usize]; for y in 0..HEIGHT { for x in 0..WIDTH { let i = ((y * WIDTH + x) * 4) as usize; let t = self.frame as f32 / 60.0; let band = if (x as f32 / 128.0 + t * 8.0).fract() < 0.5 { 1.0 } else { 0.35 }; bytes[i] = (255.0f32 * band).round() as u8; bytes[i + 1] = (255.0f32 * band * (y as f32 / HEIGHT as f32)).round() as u8; bytes[i + 2] = (255.0f32 * band * (1.0 - y as f32 / HEIGHT as f32)).round() as u8; bytes[i + 3] = 255; } } let callback_ref: Box> = Box::new(bytes); let release_con = Box::into_raw(callback_ref) as *mut c_void; self.frame += 1; unsafe { CVPixelBuffer::new_with_bytes( 0x42475241, // kCVPixelFormatType_32BGRA WIDTH as usize, HEIGHT as usize, release_con as *mut c_void, (WIDTH * 4) as usize, free_bytes, release_con, None, ) } .expect("failed to create test pixel buffer") } } extern "C" fn free_bytes(release_ref_con: *mut c_void, _base: *const *const c_void) { if !release_ref_con.is_null() { unsafe { drop(Box::from_raw(release_ref_con as *mut Vec)); } } } } struct MockClock { frame: Frame, playing: bool, rate: FrameRate, } impl PlaybackClock for MockClock { fn current_frame(&self) -> Frame { self.frame } fn is_playing(&self) -> bool { self.playing } fn frame_rate(&self) -> FrameRate { self.rate } } struct Example { clock: Entity, viewer: Entity>, #[cfg(target_os = "macos")] frame_gen: frame_gen::FrameGen, } impl Example { fn new(window: &mut Window, cx: &mut Context) -> Self { let clock = cx.new(|_| MockClock { frame: Frame(0), playing: true, rate: FrameRate::new(30, 1), }); let viewer = cx.new(|cx| ViewerWidget::new(1, clock.clone(), window, cx)); cx.subscribe( &viewer, |_this: &mut Self, _v: Entity>, event: &ViewerEvent, _cx| { println!("viewer request: {event:?}"); }, ) .detach(); let this = cx.weak_entity(); window .spawn(cx, async move |cx: &mut gpui::AsyncWindowContext| { loop { cx.background_executor() .timer(std::time::Duration::from_millis(16)) .await; let _ = cx.update(|_window, app| { if let Some(this) = this.upgrade() { this.update(app, |this, cx| this.tick(cx)); } }); } }) .detach(); Self { clock, viewer, #[cfg(target_os = "macos")] frame_gen: frame_gen::FrameGen::new(), } } fn tick(&mut self, cx: &mut Context) { // Simulate the engine: advance the clock while playing. self.clock.update(cx, |clock, _cx| { if clock.playing { clock.frame = Frame((clock.frame.0 + 1) % (30 * 60 * 5)); } }); // Feed a fresh frame (macOS only). #[cfg(target_os = "macos")] { let buffer = self.frame_gen.next(); self.viewer.update(cx, |viewer, cx| { viewer.set_frame_source(Some(buffer.into()), cx) }); } cx.notify(); } } impl Render for Example { fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { div().size_full().child(self.viewer.clone()) } } fn main() { gpui_platform::application().run(|cx: &mut App| { cx.init_colors(); let bounds = Bounds::centered(None, size(px(960.0), px(540.0)), cx); cx.open_window( WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), ..Default::default() }, |window, cx| cx.new(|cx| Example::new(window, cx)), ) .expect("Failed to open window"); cx.activate(true); cx.on_window_closed(|cx, _| { if cx.windows().is_empty() { cx.quit(); } }) .detach(); }); }