In this post I'll walk you through how you can create game user interface with imgui and ggez. I spent a lot of time trying to make this work for my game (which you can read more about here) and suffered greatly because of the lack of documentation, so I'm writing this so you don't have to!

Packages

Let's start with our tech stack, we'll be needing the following packages.

# Cargo . toml [ dependencies ] ggez = "0.4.4" gfx_core = "0.8.3" gfx_device_gl = "0.15.3" imgui = "0.0.22" imgui - gfx - renderer = "0.0.22"

Starting with ggez

We are going to start with the ggez super simple example which just draws a circle which is moving from left to write and then we'll add all the imgui bits.

extern crate ggez ; use ggez : : conf ; use ggez : : event ; use ggez : : graphics : : { self , DrawMode , Point2 } ; use ggez : : { Context , GameResult } ; struct MainState { pos_x : f32 , } impl MainState { fn new ( _ctx : & mut Context ) -> GameResult < MainState > { let s = MainState { pos_x : 0.0 } ; Ok ( s ) } } impl event : : EventHandler for MainState { fn update ( & mut self , _ctx : & mut Context ) -> GameResult < ( ) > { self . pos_x = self . pos_x % 800.0 + 1.0 ; Ok ( ( ) ) } fn draw ( & mut self , ctx : & mut Context ) -> GameResult < ( ) > { graphics : : clear ( ctx ) ; graphics : : circle ( ctx , DrawMode : : Fill , Point2 : : new ( self . pos_x , 380.0 ) , 100.0 , 2.0 , ) ? ; graphics : : present ( ctx ) ; Ok ( ( ) ) } } pub fn main ( ) { let c = conf : : Conf : : new ( ) ; let ctx = & mut Context : : load_from_conf ( "super_simple" , "ggez" , c ) . unwrap ( ) ; let state = & mut MainState : : new ( ctx ) . unwrap ( ) ; event : : run ( ctx , state ) . unwrap ( ) ; }

Adding imgui

We are going to wrap all imgui functionality in a separate class which we will then use in our main. So for now, let's define the public interface of our imgui wrapper.

There are a few pieces of functionality we'll need:

initializing imgui

rendering game ui

receiving mouse events mouse position mouse button up/down



It should look something like this.

impl ImGuiWrapper { pub fn new ( ctx : & mut Context ) -> Self { } pub fn render ( & mut self , ctx : & mut Context ) { } pub fn update_mouse_pos ( & mut self , x : i32 , y : i32 ) { } pub fn update_mouse_down ( & mut self , pressed : ( bool , bool , bool ) ) { } }

The implementation of this wrapper is essentially a bunch of boilerplate code to connect the ggez context with imgui. There are a few key parts around getting raw context bits, like the render target and factory, but apart from that it should be code you write (or copy) once and then pretty much forget about. You can see the full implementation here if you'd like to go a bit deeper into the details.

Something that is worth looking at closer is the render function, since that actually decides what goes on screen.

pub fn render ( & mut self , ctx : & mut Context ) { self . update_mouse ( ) ; let w = ctx . conf . window_mode . width ; let h = ctx . conf . window_mode . height ; let frame_size = FrameSize { logical_size : ( w as f64 , h as f64 ) , hidpi_factor : 2.0 , } ; let now = Instant : : now ( ) ; let delta = now - self . last_frame ; let delta_s = delta . as_secs ( ) as f32 + delta . subsec_nanos ( ) as f32 / 1_000_000_000.0 ; self . last_frame = now ; let ui = self . imgui . frame ( frame_size , delta_s ) ; { ui . window ( im_str! ( "Hello world" ) ) . size ( ( 300.0 , 600.0 ) , ImGuiCond : : FirstUseEver ) . position ( ( 100.0 , 100.0 ) , ImGuiCond : : FirstUseEver ) . build ( | | { ui . text ( im_str! ( "Hello world!" ) ) ; ui . text ( im_str! ( "こんにちは世界！" ) ) ; ui . text ( im_str! ( "This...is...imgui-rs!" ) ) ; ui . separator ( ) ; let mouse_pos = ui . imgui ( ) . mouse_pos ( ) ; ui . text ( im_str! ( "Mouse Position: ({:.1},{:.1})" , mouse_pos .0 , mouse_pos .1 ) ) ; if ui . small_button ( im_str! ( "small button" ) ) { println! ( "Small button clicked" ) ; } } ) ; } let ( factory , _ , encoder , _ , _ ) = graphics : : get_gfx_objects ( ctx ) ; self . renderer . render ( ui , & mut * factory , encoder ) . unwrap ( ) ; }

Integrating imgui in our main

Now that we have a clear interface for how we'll handle imgui, we can extend our main to include this.

struct MainState { pos_x : f32 , imgui_wrapper : ImGuiWrapper , } impl MainState { fn new ( mut ctx : & mut Context ) -> GameResult < MainState > { let imgui_wrapper = ImGuiWrapper : : new ( & mut ctx ) ; let s = MainState { pos_x : 0.0 , imgui_wrapper , } ; Ok ( s ) } } impl event : : EventHandler for MainState { fn update ( & mut self , _ctx : & mut Context ) -> GameResult < ( ) > { self . pos_x = self . pos_x % 800.0 + 1.0 ; Ok ( ( ) ) } fn draw ( & mut self , ctx : & mut Context ) -> GameResult < ( ) > { graphics : : clear ( ctx ) ; { graphics : : circle ( ctx , DrawMode : : Fill , Point2 : : new ( self . pos_x , 380.0 ) , 100.0 , 2.0 , ) ? ; } { self . imgui_wrapper . render ( ctx ) ; } graphics : : present ( ctx ) ; Ok ( ( ) ) } fn mouse_motion_event ( & mut self , _ctx : & mut Context , _state : MouseState , x : i32 , y : i32 , _xrel : i32 , _yrel : i32 ) { self . imgui_wrapper . update_mouse_pos ( x , y ) ; } fn mouse_button_down_event ( & mut self , _ctx : & mut Context , button : MouseButton , _x : i32 , _y : i32 ) { self . imgui_wrapper . update_mouse_down ( ( button == MouseButton : : Left , button == MouseButton : : Right , button == MouseButton : : Middle , ) ) ; } fn mouse_button_up_event ( & mut self , _ctx : & mut Context , button : MouseButton , _x : i32 , _y : i32 ) { self . imgui_wrapper . update_mouse_down ( ( match button { MouseButton : : Left => false , _ => true , } , match button { MouseButton : : Right => false , _ => true , } , match button { MouseButton : : Middle => false , _ => true , } , ) ) ; } } pub fn main ( ) { }

Wrapping up

Now that we have imgui integrated into our main, we should see the window we just added drawn on top of our ggez simple circle.

If we want to get fancy we can add more imgui stuff like popups, a menu bar and some menu items. See it in action below!

PS: You can find all this code at imgui-ggez-starter repo, which should be a pretty good reference point if you want to add imgui to your ggez project. Let me know if you have any problems or you'd like to contribute!