diff --git a/crates/bootstrap/Cargo.toml b/crates/bootstrap/Cargo.toml index c7d2054c..4a860be1 100644 --- a/crates/bootstrap/Cargo.toml +++ b/crates/bootstrap/Cargo.toml @@ -20,5 +20,6 @@ log = "0.4.17" pollster = "0.3.0" profiling = "1.0.6" tracy-client = { version = "0.15.1", optional = true } + +winit = "0.30.0" wgpu = "0.20.1" -winit = "0.29.2" diff --git a/crates/bootstrap/src/lib.rs b/crates/bootstrap/src/lib.rs index db8bfd4e..3e2fc618 100644 --- a/crates/bootstrap/src/lib.rs +++ b/crates/bootstrap/src/lib.rs @@ -3,14 +3,17 @@ mod custom_texture; use std::fmt::Write; use std::time::Instant; -use winit::dpi::LogicalSize; -use winit::event::{Event, WindowEvent}; -use winit::event_loop::{ControlFlow, EventLoop}; -use winit::window::WindowBuilder; +use winit::{ + application::ApplicationHandler, + event::{StartCause, WindowEvent}, + event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, +}; +use winit::window::{Window, WindowAttributes, WindowId}; use yakui::font::{Font, FontSettings, Fonts}; use yakui::paint::{Texture, TextureFilter, TextureFormat}; -use yakui::{ManagedTextureId, Rect, TextureId, UVec2, Vec2}; +use yakui::{ManagedTextureId, Rect, TextureId, UVec2, Vec2, Yakui}; +use yakui_app::Graphics; const MONKEY_PNG: &[u8] = include_bytes!("../assets/monkey.png"); const MONKEY_BLURRED_PNG: &[u8] = include_bytes!("../assets/monkey-blurred.png"); @@ -34,7 +37,154 @@ pub struct ExampleState { /// `TextureId` represents either a managed texture or a texture owned by /// the renderer. This image is generated in `custom_texture.rs` and /// uploaded with wgpu directly. - pub custom: TextureId, + pub custom: Option, +} + +struct App { + state: ExampleState, + yak: Yakui, + + attributes: WindowAttributes, + start: Instant, + + window: Option, + app: Option, + + body: T, +} + +impl ApplicationHandler for App { + // This is a common indicator that you can create a window. + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let window = event_loop.create_window(self.attributes.clone()).unwrap(); + window.set_ime_allowed(true); + + let sample_count = get_sample_count(); + + let mut app = pollster::block_on(yakui_app::Graphics::new(&window, sample_count)); + + // By default, yakui_winit will measure the system's scale factor and pass + // it to yakui. + // + // Sometimes, it might be desirable to scale the UI by a different factor, + // like if your game has a "UI scale" option, if you're writing tests, or + // you want to ensure your widgets work at a different scale. + // + // In these examples, setting the YAKUI_FORCE_SCALE environment variable to + // a number will override the automatic scaling. + if let Some(scale) = get_scale_override() { + app.window_mut().set_automatic_scale_factor(false); + self.yak.set_scale_factor(scale); + } + + // In these examples, set YAKUI_INSET to force the UI to be contained within + // a sub-viewport with the given edge inset on all sides. + let inset = get_inset_override(); + if inset.is_some() { + app.window_mut().set_automatic_viewport(false); + } + + let custom = app.renderer.add_texture( + custom_texture::generate(&app.device, &app.queue), + wgpu::FilterMode::Nearest, + wgpu::FilterMode::Nearest, + wgpu::FilterMode::Nearest, + wgpu::AddressMode::ClampToEdge, + ); + self.state.custom = Some(custom); + + self.app = Some(app); + self.window = Some(window); + } + + fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: winit::event::StartCause) { + if let Some(app) = self.app.as_mut() { + if cause == StartCause::Init { + app.is_init = true; + } else { + app.is_init = false; + } + } + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { + if self + .app + .as_mut() + .unwrap() + .handle_window_event(&mut self.yak, &event, event_loop) + { + return; + } + + // Handle window event. + match event { + WindowEvent::RedrawRequested => { + self.state.time = (Instant::now() - self.start).as_secs_f32(); + + { + profiling::scope!("Build UI"); + + // Every frame, call yak.start() to begin building the UI for + // this frame. Any yakui widget calls that happen on this thread + // between start() and finish() will be applied to this yakui + // State. + self.yak.start(); + + // Call out to the body of the program, passing in a bit of + // shared state that all the examples can use. + self.body.run(&mut self.state); + + // Finish building the UI and compute this frame's layout. + self.yak.finish(); + } + + // The example graphics abstraction calls yak.paint() to get + // access to the underlying PaintDom, which holds all the state + // about how to paint widgets. + self.app.as_mut().unwrap().paint(&mut self.yak, { + let bg = yakui::colors::BACKGROUND_1.to_linear(); + wgpu::Color { + r: bg.x.into(), + g: bg.y.into(), + b: bg.z.into(), + a: 1.0, + } + }); + + profiling::finish_frame!(); + + self.window.as_ref().unwrap().request_redraw(); + } + + WindowEvent::MouseInput { state, button, .. } => { + // This print is a handy way to show which mouse events are + // handled by yakui, and which ones will make it to the + // underlying application. + if button == winit::event::MouseButton::Left { + println!("Left mouse button {state:?}"); + } + } + + WindowEvent::Resized(size) => { + let inset = get_inset_override(); + if let Some(inset) = inset { + let size = Vec2::new(size.width as f32, size.height as f32); + self.yak.set_unscaled_viewport(Rect::from_pos_size( + Vec2::splat(inset), + size - Vec2::splat(inset * 2.0), + )); + } + } + + _ => (), + } + } } pub trait ExampleBody: 'static { @@ -61,10 +211,10 @@ pub fn start(body: impl ExampleBody) { init_logging(); - pollster::block_on(run(body)); + run(body); } -async fn run(body: impl ExampleBody) { +fn run(body: impl ExampleBody) { let mut title = "yakui demo".to_owned(); if let Some(scale) = get_scale_override() { @@ -73,55 +223,16 @@ async fn run(body: impl ExampleBody) { // Normal winit setup for an EventLoop and Window. let event_loop = EventLoop::new().unwrap(); - let window = WindowBuilder::new() - .with_title(title) - .with_inner_size(LogicalSize::new(800.0, 600.0)) - .build(&event_loop) - .unwrap(); - - window.set_ime_allowed(true); - - let sample_count = get_sample_count(); - - // yakui_app has a helper for setting up winit and wgpu. - let mut app = yakui_app::Graphics::new(&window, sample_count).await; + let window_attribute = Window::default_attributes().with_title(title); // Create our yakui state. This is where our UI will be built, laid out, and // calculations for painting will happen. let mut yak = yakui::Yakui::new(); - // By default, yakui_winit will measure the system's scale factor and pass - // it to yakui. - // - // Sometimes, it might be desirable to scale the UI by a different factor, - // like if your game has a "UI scale" option, if you're writing tests, or - // you want to ensure your widgets work at a different scale. - // - // In these examples, setting the YAKUI_FORCE_SCALE environment variable to - // a number will override the automatic scaling. - if let Some(scale) = get_scale_override() { - app.window_mut().set_automatic_scale_factor(false); - yak.set_scale_factor(scale); - } - - // In these examples, set YAKUI_INSET to force the UI to be contained within - // a sub-viewport with the given edge inset on all sides. - let inset = get_inset_override(); - if inset.is_some() { - app.window_mut().set_automatic_viewport(false); - } - // Preload some textures for the examples to use. let monkey = yak.add_texture(load_texture(MONKEY_PNG, TextureFilter::Linear)); let monkey_blurred = yak.add_texture(load_texture(MONKEY_BLURRED_PNG, TextureFilter::Linear)); let brown_inlay = yak.add_texture(load_texture(BROWN_INLAY_PNG, TextureFilter::Nearest)); - let custom = app.renderer.add_texture( - custom_texture::generate(&app.device, &app.queue), - wgpu::FilterMode::Nearest, - wgpu::FilterMode::Nearest, - wgpu::FilterMode::Nearest, - wgpu::AddressMode::ClampToEdge, - ); // Add a custom font for some of the examples. let fonts = yak.dom().get_global_or_init(Fonts::default); @@ -134,89 +245,25 @@ async fn run(body: impl ExampleBody) { fonts.add(font, Some("monospace")); // Set up some default state that we'll modify later. - let mut state = ExampleState { - time: 0.0, - monkey, - monkey_blurred, - brown_inlay, - custom, + let mut app = App { + yak, + attributes: window_attribute, + start: Instant::now(), + state: ExampleState { + time: 0.0, + monkey, + monkey_blurred, + brown_inlay, + custom: None, + }, + window: None, + app: None, + body, }; - let start = Instant::now(); - event_loop.set_control_flow(ControlFlow::Poll); - event_loop - .run(move |event, elwt| { - if app.handle_event(&mut yak, &event, elwt) { - return; - } - - match event { - Event::AboutToWait => { - state.time = (Instant::now() - start).as_secs_f32(); - - { - profiling::scope!("Build UI"); - - // Every frame, call yak.start() to begin building the UI for - // this frame. Any yakui widget calls that happen on this thread - // between start() and finish() will be applied to this yakui - // State. - yak.start(); - // Call out to the body of the program, passing in a bit of - // shared state that all the examples can use. - body.run(&mut state); - - // Finish building the UI and compute this frame's layout. - yak.finish(); - } - - // The example graphics abstraction calls yak.paint() to get - // access to the underlying PaintDom, which holds all the state - // about how to paint widgets. - app.paint(&mut yak, { - let bg = yakui::colors::BACKGROUND_1.to_linear(); - wgpu::Color { - r: bg.x.into(), - g: bg.y.into(), - b: bg.z.into(), - a: 1.0, - } - }); - - profiling::finish_frame!(); - } - - Event::WindowEvent { - event: WindowEvent::MouseInput { state, button, .. }, - .. - } => { - // This print is a handy way to show which mouse events are - // handled by yakui, and which ones will make it to the - // underlying application. - if button == winit::event::MouseButton::Left { - println!("Left mouse button {state:?}"); - } - } - - Event::WindowEvent { - event: WindowEvent::Resized(size), - .. - } => { - if let Some(inset) = inset { - let size = Vec2::new(size.width as f32, size.height as f32); - yak.set_unscaled_viewport(Rect::from_pos_size( - Vec2::splat(inset), - size - Vec2::splat(inset * 2.0), - )); - } - } - - _ => (), - } - }) - .unwrap(); + event_loop.run_app(&mut app).unwrap(); } /// This function takes some bytes and turns it into a yakui `Texture` object so diff --git a/crates/demo/Cargo.toml b/crates/demo/Cargo.toml index 04959007..57ce5965 100644 --- a/crates/demo/Cargo.toml +++ b/crates/demo/Cargo.toml @@ -15,7 +15,7 @@ env_logger = "0.10.0" log = "0.4.17" pollster = "0.3.0" wgpu = { version = "0.20.1", features = ["webgl"] } -winit = "0.29.2" +winit = "0.30.0" [target.'cfg(target_arch = "wasm32")'.dependencies] console_log = "0.2.1" diff --git a/crates/demo/src/main.rs b/crates/demo/src/main.rs index 0ec73c0c..b1e3125c 100644 --- a/crates/demo/src/main.rs +++ b/crates/demo/src/main.rs @@ -1,5 +1,5 @@ use winit::{ - event::{Event, WindowEvent}, + event::{Event, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::Window, }; @@ -25,35 +25,45 @@ async fn run(event_loop: EventLoop<()>, window: Window) { event_loop.set_control_flow(ControlFlow::Poll); event_loop - .run(move |event, elwt| { - if graphics.handle_event(&mut yak, &event, elwt) { - return; + .run(move |event, event_loop| match event { + Event::AboutToWait => { + window.request_redraw(); } - match event { - Event::AboutToWait => { - window.request_redraw(); + Event::NewEvents(cause) => { + if cause == StartCause::Init { + graphics.is_init = true; + } else { + graphics.is_init = false; } + } + + Event::WindowEvent { + event: WindowEvent::RedrawRequested { .. }, + .. + } => { + yak.start(); + app(); + yak.finish(); - Event::WindowEvent { - event: WindowEvent::RedrawRequested { .. }, - .. - } => { - yak.start(); - app(); - yak.finish(); + graphics.paint(&mut yak, wgpu::Color::BLACK); + } - graphics.paint(&mut yak, wgpu::Color::BLACK); + Event::WindowEvent { event, .. } => { + if graphics.handle_window_event(&mut yak, &event, event_loop) { + return; } - _ => (), } + _ => (), }) .unwrap(); } fn main() { let event_loop = EventLoop::new().unwrap(); - let window = winit::window::Window::new(&event_loop).unwrap(); + let window = event_loop + .create_window(winit::window::Window::default_attributes()) + .unwrap(); #[cfg(not(target_arch = "wasm32"))] { diff --git a/crates/yakui-app/Cargo.toml b/crates/yakui-app/Cargo.toml index a1aaa3a3..671caf01 100644 --- a/crates/yakui-app/Cargo.toml +++ b/crates/yakui-app/Cargo.toml @@ -17,5 +17,5 @@ yakui-winit = { path = "../yakui-winit" } yakui-wgpu = { path = "../yakui-wgpu" } profiling = { version = "1.0.6", optional = true } +winit = { version = "0.30.0" } wgpu = "0.20.1" -winit = { version = "0.29.2", features = ["rwh_05"] } diff --git a/crates/yakui-app/src/lib.rs b/crates/yakui-app/src/lib.rs index 0add8954..2ff7ee7e 100644 --- a/crates/yakui-app/src/lib.rs +++ b/crates/yakui-app/src/lib.rs @@ -1,11 +1,6 @@ mod multisampling; -use winit::{ - dpi::PhysicalSize, - event::{Event, StartCause, WindowEvent}, - event_loop::EventLoopWindowTarget, - window::Window, -}; +use winit::{dpi::PhysicalSize, event::WindowEvent, event_loop::ActiveEventLoop, window::Window}; use multisampling::Multisampling; @@ -23,9 +18,8 @@ pub struct Graphics { window: yakui_winit::YakuiWinit, pub renderer: yakui_wgpu::YakuiWgpu, - /// Tracks whether winit is still initializing - is_init: bool, + pub is_init: bool, } impl Graphics { @@ -110,7 +104,6 @@ impl Graphics { renderer, window, - is_init: true, } } @@ -184,35 +177,25 @@ impl Graphics { output.present(); } - pub fn handle_event( + pub fn handle_window_event( &mut self, yak: &mut yakui::Yakui, - event: &Event, - elwt: &EventLoopWindowTarget, + event: &WindowEvent, + event_loop: &ActiveEventLoop, ) -> bool { // yakui_winit will return whether it handled an event. This means that // yakui believes it should handle that event exclusively, like if a // button in the UI was clicked. - if self.window.handle_event(yak, event) { + if self.window.handle_window_event(yak, event) { return true; } match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - elwt.exit(); - } - - Event::NewEvents(cause) => { - self.is_init = *cause == StartCause::Init; + WindowEvent::CloseRequested => { + event_loop.exit(); } - Event::WindowEvent { - event: WindowEvent::Resized(size), - .. - } => { + WindowEvent::Resized(size) => { // Ignore any resize events that happen during Winit's // initialization in order to avoid racing the wgpu swapchain // and causing issues. diff --git a/crates/yakui-vulkan/Cargo.toml b/crates/yakui-vulkan/Cargo.toml index 88182ebb..cec67cf5 100644 --- a/crates/yakui-vulkan/Cargo.toml +++ b/crates/yakui-vulkan/Cargo.toml @@ -19,4 +19,4 @@ ash = { version = "0.38", default-features = false, features = ["loaded"] } ash-window = "0.13" image = "0.24.5" raw-window-handle = "0.6.0" -winit = "0.29.2" +winit = "0.30.0" diff --git a/crates/yakui-vulkan/examples/demo.rs b/crates/yakui-vulkan/examples/demo.rs index 0336c9bf..fab0ac1e 100644 --- a/crates/yakui-vulkan/examples/demo.rs +++ b/crates/yakui-vulkan/examples/demo.rs @@ -68,7 +68,7 @@ fn main() { let mut winit_initializing = true; event_loop.set_control_flow(ControlFlow::Poll); - _ = event_loop.run(|event, elwt| match event { + _ = event_loop.run(|event, event_loop| match event { Event::WindowEvent { event: WindowEvent::CloseRequested @@ -82,7 +82,7 @@ fn main() { .. }, .. - } => elwt.exit(), + } => event_loop.exit(), Event::NewEvents(cause) => { winit_initializing = cause == winit::event::StartCause::Init; @@ -708,17 +708,19 @@ fn init_winit( window_width: u32, window_height: u32, ) -> (winit::event_loop::EventLoop<()>, winit::window::Window) { - use winit::{event_loop::EventLoopBuilder, window::WindowBuilder}; - - let event_loop = EventLoopBuilder::new().build().unwrap(); - - let window = WindowBuilder::new() - .with_title("Yakui Vulkan - Test") - .with_inner_size(winit::dpi::LogicalSize::new( - f64::from(window_width), - f64::from(window_height), - )) - .build(&event_loop) + use winit::{event_loop::EventLoop, window::Window}; + + let event_loop = EventLoop::new().unwrap(); + + let window = event_loop + .create_window( + Window::default_attributes() + .with_title("Yakui Vulkan - Test") + .with_inner_size(winit::dpi::LogicalSize::new( + f64::from(window_width), + f64::from(window_height), + )), + ) .unwrap(); (event_loop, window) } diff --git a/crates/yakui-winit/Cargo.toml b/crates/yakui-winit/Cargo.toml index 3103cc08..fc707131 100644 --- a/crates/yakui-winit/Cargo.toml +++ b/crates/yakui-winit/Cargo.toml @@ -12,4 +12,4 @@ edition = "2021" yakui-core = { path = "../yakui-core", version = "0.2.0" } # TODO: Disable all default features once supported (https://github.com/rust-windowing/winit/issues/3174) -winit = { version = "0.29.2", default-features = false, features = ["x11"] } +winit = { version = "0.30.0", default-features = false, features = ["x11"] } diff --git a/crates/yakui-winit/src/lib.rs b/crates/yakui-winit/src/lib.rs index 533be1c2..2a201936 100644 --- a/crates/yakui-winit/src/lib.rs +++ b/crates/yakui-winit/src/lib.rs @@ -1,10 +1,7 @@ mod keys; use winit::dpi::PhysicalSize; -use winit::event::{ - ElementState, Event as WinitEvent, MouseButton as WinitMouseButton, MouseScrollDelta, - WindowEvent, -}; +use winit::event::{ElementState, MouseButton as WinitMouseButton, MouseScrollDelta, WindowEvent}; use winit::window::Window; use yakui_core::event::Event; use yakui_core::geometry::{Rect, Vec2}; @@ -53,10 +50,10 @@ impl YakuiWinit { self.auto_viewport = enabled; } - pub fn handle_event( + pub fn handle_window_event( &mut self, state: &mut yakui_core::Yakui, - event: &WinitEvent, + event: &WindowEvent, ) -> bool { if let Some(init) = self.init.take() { let size = Vec2::new(init.size.width as f32, init.size.height as f32); @@ -72,10 +69,7 @@ impl YakuiWinit { } match event { - WinitEvent::WindowEvent { - event: WindowEvent::Resized(size), - .. - } => { + WindowEvent::Resized(size) => { let size = Vec2::new(size.width as f32, size.height as f32); state.set_surface_size(size); @@ -85,34 +79,22 @@ impl YakuiWinit { false } - WinitEvent::WindowEvent { - event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, - .. - } => { + WindowEvent::ScaleFactorChanged { scale_factor, .. } => { if self.auto_scale { state.set_scale_factor(*scale_factor as f32) } false } - WinitEvent::WindowEvent { - event: WindowEvent::CursorMoved { position, .. }, - .. - } => { + WindowEvent::CursorMoved { position, .. } => { let pos = Vec2::new(position.x as f32, position.y as f32); state.handle_event(Event::CursorMoved(Some(pos))) } - WinitEvent::WindowEvent { - event: WindowEvent::CursorLeft { .. }, - .. - } => state.handle_event(Event::CursorMoved(None)), - WinitEvent::WindowEvent { - event: - WindowEvent::MouseInput { - button, - state: button_state, - .. - }, + WindowEvent::CursorLeft { .. } => state.handle_event(Event::CursorMoved(None)), + + WindowEvent::MouseInput { + button, + state: button_state, .. } => { let button = match button { @@ -129,10 +111,7 @@ impl YakuiWinit { state.handle_event(Event::MouseButtonChanged { button, down }) } - WinitEvent::WindowEvent { - event: WindowEvent::MouseWheel { delta, .. }, - .. - } => { + WindowEvent::MouseWheel { delta, .. } => { // Observed logical pixels per scroll wheel increment in Windows on Chrome const LINE_HEIGHT: f32 = 100.0 / 3.0; @@ -149,14 +128,10 @@ impl YakuiWinit { state.handle_event(Event::MouseScroll { delta }) } - WinitEvent::WindowEvent { - event: WindowEvent::ModifiersChanged(mods), - .. - } => state.handle_event(Event::ModifiersChanged(from_winit_modifiers(mods.state()))), - WinitEvent::WindowEvent { - event: WindowEvent::KeyboardInput { event, .. }, - .. - } => { + WindowEvent::ModifiersChanged(mods) => { + state.handle_event(Event::ModifiersChanged(from_winit_modifiers(mods.state()))) + } + WindowEvent::KeyboardInput { event, .. } => { if event.state == ElementState::Pressed { if let Some(text) = event.text.as_ref() { for c in text.chars() { @@ -180,10 +155,7 @@ impl YakuiWinit { } } - WinitEvent::WindowEvent { - event: WindowEvent::Ime(winit::event::Ime::Commit(text)), - .. - } => { + WindowEvent::Ime(winit::event::Ime::Commit(text)) => { for c in text.chars() { state.handle_event(Event::TextInput(c)); } diff --git a/crates/yakui/examples/custom_image.rs b/crates/yakui/examples/custom_image.rs index 0c65861b..7009e8ad 100644 --- a/crates/yakui/examples/custom_image.rs +++ b/crates/yakui/examples/custom_image.rs @@ -3,7 +3,7 @@ use yakui::image; use bootstrap::ExampleState; pub fn run(state: &mut ExampleState) { - image(state.custom, [512.0, 512.0]); + image(state.custom.unwrap(), [512.0, 512.0]); } fn main() {