diff --git a/imgui-glow-renderer/Cargo.toml b/imgui-glow-renderer/Cargo.toml index 4a97636..67d7a7e 100644 --- a/imgui-glow-renderer/Cargo.toml +++ b/imgui-glow-renderer/Cargo.toml @@ -17,6 +17,7 @@ memoffset = "0.6.4" [dev-dependencies] glutin = "0.26.0" imgui-winit-support = { version = "0.7.1", path = "../imgui-winit-support" } +image = "0.23" [features] # Features here are used to opt-out of compiling code that depends on certain diff --git a/imgui-glow-renderer/examples/04_custom_textures.rs b/imgui-glow-renderer/examples/04_custom_textures.rs new file mode 100644 index 0000000..a03dc91 --- /dev/null +++ b/imgui-glow-renderer/examples/04_custom_textures.rs @@ -0,0 +1,284 @@ +//! Example showing the same functionality as +//! `imgui-examples/examples/custom_textures.rs` + +use std::{io::Cursor, time::Instant}; + +use glow::HasContext; +use image::{jpeg::JpegDecoder, ImageDecoder}; +use imgui::{im_str, Condition}; +use imgui_glow_renderer::{RendererBuilder, StateBackupCsm}; + +mod utils; + +const LENNA_JPEG: &[u8] = include_bytes!("../../resources/Lenna.jpg"); + +fn main() { + let (event_loop, window) = utils::create_window("Custom textures", glutin::GlRequest::Latest); + let (mut winit_platform, mut imgui_context) = utils::imgui_init(&window); + let gl = utils::glow_context(&window); + + let mut ig_renderer = RendererBuilder::new() + .with_context_state_manager(StateBackupCsm::default()) + .with_texture_map(imgui::Textures::::default()) + .build_borrowing(&gl, &mut imgui_context) + .expect("failed to create renderer"); + let textures_ui = TexturesUi::new(&gl, &mut ig_renderer.texture_map); + + let mut last_frame = Instant::now(); + event_loop.run(move |event, _, control_flow| { + *control_flow = glutin::event_loop::ControlFlow::Wait; + match event { + glutin::event::Event::NewEvents(_) => { + let now = Instant::now(); + imgui_context + .io_mut() + .update_delta_time(now.duration_since(last_frame)); + last_frame = now; + } + glutin::event::Event::MainEventsCleared => { + winit_platform + .prepare_frame(imgui_context.io_mut(), window.window()) + .unwrap(); + + window.window().request_redraw(); + } + glutin::event::Event::RedrawRequested(_) => { + unsafe { gl.clear(glow::COLOR_BUFFER_BIT) }; + + let ui = imgui_context.frame(); + textures_ui.show(&ui); + + winit_platform.prepare_render(&ui, window.window()); + let draw_data = ui.render(); + ig_renderer + .render(&gl, &draw_data) + .expect("error rendering imgui"); + + window.swap_buffers().unwrap(); + } + glutin::event::Event::WindowEvent { + event: glutin::event::WindowEvent::CloseRequested, + .. + } => { + *control_flow = glutin::event_loop::ControlFlow::Exit; + } + glutin::event::Event::LoopDestroyed => { + ig_renderer.destroy(&gl); + } + event => { + winit_platform.handle_event(imgui_context.io_mut(), window.window(), &event); + } + } + }); +} + +struct TexturesUi { + generated_texture: imgui::TextureId, + lenna: Lenna, +} + +impl TexturesUi { + fn new(gl: &glow::Context, textures: &mut imgui::Textures) -> Self { + Self { + generated_texture: Self::generate(gl, textures), + lenna: Lenna::load(gl, textures), + } + } + + /// Generate dummy texture + fn generate( + gl: &glow::Context, + textures: &mut imgui::Textures, + ) -> imgui::TextureId { + const WIDTH: usize = 100; + const HEIGHT: usize = 100; + + let mut data = Vec::with_capacity(WIDTH * HEIGHT); + for i in 0..WIDTH { + for j in 0..HEIGHT { + // Insert RGB values + data.push(i as u8); + data.push(j as u8); + data.push((i + j) as u8); + } + } + + let gl_texture = unsafe { gl.create_texture() }.expect("unable to create GL texture"); + + unsafe { + gl.bind_texture(glow::TEXTURE_2D, Some(gl_texture)); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MIN_FILTER, + glow::LINEAR as _, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MAG_FILTER, + glow::LINEAR as _, + ); + gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::RGB as _, + WIDTH as _, + HEIGHT as _, + 0, + glow::RGB, + glow::UNSIGNED_BYTE, + Some(&data), + ) + } + + textures.insert(gl_texture) + } + + fn show(&self, ui: &imgui::Ui) { + imgui::Window::new(im_str!("Hello textures")) + .size([400.0, 400.0], Condition::FirstUseEver) + .build(ui, || { + ui.text(im_str!("Hello textures!")); + ui.text("Some generated texture"); + imgui::Image::new(self.generated_texture, [100.0, 100.0]).build(ui); + + ui.text("Say hello to Lenna.jpg"); + self.lenna.show(ui); + + // Example of using custom textures on a button + ui.text("The Lenna buttons"); + { + ui.invisible_button(im_str!("Boring Button"), [100.0, 100.0]); + // See also `imgui::Ui::style_color` + let tint_none = [1.0, 1.0, 1.0, 1.0]; + let tint_green = [0.5, 1.0, 0.5, 1.0]; + let tint_red = [1.0, 0.5, 0.5, 1.0]; + + let tint = match ( + ui.is_item_hovered(), + ui.is_mouse_down(imgui::MouseButton::Left), + ) { + (false, _) => tint_none, + (true, false) => tint_green, + (true, true) => tint_red, + }; + + let draw_list = ui.get_window_draw_list(); + draw_list + .add_image( + self.lenna.texture_id, + ui.item_rect_min(), + ui.item_rect_max(), + ) + .col(tint) + .build(); + } + + { + ui.same_line(); + + // Button using quad positioned image + ui.invisible_button(im_str!("Exciting Button"), [100.0, 100.0]); + + // Button bounds + let min = ui.item_rect_min(); + let max = ui.item_rect_max(); + + // get corner coordinates + let tl = [ + min[0], + min[1] + (ui.frame_count() as f32 / 10.0).cos() * 10.0, + ]; + let tr = [ + max[0], + min[1] + (ui.frame_count() as f32 / 10.0).sin() * 10.0, + ]; + let bl = [min[0], max[1]]; + let br = max; + + let draw_list = ui.get_window_draw_list(); + draw_list + .add_image_quad(self.lenna.texture_id, tl, tr, br, bl) + .build(); + } + + // Rounded image + { + ui.same_line(); + ui.invisible_button(im_str!("Smooth Button"), [100.0, 100.0]); + + let draw_list = ui.get_window_draw_list(); + draw_list + .add_image_rounded( + self.lenna.texture_id, + ui.item_rect_min(), + ui.item_rect_max(), + 16.0, + ) + // Tint brighter for visiblity of corners + .col([2.0, 0.5, 0.5, 1.0]) + // Rounding on each corner can be changed separately + .round_top_left(ui.frame_count() / 60 % 4 == 0) + .round_top_right((ui.frame_count() + 1) / 60 % 4 == 1) + .round_bot_right((ui.frame_count() + 3) / 60 % 4 == 2) + .round_bot_left((ui.frame_count() + 2) / 60 % 4 == 3) + .build(); + } + }); + } +} + +struct Lenna { + texture_id: imgui::TextureId, + size: [f32; 2], +} + +impl Lenna { + fn load(gl: &glow::Context, textures: &mut imgui::Textures) -> Self { + let decoder = JpegDecoder::new(Cursor::new(LENNA_JPEG)).expect("could not create decoder"); + let (width, height) = decoder.dimensions(); + + let lenna_image = { + let mut bytes = vec![0; decoder.total_bytes() as usize]; + decoder + .read_image(&mut bytes) + .expect("unable to decode jpeg"); + bytes + }; + + let gl_texture = unsafe { gl.create_texture() }.expect("unable to create GL texture"); + + unsafe { + gl.bind_texture(glow::TEXTURE_2D, Some(gl_texture)); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MIN_FILTER, + glow::LINEAR as _, + ); + gl.tex_parameter_i32( + glow::TEXTURE_2D, + glow::TEXTURE_MAG_FILTER, + glow::LINEAR as _, + ); + gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::RGB as _, + width as _, + height as _, + 0, + glow::RGB, + glow::UNSIGNED_BYTE, + Some(&lenna_image), + ) + } + + Self { + texture_id: textures.insert(gl_texture), + size: [width as _, height as _], + } + } + + fn show(&self, ui: &imgui::Ui) { + imgui::Image::new(self.texture_id, self.size).build(ui); + } +}