From a06afdde3848a4b27680b2ee8f5377730d66bdbb Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 4 May 2020 02:05:11 -0400 Subject: [PATCH] handle zoom only within imgui window --- src/graphics_imgui.rs | 68 ++++++---- src/gui/enhanced_window.rs | 168 ++++++++++++++++++++++++ src/gui/mod.rs | 2 + src/main.rs | 1 + src/mouse.rs | 26 +++- src/point_cloud/point_cloud_renderer.rs | 8 -- 6 files changed, 234 insertions(+), 39 deletions(-) create mode 100644 src/gui/enhanced_window.rs create mode 100644 src/gui/mod.rs diff --git a/src/graphics_imgui.rs b/src/graphics_imgui.rs index 0ab55e71e7..bcecb09e35 100644 --- a/src/graphics_imgui.rs +++ b/src/graphics_imgui.rs @@ -13,11 +13,18 @@ use point_cloud::point_cloud_visualiser::{ColorizationStrategy, PointCloudVisual use point_cloud::viewer_image::ViewerImage; use sensor_control::CaptureProvider; use webcam::WebcamWriter; +use gui::enhanced_window::EnhancedWindow; + +pub const ENABLE_WEBCAM: bool = false; pub fn run(capture_provider: Arc, calibration_data: k4a_sys::k4a_calibration_t) { - let mut webcam_writer = WebcamWriter::open_file("/dev/video0", 1280, 720, 3) - .expect("should be able to create webcamwriter"); + let mut webcam_writer = None; + + if ENABLE_WEBCAM { + webcam_writer = Some(WebcamWriter::open_file("/dev/video0", 1280, 720, 3) + .expect("should be able to create webcamwriter")); + } let sdl_arcball = Arc::new(Mutex::new(SdlArcball::new(1280, 720))); @@ -47,6 +54,7 @@ pub fn run(capture_provider: Arc, calibration_data: k4a_sys::k4 let mut imgui = imgui::Context::create(); imgui.set_ini_filename(None); + imgui.io_mut().config_windows_move_from_title_bar_only = true; /*let gl_texture_n64 = load_texture("n64logo.png"); let imgui_texture_n64 = TextureId::from(gl_texture_n64 as usize); @@ -96,10 +104,10 @@ pub fn run(capture_provider: Arc, calibration_data: k4a_sys::k4 use sdl2::event::Event; use sdl2::keyboard::Keycode; - let _rebinder = Rebinder::snapshot(); - for event in event_pump.poll_iter() { imgui_sdl2.handle_event(&mut imgui, &event); + + // TODO: This is how Imgui appears to tell SDL to ignore certain events. if imgui_sdl2.ignore_event(&event) { continue; } match event { @@ -116,7 +124,6 @@ pub fn run(capture_provider: Arc, calibration_data: k4a_sys::k4 } } - imgui_sdl2.prepare_frame(imgui.io_mut(), &window, &event_pump.mouse_state()); let now = Instant::now(); @@ -126,28 +133,14 @@ pub fn run(capture_provider: Arc, calibration_data: k4a_sys::k4 imgui.io_mut().delta_time = delta_s; let ui = imgui.frame(); - //ui.show_demo_window(&mut true); - /*Window::new(im_str!("Loading Images")) - .size([1500.0, 1500.0], Condition::FirstUseEver) - .position([0.0, 0.0], Condition::FirstUseEver) - .build(&ui, || { - let mouse_pos = ui.io().mouse_pos; - ui.text(format!( - "Mouse Position: ({:.1},{:.1})", - mouse_pos[0], mouse_pos[1] - )); - ui.separator(); - Image::new(imgui_texture_n64, [100.0, 100.0]).build(&ui); - ui.separator(); - Image::new(imgui_texture_snes, [200.0, 200.0]).build(&ui); - ui.separator(); - });*/ + //ui.show_demo_window(&mut true); const window_width : f32 = 1290.0; const window_height : f32 = 760.0; Window::new(im_str!("Point Cloud Converter Depth Image")) + .scrollable(true) .size([window_width, window_height], Condition::FirstUseEver) .position([0.0, 0.0], Condition::FirstUseEver) .build(&ui, || { @@ -169,6 +162,7 @@ pub fn run(capture_provider: Arc, calibration_data: k4a_sys::k4 Window::new(im_str!("Visualizer XYZ Texture")) .size([window_width, window_height], Condition::FirstUseEver) .position([window_width + 50.0, 0.0], Condition::FirstUseEver) + .movable(false) .build(&ui, || { match imgui_visualizer_xyz_texture.as_ref() { None => {}, @@ -177,16 +171,34 @@ pub fn run(capture_provider: Arc, calibration_data: k4a_sys::k4 }, } }); + // NB: This is larger. The texture is 1280x1152. - Window::new(&im_str!("Final Output: {:?}", colorization_strategy)) + EnhancedWindow::new(&im_str!("Final Output: {:?}", colorization_strategy)) .size([window_width, window_height + 435.0], Condition::FirstUseEver) .position([window_width + 50.0, window_height + 50.0], Condition::FirstUseEver) - .build(&ui, || { - //Image::new(imgui_kinect_final_output, [1280.0, 1152.0]).build(&ui); - //Image::new(imgui_kinect_final_output, [640.0, 480.0]).build(&ui); + .mouse_inputs(true) + .build(&ui, |window| { + + if let Some(mouse_state) = window.get_window_bounded_mouse_state() { + println!("\nwindow_helper.cursor_pos: {:?}", mouse_state); + match sdl_arcball.lock() { + Ok(mut arcball) => arcball.process_mouse_state(mouse_state), + Err(_) => {}, + } + } + + /*println!("is_window_hovered: {:?}", ui.is_window_hovered()); + println!("cursor_pos: {:?}", ui.cursor_pos()); + println!("cursor_screen_pos: {:?}", ui.cursor_screen_pos()); + println!("window_pos: {:?}", ui.window_pos()); + println!("IO mouse pos: {:?}\n", ui.io().mouse_pos);*/ + Image::new(imgui_kinect_final_output, [1280.0, 720.0]).build(&ui); }); + //println!("imgui.cursor_pos: {:?}", ui.cursor_pos()); + //println!("imgui.cursor_screen_pos: {:?}", ui.cursor_screen_pos()); + unsafe { gl::ClearColor(0.2, 0.2, 0.2, 1.0); gl::Clear(gl::COLOR_BUFFER_BIT); @@ -219,8 +231,10 @@ pub fn run(capture_provider: Arc, calibration_data: k4a_sys::k4 Err(_) => {}, } - webcam_writer.write_current_frame_to_file(texture.texture_id()) - .expect("should write"); + if let Some(mut webcam) = webcam_writer.as_mut() { + webcam.write_current_frame_to_file(texture.texture_id()) + .expect("should write"); + } //let change_delta = last_frame - last_change; //if change_delta > Duration::from_millis(5_000) { diff --git a/src/gui/enhanced_window.rs b/src/gui/enhanced_window.rs new file mode 100644 index 0000000000..a3127ffbbe --- /dev/null +++ b/src/gui/enhanced_window.rs @@ -0,0 +1,168 @@ +/// A wrapper around `Imgui::Window` that adds window-scoped devices. +pub struct EnhancedWindow <'a> { + pub window: imgui::Window<'a>, +} + +impl <'a> EnhancedWindow <'a> { + pub fn new(name: &'a imgui::ImStr) -> Self { + let mut window = imgui::Window::new(name) + .scrollable(true); + + Self { + window, + } + } + + /// Enables/disables catching mouse input. + /// + /// Enabled by default. + /// Note: Hovering test will pass through when disabled + #[inline] + pub fn mouse_inputs(mut self, value: bool) -> Self { + // NB: Builder API consumes window, so we have to move it. + self.window = self.window.mouse_inputs(value); + self + } + + /// Sets the window position, which is applied based on the given condition value + #[inline] + pub fn position(mut self, position: [f32; 2], condition: imgui::Condition) -> Self { + // NB: Builder API consumes window, so we have to move it. + self.window = self.window.position(position, condition); + self + } + + /// Sets the window size, which is applied based on the given condition value + #[inline] + pub fn size(mut self, size: [f32; 2], condition: imgui::Condition) -> Self { + // NB: Builder API consumes window, so we have to move it. + self.window = self.window.size(size, condition); + self + } + + /// Creates a window and runs a closure to construct the contents. + /// + /// Note: the closure is not called if no window content is visible (e.g. window is collapsed + /// or fully clipped). + pub fn build(self, ui: &imgui::Ui, f: F) + where F: FnOnce(WindowScopedUi) + { + if let Some(window) = self.begin(ui) { + let win_helper = WindowScopedUi::new(&ui); + f(win_helper); + window.end(ui); + } + } + + /// Creates a window and starts appending to it. + /// + /// Returns `Some(WindowToken)` if the window is visible. After content has been + /// rendered, the token must be ended by calling `.end()`. + /// + /// Returns `None` if the window is not visible and no content should be rendered. + #[must_use] + pub fn begin(self, ui: &imgui::Ui) -> Option { + self.window.begin(ui) + } +} + +/// State for a mouse mapped to the dimensions of an `Imgui::Window`. +#[derive(Debug, Clone)] +pub struct CompleteMouseState { + // Cursor position + pub cursor_x: f32, + pub cursor_y: f32, + // Scroll wheel velocity (typically just '0', '1', or '-1'). + pub scroll_wheel: f32, + pub scroll_wheel_h: f32, + // Buttons + pub left_button_down: bool, + pub right_button_down: bool, + pub middle_button_down: bool, + pub extra_button_1_down: bool, + pub extra_button_2_down: bool, +} + +/// Exposes some reusable calculations on top of `imgui::Ui` in the context of window usage. +pub struct WindowScopedUi<'a> { + ui: &'a imgui::Ui<'a>, +} + +impl <'a> WindowScopedUi<'a> { + pub fn new(ui: &'a imgui::Ui) -> Self { + Self { + ui + } + } + + /// Returns true if the current window is hovered + #[inline] + pub fn is_window_hovered(&self) -> bool { + self.ui.is_window_hovered() + } + + /// Return the cursor position within the window. + /// None is returned if the window isn't hovered, or the mouse is logically not within the drawn + /// texture area. + pub fn get_cursor(&self) -> Option<[f32; 2]> { + if !self.is_window_hovered() { + return None; + } + + // Global mouse coordinate + let mouse_pos = self.ui.io().mouse_pos; + // Anchor point of the window's top left corner + let window_pos = self.ui.window_pos(); + // Start of textured data (padding offset created by title and window padding). + let cursor_pos = self.ui.cursor_pos(); + + let x = mouse_pos[0] - window_pos[0] - cursor_pos[0]; + let y = mouse_pos[1] - window_pos[1] - cursor_pos[1]; + + if x.is_sign_negative() || y.is_sign_negative() { + return None; // Outside of bounds (eg. cursor within title area) + } + + Some([x, y]) + } + + /// Return the mouse scroll state. If the mouse is not hovering our window, None is returned. + pub fn get_mouse_wheel(&self) -> Option { + if self.get_cursor().is_none() { + return None; + } + Some(self.ui.io().mouse_wheel) + } + + /// Mouse buttons: 0=left, 1=right, 2=middle + extras + pub fn get_mouse_down(&self) -> Option<[bool; 5]> { + if self.get_cursor().is_none() { + return None; + } + + Some(self.ui.io().mouse_down) + } + + /// Get the current mouse state + /// If the cursor isn't within the window, None is returned. + pub fn get_window_bounded_mouse_state(&self) -> Option { + self.get_cursor() + .map(|cursor| { + let io = self.ui.io(); + let mouse = io.mouse_down; + + CompleteMouseState { + cursor_x: cursor[0], + cursor_y: cursor[1], + scroll_wheel: io.mouse_wheel, + scroll_wheel_h: io.mouse_wheel_h, + left_button_down: mouse[0], + right_button_down: mouse[1], + middle_button_down: mouse[2], + extra_button_1_down: mouse[3], + extra_button_2_down: mouse[4], + } + }) + } +} + diff --git a/src/gui/mod.rs b/src/gui/mod.rs new file mode 100644 index 0000000000..1891be5665 --- /dev/null +++ b/src/gui/mod.rs @@ -0,0 +1,2 @@ +//! This module will contain all the Imgui, SDL, mouse controls, etc. code +pub mod enhanced_window; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 8e3a1b8547..10e8ed71a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,6 +32,7 @@ use sensor_control::{capture_thread, CaptureProvider}; pub mod conversion; pub mod core_types; pub mod graphics_imgui; +pub mod gui; pub mod imgui_support; pub mod k4a_sys_wrapper; pub mod mouse; diff --git a/src/mouse.rs b/src/mouse.rs index 5e5d450389..ebdeedd18f 100644 --- a/src/mouse.rs +++ b/src/mouse.rs @@ -3,6 +3,7 @@ use cgmath::{Matrix4, Vector2}; use cgmath::Vector3; use sdl2::event::Event; use sdl2::mouse::MouseButton; +use gui::enhanced_window::CompleteMouseState; const MIN_ZOOM : f32 = 1.0; const MAX_ZOOM : f32 = 120.0; @@ -67,15 +68,22 @@ impl SdlArcball { } /// Process an SDL event. + /// This is a bad idea in a multi-window Imgui setup since the SDL coordinates and events take up + /// the entire process window and may be sequestered on a per-window level. That said, this is + /// reasonable for single-window apps. pub fn process_event(&mut self, event: &Event) { match event { Event::MouseWheel { timestamp: _, window_id: _, which: _, x: _, y, direction: _ } => { - self.mouse_wheel(*y); + //println!("Mouse wheel event."); + let y = *y as f32; + self.mouse_wheel(y); }, Event::MouseButtonDown { timestamp: _, window_id: _, which: _, mouse_btn, clicks: _, x: _, y: _ } => { + //println!("Mouse button down event."); self.mouse_button_press(mouse_btn, true); }, Event::MouseButtonUp { timestamp: _, window_id: _, which: _, mouse_btn, clicks: _, x: _, y: _ } => { + //println!("Mouse button up event."); self.mouse_button_press(mouse_btn, false); }, Event::MouseMotion { timestamp: _, window_id: _, which: _, mousestate: _, x, y, xrel: _, yrel: _ } => { @@ -89,6 +97,18 @@ impl SdlArcball { } } + /// Process Imgui window-scoped mouse state. + /// Since we don't process individual events, we set all of the internal state at once. + pub fn process_mouse_state(&mut self, mouse_state: CompleteMouseState) { + // TODO: Cleanup + self.right_mouse_active = mouse_state.right_button_down; + self.left_mouse_active = mouse_state.left_button_down; + self.mouse_motion(mouse_state.cursor_x, mouse_state.cursor_y); + self.mouse_wheel(mouse_state.scroll_wheel); + self.last_x = mouse_state.cursor_x; + self.last_y = mouse_state.cursor_y; + } + /// Get the view matrix pub fn get_view_matrix(&self) -> Matrix4 { self.arcball.get_mat4() @@ -159,8 +179,7 @@ impl SdlArcball { self.animation_counter += 0.05; } - fn mouse_wheel(&mut self, y: i32) { - let y = y as f32; + fn mouse_wheel(&mut self, y: f32) { self.arcball.zoom(y, 0.16); // NB: 'y' is a scroll amount, typically 1 or -1. @@ -169,7 +188,6 @@ impl SdlArcball { .min(MAX_ZOOM); self.last_zoom = zoom; - println!("Zoom: {}", zoom); } fn mouse_button_press(&mut self, mouse_button: &MouseButton, down: bool) { diff --git a/src/point_cloud/point_cloud_renderer.rs b/src/point_cloud/point_cloud_renderer.rs index 29cb366c73..437f7ea5f7 100644 --- a/src/point_cloud/point_cloud_renderer.rs +++ b/src/point_cloud/point_cloud_renderer.rs @@ -329,11 +329,6 @@ impl PointCloudRenderer { point_cloud_texture_index = gl::GetUniformLocation(program_id, POINT_CLOUD_PTR); } - println!("Uniform view location - view: {:?}", view_index); - println!("Uniform projection location - view: {:?}", projection_index); - println!("Uniform enable shading location - view: {:?}", enable_shading_index); - println!("Uniform point cloud texture location - view: {:?}", point_cloud_texture_index); - let initial_view = [ [-1.0, 0.0, 8.74228e-08, 0.0], [0.0, 1.0, 0.0, 0.0], @@ -373,8 +368,6 @@ impl PointCloudRenderer { /// Update the view matrices /// pub fn update_view_projection(&mut self, view: [[f32; 4]; 4], projection: [[f32; 4]; 4]) { - println!("\nView Matrix: {:?}", view); - println!("Updated: {:?}", projection); self.view_matrix = view; //self.projection_matrix = projection; @@ -387,7 +380,6 @@ impl PointCloudRenderer { // Initial: [[1.41272, 0.0, 0.0, 0.0], [0.0, 1.56969, 0.0, 0.0], [0.0, 0.0, -1.002, -1.0], [0.0, 0.0, -0.2002, 0.0]] // Updated: [[0.88294804, 0.0, 0.0, 0.0], [0.0, 1.5696855, 0.0, 0.0], [0.0, 0.0, -1.002002, -0.2002002], [0.0, 0.0, -1.0, 0.0]] - println!("Initial: {:?}", initial_projection); self.projection_matrix = initial_projection; }