|
| 1 | +use std::cell::Ref; |
| 2 | +use std::rc::Rc; |
| 3 | +use std::time::Instant; |
| 4 | + |
| 5 | +use glium::backend::glutin::DisplayCreationError; |
| 6 | +use glium::backend::{Context, Facade}; |
| 7 | +use glium::{glutin, Display, Surface, SwapBuffersError}; |
| 8 | + |
| 9 | +use super::{Renderer, RendererError}; |
| 10 | +use imgui::{ImGui, ImGuiMouseCursor, ImString, Ui}; |
| 11 | + |
| 12 | +#[derive(Copy, Clone, PartialEq, Debug, Default)] |
| 13 | +struct MouseState { |
| 14 | + pos: (i32, i32), |
| 15 | + pressed: (bool, bool, bool), |
| 16 | + wheel: f32, |
| 17 | +} |
| 18 | + |
| 19 | +pub struct AppContext { |
| 20 | + renderer: Renderer, |
| 21 | + display: Display, |
| 22 | + events_loop: glutin::EventsLoop, |
| 23 | + imgui: ImGui, |
| 24 | + quit: bool, |
| 25 | + mouse_state: MouseState, |
| 26 | + last_frame: Instant, |
| 27 | + clear_color: [f32; 4], |
| 28 | +} |
| 29 | + |
| 30 | +#[derive(Debug)] |
| 31 | +pub enum ContextError { |
| 32 | + Glutin(DisplayCreationError), |
| 33 | + Render(RendererError), |
| 34 | + SwapBuffers(SwapBuffersError), |
| 35 | + Message(String), |
| 36 | +} |
| 37 | + |
| 38 | +impl From<DisplayCreationError> for ContextError { |
| 39 | + fn from(e: DisplayCreationError) -> Self { ContextError::Glutin(e) } |
| 40 | +} |
| 41 | + |
| 42 | +impl From<RendererError> for ContextError { |
| 43 | + fn from(e: RendererError) -> Self { ContextError::Render(e) } |
| 44 | +} |
| 45 | + |
| 46 | +impl From<SwapBuffersError> for ContextError { |
| 47 | + fn from(e: SwapBuffersError) -> Self { ContextError::SwapBuffers(e) } |
| 48 | +} |
| 49 | + |
| 50 | +#[derive(Clone, Debug)] |
| 51 | +pub struct AppConfig { |
| 52 | + pub clear_color: [f32; 4], |
| 53 | + pub ini_filename: Option<ImString>, |
| 54 | + pub log_filename: Option<ImString>, |
| 55 | + pub window_width: u32, |
| 56 | + pub window_height: u32, |
| 57 | +} |
| 58 | + |
| 59 | +impl Default for AppConfig { |
| 60 | + fn default() -> Self { |
| 61 | + Self { |
| 62 | + clear_color: [1.0, 1.0, 1.0, 1.0], |
| 63 | + ini_filename: None, |
| 64 | + log_filename: None, |
| 65 | + window_width: 1024, |
| 66 | + window_height: 768, |
| 67 | + } |
| 68 | + } |
| 69 | +} |
| 70 | + |
| 71 | +impl Facade for AppContext { |
| 72 | + fn get_context(&self) -> &Rc<Context> { self.display.get_context() } |
| 73 | +} |
| 74 | + |
| 75 | +impl AppContext { |
| 76 | + pub fn init(title: String, config: AppConfig) -> Result<Self, ContextError> { |
| 77 | + let events_loop = glutin::EventsLoop::new(); |
| 78 | + let context = glutin::ContextBuilder::new().with_vsync(true); |
| 79 | + let window = glutin::WindowBuilder::new() |
| 80 | + .with_title(title) |
| 81 | + .with_dimensions((config.window_width, config.window_height).into()); |
| 82 | + let display = Display::new(window, context, &events_loop)?; |
| 83 | + let mut imgui = ImGui::init(); |
| 84 | + imgui.set_ini_filename(config.ini_filename); |
| 85 | + imgui.set_log_filename(config.log_filename); |
| 86 | + |
| 87 | + let renderer = Renderer::init(&mut imgui, &display)?; |
| 88 | + |
| 89 | + configure_keys(&mut imgui); |
| 90 | + |
| 91 | + Ok(AppContext { |
| 92 | + renderer, |
| 93 | + display, |
| 94 | + events_loop, |
| 95 | + imgui, |
| 96 | + quit: false, |
| 97 | + mouse_state: Default::default(), |
| 98 | + last_frame: Instant::now(), |
| 99 | + clear_color: config.clear_color, |
| 100 | + }) |
| 101 | + } |
| 102 | + |
| 103 | + pub fn run<F: FnMut(&Ui) -> bool>(&mut self, mut run_ui: F) -> Result<(), ContextError> { |
| 104 | + loop { |
| 105 | + self.poll_events(); |
| 106 | + let now = Instant::now(); |
| 107 | + let delta = now - self.last_frame; |
| 108 | + let delta_s = delta.as_secs() as f32 + delta.subsec_nanos() as f32 / 1_000_000_000.0; |
| 109 | + update_mouse(&mut self.imgui, &mut self.mouse_state); |
| 110 | + let gl_window = self.display.gl_window(); |
| 111 | + |
| 112 | + update_os_cursor(&self.imgui, &gl_window); |
| 113 | + |
| 114 | + let size_pixels = gl_window |
| 115 | + .get_inner_size() |
| 116 | + .ok_or_else(|| ContextError::Message("Window no longer exists!".to_owned()))?; |
| 117 | + let hdipi = gl_window.get_hidpi_factor(); |
| 118 | + let size_points = ( |
| 119 | + (size_pixels.width as f64 / hdipi) as u32, |
| 120 | + (size_pixels.height as f64 / hdipi) as u32, |
| 121 | + ); |
| 122 | + |
| 123 | + let ui = self.imgui.frame(size_points, size_pixels.into(), delta_s); |
| 124 | + if !run_ui(&ui) { |
| 125 | + break; |
| 126 | + } |
| 127 | + let mut target = self.display.draw(); |
| 128 | + target.clear_color( |
| 129 | + self.clear_color[0], |
| 130 | + self.clear_color[1], |
| 131 | + self.clear_color[2], |
| 132 | + self.clear_color[3], |
| 133 | + ); |
| 134 | + self.renderer.render(&mut target, ui)?; |
| 135 | + target.finish()?; |
| 136 | + |
| 137 | + if self.quit { |
| 138 | + break; |
| 139 | + } |
| 140 | + } |
| 141 | + Ok(()) |
| 142 | + } |
| 143 | + |
| 144 | + fn poll_events(&mut self) { |
| 145 | + let quit = &mut self.quit; |
| 146 | + let imgui = &mut self.imgui; |
| 147 | + let events_loop = &mut self.events_loop; |
| 148 | + let mouse_state = &mut self.mouse_state; |
| 149 | + events_loop.poll_events(|event| { |
| 150 | + use glium::glutin::ElementState::Pressed; |
| 151 | + use glium::glutin::WindowEvent::*; |
| 152 | + use glium::glutin::{Event, MouseButton, MouseScrollDelta, TouchPhase}; |
| 153 | + |
| 154 | + if let Event::WindowEvent { event, .. } = event { |
| 155 | + match event { |
| 156 | + CloseRequested => *quit = true, |
| 157 | + KeyboardInput { input, .. } => { |
| 158 | + use glium::glutin::VirtualKeyCode as Key; |
| 159 | + |
| 160 | + let pressed = input.state == Pressed; |
| 161 | + match input.virtual_keycode { |
| 162 | + Some(Key::Tab) => imgui.set_key(0, pressed), |
| 163 | + Some(Key::Left) => imgui.set_key(1, pressed), |
| 164 | + Some(Key::Right) => imgui.set_key(2, pressed), |
| 165 | + Some(Key::Up) => imgui.set_key(3, pressed), |
| 166 | + Some(Key::Down) => imgui.set_key(4, pressed), |
| 167 | + Some(Key::PageUp) => imgui.set_key(5, pressed), |
| 168 | + Some(Key::PageDown) => imgui.set_key(6, pressed), |
| 169 | + Some(Key::Home) => imgui.set_key(7, pressed), |
| 170 | + Some(Key::End) => imgui.set_key(8, pressed), |
| 171 | + Some(Key::Delete) => imgui.set_key(9, pressed), |
| 172 | + Some(Key::Back) => imgui.set_key(10, pressed), |
| 173 | + Some(Key::Return) => imgui.set_key(11, pressed), |
| 174 | + Some(Key::Escape) => imgui.set_key(12, pressed), |
| 175 | + Some(Key::A) => imgui.set_key(13, pressed), |
| 176 | + Some(Key::C) => imgui.set_key(14, pressed), |
| 177 | + Some(Key::V) => imgui.set_key(15, pressed), |
| 178 | + Some(Key::X) => imgui.set_key(16, pressed), |
| 179 | + Some(Key::Y) => imgui.set_key(17, pressed), |
| 180 | + Some(Key::Z) => imgui.set_key(18, pressed), |
| 181 | + Some(Key::LControl) | Some(Key::RControl) => { |
| 182 | + imgui.set_key_ctrl(pressed) |
| 183 | + } |
| 184 | + Some(Key::LShift) | Some(Key::RShift) => imgui.set_key_shift(pressed), |
| 185 | + Some(Key::LAlt) | Some(Key::RAlt) => imgui.set_key_alt(pressed), |
| 186 | + Some(Key::LWin) | Some(Key::RWin) => imgui.set_key_super(pressed), |
| 187 | + _ => {} |
| 188 | + } |
| 189 | + } |
| 190 | + CursorMoved { |
| 191 | + position, .. |
| 192 | + } => mouse_state.pos = position.into(), |
| 193 | + MouseInput { state, button, .. } => match button { |
| 194 | + MouseButton::Left => mouse_state.pressed.0 = state == Pressed, |
| 195 | + MouseButton::Right => mouse_state.pressed.1 = state == Pressed, |
| 196 | + MouseButton::Middle => mouse_state.pressed.2 = state == Pressed, |
| 197 | + _ => {} |
| 198 | + }, |
| 199 | + MouseWheel { |
| 200 | + delta: MouseScrollDelta::LineDelta(_, y), |
| 201 | + phase: TouchPhase::Moved, |
| 202 | + .. |
| 203 | + } => mouse_state.wheel = y, |
| 204 | + MouseWheel { |
| 205 | + delta: MouseScrollDelta::PixelDelta(pos), |
| 206 | + phase: TouchPhase::Moved, |
| 207 | + .. |
| 208 | + } => mouse_state.wheel = pos.y as f32, |
| 209 | + ReceivedCharacter(c) => imgui.add_input_character(c), |
| 210 | + _ => (), |
| 211 | + } |
| 212 | + } |
| 213 | + }); |
| 214 | + } |
| 215 | + |
| 216 | + pub fn imgui(&self) -> &ImGui { &self.imgui } |
| 217 | + |
| 218 | + pub fn imgui_mut(&mut self) -> &mut ImGui { &mut self.imgui } |
| 219 | +} |
| 220 | + |
| 221 | +fn configure_keys(imgui: &mut ImGui) { |
| 222 | + use imgui::ImGuiKey; |
| 223 | + |
| 224 | + imgui.set_imgui_key(ImGuiKey::Tab, 0); |
| 225 | + imgui.set_imgui_key(ImGuiKey::LeftArrow, 1); |
| 226 | + imgui.set_imgui_key(ImGuiKey::RightArrow, 2); |
| 227 | + imgui.set_imgui_key(ImGuiKey::UpArrow, 3); |
| 228 | + imgui.set_imgui_key(ImGuiKey::DownArrow, 4); |
| 229 | + imgui.set_imgui_key(ImGuiKey::PageUp, 5); |
| 230 | + imgui.set_imgui_key(ImGuiKey::PageDown, 6); |
| 231 | + imgui.set_imgui_key(ImGuiKey::Home, 7); |
| 232 | + imgui.set_imgui_key(ImGuiKey::End, 8); |
| 233 | + imgui.set_imgui_key(ImGuiKey::Delete, 9); |
| 234 | + imgui.set_imgui_key(ImGuiKey::Backspace, 10); |
| 235 | + imgui.set_imgui_key(ImGuiKey::Enter, 11); |
| 236 | + imgui.set_imgui_key(ImGuiKey::Escape, 12); |
| 237 | + imgui.set_imgui_key(ImGuiKey::A, 13); |
| 238 | + imgui.set_imgui_key(ImGuiKey::C, 14); |
| 239 | + imgui.set_imgui_key(ImGuiKey::V, 15); |
| 240 | + imgui.set_imgui_key(ImGuiKey::X, 16); |
| 241 | + imgui.set_imgui_key(ImGuiKey::Y, 17); |
| 242 | + imgui.set_imgui_key(ImGuiKey::Z, 18); |
| 243 | +} |
| 244 | + |
| 245 | +fn update_mouse(imgui: &mut ImGui, mouse_state: &mut MouseState) { |
| 246 | + let scale = imgui.display_framebuffer_scale(); |
| 247 | + imgui.set_mouse_pos( |
| 248 | + mouse_state.pos.0 as f32 / scale.0, |
| 249 | + mouse_state.pos.1 as f32 / scale.1, |
| 250 | + ); |
| 251 | + imgui.set_mouse_down(&[ |
| 252 | + mouse_state.pressed.0, |
| 253 | + mouse_state.pressed.1, |
| 254 | + mouse_state.pressed.2, |
| 255 | + false, |
| 256 | + false, |
| 257 | + ]); |
| 258 | + imgui.set_mouse_wheel(mouse_state.wheel / scale.1); |
| 259 | + mouse_state.wheel = 0.0; |
| 260 | +} |
| 261 | + |
| 262 | +fn update_os_cursor(imgui: &ImGui, gl_window: &Ref<glutin::GlWindow>) { |
| 263 | + let mouse_cursor = imgui.mouse_cursor(); |
| 264 | + if imgui.mouse_draw_cursor() || mouse_cursor == ImGuiMouseCursor::None { |
| 265 | + // Hide OS cursor |
| 266 | + gl_window.hide_cursor(true); |
| 267 | + } else { |
| 268 | + // Set OS cursor |
| 269 | + gl_window.hide_cursor(false); |
| 270 | + gl_window.set_cursor(match mouse_cursor { |
| 271 | + ImGuiMouseCursor::None => unreachable!("mouse_cursor was None!"), |
| 272 | + ImGuiMouseCursor::Arrow => glutin::MouseCursor::Arrow, |
| 273 | + ImGuiMouseCursor::TextInput => glutin::MouseCursor::Text, |
| 274 | + ImGuiMouseCursor::Move => glutin::MouseCursor::Move, |
| 275 | + ImGuiMouseCursor::ResizeNS => glutin::MouseCursor::NsResize, |
| 276 | + ImGuiMouseCursor::ResizeEW => glutin::MouseCursor::EwResize, |
| 277 | + ImGuiMouseCursor::ResizeNESW => glutin::MouseCursor::NeswResize, |
| 278 | + ImGuiMouseCursor::ResizeNWSE => glutin::MouseCursor::NwseResize, |
| 279 | + }); |
| 280 | + } |
| 281 | +} |
0 commit comments