pixel_widgets/
sandbox.rs

1use winit::{
2    event::{Event, WindowEvent},
3    event_loop::{ControlFlow, EventLoop},
4    window::{Window, WindowBuilder},
5};
6
7use crate::prelude::*;
8use std::sync::{Arc, Mutex};
9use std::task::Wake;
10use winit::event_loop::EventLoopProxy;
11
12/// Sandbox for quick prototyping of pixel widgets applications
13pub struct Sandbox<M: 'static + Component> {
14    /// The `Ui` being used in the sandbox
15    pub ui: crate::backend::wgpu::Ui<M>,
16    event_loop: Option<EventLoop<PollUi>>,
17    surface: wgpu::Surface,
18    #[allow(unused)]
19    adapter: wgpu::Adapter,
20    device: wgpu::Device,
21    queue: wgpu::Queue,
22    surface_config: wgpu::SurfaceConfiguration,
23    window: Window,
24}
25
26#[derive(Clone)]
27struct PollUi;
28
29struct Waker<T: 'static> {
30    message: T,
31    event_loop: Mutex<EventLoopProxy<T>>,
32}
33
34impl<T: 'static + Clone> Wake for Waker<T> {
35    fn wake(self: Arc<Self>) {
36        self.event_loop.lock().unwrap().send_event(self.message.clone()).ok();
37    }
38}
39
40impl<T> Sandbox<T>
41where
42    T: 'static + Component,
43{
44    /// Construct a new `Sandbox` with a root component, style and window builder.
45    /// The `Sandbox` will finish building the window and setup the graphics.
46    /// To start the main event loop, call [`run()`](#method.run) on the result.
47    pub async fn new<S, E>(root_component: T, style: S, window: WindowBuilder) -> anyhow::Result<Self>
48    where
49        S: TryInto<Style, Error = E>,
50        anyhow::Error: From<E>,
51    {
52        let event_loop = EventLoop::with_user_event();
53        let window = window.build(&event_loop).unwrap();
54        let size = window.inner_size();
55
56        let swapchain_format = wgpu::TextureFormat::Bgra8Unorm;
57
58        let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY);
59        let surface = unsafe { instance.create_surface(&window) };
60        let adapter = instance
61            .request_adapter(&wgpu::RequestAdapterOptions {
62                power_preference: wgpu::PowerPreference::LowPower,
63                force_fallback_adapter: false,
64                compatible_surface: Some(&surface),
65            })
66            .await
67            .expect("Failed to find an appropriate adapter");
68
69        // Create the logical device and command queue
70        let trace_dir = std::env::var("WGPU_TRACE");
71        let (device, queue) = adapter
72            .request_device(
73                &wgpu::DeviceDescriptor {
74                    label: None,
75                    features: Default::default(),
76                    limits: wgpu::Limits::default(),
77                },
78                trace_dir.ok().as_ref().map(std::path::Path::new),
79            )
80            .await
81            .expect("Failed retrieve device and queue");
82
83        let surface_config = wgpu::SurfaceConfiguration {
84            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
85            format: swapchain_format,
86            width: size.width,
87            height: size.height,
88            present_mode: wgpu::PresentMode::Mailbox,
89        };
90
91        surface.configure(&device, &surface_config);
92
93        let ui = crate::backend::wgpu::Ui::new(
94            root_component,
95            Rectangle::from_wh(size.width as f32, size.height as f32),
96            window.scale_factor() as f32,
97            style,
98            swapchain_format,
99            &device,
100        )?;
101
102        Ok(Sandbox {
103            ui,
104            event_loop: Some(event_loop),
105            surface,
106            adapter,
107            device,
108            queue,
109            surface_config,
110            window,
111        })
112    }
113
114    /// Update the root component with a message.
115    /// Returns any output messages from the root component.
116    pub fn update(&mut self, message: T::Message) -> Vec<T::Output> {
117        let waker = self
118            .event_loop
119            .as_ref()
120            .map(|event_loop| {
121                std::task::Waker::from(Arc::new(Waker {
122                    message: PollUi,
123                    event_loop: Mutex::new(event_loop.create_proxy()),
124                }))
125            })
126            .unwrap();
127
128        self.ui.update_and_poll(message, waker)
129    }
130
131    /// Run the application
132    pub async fn run(mut self) {
133        let event_loop = self.event_loop.take().unwrap();
134        let waker = std::task::Waker::from(Arc::new(Waker {
135            message: PollUi,
136            event_loop: Mutex::new(event_loop.create_proxy()),
137        }));
138
139        event_loop.run(move |event, _, control_flow| {
140            *control_flow = ControlFlow::Wait;
141            match event {
142                Event::UserEvent(_) => {
143                    self.ui.poll(waker.clone());
144                }
145                Event::WindowEvent {
146                    event: WindowEvent::Resized(size),
147                    ..
148                } => {
149                    // Recreate the swap chain with the new size
150                    self.surface_config.width = size.width;
151                    self.surface_config.height = size.height;
152                    self.surface.configure(&self.device, &self.surface_config);
153                    self.ui
154                        .resize(Rectangle::from_wh(size.width as f32, size.height as f32));
155                }
156                Event::RedrawRequested(_) => {
157                    let frame = self
158                        .surface
159                        .get_current_texture()
160                        .expect("Failed to acquire next swap chain texture");
161                    let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
162                    let mut encoder = self
163                        .device
164                        .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
165                    {
166                        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
167                            label: None,
168                            color_attachments: &[wgpu::RenderPassColorAttachment {
169                                view: &view,
170                                resolve_target: None,
171                                ops: wgpu::Operations {
172                                    load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
173                                    store: true,
174                                },
175                            }],
176                            depth_stencil_attachment: None,
177                        });
178
179                        self.ui.draw(&self.device, &self.queue, &mut pass);
180                    }
181
182                    self.queue.submit(Some(encoder.finish()));
183                    frame.present();
184                }
185                Event::WindowEvent {
186                    event: WindowEvent::CloseRequested,
187                    ..
188                } => *control_flow = ControlFlow::Exit,
189                other => {
190                    if let Some(event) = crate::backend::winit::convert_event(other) {
191                        self.ui.handle_event_and_poll(event, waker.clone());
192                    }
193                }
194            }
195
196            if self.ui.needs_redraw() {
197                self.window.request_redraw();
198            }
199        });
200    }
201}