-
-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add Dear ImGui example - Closes #90 * Change argument order to match `render_with` * Remove unnecessary borrow * Refactor error messages * Add a space * Refactor Gui field privacy * Add a menu bar and allow the about window to be closed - The local bool is necessary because the menu bar closures are not allowed to borrow `self` for mutable access while `imgui::Ui<'ui>` is alive. - The token-based menu bar lifetime is even more verbose than this.
- Loading branch information
Showing
6 changed files
with
333 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
[package] | ||
name = "imgui-winit" | ||
version = "0.1.0" | ||
authors = ["Jay Oster <jay@kodewerx.org>"] | ||
edition = "2018" | ||
publish = false | ||
|
||
[features] | ||
optimize = ["log/release_max_level_warn"] | ||
default = ["optimize"] | ||
|
||
[dependencies] | ||
env_logger = "0.7" | ||
imgui = "0.4" | ||
imgui-wgpu = "0.9" | ||
imgui-winit-support = { version = "0.4", default-features = false, features = ["winit-22"] } | ||
log = "0.4" | ||
pixels = { path = "../.." } | ||
winit = "0.22" | ||
winit_input_helper = "0.6" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Dear-ImGui Example | ||
|
||
![Dear-ImGui Example](../../img/imgui-winit.png) | ||
|
||
Minimal example with `imgui` and `winit`. | ||
|
||
## Running | ||
|
||
```bash | ||
cargo run --release --package imgui-winit | ||
``` | ||
|
||
## About | ||
|
||
This example is based on `minimal-winit`, and extends it with `imgui` to render custom GUI elements over your pixel frame buffer. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
use pixels::{raw_window_handle::HasRawWindowHandle, wgpu, PixelsContext}; | ||
use std::time::Instant; | ||
|
||
/// Manages all state required for rendering Dear ImGui over `Pixels`. | ||
pub(crate) struct Gui { | ||
imgui: imgui::Context, | ||
platform: imgui_winit_support::WinitPlatform, | ||
renderer: imgui_wgpu::Renderer, | ||
last_frame: Instant, | ||
last_cursor: Option<imgui::MouseCursor>, | ||
about_open: bool, | ||
} | ||
|
||
impl Gui { | ||
/// Create Dear ImGui. | ||
pub(crate) fn new<W: HasRawWindowHandle>( | ||
window: &winit::window::Window, | ||
pixels: &pixels::Pixels<W>, | ||
) -> Self { | ||
// Create Dear ImGui context | ||
let mut imgui = imgui::Context::create(); | ||
imgui.set_ini_filename(None); | ||
|
||
// Initialize winit platform support | ||
let mut platform = imgui_winit_support::WinitPlatform::init(&mut imgui); | ||
platform.attach_window( | ||
imgui.io_mut(), | ||
&window, | ||
imgui_winit_support::HiDpiMode::Default, | ||
); | ||
|
||
// Configure Dear ImGui fonts | ||
let hidpi_factor = window.scale_factor(); | ||
let font_size = (13.0 * hidpi_factor) as f32; | ||
imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; | ||
imgui | ||
.fonts() | ||
.add_font(&[imgui::FontSource::DefaultFontData { | ||
config: Some(imgui::FontConfig { | ||
oversample_h: 1, | ||
pixel_snap_h: true, | ||
size_pixels: font_size, | ||
..Default::default() | ||
}), | ||
}]); | ||
|
||
// Fix incorrect colors with sRGB framebuffer | ||
let style = imgui.style_mut(); | ||
for color in 0..style.colors.len() { | ||
style.colors[color] = gamma_to_linear(style.colors[color]); | ||
} | ||
|
||
// Create Dear ImGui WGPU renderer | ||
let device = pixels.device(); | ||
let queue = pixels.queue(); | ||
let texture_format = wgpu::TextureFormat::Bgra8UnormSrgb; | ||
let renderer = imgui_wgpu::Renderer::new(&mut imgui, &device, &queue, texture_format); | ||
|
||
// Return GUI context | ||
Self { | ||
imgui, | ||
platform, | ||
renderer, | ||
last_frame: Instant::now(), | ||
last_cursor: None, | ||
about_open: true, | ||
} | ||
} | ||
|
||
/// Prepare Dear ImGui. | ||
pub(crate) fn prepare( | ||
&mut self, | ||
window: &winit::window::Window, | ||
) -> Result<(), winit::error::ExternalError> { | ||
// Prepare Dear ImGui | ||
let io = self.imgui.io_mut(); | ||
self.last_frame = io.update_delta_time(self.last_frame); | ||
self.platform.prepare_frame(io, window) | ||
} | ||
|
||
/// Render Dear ImGui. | ||
pub(crate) fn render( | ||
&mut self, | ||
window: &winit::window::Window, | ||
encoder: &mut wgpu::CommandEncoder, | ||
render_target: &wgpu::TextureView, | ||
context: &PixelsContext, | ||
) -> imgui_wgpu::RendererResult<()> { | ||
// Start a new Dear ImGui frame and update the cursor | ||
let ui = self.imgui.frame(); | ||
|
||
let mouse_cursor = ui.mouse_cursor(); | ||
if self.last_cursor != mouse_cursor { | ||
self.last_cursor = mouse_cursor; | ||
self.platform.prepare_render(&ui, window); | ||
} | ||
|
||
// Draw windows and GUI elements here | ||
let mut about_open = false; | ||
ui.main_menu_bar(|| { | ||
ui.menu(imgui::im_str!("Help"), true, || { | ||
about_open = imgui::MenuItem::new(imgui::im_str!("About...")).build(&ui); | ||
}); | ||
}); | ||
if about_open { | ||
self.about_open = true; | ||
} | ||
|
||
if self.about_open { | ||
ui.show_about_window(&mut self.about_open); | ||
} | ||
|
||
// Render Dear ImGui with WGPU | ||
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { | ||
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { | ||
attachment: render_target, | ||
resolve_target: None, | ||
ops: wgpu::Operations { | ||
load: wgpu::LoadOp::Load, | ||
store: true, | ||
}, | ||
}], | ||
depth_stencil_attachment: None, | ||
}); | ||
|
||
self.renderer | ||
.render(ui.render(), &context.queue, &context.device, &mut rpass) | ||
} | ||
|
||
/// Handle any outstanding events. | ||
pub(crate) fn handle_event( | ||
&mut self, | ||
window: &winit::window::Window, | ||
event: &winit::event::Event<()>, | ||
) { | ||
self.platform | ||
.handle_event(self.imgui.io_mut(), window, event); | ||
} | ||
} | ||
|
||
fn gamma_to_linear(color: [f32; 4]) -> [f32; 4] { | ||
const GAMMA: f32 = 2.2; | ||
|
||
let x = color[0].powf(GAMMA); | ||
let y = color[1].powf(GAMMA); | ||
let z = color[2].powf(GAMMA); | ||
let w = 1.0 - (1.0 - color[3]).powf(GAMMA); | ||
|
||
[x, y, z, w] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
#![deny(clippy::all)] | ||
#![forbid(unsafe_code)] | ||
|
||
use crate::gui::Gui; | ||
use log::error; | ||
use pixels::{Error, Pixels, SurfaceTexture}; | ||
use winit::dpi::LogicalSize; | ||
use winit::event::{Event, VirtualKeyCode}; | ||
use winit::event_loop::{ControlFlow, EventLoop}; | ||
use winit::window::WindowBuilder; | ||
use winit_input_helper::WinitInputHelper; | ||
|
||
mod gui; | ||
|
||
const WIDTH: u32 = 640; | ||
const HEIGHT: u32 = 480; | ||
const BOX_SIZE: i16 = 64; | ||
|
||
/// Representation of the application state. In this example, a box will bounce around the screen. | ||
struct World { | ||
box_x: i16, | ||
box_y: i16, | ||
velocity_x: i16, | ||
velocity_y: i16, | ||
} | ||
|
||
fn main() -> Result<(), Error> { | ||
env_logger::init(); | ||
let event_loop = EventLoop::new(); | ||
let mut input = WinitInputHelper::new(); | ||
let window = { | ||
let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64); | ||
WindowBuilder::new() | ||
.with_title("Hello Pixels + Dear ImGui") | ||
.with_inner_size(size) | ||
.with_min_inner_size(size) | ||
.build(&event_loop) | ||
.unwrap() | ||
}; | ||
|
||
let mut pixels = { | ||
let window_size = window.inner_size(); | ||
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window); | ||
Pixels::new(WIDTH, HEIGHT, surface_texture)? | ||
}; | ||
let mut world = World::new(); | ||
|
||
// Set up Dear ImGui | ||
let mut gui = Gui::new(&window, &pixels); | ||
|
||
event_loop.run(move |event, _, control_flow| { | ||
// Draw the current frame | ||
if let Event::RedrawRequested(_) = event { | ||
// Draw the world | ||
world.draw(pixels.get_frame()); | ||
|
||
// Prepare Dear ImGui | ||
gui.prepare(&window).expect("gui.prepare() failed"); | ||
|
||
// Render everything together | ||
let render_result = pixels.render_with(|encoder, render_target, context| { | ||
// Render the world texture | ||
context.scaling_renderer.render(encoder, render_target); | ||
|
||
// Render Dear ImGui | ||
gui.render(&window, encoder, render_target, context) | ||
.expect("gui.render() failed"); | ||
}); | ||
|
||
// Basic error handling | ||
if render_result | ||
.map_err(|e| error!("pixels.render() failed: {}", e)) | ||
.is_err() | ||
{ | ||
*control_flow = ControlFlow::Exit; | ||
return; | ||
} | ||
} | ||
|
||
// Handle input events | ||
gui.handle_event(&window, &event); | ||
if input.update(event) { | ||
// Close events | ||
if input.key_pressed(VirtualKeyCode::Escape) || input.quit() { | ||
*control_flow = ControlFlow::Exit; | ||
return; | ||
} | ||
|
||
// Resize the window | ||
if let Some(size) = input.window_resized() { | ||
pixels.resize(size.width, size.height); | ||
} | ||
|
||
// Update internal state and request a redraw | ||
world.update(); | ||
window.request_redraw(); | ||
} | ||
}); | ||
} | ||
|
||
impl World { | ||
/// Create a new `World` instance that can draw a moving box. | ||
fn new() -> Self { | ||
Self { | ||
box_x: 24, | ||
box_y: 16, | ||
velocity_x: 1, | ||
velocity_y: 1, | ||
} | ||
} | ||
|
||
/// Update the `World` internal state; bounce the box around the screen. | ||
fn update(&mut self) { | ||
if self.box_x <= 0 || self.box_x + BOX_SIZE > WIDTH as i16 { | ||
self.velocity_x *= -1; | ||
} | ||
if self.box_y <= 0 || self.box_y + BOX_SIZE > HEIGHT as i16 { | ||
self.velocity_y *= -1; | ||
} | ||
|
||
self.box_x += self.velocity_x; | ||
self.box_y += self.velocity_y; | ||
} | ||
|
||
/// Draw the `World` state to the frame buffer. | ||
/// | ||
/// Assumes the default texture format: [`wgpu::TextureFormat::Rgba8UnormSrgb`] | ||
fn draw(&self, frame: &mut [u8]) { | ||
for (i, pixel) in frame.chunks_exact_mut(4).enumerate() { | ||
let x = (i % WIDTH as usize) as i16; | ||
let y = (i / WIDTH as usize) as i16; | ||
|
||
let inside_the_box = x >= self.box_x | ||
&& x < self.box_x + BOX_SIZE | ||
&& y >= self.box_y | ||
&& y < self.box_y + BOX_SIZE; | ||
|
||
let rgba = if inside_the_box { | ||
[0x5e, 0x48, 0xe8, 0xff] | ||
} else { | ||
[0x48, 0xb2, 0xe8, 0xff] | ||
}; | ||
|
||
pixel.copy_from_slice(&rgba); | ||
} | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.