diff --git a/.github/workflows/compile_lambda_rs.yml b/.github/workflows/compile_lambda_rs.yml index fc6851d2..44dbf3fa 100644 --- a/.github/workflows/compile_lambda_rs.yml +++ b/.github/workflows/compile_lambda_rs.yml @@ -66,19 +66,5 @@ jobs: rustup toolchain install ${{ matrix.rustup-toolchain }} rustup default ${{ matrix.rustup-toolchain }} - - name: Build Lambda & other default workspace members. + - name: Build Lambda using platform specific features. run: cargo test --all --features ${{ matrix.features }} --no-default-features - - - uses: actions/setup-ruby@v1 - - name: Send Webhook Notification for build status. - if: ${{ github.ref == 'refs/heads/main' }} - env: - JOB_STATUS: ${{ job.status }} - WEBHOOK_URL: ${{ secrets.LAMBDA_BUILD_WEBHOOK }} - HOOK_OS_NAME: ${{ runner.os }} - WORKFLOW_NAME: ${{ github.workflow }} - JOB_ID: ${{ github.job }} - run: | - git clone https://github.com/dhinakg/github-actions-discord-webhook.git webhook - bash webhook/send.sh $JOB_STATUS $WEBHOOK_URL - shell: bash diff --git a/Cargo.lock b/Cargo.lock index fc499c1b..95d03687 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,22 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ab_glyph" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe21446ad43aa56417a767f3e2f3d7c4ca522904de1dd640529a76e9c5c3b33c" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + [[package]] name = "abscissa_core" version = "0.5.2" @@ -65,6 +81,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "0.7.18" @@ -113,6 +140,12 @@ dependencies = [ "libloading", ] +[[package]] +name = "atomic_refcell" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "857253367827bd9d0fd973f0ef15506a96e79e41b0ad7aa691203a4e3214f6c8" + [[package]] name = "atty" version = "0.2.14" @@ -735,12 +768,50 @@ dependencies = [ "wio", ] +[[package]] +name = "ecolor" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b601108bca3af7650440ace4ca55b2daf52c36f2635be3587d77b16efd8d0691" + +[[package]] +name = "egui" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65a5e883a316e53866977450eecfbcac9c48109c2ab3394af29feb83fcde4ea9" +dependencies = [ + "ahash", + "epaint", + "nohash-hasher", +] + [[package]] name = "either" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "emath" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5277249c8c3430e7127e4f2c40a77485e7baf11ae132ce9b3253a8ed710df0a0" + +[[package]] +name = "epaint" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de14b65fe5e423e0058f77a8beb2c863b056d0566d6c4ce0d097aa5814cb705a" +dependencies = [ + "ab_glyph", + "ahash", + "atomic_refcell", + "ecolor", + "emath", + "nohash-hasher", + "parking_lot 0.12.1", +] + [[package]] name = "expat-sys" version = "2.1.6" @@ -1328,9 +1399,10 @@ version = "2023.1.30" [[package]] name = "lambda-rs-platform" -version = "2023.1.30" +version = "2023.2.4" dependencies = [ "cfg-if 1.0.0", + "egui", "gfx-backend-dx11", "gfx-backend-dx12", "gfx-backend-empty", @@ -1674,6 +1746,12 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.0" @@ -1815,6 +1893,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "owned_ttf_parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25e9fb15717794fae58ab55c26e044103aad13186fbb625893f9a3bbcc24228" +dependencies = [ + "ttf-parser", +] + [[package]] name = "owning_ref" version = "0.4.1" @@ -2718,6 +2805,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "ttf-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633" + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -2778,9 +2871,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wait-timeout" diff --git a/crates/lambda-rs-platform/Cargo.toml b/crates/lambda-rs-platform/Cargo.toml index f398d13d..581e4322 100644 --- a/crates/lambda-rs-platform/Cargo.toml +++ b/crates/lambda-rs-platform/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lambda-rs-platform" description = "Platform implementations for lambda-rs" -version = "2023.1.30" +version = "2023.2.4" edition = "2021" resolver = "2" license = "MIT" @@ -18,6 +18,7 @@ cfg-if = "=1.0.0" rand = "=0.8.5" obj-rs = "=0.7.0" gfx-backend-empty = "=0.9.0" +egui = "0.20.1" lambda-rs-logging = { path = "../lambda-rs-logging", version = "2023.1.30" } diff --git a/crates/lambda-rs-platform/README.md b/crates/lambda-rs-platform/README.md index 097d8d88..91924d81 100644 --- a/crates/lambda-rs-platform/README.md +++ b/crates/lambda-rs-platform/README.md @@ -6,13 +6,18 @@ Platform implementations for lambda-rs. This crate is not intended to be used di ## Platforms The following platforms are currently supported: -* Windows - * Vulkan - * DirectX 11 - * DirectX 12 -* Linux - * Vulkan - * OpenGL -* MacOS - * Metal - * Vulkan +* Rendering & Compute support + * Windows + * Vulkan + * DirectX 11 + * DirectX 12 + * Linux + * Vulkan + * OpenGL + * MacOS + * Metal + * Vulkan +* Window support + * winit +* UI support + * egui (via winit) diff --git a/crates/lambda-rs-platform/src/egui/gfx/mod.rs b/crates/lambda-rs-platform/src/egui/gfx/mod.rs new file mode 100644 index 00000000..a68852d7 --- /dev/null +++ b/crates/lambda-rs-platform/src/egui/gfx/mod.rs @@ -0,0 +1,52 @@ +//! Support module for rendering `egui` elements within our rendering +//! infrastructure. +pub enum GridDirection { + Horizontal, + Vertical, +} + +pub enum UIElement { + Button { + text: String, + width: f32, + height: f32, + on_click: Option, + }, + Grid { + rows: usize, + columns: usize, + width: f32, + height: f32, + direction: GridDirection, + children: Vec, + }, +} + +impl UIElement { + /// Renders the UI element + pub(crate) fn render(&mut self, ui_for_frame: &mut egui::Ui) { + match self { + UIElement::Button { + text, + width, + height, + on_click, + } => { + if ui_for_frame.button(text.as_str()).clicked() { + match on_click { + Some(on_click) => on_click(), + None => {} + } + } + } + UIElement::Grid { + rows, + columns, + width, + height, + direction, + children, + } => todo!(), + } + } +} diff --git a/crates/lambda-rs-platform/src/egui/mod.rs b/crates/lambda-rs-platform/src/egui/mod.rs new file mode 100644 index 00000000..9cdadb60 --- /dev/null +++ b/crates/lambda-rs-platform/src/egui/mod.rs @@ -0,0 +1,20 @@ +use egui::{ + Context, + Pos2, + RawInput, + TouchDeviceId, +}; + +pub mod gfx; +pub mod winit; + +/// A context for managing egui input & rendering. +pub struct EguiContext { + input_handler: RawInput, + context: Context, + mouse_position: Option, + mouse_button_active: bool, + current_pixels_per_point: f32, + emulate_touch_screen: bool, + active_touch_device: Option, +} diff --git a/crates/lambda-rs-platform/src/egui/winit/input.rs b/crates/lambda-rs-platform/src/egui/winit/input.rs new file mode 100644 index 00000000..b849d9f4 --- /dev/null +++ b/crates/lambda-rs-platform/src/egui/winit/input.rs @@ -0,0 +1,194 @@ +//! This module contains the code to convert winit input events to egui +//! input events. + +use egui::PointerButton; +use winit::event::{ + MouseButton, + VirtualKeyCode as WinitKey, +}; + +/// Convert a winit mouse button to an egui mouse button. +pub fn winit_to_egui_mouse_button( + button: MouseButton, +) -> Option { + return match button { + MouseButton::Left => Some(PointerButton::Primary), + MouseButton::Right => Some(PointerButton::Secondary), + MouseButton::Middle => Some(PointerButton::Middle), + MouseButton::Other(1) => Some(PointerButton::Extra1), + MouseButton::Other(2) => Some(PointerButton::Extra2), + MouseButton::Other(_) => None, + }; +} + +/// Convert a winit virtual key code to an egui key code. +pub fn winit_to_egui_key(key: WinitKey) -> Option { + return Some(match key { + WinitKey::Down => egui::Key::ArrowDown, + WinitKey::Left => egui::Key::ArrowLeft, + WinitKey::Right => egui::Key::ArrowRight, + WinitKey::Up => egui::Key::ArrowUp, + WinitKey::Escape => egui::Key::Escape, + WinitKey::Tab => egui::Key::Tab, + WinitKey::Back => egui::Key::Backspace, + WinitKey::Return => egui::Key::Enter, + WinitKey::Space => egui::Key::Space, + WinitKey::Insert => egui::Key::Insert, + WinitKey::Delete => egui::Key::Delete, + WinitKey::Home => egui::Key::Home, + WinitKey::End => egui::Key::End, + WinitKey::PageUp => egui::Key::PageUp, + WinitKey::PageDown => egui::Key::PageDown, + WinitKey::Minus => egui::Key::Minus, + WinitKey::Equals => egui::Key::PlusEquals, + WinitKey::Key0 | WinitKey::Numpad0 => egui::Key::Num0, + WinitKey::Key1 | WinitKey::Numpad1 => egui::Key::Num1, + WinitKey::Key2 | WinitKey::Numpad2 => egui::Key::Num2, + WinitKey::Key3 | WinitKey::Numpad3 => egui::Key::Num3, + WinitKey::Key4 | WinitKey::Numpad4 => egui::Key::Num4, + WinitKey::Key5 | WinitKey::Numpad5 => egui::Key::Num5, + WinitKey::Key6 | WinitKey::Numpad6 => egui::Key::Num6, + WinitKey::Key7 | WinitKey::Numpad7 => egui::Key::Num7, + WinitKey::Key8 | WinitKey::Numpad8 => egui::Key::Num8, + WinitKey::Key9 | WinitKey::Numpad9 => egui::Key::Num9, + WinitKey::A => egui::Key::A, + WinitKey::B => egui::Key::B, + WinitKey::C => egui::Key::C, + WinitKey::D => egui::Key::D, + WinitKey::E => egui::Key::E, + WinitKey::F => egui::Key::F, + WinitKey::G => egui::Key::G, + WinitKey::H => egui::Key::H, + WinitKey::I => egui::Key::I, + WinitKey::J => egui::Key::J, + WinitKey::K => egui::Key::K, + WinitKey::L => egui::Key::L, + WinitKey::M => egui::Key::M, + WinitKey::N => egui::Key::N, + WinitKey::O => egui::Key::O, + WinitKey::P => egui::Key::P, + WinitKey::Q => egui::Key::Q, + WinitKey::R => egui::Key::R, + WinitKey::S => egui::Key::S, + WinitKey::T => egui::Key::T, + WinitKey::U => egui::Key::U, + WinitKey::V => egui::Key::V, + WinitKey::W => egui::Key::W, + WinitKey::X => egui::Key::X, + WinitKey::Y => egui::Key::Y, + WinitKey::Z => egui::Key::Z, + WinitKey::F1 => egui::Key::F1, + WinitKey::F2 => egui::Key::F2, + WinitKey::F3 => egui::Key::F3, + WinitKey::F4 => egui::Key::F4, + WinitKey::F5 => egui::Key::F5, + WinitKey::F6 => egui::Key::F6, + WinitKey::F7 => egui::Key::F7, + WinitKey::F8 => egui::Key::F8, + WinitKey::F9 => egui::Key::F9, + WinitKey::F10 => egui::Key::F10, + WinitKey::F11 => egui::Key::F11, + WinitKey::F12 => egui::Key::F12, + WinitKey::F13 => egui::Key::F13, + WinitKey::F14 => egui::Key::F14, + WinitKey::F15 => egui::Key::F15, + WinitKey::F16 => egui::Key::F16, + WinitKey::F17 => egui::Key::F17, + WinitKey::F18 => egui::Key::F18, + WinitKey::F19 => egui::Key::F19, + WinitKey::F20 => egui::Key::F20, + _ => { + return None; + } + }); +} + +use winit::window::CursorIcon as WinitCursorIcon; + +/// Convert an egui mouse cursor icon to a winit mouse cursor icon. +pub fn egui_to_winit_mouse_cursor_icon( + mouse_cursor_icon: egui::CursorIcon, +) -> Option { + return match mouse_cursor_icon { + egui::CursorIcon::None => None, + egui::CursorIcon::Alias => Some(WinitCursorIcon::Alias), + egui::CursorIcon::AllScroll => Some(WinitCursorIcon::AllScroll), + egui::CursorIcon::Cell => Some(WinitCursorIcon::Cell), + egui::CursorIcon::ContextMenu => Some(WinitCursorIcon::ContextMenu), + egui::CursorIcon::Copy => Some(WinitCursorIcon::Copy), + egui::CursorIcon::Crosshair => Some(WinitCursorIcon::Crosshair), + egui::CursorIcon::Default => Some(WinitCursorIcon::Default), + egui::CursorIcon::Grab => Some(WinitCursorIcon::Grab), + egui::CursorIcon::Grabbing => Some(WinitCursorIcon::Grabbing), + egui::CursorIcon::Help => Some(WinitCursorIcon::Help), + egui::CursorIcon::Move => Some(WinitCursorIcon::Move), + egui::CursorIcon::NoDrop => Some(WinitCursorIcon::NoDrop), + egui::CursorIcon::NotAllowed => Some(WinitCursorIcon::NotAllowed), + egui::CursorIcon::PointingHand => Some(WinitCursorIcon::Hand), + egui::CursorIcon::Progress => Some(WinitCursorIcon::Progress), + egui::CursorIcon::ResizeHorizontal => Some(WinitCursorIcon::EwResize), + egui::CursorIcon::ResizeNeSw => Some(WinitCursorIcon::NeswResize), + egui::CursorIcon::ResizeNwSe => Some(WinitCursorIcon::NwseResize), + egui::CursorIcon::ResizeVertical => Some(WinitCursorIcon::NsResize), + egui::CursorIcon::ResizeEast => Some(WinitCursorIcon::EResize), + egui::CursorIcon::ResizeSouthEast => Some(WinitCursorIcon::SeResize), + egui::CursorIcon::ResizeSouth => Some(WinitCursorIcon::SResize), + egui::CursorIcon::ResizeSouthWest => Some(WinitCursorIcon::SwResize), + egui::CursorIcon::ResizeWest => Some(WinitCursorIcon::WResize), + egui::CursorIcon::ResizeNorthWest => Some(WinitCursorIcon::NwResize), + egui::CursorIcon::ResizeNorth => Some(WinitCursorIcon::NResize), + egui::CursorIcon::ResizeNorthEast => Some(WinitCursorIcon::NeResize), + egui::CursorIcon::ResizeColumn => Some(WinitCursorIcon::ColResize), + egui::CursorIcon::ResizeRow => Some(WinitCursorIcon::RowResize), + egui::CursorIcon::Text => Some(WinitCursorIcon::Text), + egui::CursorIcon::VerticalText => Some(WinitCursorIcon::VerticalText), + egui::CursorIcon::Wait => Some(WinitCursorIcon::Wait), + egui::CursorIcon::ZoomIn => Some(WinitCursorIcon::ZoomIn), + egui::CursorIcon::ZoomOut => Some(WinitCursorIcon::ZoomOut), + }; +} + +/// Check if the keyboard event is a cut event. +pub fn is_keyboard_cut(modifiers: egui::Modifiers, key_code: WinitKey) -> bool { + let is_cut = modifiers.command && key_code == WinitKey::X; + + let is_cut_with_delete = cfg!(any( + target_os = "windows", + all(unix, not(target_os = "macos")) + )) && modifiers.ctrl + && key_code == WinitKey::Delete; + + return is_cut || is_cut_with_delete; +} + +/// Check if the keyboard event is a copy event. +pub fn is_keyboard_copy( + modifiers: egui::Modifiers, + key_code: WinitKey, +) -> bool { + let is_copy = modifiers.command && key_code == WinitKey::C; + + let is_copy_with_insert = cfg!(any( + target_os = "windows", + all(unix, not(target_os = "macos")) + )) && modifiers.ctrl + && key_code == WinitKey::Insert; + + return is_copy || is_copy_with_insert; +} + +/// Check if the keyboard event is a paste event. +pub fn is_keyboard_paste( + modifiers: egui::Modifiers, + key_code: WinitKey, +) -> bool { + let is_paste = modifiers.command && key_code == WinitKey::V; + + let is_paste_with_insert = cfg!(any( + target_os = "windows", + all(unix, not(target_os = "macos")) + )) && modifiers.shift + && key_code == WinitKey::Insert; + + return is_paste || is_paste_with_insert; +} diff --git a/crates/lambda-rs-platform/src/egui/winit/mod.rs b/crates/lambda-rs-platform/src/egui/winit/mod.rs new file mode 100644 index 00000000..3f3c713f --- /dev/null +++ b/crates/lambda-rs-platform/src/egui/winit/mod.rs @@ -0,0 +1,419 @@ +//! Custom integration between [egui](https://crates.io/crates/egui) +//! and [winit](https://crates.io/crates/winit). +//! +//! This module implements the following for winit / egui compatibility: +//! * Mouse support +//! * Touch support +//! * File support + +pub mod input; + +use egui::{ + Context, + Modifiers, + RawInput, +}; +use logging::{ + debug, + Logger, +}; +use winit::{ + dpi::PhysicalPosition, + event::{ + DeviceId, + ElementState, + Event, + MouseButton, + TouchPhase, + VirtualKeyCode, + WindowEvent, + }, +}; + +use self::input::winit_to_egui_mouse_button; +use crate::egui::winit::input::winit_to_egui_key; +pub struct EventResult { + pub processed: bool, + pub redraw: bool, +} + +impl super::EguiContext { + /// Create a new input manager prepped for winit usage. + pub fn new() -> Self { + Self { + input_handler: RawInput { + has_focus: false, + ..Default::default() + }, + context: Context::default(), + mouse_position: None, + mouse_button_active: false, + current_pixels_per_point: 1.0, + emulate_touch_screen: false, + active_touch_device: None, + } + } + + /// Process a winit mouse input event. First checks if the mouse position is on + /// the screen and then if a winit mouse button is pressed. + fn process_winit_mouse_button( + &mut self, + state: ElementState, + button: MouseButton, + ) { + match self.mouse_position { + Some(position) => match winit_to_egui_mouse_button(button) { + Some(button) => { + let is_pressed = state == winit::event::ElementState::Pressed; + + self.input_handler.events.push(egui::Event::PointerButton { + pos: position, + button, + pressed: is_pressed, + modifiers: self.input_handler.modifiers, + }); + + // If we emulate a touch screen & a mouse button is being pressed, + // we set the mouse button as active to send touch events. + match self.emulate_touch_screen { + false => {} + true => match is_pressed { + true => { + self.mouse_button_active = true; + } + false => { + self.mouse_button_active = false; + } + }, + } + } + None => { + logging::debug!("Couldn't convert the winit mouse button to an egui mouse button. Ignoring input."); + } + }, + None => { + logging::debug!( + "Mouse position not within the bounds of the window. Ignoring input." + ); + } + } + } + + /// Process a winit mouse movement event. + fn process_winit_mouse_movement( + &mut self, + physical_mouse_position: PhysicalPosition, + ) { + // Normalize the mouse position by the current pixels per point. + let normalized_position = egui::pos2( + physical_mouse_position.x as f32 / self.current_pixels_per_point, + physical_mouse_position.y as f32 / self.current_pixels_per_point, + ); + + self.mouse_position = Some(normalized_position); + + // If we are emulating a touch screen, we need to send a touch event. + // Otherwise, we send a mouse event. + match self.emulate_touch_screen { + true => { + if self.mouse_button_active { + self + .input_handler + .events + .push(egui::Event::PointerMoved(normalized_position)); + self.input_handler.events.push(egui::Event::Touch { + device_id: egui::TouchDeviceId(0), + id: egui::TouchId(0), + phase: egui::TouchPhase::Move, + pos: normalized_position, + force: 0.0, + }) + } + } + false => self + .input_handler + .events + .push(egui::Event::PointerMoved(normalized_position)), + } + } + + fn process_winit_touch_event(&mut self, event: winit::event::Touch) { + let winit::event::Touch { + location, + phase, + device_id, + force, + id, + } = event; + let egui_phase = match phase { + TouchPhase::Started => { + self.mouse_button_active = true; + egui::TouchPhase::Start + } + TouchPhase::Moved => { + self.mouse_button_active = true; + egui::TouchPhase::Move + } + TouchPhase::Ended => { + self.mouse_button_active = false; + egui::TouchPhase::End + } + TouchPhase::Cancelled => { + self.mouse_button_active = false; + egui::TouchPhase::Cancel + } + }; + let touch_device_id = + egui::TouchDeviceId(egui::epaint::util::hash(device_id)); + self.input_handler.events.push(egui::Event::Touch { + device_id: touch_device_id, + id: egui::TouchId(id), + phase: egui_phase, + pos: egui::pos2( + location.x as f32 / self.current_pixels_per_point, + location.y as f32 / self.current_pixels_per_point, + ), + force: match force { + Some(winit::event::Force::Normalized(force)) => force as f32, + Some(winit::event::Force::Calibrated { + force, + max_possible_force, + altitude_angle, + }) => match altitude_angle { + // Applies the altitude angle to the force + Some(altitude_angle) => { + (force / max_possible_force) as f32 * (altitude_angle.cos() as f32) + } + None => (force / max_possible_force) as f32, + }, + None => 0.0 as f32, + }, + }); + let processing_touch = self.active_touch_device.is_none() + || self.active_touch_device == Some(touch_device_id); + + if processing_touch { + match phase { + TouchPhase::Started => { + self.active_touch_device = Some(touch_device_id); + self.process_winit_mouse_movement(location); + self.process_winit_mouse_button( + ElementState::Pressed, + MouseButton::Left, + ); + } + TouchPhase::Moved => { + self.process_winit_mouse_movement(location); + } + TouchPhase::Ended => { + self.active_touch_device = None; + self.process_winit_mouse_movement(location); + self.process_winit_mouse_button( + ElementState::Released, + MouseButton::Left, + ); + } + TouchPhase::Cancelled => { + self.process_winit_mouse_movement(location); + self.process_winit_mouse_button( + ElementState::Released, + MouseButton::Left, + ); + } + } + } + } + + pub fn on_event( + &mut self, + event: &Event, + ) -> EventResult { + return match event { + Event::WindowEvent { window_id, event } => match event { + // File events. + WindowEvent::DroppedFile(path) => { + self.input_handler.dropped_files.clear(); + self.input_handler.dropped_files.push(egui::DroppedFile { + path: Some(path.clone()), + ..Default::default() + }); + return EventResult { + redraw: true, + processed: false, + }; + } + WindowEvent::HoveredFile(path) => { + self.input_handler.hovered_files.push(egui::HoveredFile { + path: Some(path.clone()), + ..Default::default() + }); + return EventResult { + redraw: true, + processed: false, + }; + } + WindowEvent::HoveredFileCancelled => { + self.input_handler.hovered_files.clear(); + return EventResult { + redraw: true, + processed: false, + }; + } + // Keyboard events. + WindowEvent::ReceivedCharacter(character) => { + if !character.is_control() { + self + .input_handler + .events + .push(egui::Event::Text(character.to_string())); + } + return EventResult { + redraw: true, + processed: false, + }; + } + + WindowEvent::KeyboardInput { + device_id, + input, + is_synthetic, + } => { + let pressed = input.state == ElementState::Pressed; + + if let Some(key) = winit_to_egui_key( + input.virtual_keycode.expect("No virtual keycode"), + ) { + self.input_handler.events.push(egui::Event::Key { + key, + pressed, + modifiers: self.input_handler.modifiers, + }); + } + return EventResult { + redraw: true, + processed: false, + }; + } + WindowEvent::ModifiersChanged(state) => { + self.input_handler.modifiers.alt = state.alt(); + self.input_handler.modifiers.ctrl = state.ctrl(); + self.input_handler.modifiers.shift = state.shift(); + self.input_handler.modifiers.mac_cmd = + cfg!(target_os = "macos") && state.logo(); + self.input_handler.modifiers.command = match cfg!(target_os = "macos") + { + true => state.logo(), + false => state.ctrl(), + }; + + return EventResult { + redraw: true, + processed: false, + }; + } + WindowEvent::Ime(ime) => { + debug!("IME event received, but cannot be handled yet: {:?}", ime); + return EventResult { + redraw: false, + processed: false, + }; + } + WindowEvent::CursorMoved { + device_id, + position, + modifiers, + } => { + self.process_winit_mouse_movement(*position); + return EventResult { + processed: self.context.wants_pointer_input(), + redraw: true, + }; + } + // Mouse input events + WindowEvent::MouseInput { + device_id, + state, + button, + modifiers, + } => { + self.process_winit_mouse_button(state.clone(), button.clone()); + let processed = self.context.wants_pointer_input(); + return EventResult { + processed, + redraw: true, + }; + } + WindowEvent::MouseWheel { + device_id, + delta, + phase, + modifiers, + } => todo!(), + WindowEvent::CursorLeft { .. } => { + self.mouse_position = None; + self.input_handler.events.push(egui::Event::PointerGone); + return EventResult { + processed: false, + redraw: true, + }; + } + + // Repaint events + WindowEvent::CloseRequested + | WindowEvent::CursorEntered { .. } + | WindowEvent::Destroyed + | WindowEvent::ThemeChanged(_) + | WindowEvent::Occluded(_) + | WindowEvent::Resized(_) + | WindowEvent::TouchpadPressure { .. } => EventResult { + processed: false, + redraw: true, + }, + + // Noop events + WindowEvent::Moved(_) | WindowEvent::AxisMotion { .. } => EventResult { + processed: false, + redraw: false, + }, + WindowEvent::Touch(event) => { + self.process_winit_touch_event(event.clone()); + return EventResult { + processed: self.context.wants_pointer_input(), + redraw: true, + }; + } + + // Window Events + WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size, + } => { + let pixels_per_point = *scale_factor as f32; + self.input_handler.pixels_per_point = Some(pixels_per_point); + self.context.set_pixels_per_point(pixels_per_point); + return EventResult { + processed: false, + redraw: true, + }; + } + WindowEvent::Focused(focused) => { + self.input_handler.has_focus = *focused; + match focused { + false => self.input_handler.modifiers = Modifiers::default(), + _ => {} + } + return EventResult { + processed: false, + redraw: true, + }; + } + }, + _ => { + return EventResult { + processed: false, + redraw: false, + }; + } + }; + } +} diff --git a/crates/lambda-rs-platform/src/lib.rs b/crates/lambda-rs-platform/src/lib.rs index ab1667f1..465b3ca8 100644 --- a/crates/lambda-rs-platform/src/lib.rs +++ b/crates/lambda-rs-platform/src/lib.rs @@ -1,3 +1,4 @@ +pub mod egui; pub mod gfx; pub mod obj; pub mod rand; diff --git a/crates/lambda-rs/src/render/mod.rs b/crates/lambda-rs/src/render/mod.rs index 27cd0c22..e22da4d8 100644 --- a/crates/lambda-rs/src/render/mod.rs +++ b/crates/lambda-rs/src/render/mod.rs @@ -8,6 +8,7 @@ pub mod mesh; pub mod pipeline; pub mod render_pass; pub mod shader; +pub mod ui; pub mod vertex; pub mod viewport; pub mod window; diff --git a/crates/lambda-rs/src/render/ui/mod.rs b/crates/lambda-rs/src/render/ui/mod.rs new file mode 100644 index 00000000..a1e0375e --- /dev/null +++ b/crates/lambda-rs/src/render/ui/mod.rs @@ -0,0 +1,30 @@ +pub use lambda_platform::egui; +use lambda_platform::egui::gfx::UIElement; + +/// The context for managing UI state & rendering. +pub struct UIContext { + pub elements: Vec, + egui_context: egui::EguiContext, +} + +impl UIContext { + pub fn new() -> Self { + Self { + elements: Vec::new(), + egui_context: egui::EguiContext::new(), + } + } + + pub fn add_element(&mut self, element: UIElement) { + self.elements.push(element); + } + + /// Passes lower level winit events to egui for processing. + // TODO(vmarcella): Can we change this function signature? + pub(crate) fn on_winit_event( + &mut self, + event: &lambda_platform::winit::winit_exports::Event<()>, + ) -> egui::winit::EventResult { + self.egui_context.on_event(event) + } +} diff --git a/crates/lambda-rs/src/runtime.rs b/crates/lambda-rs/src/runtime.rs index 8e680c6b..4d149216 100644 --- a/crates/lambda-rs/src/runtime.rs +++ b/crates/lambda-rs/src/runtime.rs @@ -13,19 +13,29 @@ where RuntimeError: Sized + Debug, { type Component; - fn on_start(&mut self); - fn on_stop(&mut self); + fn before_start(&mut self); fn run(self) -> Result; } -/// Simple function for starting any prebuilt Runnable. -pub fn start_runtime>( - runtime: T, +/// Starts a runtime and waits for it to finish. This function will not return +/// until the runtime has finished executing. +/// +/// The type `ImplementedRuntime` represents any struct which implements the +/// `Runtime` trait with valid `RuntimeResult` & `RuntimeError` parameters. +pub fn start_runtime< + RuntimeResult: Sized + Debug, + RuntimeError: Sized + Debug, + ImplementedRuntime: Runtime, +>( + runtime: ImplementedRuntime, ) { let runtime_result = runtime.run(); match runtime_result { - Ok(_) => { - logging::info!("Runtime finished successfully."); + Ok(result) => { + logging::info!( + "Runtime finished successfully with the result: {:?}", + result + ); } Err(e) => { logging::fatal!("Runtime panicked because: {:?}", e); diff --git a/crates/lambda-rs/src/runtimes/application.rs b/crates/lambda-rs/src/runtimes/application.rs index 5bd3dbb4..395da2e5 100644 --- a/crates/lambda-rs/src/runtimes/application.rs +++ b/crates/lambda-rs/src/runtimes/application.rs @@ -421,11 +421,7 @@ impl Runtime<(), String> for ApplicationRuntime { /// When an application runtime starts, it will attach all of the components that /// have been added during the construction phase in the users code. - fn on_start(&mut self) { + fn before_start(&mut self) { logging::info!("Starting the runtime: {}", self.name); } - - fn on_stop(&mut self) { - logging::info!("Stopping the runtime: {}", self.name); - } }