commit e6595cd7fde1cb44341f0ad14e6318d9e6bd31ca Author: skybldev Date: Tue Feb 1 18:30:08 2022 -0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..01eb1c9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "imgui-test" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +glium = { version = "0.30", default-features = true } +image = "0.23" +imgui = { version = "0.8.2", features = ["tables-api"] } +imgui-glium-renderer = "0.8.2" +imgui-winit-support = "0.8.2" diff --git a/assets/SFProDisplay_Regular.ttf b/assets/SFProDisplay_Regular.ttf new file mode 100755 index 0000000..5ae2faf Binary files /dev/null and b/assets/SFProDisplay_Regular.ttf differ diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..5b1c051 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,138 @@ +use glium::glutin; +use glium::glutin::event::{Event, WindowEvent}; +use glium::glutin::event_loop::{ControlFlow, EventLoop}; +use glium::glutin::window::WindowBuilder; +use glium::{Display, Surface}; +use imgui::{Context, FontConfig, FontSource, Ui}; +use imgui_glium_renderer::Renderer; +use imgui_winit_support::{HiDpiMode, WinitPlatform}; +use std::time::Instant; + +pub struct System { + pub event_loop: EventLoop<()>, + pub display: glium::Display, + pub imgui: Context, + pub platform: WinitPlatform, + pub renderer: Renderer, + pub font_size: f32 +} + +pub fn init(title: &str, size: (f64, f64)) -> System { + let event_loop = EventLoop::new(); + let context = glutin::ContextBuilder::new().with_vsync(true); + let builder = WindowBuilder::new() + .with_title(title.to_owned()) + .with_inner_size(glium::glutin::dpi::LogicalSize::new(size.0, size.1)); + let display = Display::new(builder, context, &event_loop) + .expect("Failed to initialize display!"); + + let mut imgui = Context::create(); + imgui.set_ini_filename(None); + + let mut platform = WinitPlatform::init(&mut imgui); + + { + let gl_window = display.gl_window(); + let window = gl_window.window(); + + let dpi_mode = if let Ok(factor) = std::env::var("FORCE_DPI_FACTOR") { + match factor.parse::() { + Ok(f) => HiDpiMode::Locked(f), + Err(e) => panic!("Invalid scaling factor: {}", e), + } + } else { + HiDpiMode::Default + }; + + platform.attach_window(imgui.io_mut(), window, dpi_mode); + } + + let font_size = 13.0; + + imgui.fonts().add_font(&[ + FontSource::TtfData { + data: include_bytes!("/home/skybl/code/rs/imgui-test/assets/SFProDisplay_Regular.ttf"), + size_pixels: font_size, + config: Some(FontConfig { + rasterizer_multiply: 1.5, + oversample_h: 4, + oversample_v: 4, + ..FontConfig::default() + }), + } + ]); + + let renderer = Renderer::init(&mut imgui, &display) + .expect("failed to initialize renderer!"); + + System { + event_loop, + display, + imgui, + platform, + renderer, + font_size + } +} + +impl System { + pub fn main_loop(self, mut run_ui: F) { + let System { + event_loop, + display, + mut imgui, + mut platform, + mut renderer, + .. + } = self; + + let mut last_frame = Instant::now(); + + event_loop.run(move |event, _, control_flow| match event { + Event::NewEvents(_) => { + let now = Instant::now(); + imgui.io_mut().update_delta_time(now - last_frame); + last_frame = now; + } + Event::MainEventsCleared => { + let gl_window = display.gl_window(); + platform + .prepare_frame(imgui.io_mut(), gl_window.window()) + .expect("Failed to prepare frame"); + gl_window.window().request_redraw(); + } + Event::RedrawRequested(_) => { + let mut ui = imgui.frame(); + + let mut run = true; + run_ui(&mut run, &mut ui, &display); + if !run { + println!("exiting"); + *control_flow = ControlFlow::Exit; + } + + let gl_window = display.gl_window(); + let mut target = display.draw(); + target.clear_color_srgb(1.0, 1.0, 1.0, 1.0); + platform.prepare_render(&ui, gl_window.window()); + let draw_data = ui.render(); + renderer + .render(&mut target, draw_data) + .expect("Rendering failed!"); + target + .finish() + .expect("Failed to swap buffers!"); + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + event => { + let gl_window = display.gl_window(); + platform.handle_event(imgui.io_mut(), gl_window.window(), &event); + } + }) + } +} + + diff --git a/src/drag_area.rs b/src/drag_area.rs new file mode 100644 index 0000000..8868522 --- /dev/null +++ b/src/drag_area.rs @@ -0,0 +1,92 @@ +// Node editor UI element + +use glium::glutin::window::Window as GlutinWindow; +use glium::glutin::dpi::LogicalPosition; +use imgui::{Ui, MouseButton}; +use imgui::color::ImColor32; +use imgui::draw_list::DrawListMut; + +const COLOR_BLUE: ImColor32 = ImColor32::from_rgb(76u8, 177u8, 230u8); + +pub struct DragArea { + pub offset: [f32; 2], + pub size: [f32; 2], + pub border_color: ImColor32, + default_offset: [f32; 2], + default_size: [f32; 2] +} + +pub fn init( + offset: [f32; 2], + size: [f32; 2] +) -> DragArea { + DragArea { + offset, + size, + border_color: COLOR_BLUE, + default_offset: offset, + default_size: size + } +} + +impl DragArea { + pub fn build + + (&mut self, ui: &Ui, window: &GlutinWindow, mut build_children: F) + { + let mouse_delta = ui.io().mouse_delta; + let mouse_pos = ui.io().mouse_pos; + + if ui.button("reset view") { + self.offset = self.default_offset; + } + + ui.invisible_button("##empty", self.size); + + let rect_min = ui.item_rect_min(); + let rect_max = ui.item_rect_max(); + + let lmb_down = ui.is_mouse_dragging(MouseButton::Left); + let mmb_down = ui.is_mouse_dragging(MouseButton::Middle); + + if ui.is_item_active() && (lmb_down || mmb_down) { + if rect_min[0] > mouse_pos[0] { + window.set_cursor_position( + LogicalPosition { x: rect_max[0], y: mouse_pos[1] } + ).unwrap(); + self.offset[0] -= self.size[0]; + } + + if rect_max[0] < mouse_pos[0] { + window.set_cursor_position( + LogicalPosition { x: rect_min[0], y: mouse_pos[1] } + ).unwrap(); + self.offset[0] += self.size[0]; + } + + if rect_min[1] > mouse_pos[1] { + window.set_cursor_position( + LogicalPosition { x: mouse_pos[0], y: rect_max[1] } + ).unwrap(); + self.offset[1] -= self.size[1]; + } + + if rect_max[1] < mouse_pos[1] { + window.set_cursor_position( + LogicalPosition { x: mouse_pos[0], y: rect_min[1] } + ).unwrap(); + self.offset[1] += self.size[1]; + } + + self.offset[0] += mouse_delta[0]; + self.offset[1] += mouse_delta[1]; + } + + let draw_list = ui.get_window_draw_list(); + + draw_list.with_clip_rect(rect_min, rect_max, || { + build_children(&draw_list, &rect_min, &rect_max, &self.offset); + draw_list.add_rect(rect_min, rect_max, self.border_color).build(); + }); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..677ff98 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,61 @@ +use glium::glutin::dpi::LogicalSize; +use imgui::*; +use imgui::color::ImColor32; + +mod app; +mod drag_area; + +const INIT_SIZE: (f64, f64) = (640.0, 480.0); +const MIN_SIZE: (f64, f64) = (300.0, 250.0); + + +fn main() { + let main_app = app::init("muh window", INIT_SIZE); + + main_app.display + .gl_window() + .window() + .set_min_inner_size(Some(LogicalSize::new(MIN_SIZE.0, MIN_SIZE.1))); + + let mut main_drag_area = drag_area::init( + [30.0, 30.0], + [200.0, 200.0] + ); + + main_app.main_loop(move |_, ui, display| { + Window::new("Node Editor") + .size([300.0, 110.0], Condition::FirstUseEver) + .build(&ui, || { + ui.text("Basic Drag Area"); + ui.separator(); + + main_drag_area.build( + &ui, + &display.gl_window().window(), + move |draw_list, area_min, area_max, offset| { + let text_pos: [f32; 2] = [ + area_min[0] + offset[0], + area_min[1] + offset[1] + ]; + + draw_list.add_text( + text_pos, + ImColor32::WHITE, + "Basic Drag Area Contents" + ); + } + ); + + draw_mouse_pos_text(ui); + }); + }); +} + +fn draw_mouse_pos_text(ui: &Ui) { + let mouse_pos = ui.io().mouse_pos; + + ui.text(format!( + "mouse pos: ({:.1}, {:.1})", + mouse_pos[0], mouse_pos[1] + )); +} diff --git a/src/node_editor.rs b/src/node_editor.rs new file mode 100644 index 0000000..d19c828 --- /dev/null +++ b/src/node_editor.rs @@ -0,0 +1,77 @@ +// Node editor UI element + +pub struct NodeEditor { + mut offset: [f32; 2], + mut size: [f32; 2] +} + +impl NodeEditor { + pub fn build(&self, ui: &Ui) { + const MOUSE_DELTA = ui.io().mouse_delta; + const MOUSE_POS = ui.io().mouse_pos; + + ui.invisible_button("##empty", self.size); + + const RECT_MIN = ui.item_rect_min(); + const RECT_MAX = ui.item_rect_max(); + + const LMB_DOWN = ui.is_mouse_dragging(MouseButton::Left); + const MMB_DOWN = ui.is_mouse_dragging(MouseButton::Middle); + + let mut info = String::new(); + + if ui.is_item_active() && (lmb_down || mmb_down) { + let cursor_wrapping = false + + if rect_min[0] > mouse_pos[0] { + info += "mouse x outside of left boundary\n"; + window.set_cursor_position( + LogicalPosition { x: rect_max[0] - 1.0, y: mouse_pos[1] } + ).unwrap(); + cursor_wrapping = true; + } + + if rect_max[0] < mouse_pos[0] { + info += "mouse x outside of right boundary\n"; + window.set_cursor_position( + LogicalPosition { x: rect_min[0] + 1.0, y: mouse_pos[1] } + ).unwrap(); + cursor_wrapping = true; + } + + if rect_min[1] > mouse_pos[1] { + info += "mouse y outside of top boundary\n"; + window.set_cursor_position( + LogicalPosition { x: mouse_pos[0], y: rect_max[1] - 1.0 } + ).unwrap(); + cursor_wrapping = true; + } + + if rect_max[1] < mouse_pos[1] { + info += "mouse y outside of bottom boundary\n"; + window.set_cursor_position( + LogicalPosition { x: mouse_pos[0], y: rect_min[1] + 1.0 } + ).unwrap(); + cursor_wrapping = true; + } + + if !cursor_wrapping { + offset[0] += mouse_delta[0]; + offset[1] += mouse_delta[1]; + } + } + + let text_str = "i hope this works!"; + let text_pos: [f32; 2] = [ + rect_min[0] + self.offset[0], + rect_min[1] + self.offset[1] + ]; + + let draw_list = ui.get_window_draw_list(); + + draw_list.with_clip_rect(rect_min, rect_max, || { + draw_list.add_text(text_pos, ImColor32::WHITE, text_str); + draw_list.add_rect(rect_min, rect_max, COLOR_BLUE).build(); + }); + + ui.text(format!("{}", info)); diff --git a/src/support.rs b/src/support.rs new file mode 100644 index 0000000..5b1c051 --- /dev/null +++ b/src/support.rs @@ -0,0 +1,138 @@ +use glium::glutin; +use glium::glutin::event::{Event, WindowEvent}; +use glium::glutin::event_loop::{ControlFlow, EventLoop}; +use glium::glutin::window::WindowBuilder; +use glium::{Display, Surface}; +use imgui::{Context, FontConfig, FontSource, Ui}; +use imgui_glium_renderer::Renderer; +use imgui_winit_support::{HiDpiMode, WinitPlatform}; +use std::time::Instant; + +pub struct System { + pub event_loop: EventLoop<()>, + pub display: glium::Display, + pub imgui: Context, + pub platform: WinitPlatform, + pub renderer: Renderer, + pub font_size: f32 +} + +pub fn init(title: &str, size: (f64, f64)) -> System { + let event_loop = EventLoop::new(); + let context = glutin::ContextBuilder::new().with_vsync(true); + let builder = WindowBuilder::new() + .with_title(title.to_owned()) + .with_inner_size(glium::glutin::dpi::LogicalSize::new(size.0, size.1)); + let display = Display::new(builder, context, &event_loop) + .expect("Failed to initialize display!"); + + let mut imgui = Context::create(); + imgui.set_ini_filename(None); + + let mut platform = WinitPlatform::init(&mut imgui); + + { + let gl_window = display.gl_window(); + let window = gl_window.window(); + + let dpi_mode = if let Ok(factor) = std::env::var("FORCE_DPI_FACTOR") { + match factor.parse::() { + Ok(f) => HiDpiMode::Locked(f), + Err(e) => panic!("Invalid scaling factor: {}", e), + } + } else { + HiDpiMode::Default + }; + + platform.attach_window(imgui.io_mut(), window, dpi_mode); + } + + let font_size = 13.0; + + imgui.fonts().add_font(&[ + FontSource::TtfData { + data: include_bytes!("/home/skybl/code/rs/imgui-test/assets/SFProDisplay_Regular.ttf"), + size_pixels: font_size, + config: Some(FontConfig { + rasterizer_multiply: 1.5, + oversample_h: 4, + oversample_v: 4, + ..FontConfig::default() + }), + } + ]); + + let renderer = Renderer::init(&mut imgui, &display) + .expect("failed to initialize renderer!"); + + System { + event_loop, + display, + imgui, + platform, + renderer, + font_size + } +} + +impl System { + pub fn main_loop(self, mut run_ui: F) { + let System { + event_loop, + display, + mut imgui, + mut platform, + mut renderer, + .. + } = self; + + let mut last_frame = Instant::now(); + + event_loop.run(move |event, _, control_flow| match event { + Event::NewEvents(_) => { + let now = Instant::now(); + imgui.io_mut().update_delta_time(now - last_frame); + last_frame = now; + } + Event::MainEventsCleared => { + let gl_window = display.gl_window(); + platform + .prepare_frame(imgui.io_mut(), gl_window.window()) + .expect("Failed to prepare frame"); + gl_window.window().request_redraw(); + } + Event::RedrawRequested(_) => { + let mut ui = imgui.frame(); + + let mut run = true; + run_ui(&mut run, &mut ui, &display); + if !run { + println!("exiting"); + *control_flow = ControlFlow::Exit; + } + + let gl_window = display.gl_window(); + let mut target = display.draw(); + target.clear_color_srgb(1.0, 1.0, 1.0, 1.0); + platform.prepare_render(&ui, gl_window.window()); + let draw_data = ui.render(); + renderer + .render(&mut target, draw_data) + .expect("Rendering failed!"); + target + .finish() + .expect("Failed to swap buffers!"); + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + event => { + let gl_window = display.gl_window(); + platform.handle_event(imgui.io_mut(), gl_window.window(), &event); + } + }) + } +} + +