|
| 1 | +pub extern crate imgui; |
| 2 | +extern crate amethyst; |
| 3 | +extern crate gfx; |
| 4 | +#[macro_use] extern crate glsl_layout; |
| 5 | +extern crate imgui_gfx_renderer; |
| 6 | + |
| 7 | +use amethyst::{ |
| 8 | + core::{cgmath, specs::prelude::*}, |
| 9 | + renderer::{ |
| 10 | + error::Result, |
| 11 | + pipe::{ |
| 12 | + pass::{Pass, PassData}, |
| 13 | + Effect, |
| 14 | + NewEffect, |
| 15 | + }, |
| 16 | + Encoder, |
| 17 | + Mesh, |
| 18 | + PosTex, |
| 19 | + Resources, |
| 20 | + VertexFormat, |
| 21 | + }, |
| 22 | +}; |
| 23 | +use gfx::{memory::Typed, preset::blend, pso::buffer::ElemStride, state::ColorMask}; |
| 24 | +use gfx::traits::Factory; |
| 25 | +use glsl_layout::{vec2, vec4, Uniform}; |
| 26 | +use imgui::{FontGlyphRange, FrameSize, ImFontConfig, ImGui, ImVec4}; |
| 27 | +use imgui_gfx_renderer::{Renderer as ImguiRenderer, Shaders}; |
| 28 | + |
| 29 | +const VERT_SRC: &[u8] = include_bytes!("shaders/vertex.glsl"); |
| 30 | +const FRAG_SRC: &[u8] = include_bytes!("shaders/frag.glsl"); |
| 31 | + |
| 32 | +#[derive(Copy, Clone, Debug, Uniform)] |
| 33 | +#[allow(dead_code)] // This is used by the shaders |
| 34 | +#[repr(C)] |
| 35 | +struct VertexArgs { |
| 36 | + proj_vec: vec4, |
| 37 | + coord: vec2, |
| 38 | + dimension: vec2, |
| 39 | +} |
| 40 | + |
| 41 | +struct RendererThing { |
| 42 | + renderer: ImguiRenderer<Resources>, |
| 43 | + texture: gfx::handle::Texture<Resources, gfx::format::R8_G8_B8_A8>, |
| 44 | + shader_resource_view: gfx::handle::ShaderResourceView<Resources, [f32; 4]>, |
| 45 | + mesh: Mesh, |
| 46 | +} |
| 47 | + |
| 48 | +#[derive(Default)] |
| 49 | +pub struct DrawUi { |
| 50 | + imgui: Option<ImGui>, |
| 51 | + renderer: Option<RendererThing>, |
| 52 | +} |
| 53 | + |
| 54 | +impl DrawUi { |
| 55 | + pub fn new() -> Self { |
| 56 | + Self::default() |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +pub struct ImguiState { |
| 61 | + pub run_ui: Option<Box<dyn Fn(&mut imgui::Ui<'_>) + Sync + Send>>, |
| 62 | + imgui: ImGui, |
| 63 | + mouse_state: MouseState, |
| 64 | + size: (u16, u16), |
| 65 | +} |
| 66 | + |
| 67 | +type UiPassData<'pass_data> = ( |
| 68 | + ReadExpect<'pass_data, amethyst::renderer::ScreenDimensions>, |
| 69 | + Read<'pass_data, amethyst::core::timing::Time>, |
| 70 | + Write<'pass_data, Option<ImguiState>>, |
| 71 | +); |
| 72 | + |
| 73 | +impl<'pass_data> PassData<'pass_data> for DrawUi { |
| 74 | + type Data = UiPassData<'pass_data>; |
| 75 | +} |
| 76 | + |
| 77 | +type FormattedT = (gfx::format::R8_G8_B8_A8, gfx::format::Unorm); |
| 78 | + |
| 79 | +impl Pass for DrawUi { |
| 80 | + fn compile(&mut self, mut effect: NewEffect<'_>) -> Result<Effect> { |
| 81 | + let mut imgui = ImGui::init(); |
| 82 | + { |
| 83 | + // Fix incorrect colors with sRGB framebuffer |
| 84 | + fn imgui_gamma_to_linear(col: ImVec4) -> ImVec4 { |
| 85 | + let x = col.x.powf(2.2); |
| 86 | + let y = col.y.powf(2.2); |
| 87 | + let z = col.z.powf(2.2); |
| 88 | + let w = 1.0 - (1.0 - col.w).powf(2.2); |
| 89 | + ImVec4::new(x, y, z, w) |
| 90 | + } |
| 91 | + |
| 92 | + let style = imgui.style_mut(); |
| 93 | + for col in 0..style.colors.len() { |
| 94 | + style.colors[col] = imgui_gamma_to_linear(style.colors[col]); |
| 95 | + } |
| 96 | + } |
| 97 | + imgui.set_ini_filename(None); |
| 98 | + |
| 99 | + let font_size = 13.; |
| 100 | + |
| 101 | + let _ = imgui.fonts().add_font_with_config( |
| 102 | + include_bytes!("../mplus-1p-regular.ttf"), |
| 103 | + ImFontConfig::new() |
| 104 | + .oversample_h(1) |
| 105 | + .pixel_snap_h(true) |
| 106 | + .size_pixels(font_size) |
| 107 | + .rasterizer_multiply(1.75), |
| 108 | + &FontGlyphRange::japanese(), |
| 109 | + ); |
| 110 | + |
| 111 | + let _ = imgui.fonts().add_default_font_with_config( |
| 112 | + ImFontConfig::new() |
| 113 | + .merge_mode(true) |
| 114 | + .oversample_h(1) |
| 115 | + .pixel_snap_h(true) |
| 116 | + .size_pixels(font_size), |
| 117 | + ); |
| 118 | + |
| 119 | + { |
| 120 | + macro_rules! set_keys { |
| 121 | + ($($key:ident => $id:expr),+$(,)*) => { |
| 122 | + $(imgui.set_imgui_key(imgui::ImGuiKey::$key, $id);)+ |
| 123 | + }; |
| 124 | + } |
| 125 | + |
| 126 | + set_keys![ |
| 127 | + Tab => 0, |
| 128 | + LeftArrow => 1, |
| 129 | + RightArrow => 2, |
| 130 | + UpArrow => 3, |
| 131 | + DownArrow => 4, |
| 132 | + PageUp => 5, |
| 133 | + PageDown => 6, |
| 134 | + Home => 7, |
| 135 | + End => 8, |
| 136 | + Delete => 9, |
| 137 | + Backspace => 10, |
| 138 | + Enter => 11, |
| 139 | + Escape => 12, |
| 140 | + A => 13, |
| 141 | + C => 14, |
| 142 | + V => 15, |
| 143 | + X => 16, |
| 144 | + Y => 17, |
| 145 | + Z => 18, |
| 146 | + ]; |
| 147 | + } |
| 148 | + |
| 149 | + let data = vec![ |
| 150 | + PosTex { |
| 151 | + position: [0., 1., 0.], |
| 152 | + tex_coord: [0., 0.], |
| 153 | + }, |
| 154 | + PosTex { |
| 155 | + position: [1., 1., 0.], |
| 156 | + tex_coord: [1., 0.], |
| 157 | + }, |
| 158 | + PosTex { |
| 159 | + position: [1., 0., 0.], |
| 160 | + tex_coord: [1., 1.], |
| 161 | + }, |
| 162 | + PosTex { |
| 163 | + position: [0., 1., 0.], |
| 164 | + tex_coord: [0., 0.], |
| 165 | + }, |
| 166 | + PosTex { |
| 167 | + position: [1., 0., 0.], |
| 168 | + tex_coord: [1., 1.], |
| 169 | + }, |
| 170 | + PosTex { |
| 171 | + position: [0., 0., 0.], |
| 172 | + tex_coord: [0., 1.], |
| 173 | + }, |
| 174 | + ]; |
| 175 | + |
| 176 | + let (texture, shader_resource_view, target) = effect.factory.create_render_target::<FormattedT>(1024, 1024).unwrap(); |
| 177 | + let renderer = ImguiRenderer::init(&mut imgui, effect.factory, Shaders::GlSl130, target).unwrap(); |
| 178 | + self.renderer = Some(RendererThing { |
| 179 | + renderer, |
| 180 | + texture, |
| 181 | + shader_resource_view, |
| 182 | + mesh: Mesh::build(data).build(&mut effect.factory)?, |
| 183 | + }); |
| 184 | + self.imgui = Some(imgui); |
| 185 | + |
| 186 | + effect |
| 187 | + .simple(VERT_SRC, FRAG_SRC) |
| 188 | + .with_raw_constant_buffer("VertexArgs", std::mem::size_of::<<VertexArgs as Uniform>::Std140>(), 1) |
| 189 | + .with_raw_vertex_buffer(PosTex::ATTRIBUTES, PosTex::size() as ElemStride, 0) |
| 190 | + .with_texture("albedo") |
| 191 | + .with_blended_output("color", ColorMask::all(), blend::ALPHA, None) |
| 192 | + .build() |
| 193 | + } |
| 194 | + |
| 195 | + fn apply<'ui, 'pass_data: 'ui>( |
| 196 | + &'ui mut self, |
| 197 | + encoder: &mut Encoder, |
| 198 | + effect: &mut Effect, |
| 199 | + mut factory: amethyst::renderer::Factory, |
| 200 | + (screen_dimensions, time, mut imgui_state): UiPassData<'pass_data>, |
| 201 | + ) { |
| 202 | + let imgui_state = imgui_state.get_or_insert_with(|| ImguiState { |
| 203 | + imgui: self.imgui.take().unwrap(), |
| 204 | + mouse_state: MouseState::default(), |
| 205 | + run_ui: None, |
| 206 | + size: (1024, 1024), |
| 207 | + }); |
| 208 | + let imgui = &mut imgui_state.imgui; |
| 209 | + |
| 210 | + let (width, height) = (screen_dimensions.width(), screen_dimensions.height()); |
| 211 | + let renderer_thing = self.renderer.as_mut().unwrap(); |
| 212 | + |
| 213 | + let vertex_args = VertexArgs { |
| 214 | + proj_vec: cgmath::vec4(2. / width, -2. / height, 0., 1.).into(), |
| 215 | + coord: [0., 0.].into(), |
| 216 | + dimension: [width, height].into(), |
| 217 | + }; |
| 218 | + |
| 219 | + if imgui_state.size.0 != width as u16 || imgui_state.size.1 != height as u16 { |
| 220 | + let (texture, shader_resource_view, target) = factory.create_render_target::<FormattedT>(width as u16, height as u16).unwrap(); |
| 221 | + renderer_thing.renderer.update_render_target(target); |
| 222 | + renderer_thing.shader_resource_view = shader_resource_view; |
| 223 | + renderer_thing.texture = texture; |
| 224 | + } |
| 225 | + |
| 226 | + encoder.clear( |
| 227 | + &factory |
| 228 | + .view_texture_as_render_target::<FormattedT>(&renderer_thing.texture, 0, None) |
| 229 | + .unwrap(), |
| 230 | + [0., 0., 0., 0.], |
| 231 | + ); |
| 232 | + { |
| 233 | + if let Some(ref run_ui) = imgui_state.run_ui { |
| 234 | + let mut ui = imgui.frame(FrameSize::new(f64::from(width), f64::from(height), 1.), time.delta_seconds()); |
| 235 | + run_ui(&mut ui); |
| 236 | + renderer_thing.renderer.render(ui, &mut factory, encoder).unwrap(); |
| 237 | + } |
| 238 | + } |
| 239 | + |
| 240 | + { |
| 241 | + use gfx::texture::{FilterMethod, SamplerInfo, WrapMode}; |
| 242 | + let sampler = factory.create_sampler(SamplerInfo::new(FilterMethod::Trilinear, WrapMode::Clamp)); |
| 243 | + effect.data.samplers.push(sampler); |
| 244 | + } |
| 245 | + |
| 246 | + effect.update_constant_buffer("VertexArgs", &vertex_args.std140(), encoder); |
| 247 | + effect.data.textures.push(renderer_thing.shader_resource_view.raw().clone()); |
| 248 | + effect |
| 249 | + .data |
| 250 | + .vertex_bufs |
| 251 | + .push(renderer_thing.mesh.buffer(PosTex::ATTRIBUTES).unwrap().clone()); |
| 252 | + |
| 253 | + effect.draw(renderer_thing.mesh.slice(), encoder); |
| 254 | + |
| 255 | + effect.data.textures.clear(); |
| 256 | + effect.data.samplers.clear(); |
| 257 | + } |
| 258 | +} |
| 259 | + |
| 260 | +#[derive(Copy, Clone, PartialEq, Debug, Default)] |
| 261 | +struct MouseState { |
| 262 | + pos: (i32, i32), |
| 263 | + pressed: (bool, bool, bool), |
| 264 | + wheel: f32, |
| 265 | +} |
| 266 | + |
| 267 | +pub fn handle_imgui_events(imgui_state: &mut ImguiState, event: &amethyst::renderer::Event) { |
| 268 | + use amethyst::{ |
| 269 | + renderer::{ |
| 270 | + ElementState, |
| 271 | + Event, |
| 272 | + MouseButton, |
| 273 | + VirtualKeyCode as VK, |
| 274 | + WindowEvent::{self, ReceivedCharacter}, |
| 275 | + }, |
| 276 | + winit::{MouseScrollDelta, TouchPhase}, |
| 277 | + }; |
| 278 | + |
| 279 | + let imgui = &mut imgui_state.imgui; |
| 280 | + let mouse_state = &mut imgui_state.mouse_state; |
| 281 | + |
| 282 | + if let Event::WindowEvent { event, .. } = event { |
| 283 | + match event { |
| 284 | + WindowEvent::KeyboardInput { input, .. } => { |
| 285 | + let pressed = input.state == ElementState::Pressed; |
| 286 | + match input.virtual_keycode { |
| 287 | + Some(VK::Tab) => imgui.set_key(0, pressed), |
| 288 | + Some(VK::Left) => imgui.set_key(1, pressed), |
| 289 | + Some(VK::Right) => imgui.set_key(2, pressed), |
| 290 | + Some(VK::Up) => imgui.set_key(3, pressed), |
| 291 | + Some(VK::Down) => imgui.set_key(4, pressed), |
| 292 | + Some(VK::PageUp) => imgui.set_key(5, pressed), |
| 293 | + Some(VK::PageDown) => imgui.set_key(6, pressed), |
| 294 | + Some(VK::Home) => imgui.set_key(7, pressed), |
| 295 | + Some(VK::End) => imgui.set_key(8, pressed), |
| 296 | + Some(VK::Delete) => imgui.set_key(9, pressed), |
| 297 | + Some(VK::Back) => imgui.set_key(10, pressed), |
| 298 | + Some(VK::Return) => imgui.set_key(11, pressed), |
| 299 | + Some(VK::Escape) => imgui.set_key(12, pressed), |
| 300 | + Some(VK::A) => imgui.set_key(13, pressed), |
| 301 | + Some(VK::C) => imgui.set_key(14, pressed), |
| 302 | + Some(VK::V) => imgui.set_key(15, pressed), |
| 303 | + Some(VK::X) => imgui.set_key(16, pressed), |
| 304 | + Some(VK::Y) => imgui.set_key(17, pressed), |
| 305 | + Some(VK::Z) => imgui.set_key(18, pressed), |
| 306 | + Some(VK::LControl) | Some(VK::RControl) => imgui.set_key_ctrl(pressed), |
| 307 | + Some(VK::LShift) | Some(VK::RShift) => imgui.set_key_shift(pressed), |
| 308 | + Some(VK::LAlt) | Some(VK::RAlt) => imgui.set_key_alt(pressed), |
| 309 | + Some(VK::LWin) | Some(VK::RWin) => imgui.set_key_super(pressed), |
| 310 | + _ => {}, |
| 311 | + } |
| 312 | + }, |
| 313 | + WindowEvent::CursorMoved { position: pos, .. } => { |
| 314 | + mouse_state.pos = (pos.0 as i32, pos.1 as i32); |
| 315 | + }, |
| 316 | + WindowEvent::MouseInput { state, button, .. } => match button { |
| 317 | + MouseButton::Left => mouse_state.pressed.0 = *state == ElementState::Pressed, |
| 318 | + MouseButton::Right => mouse_state.pressed.1 = *state == ElementState::Pressed, |
| 319 | + MouseButton::Middle => mouse_state.pressed.2 = *state == ElementState::Pressed, |
| 320 | + _ => {}, |
| 321 | + }, |
| 322 | + WindowEvent::MouseWheel { |
| 323 | + delta: MouseScrollDelta::LineDelta(_, y), |
| 324 | + phase: TouchPhase::Moved, |
| 325 | + .. |
| 326 | + } | WindowEvent::MouseWheel { |
| 327 | + delta: MouseScrollDelta::PixelDelta(_, y), |
| 328 | + phase: TouchPhase::Moved, |
| 329 | + .. |
| 330 | + } => mouse_state.wheel = *y, |
| 331 | + ReceivedCharacter(c) => imgui.add_input_character(*c), |
| 332 | + _ => (), |
| 333 | + } |
| 334 | + } |
| 335 | + |
| 336 | + imgui.set_mouse_pos(mouse_state.pos.0 as f32, mouse_state.pos.1 as f32); |
| 337 | + imgui.set_mouse_down([mouse_state.pressed.0, mouse_state.pressed.1, mouse_state.pressed.2, false, false]); |
| 338 | + imgui.set_mouse_wheel(mouse_state.wheel); |
| 339 | + mouse_state.wheel = 0.0; |
| 340 | +} |
0 commit comments