From 8bf34e8ced16daa40adfa39df00622b77db9d7cd Mon Sep 17 00:00:00 2001 From: Joonas Javanainen Date: Thu, 20 Aug 2015 18:36:03 +0300 Subject: [PATCH] API changes + additions --- examples/support/mod.rs | 83 +++++++++++------- examples/test_window.rs | 188 +++++++++++++++++++++++++++++++++++++--- src/ffi.rs | 7 ++ src/lib.rs | 58 +++++++++++-- src/menus.rs | 4 + src/window.rs | 183 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 473 insertions(+), 50 deletions(-) create mode 100644 src/window.rs diff --git a/examples/support/mod.rs b/examples/support/mod.rs index 6ec192f..126d6d5 100644 --- a/examples/support/mod.rs +++ b/examples/support/mod.rs @@ -1,55 +1,80 @@ use glium::{DisplayBuild, Surface}; +use glium::backend::glutin_backend::GlutinFacade; use glium::glutin; use glium::glutin::{ElementState, Event, MouseButton, VirtualKeyCode}; use imgui::{ImGui, Frame}; use imgui::glium_renderer::Renderer; use time::SteadyTime; -pub fn main_with_frame<'a, F: Fn(&Frame<'a>)>(f: F) { - let display = glutin::WindowBuilder::new() - .build_glium() - .unwrap(); +pub struct Support { + display: GlutinFacade, + imgui: ImGui, + renderer: Renderer, + last_frame: SteadyTime, + mouse_pos: (i32, i32), + mouse_pressed: (bool, bool, bool) +} - let mut imgui = ImGui::init(); - let mut renderer = Renderer::init(&mut imgui, &display).unwrap(); +impl Support { + pub fn init() -> Support { + let display = glutin::WindowBuilder::new() + .build_glium() + .unwrap(); - let mut last_frame = SteadyTime::now(); - let mut mouse_pos = (0, 0); - let mut mouse_pressed = (false, false, false); + let mut imgui = ImGui::init(); + let renderer = Renderer::init(&mut imgui, &display).unwrap(); - 'main: loop { + Support { + display: display, + imgui: imgui, + renderer: renderer, + last_frame: SteadyTime::now(), + mouse_pos: (0, 0), + mouse_pressed: (false, false, false) + } + } + + pub fn update_mouse(&mut self) { + self.imgui.set_mouse_pos(self.mouse_pos.0 as f32, self.mouse_pos.1 as f32); + self.imgui.set_mouse_down(&[self.mouse_pressed.0, self.mouse_pressed.1, self.mouse_pressed.2, false, false]); + } + + pub fn render<'fr, 'a: 'fr , F: FnMut(&Frame<'fr>) -> bool>( + &'a mut self, clear_color: (f32, f32, f32, f32), mut f: F) -> bool { + let mut result; let now = SteadyTime::now(); - let delta = now - last_frame; + let delta = now - self.last_frame; let delta_f = delta.num_nanoseconds().unwrap() as f32 / 1_000_000_000.0; - last_frame = now; + self.last_frame = now; - imgui.set_mouse_pos(mouse_pos.0 as f32, mouse_pos.1 as f32); - imgui.set_mouse_down(&[mouse_pressed.0, mouse_pressed.1, mouse_pressed.2, false, false]); + self.update_mouse(); - let mut target = display.draw(); - target.clear_color(1.0, 1.0, 1.0, 1.0); + let mut target = self.display.draw(); + target.clear_color(clear_color.0, clear_color.1, + clear_color.2, clear_color.3); let (width, height) = target.get_dimensions(); - let frame = imgui.frame(width, height, delta_f); - f(&frame); - renderer.render(&mut target, frame).unwrap(); + let frame = self.imgui.frame(width, height, delta_f); + result = f(&frame); + self.renderer.render(&mut target, frame).unwrap(); target.finish().unwrap(); - for event in display.poll_events() { + for event in self.display.poll_events() { match event { Event::Closed | Event::KeyboardInput(ElementState::Pressed, _, Some(VirtualKeyCode::Escape)) - => break 'main, - Event::MouseMoved(pos) => mouse_pos = pos, - Event::MouseInput(state, MouseButton::Left) => - mouse_pressed.0 = state == ElementState::Pressed, - Event::MouseInput(state, MouseButton::Right) => - mouse_pressed.1 = state == ElementState::Pressed, - Event::MouseInput(state, MouseButton::Middle) => - mouse_pressed.2 = state == ElementState::Pressed, - _ => () + => result = false, + Event::MouseMoved(pos) => self.mouse_pos = pos, + Event::MouseInput(state, MouseButton::Left) => + self.mouse_pressed.0 = state == ElementState::Pressed, + Event::MouseInput(state, MouseButton::Right) => + self.mouse_pressed.1 = state == ElementState::Pressed, + Event::MouseInput(state, MouseButton::Middle) => + self.mouse_pressed.2 = state == ElementState::Pressed, + _ => () } } + result } } diff --git a/examples/test_window.rs b/examples/test_window.rs index 7384c8c..1d8b716 100644 --- a/examples/test_window.rs +++ b/examples/test_window.rs @@ -4,24 +4,166 @@ extern crate glium; extern crate imgui; extern crate time; -use imgui::Frame; +use imgui::*; + +use self::support::Support; mod support; -fn main() { - // let mut show_app_metrics = false; - let show_app_main_menu_bar = true; - - support::main_with_frame(|frame| { - // if show_app_metrics { show_metrics_window(&mut show_app_metrics) } - if show_app_main_menu_bar { show_example_app_main_menu_bar(frame) } - }); +struct State { + clear_color: (f32, f32, f32, f32), + show_app_metrics: bool, + show_app_main_menu_bar: bool, + show_app_console: bool, + show_app_layout: bool, + show_app_long_text: bool, + show_app_auto_resize: bool, + show_app_fixed_overlay: bool, + show_app_custom_rendering: bool, + show_app_manipulating_window_title: bool, + show_app_about: bool, + no_titlebar: bool, + no_border: bool, + no_resize: bool, + no_move: bool, + no_scrollbar: bool, + no_collapse: bool, + no_menu: bool, + bg_alpha: f32, + file_menu: FileMenuState } -fn show_example_app_main_menu_bar<'a>(frame: &Frame<'a>) { +impl Default for State { + fn default() -> Self { + State { + clear_color: (114.0 / 255.0, 144.0 / 255.0, 154.0 / 255.0, 1.0), + show_app_metrics: false, + show_app_main_menu_bar: false, + show_app_console: false, + show_app_layout: false, + show_app_long_text: false, + show_app_auto_resize: false, + show_app_fixed_overlay: false, + show_app_custom_rendering: false, + show_app_manipulating_window_title: false, + show_app_about: false, + no_titlebar: false, + no_border: false, + no_resize: false, + no_move: false, + no_scrollbar: false, + no_collapse: false, + no_menu: false, + bg_alpha: 0.65, + file_menu: Default::default() + } + } +} + +struct FileMenuState { + enabled: bool +} + +impl Default for FileMenuState { + fn default() -> Self { + FileMenuState { + enabled: true + } + } +} + +fn main() { + let mut state = State { + .. Default::default() + }; + let mut support = Support::init(); + + loop { + let active = support.render(state.clear_color, |frame| { + show_test_window(frame, &mut state) + }); + if !active { break } + } +} + +fn show_test_window<'a>(frame: &Frame<'a>, state: &mut State) -> bool { + if state.show_app_main_menu_bar { show_example_app_main_menu_bar(frame, state) } + if state.show_app_fixed_overlay { + state.show_app_fixed_overlay = show_example_app_fixed_overlay(frame); + } + if state.show_app_about { + state.show_app_about = frame.window() + .name(im_str!("About ImGui")) + .always_auto_resize(true) + .closable(true) + .build(|| { + frame.text(ImStr::from_str(&format!("ImGui {}", imgui::get_version()))); + frame.separator(); + frame.text(im_str!("By Omar Cornut and all github contributors.")); + frame.text(im_str!("ImGui is licensed under the MIT License, see LICENSE for more information.")); + }) + } + + frame.window().name(im_str!("ImGui Demo")) + .title_bar(!state.no_titlebar) + .show_borders(!state.no_border) + .resizable(!state.no_resize) + .movable(!state.no_move) + .scroll_bar(!state.no_scrollbar) + .collapsible(!state.no_collapse) + .menu_bar(!state.no_menu) + .bg_alpha(state.bg_alpha) + .size((550.0, 680.0), ImGuiSetCond_FirstUseEver) + .closable(true) + .build(|| { + frame.text(im_str!("ImGui says hello.")); + frame.menu_bar(|| { + frame.menu(im_str!("Menu")).build(|| { + show_example_menu_file(frame, &mut state.file_menu); + }); + frame.menu(im_str!("Examples")).build(|| { + if frame.menu_item(im_str!("Main menu bar")).build() { + state.show_app_main_menu_bar = !state.show_app_main_menu_bar; + } + if frame.menu_item(im_str!("Console")).build() { + state.show_app_console = !state.show_app_console; + } + if frame.menu_item(im_str!("Simple layout")).build() { + state.show_app_layout = !state.show_app_layout; + } + if frame.menu_item(im_str!("Long text display")).build() { + state.show_app_long_text = !state.show_app_long_text; + } + if frame.menu_item(im_str!("Auto-resizing window")).build() { + state.show_app_auto_resize = !state.show_app_auto_resize; + } + if frame.menu_item(im_str!("Simple overlay")).build() { + state.show_app_fixed_overlay = !state.show_app_fixed_overlay; + } + if frame.menu_item(im_str!("Manipulating window title")).build() { + state.show_app_manipulating_window_title = + !state.show_app_manipulating_window_title; + } + if frame.menu_item(im_str!("Custom rendering")).build() { + state.show_app_custom_rendering = !state.show_app_custom_rendering; + } + }); + frame.menu(im_str!("Help")).build(|| { + if frame.menu_item(im_str!("Metrics")).build() { + state.show_app_metrics = !state.show_app_metrics; + } + if frame.menu_item(im_str!("About ImGui")).build() { + state.show_app_about = !state.show_app_about; + } + }); + }); + }) +} + +fn show_example_app_main_menu_bar<'a>(frame: &Frame<'a>, state: &mut State) { frame.main_menu_bar(|| { frame.menu(im_str!("File")).build(|| { - show_example_menu_file(frame); + show_example_menu_file(frame, &mut state.file_menu); }); frame.menu(im_str!("Edit")).build(|| { if frame.menu_item(im_str!("Undo")).shortcut(im_str!("CTRL+Z")).build() { } @@ -35,7 +177,7 @@ fn show_example_app_main_menu_bar<'a>(frame: &Frame<'a>) { }); } -fn show_example_menu_file<'a>(frame: &Frame<'a>) { +fn show_example_menu_file<'a>(frame: &Frame<'a>, state: &mut FileMenuState) { frame.menu_item(im_str!("(dummy menu)")).enabled(false).build(); if frame.menu_item(im_str!("New")).build() { } if frame.menu_item(im_str!("Open")).shortcut(im_str!("Ctrl+O")).build() { } @@ -47,7 +189,7 @@ fn show_example_menu_file<'a>(frame: &Frame<'a>) { frame.menu_item(im_str!("Hello")); frame.menu_item(im_str!("Sailor")); frame.menu(im_str!("Recurse..")).build(|| { - show_example_menu_file(frame); + show_example_menu_file(frame, state); }); }); }); @@ -55,6 +197,9 @@ fn show_example_menu_file<'a>(frame: &Frame<'a>) { if frame.menu_item(im_str!("Save As..")).build() { } frame.separator(); frame.menu(im_str!("Options")).build(|| { + if frame.menu_item(im_str!("Enabled")).selected(state.enabled).build() { + state.enabled = !state.enabled; + } // TODO }); frame.menu(im_str!("Colors")).build(|| { @@ -66,3 +211,20 @@ fn show_example_menu_file<'a>(frame: &Frame<'a>) { if frame.menu_item(im_str!("Checked")).selected(true).build() { } if frame.menu_item(im_str!("Quit")).shortcut(im_str!("Alt+F4")).build() { } } + +fn show_example_app_fixed_overlay<'a>(frame: &Frame<'a>) -> bool { + frame.window() + .name(im_str!("Example: Fixed Overlay")) + .closable(true) + .bg_alpha(0.3) + .title_bar(false) + .resizable(false) + .movable(false) + .save_settings(false) + .build(|| { + frame.text(im_str!("Simple overlay\non the top-left side of the screen.")); + frame.separator(); + let mouse_pos = frame.imgui().mouse_pos(); + frame.text(im_str!("Mouse Position: ({:.1},{:.1})", mouse_pos.0, mouse_pos.1)); + }) +} diff --git a/src/ffi.rs b/src/ffi.rs index 35b4376..9272b2a 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -161,6 +161,13 @@ bitflags!( } ); +impl ImGuiWindowFlags { + #[inline] + pub fn with(self, mask: ImGuiWindowFlags, value: bool) -> ImGuiWindowFlags { + if value { self | mask } else { self - mask } + } +} + bitflags!( #[repr(C)] flags ImGuiSetCond: c_int { diff --git a/src/lib.rs b/src/lib.rs index a8dd354..9240ff5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,16 +11,31 @@ extern crate libc; extern crate sdl2; use libc::{c_char, c_float, c_int, c_uchar}; -use std::marker::PhantomData; +use std::borrow::Cow; +use std::ffi::CStr; use std::mem; use std::ptr; use std::slice; +use std::str; -pub use ffi::{ImDrawIdx, ImDrawVert, ImGuiWindowFlags, ImVec2, ImVec4}; +pub use ffi::{ + ImDrawIdx, ImDrawVert, + ImGuiSetCond, + ImGuiSetCond_Always, ImGuiSetCond_Once, + ImGuiSetCond_FirstUseEver, ImGuiSetCond_Appearing, + ImGuiWindowFlags, + ImGuiWindowFlags_NoTitleBar, ImGuiWindowFlags_NoResize, ImGuiWindowFlags_NoMove, + ImGuiWindowFlags_NoScrollbar, ImGuiWindowFlags_NoScrollWithMouse, ImGuiWindowFlags_NoCollapse, + ImGuiWindowFlags_AlwaysAutoResize, ImGuiWindowFlags_ShowBorders, + ImGuiWindowFlags_NoSavedSettings, ImGuiWindowFlags_NoInputs, ImGuiWindowFlags_MenuBar, + ImVec2, ImVec4 +}; pub use menus::{Menu, MenuItem}; +pub use window::{Window}; pub mod ffi; mod menus; +mod window; #[cfg(feature = "glium")] pub mod glium_renderer; @@ -29,20 +44,30 @@ pub struct ImGui; #[macro_export] macro_rules! im_str { - ($e:expr) => ({ + ($e:tt) => ({ let value = concat!($e, "\0"); unsafe { ::imgui::ImStr::from_bytes(value.as_bytes()) } }); + ($e:tt, $($arg:tt)*) => ({ + ::imgui::ImStr::from_str(&format!($e, $($arg)*)) + }) } pub struct ImStr<'a> { - bytes: &'a [u8] + bytes: Cow<'a, [u8]> } impl<'a> ImStr<'a> { pub unsafe fn from_bytes(bytes: &'a [u8]) -> ImStr<'a> { ImStr { - bytes: bytes + bytes: Cow::Borrowed(bytes) + } + } + pub fn from_str(value: &str) -> ImStr<'a> { + let mut bytes: Vec = value.bytes().collect(); + bytes.push(0); + ImStr { + bytes: Cow::Owned(bytes) } } fn as_ptr(&self) -> *const c_char { self.bytes.as_ptr() as *const c_char } @@ -54,6 +79,13 @@ pub struct TextureHandle<'a> { pub pixels: &'a [c_uchar] } +pub fn get_version() -> &'static str { + unsafe { + let bytes = CStr::from_ptr(ffi::igGetVersion()).to_bytes(); + str::from_utf8_unchecked(bytes) + } +} + impl ImGui { pub fn init() -> ImGui { let io: &mut ffi::ImGuiIO = unsafe { mem::transmute(ffi::igGetIO()) }; @@ -80,6 +112,10 @@ impl ImGui { let io: &mut ffi::ImGuiIO = unsafe { mem::transmute(ffi::igGetIO()) }; io.mouse_draw_cursor = value; } + pub fn mouse_pos(&self) -> (f32, f32) { + let io: &mut ffi::ImGuiIO = unsafe { mem::transmute(ffi::igGetIO()) }; + (io.mouse_pos.x as f32, io.mouse_pos.y as f32) + } pub fn set_mouse_pos(&mut self, x: f32, y: f32) { let io: &mut ffi::ImGuiIO = unsafe { mem::transmute(ffi::igGetIO()) }; io.mouse_pos.x = x; @@ -89,7 +125,7 @@ impl ImGui { let io: &mut ffi::ImGuiIO = unsafe { mem::transmute(ffi::igGetIO()) }; io.mouse_down = *states; } - pub fn frame<'a>(&mut self, width: u32, height: u32, delta_time: f32) -> Frame<'a> { + pub fn frame<'fr, 'a: 'fr>(&'a mut self, width: u32, height: u32, delta_time: f32) -> Frame<'fr> { unsafe { let io: &mut ffi::ImGuiIO = mem::transmute(ffi::igGetIO()); io.display_size.x = width as c_float; @@ -99,7 +135,7 @@ impl ImGui { ffi::igNewFrame(); } Frame { - _phantom: PhantomData + imgui: self } } } @@ -136,7 +172,7 @@ pub struct DrawList<'a> { } pub struct Frame<'fr> { - _phantom: PhantomData<&'fr ImGui> + imgui: &'fr ImGui } static FMT: &'static [u8] = b"%s\0"; @@ -144,6 +180,7 @@ static FMT: &'static [u8] = b"%s\0"; fn fmt_ptr() -> *const c_char { FMT.as_ptr() as *const c_char } impl<'fr> Frame<'fr> { + pub fn imgui(&self) -> &ImGui { self.imgui } pub fn render(self, mut f: F) -> Result<(), E> where F: FnMut(DrawList<'fr>) -> Result<(), E> { unsafe { @@ -173,6 +210,11 @@ impl<'fr> Frame<'fr> { } } +// Window +impl<'fr> Frame<'fr> { + pub fn window<'p>(&self) -> Window<'fr, 'p> { Window::new() } +} + // Widgets impl<'fr> Frame<'fr> { pub fn text<'b>(&self, text: ImStr<'b>) { diff --git a/src/menus.rs b/src/menus.rs index 074c6ee..28db298 100644 --- a/src/menus.rs +++ b/src/menus.rs @@ -18,6 +18,7 @@ impl<'fr, 'p> Menu<'fr, 'p> { _phantom: PhantomData } } + #[inline] pub fn enabled(self, enabled: bool) -> Self { Menu { enabled: enabled, @@ -51,18 +52,21 @@ impl<'fr, 'p> MenuItem<'fr, 'p> { _phantom: PhantomData } } + #[inline] pub fn shortcut(self, shortcut: ImStr<'p>) -> Self { MenuItem { shortcut: Some(shortcut), .. self } } + #[inline] pub fn selected(self, selected: bool) -> Self { MenuItem { selected: selected, .. self } } + #[inline] pub fn enabled(self, enabled: bool) -> Self { MenuItem { enabled: enabled, diff --git a/src/window.rs b/src/window.rs new file mode 100644 index 0000000..8afbc09 --- /dev/null +++ b/src/window.rs @@ -0,0 +1,183 @@ +use libc::c_float; +use std::marker::PhantomData; +use std::ptr; + +use super::ffi; +use super::{ + Frame, + ImGuiSetCond, + ImGuiWindowFlags, + ImGuiWindowFlags_NoTitleBar, ImGuiWindowFlags_NoResize, ImGuiWindowFlags_NoMove, + ImGuiWindowFlags_NoScrollbar, ImGuiWindowFlags_NoScrollWithMouse, ImGuiWindowFlags_NoCollapse, + ImGuiWindowFlags_AlwaysAutoResize, ImGuiWindowFlags_ShowBorders, + ImGuiWindowFlags_NoSavedSettings, ImGuiWindowFlags_NoInputs, ImGuiWindowFlags_MenuBar, + ImStr, ImVec2 +}; + +pub struct Window<'fr, 'p> { + pos: (f32, f32), + pos_cond: ImGuiSetCond, + size: (f32, f32), + size_cond: ImGuiSetCond, + name: ImStr<'p>, + closable: bool, + bg_alpha: f32, + flags: ImGuiWindowFlags, + _phantom: PhantomData<&'fr Frame<'fr>> +} + +impl<'fr, 'p> Window<'fr, 'p> { + pub fn new() -> Window<'fr, 'p> { + Window { + pos: (0.0, 0.0), + pos_cond: ImGuiSetCond::empty(), + size: (0.0, 0.0), + size_cond: ImGuiSetCond::empty(), + name: unsafe { ImStr::from_bytes(b"Debug\0") }, + closable: false, + bg_alpha: -1.0, + flags: ImGuiWindowFlags::empty(), + _phantom: PhantomData + } + } + #[inline] + pub fn position(self, pos: (f32, f32), cond: ImGuiSetCond) -> Self { + Window { + pos: pos, + pos_cond: cond, + .. self + } + } + #[inline] + pub fn size(self, size: (f32, f32), cond: ImGuiSetCond) -> Self { + Window { + size: size, + size_cond: cond, + .. self + } + } + #[inline] + pub fn name(self, name: ImStr<'p>) -> Self { + Window { + name: name, + .. self + } + } + #[inline] + pub fn closable(self, closable: bool) -> Self { + Window { + closable: closable, + .. self + } + } + #[inline] + pub fn bg_alpha(self, bg_alpha: f32) -> Self { + Window { + bg_alpha: bg_alpha, + .. self + } + } + pub fn flags(self, flags: ImGuiWindowFlags) -> Self { + Window { + flags: flags, + .. self + } + } + #[inline] + pub fn title_bar(self, value: bool) -> Self { + Window { + flags: self.flags.with(ImGuiWindowFlags_NoTitleBar, !value), + .. self + } + } + #[inline] + pub fn resizable(self, value: bool) -> Self { + Window { + flags: self.flags.with(ImGuiWindowFlags_NoResize, !value), + .. self + } + } + #[inline] + pub fn movable(self, value: bool) -> Self { + Window { + flags: self.flags.with(ImGuiWindowFlags_NoMove, !value), + .. self + } + } + #[inline] + pub fn scroll_bar(self, value: bool) -> Self { + Window { + flags: self.flags.with(ImGuiWindowFlags_NoScrollbar, !value), + .. self + } + } + #[inline] + pub fn scrollable(self, value: bool) -> Self { + Window { + flags: self.flags.with(ImGuiWindowFlags_NoScrollWithMouse, !value), + .. self + } + } + #[inline] + pub fn collapsible(self, value: bool) -> Self { + Window { + flags: self.flags.with(ImGuiWindowFlags_NoCollapse, !value), + .. self + } + } + #[inline] + pub fn always_auto_resize(self, value: bool) -> Self { + Window { + flags: self.flags.with(ImGuiWindowFlags_AlwaysAutoResize, value), + .. self + } + } + #[inline] + pub fn show_borders(self, value: bool) -> Self { + Window { + flags: self.flags.with(ImGuiWindowFlags_ShowBorders, value), + .. self + } + } + #[inline] + pub fn save_settings(self, value: bool) -> Self { + Window { + flags: self.flags.with(ImGuiWindowFlags_NoSavedSettings, !value), + .. self + } + } + #[inline] + pub fn inputs(self, value: bool) -> Self { + Window { + flags: self.flags.with(ImGuiWindowFlags_NoInputs, !value), + .. self + } + } + #[inline] + pub fn menu_bar(self, value: bool) -> Self { + Window { + flags: self.flags.with(ImGuiWindowFlags_MenuBar, value), + .. self + } + } + pub fn build(self, f: F) -> bool { + let mut opened = true; + let render = unsafe { + if !self.pos_cond.is_empty() { + ffi::igSetNextWindowPos(ImVec2::new(self.pos.0, self.pos.1), self.pos_cond); + } + if !self.size_cond.is_empty() { + ffi::igSetNextWindowSize(ImVec2::new(self.size.0, self.size.1), self.size_cond); + } + ffi::igBegin2(self.name.as_ptr(), + if self.closable { &mut opened } else { ptr::null_mut() }, + ImVec2::new(0.0, 0.0), self.bg_alpha as c_float, self.flags + ) + }; + if render { + f(); + } + unsafe { ffi::igEnd() }; + opened + } +}