diff --git a/imgui-examples/examples/LICENSE.mplus b/imgui-examples/examples/LICENSE.mplus deleted file mode 100644 index e8fa893f9..000000000 --- a/imgui-examples/examples/LICENSE.mplus +++ /dev/null @@ -1,16 +0,0 @@ -M+ FONTS Copyright (C) 2002-2017 M+ FONTS PROJECT - -- - -LICENSE_E - - - - -These fonts are free software. -Unlimited permission is granted to use, copy, and distribute them, with -or without modification, either commercially or noncommercially. -THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY. - - -http://mplus-fonts.osdn.jp diff --git a/imgui-examples/examples/color_button.rs b/imgui-examples/examples/color_button.rs index 55fc6dd01..f07e8cf21 100644 --- a/imgui-examples/examples/color_button.rs +++ b/imgui-examples/examples/color_button.rs @@ -2,8 +2,6 @@ use imgui::*; mod support; -const CLEAR_COLOR: [f32; 4] = [1.0, 1.0, 1.0, 1.0]; - struct State { example: i32, notify_text: &'static str, @@ -25,15 +23,15 @@ impl Default for State { } fn main() { + let system = support::init(file!()); let mut state = State::default(); - support::run("color_button.rs".to_owned(), CLEAR_COLOR, |ui, _, _| { + system.main_loop(|_, ui| { example_selector(&mut state, ui); match state.example { 1 => example_1(&mut state, ui), 2 => example_2(ui), _ => (), } - true }); } diff --git a/imgui-examples/examples/custom_textures.rs b/imgui-examples/examples/custom_textures.rs index a75936a40..8d95caeef 100644 --- a/imgui-examples/examples/custom_textures.rs +++ b/imgui-examples/examples/custom_textures.rs @@ -12,9 +12,6 @@ use image::{jpeg::JPEGDecoder, ImageDecoder}; use imgui::*; mod support; -use self::support::Textures; - -const CLEAR_COLOR: [f32; 4] = [1.0, 1.0, 1.0, 1.0]; #[derive(Default)] struct CustomTexturesApp { @@ -31,7 +28,7 @@ impl CustomTexturesApp { fn register_textures( &mut self, gl_ctx: &F, - textures: &mut Textures, + textures: &mut Textures>, ) -> Result<(), Box> where F: Facade, @@ -89,7 +86,7 @@ impl CustomTexturesApp { } impl Lenna { - fn new(gl_ctx: &F, textures: &mut Textures) -> Result> + fn new(gl_ctx: &F, textures: &mut Textures>) -> Result> where F: Facade, { @@ -121,16 +118,9 @@ impl Lenna { fn main() { let mut my_app = CustomTexturesApp::default(); - support::run( - "custom_textures.rs".to_owned(), - CLEAR_COLOR, - |ui, gl_ctx, textures| { - if let Err(e) = my_app.register_textures(gl_ctx, textures) { - panic!("Failed to register textures! {}", e); - } - my_app.show_textures(ui); - - true - }, - ); + let mut system = support::init(file!()); + my_app + .register_textures(system.display.get_context(), system.renderer.textures()) + .expect("Failed to register textures"); + system.main_loop(|_, ui| my_app.show_textures(ui)); } diff --git a/imgui-examples/examples/hello_world.rs b/imgui-examples/examples/hello_world.rs index 1e3492507..aca7539c4 100644 --- a/imgui-examples/examples/hello_world.rs +++ b/imgui-examples/examples/hello_world.rs @@ -2,29 +2,21 @@ use imgui::*; mod support; -const CLEAR_COLOR: [f32; 4] = [1.0, 1.0, 1.0, 1.0]; - fn main() { - support::run("hello_world.rs".to_owned(), CLEAR_COLOR, |ui, _, _| { - hello_world(ui) + let system = support::init(file!()); + system.main_loop(|_, ui| { + ui.window(im_str!("Hello world")) + .size([300.0, 100.0], Condition::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.io().mouse_pos; + ui.text(format!( + "Mouse Position: ({:.1},{:.1})", + mouse_pos[0], mouse_pos[1] + )); + }); }); } - -fn hello_world<'a>(ui: &Ui<'a>) -> bool { - ui.window(im_str!("Hello world")) - .size([300.0, 100.0], Condition::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.io().mouse_pos; - ui.text(im_str!( - "Mouse Position: ({:.1},{:.1})", - mouse_pos[0], - mouse_pos[1] - )); - }); - - true -} diff --git a/imgui-examples/examples/support/mod.rs b/imgui-examples/examples/support/mod.rs index 50b1925ce..a8bdbd82a 100644 --- a/imgui-examples/examples/support/mod.rs +++ b/imgui-examples/examples/support/mod.rs @@ -1,103 +1,112 @@ -use glium::{ - backend::{Context, Facade}, - Texture2d, -}; -use imgui::{self, FontGlyphRange, ImFontConfig, Ui}; +use glium::glutin::{self, Event, WindowEvent}; +use glium::{Display, Surface}; +use imgui::{Context, FontConfig, FontGlyphRanges, FontSource, Ui}; +use imgui_glium_renderer::GliumRenderer; use imgui_winit_support::{HiDpiMode, WinitPlatform}; -use std::rc::Rc; use std::time::Instant; -pub type Textures = imgui::Textures>; - -pub fn run(title: String, clear_color: [f32; 4], mut run_ui: F) -where - F: FnMut(&Ui, &Rc, &mut Textures) -> bool, -{ - use glium::glutin; - use glium::{Display, Surface}; - use imgui_glium_renderer::GliumRenderer; +pub struct System { + pub events_loop: glutin::EventsLoop, + pub display: glium::Display, + pub imgui: Context, + pub platform: WinitPlatform, + pub renderer: GliumRenderer, + pub font_size: f32, +} - let mut events_loop = glutin::EventsLoop::new(); +pub fn init(title: &str) -> System { + let events_loop = glutin::EventsLoop::new(); let context = glutin::ContextBuilder::new().with_vsync(true); let builder = glutin::WindowBuilder::new() - .with_title(title) + .with_title(title.to_owned()) .with_dimensions(glutin::dpi::LogicalSize::new(1024f64, 768f64)); - let display = Display::new(builder, context, &events_loop).unwrap(); - let gl_window = display.gl_window(); - let window = gl_window.window(); + let display = + Display::new(builder, context, &events_loop).expect("Failed to initialize display"); - let mut imgui = imgui::Context::create(); + let mut imgui = Context::create(); imgui.set_ini_filename(None); let mut platform = WinitPlatform::init(&mut imgui); - platform.attach_window(imgui.io_mut(), &window, HiDpiMode::Rounded); + { + let gl_window = display.gl_window(); + let window = gl_window.window(); + platform.attach_window(imgui.io_mut(), &window, HiDpiMode::Rounded); + } let hidpi_factor = platform.hidpi_factor(); let font_size = (13.0 * hidpi_factor) as f32; - - imgui.fonts().add_default_font_with_config( - ImFontConfig::new() - .oversample_h(1) - .pixel_snap_h(true) - .size_pixels(font_size), - ); - - imgui.fonts().add_font_with_config( - include_bytes!("../../../resources/mplus-1p-regular.ttf"), - ImFontConfig::new() - .merge_mode(true) - .oversample_h(1) - .pixel_snap_h(true) - .size_pixels(font_size) - .rasterizer_multiply(1.75), - &FontGlyphRange::japanese(), - ); + imgui.fonts().add_font(&[ + FontSource::DefaultFontData { + config: Some(FontConfig { + size_pixels: font_size, + ..FontConfig::default() + }), + }, + FontSource::TtfData { + data: include_bytes!("../../../resources/mplus-1p-regular.ttf"), + size_pixels: font_size, + config: Some(FontConfig { + rasterizer_multiply: 1.75, + glyph_ranges: FontGlyphRanges::japanese(), + ..FontConfig::default() + }), + }, + ]); imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; - let mut renderer = + let renderer = GliumRenderer::init(&mut imgui, &display).expect("Failed to initialize renderer"); - let mut last_frame = Instant::now(); - let mut quit = false; - - loop { - events_loop.poll_events(|event| { - use glium::glutin::{Event, WindowEvent::CloseRequested}; - - platform.handle_event(imgui.io_mut(), &window, &event); + System { + events_loop, + display, + imgui, + platform, + renderer, + font_size, + } +} - if let Event::WindowEvent { event, .. } = event { - match event { - CloseRequested => quit = true, - _ => (), +impl System { + pub fn main_loop(self, mut run_ui: F) { + let System { + mut events_loop, + display, + mut imgui, + mut platform, + mut renderer, + .. + } = self; + let gl_window = display.gl_window(); + let window = gl_window.window(); + let mut last_frame = Instant::now(); + let mut run = true; + + while run { + events_loop.poll_events(|event| { + platform.handle_event(imgui.io_mut(), &window, &event); + + if let Event::WindowEvent { event, .. } = event { + if let WindowEvent::CloseRequested = event { + run = false; + } } - } - }); - - let io = imgui.io_mut(); - platform - .prepare_frame(io, &window) - .expect("Failed to start frame"); - last_frame = io.update_delta_time(last_frame); - let ui = imgui.frame(); - if !run_ui(&ui, display.get_context(), renderer.textures()) { - break; - } - - let mut target = display.draw(); - target.clear_color( - clear_color[0], - clear_color[1], - clear_color[2], - clear_color[3], - ); - platform.prepare_render(&ui, &window); - renderer.render(&mut target, ui).expect("Rendering failed"); - target.finish().unwrap(); - - if quit { - break; + }); + + let io = imgui.io_mut(); + platform + .prepare_frame(io, &window) + .expect("Failed to start frame"); + last_frame = io.update_delta_time(last_frame); + let mut ui = imgui.frame(); + run_ui(&mut run, &mut ui); + + let mut target = display.draw(); + target.clear_color_srgb(1.0, 1.0, 1.0, 1.0); + platform.prepare_render(&ui, &window); + renderer.render(&mut target, ui).expect("Rendering failed"); + target.finish().expect("Failed to swap buffers"); } } } diff --git a/imgui-examples/examples/test_drawing_channels_split.rs b/imgui-examples/examples/test_drawing_channels_split.rs index 9f8ec2c26..7ba86840a 100644 --- a/imgui-examples/examples/test_drawing_channels_split.rs +++ b/imgui-examples/examples/test_drawing_channels_split.rs @@ -1,43 +1,38 @@ mod support; -const CLEAR_COLOR: [f32; 4] = [0.2, 0.2, 0.2, 1.0]; const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0]; const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0]; fn main() { - support::run( - "test_drawing_channels_split".to_owned(), - CLEAR_COLOR, - |ui, _, _| { - let draw_list = ui.get_window_draw_list(); - // Will draw channel 0 first, then channel 1, whatever the order of - // the calls in the code. - // - // Here, we draw a red line on channel 1 then a white circle on - // channel 0. As a result, the red line will always appear on top of - // the white circle. - draw_list.channels_split(2, |channels| { - const RADIUS: f32 = 100.0; - let canvas_pos = ui.get_cursor_screen_pos(); - channels.set_current(1); - draw_list - .add_line( - canvas_pos, - [canvas_pos[0] + RADIUS, canvas_pos[1] + RADIUS], - RED, - ) - .thickness(5.0) - .build(); + let system = support::init(file!()); + system.main_loop(|_, ui| { + let draw_list = ui.get_window_draw_list(); + // Will draw channel 0 first, then channel 1, whatever the order of + // the calls in the code. + // + // Here, we draw a red line on channel 1 then a white circle on + // channel 0. As a result, the red line will always appear on top of + // the white circle. + draw_list.channels_split(2, |channels| { + const RADIUS: f32 = 100.0; + let canvas_pos = ui.get_cursor_screen_pos(); + channels.set_current(1); + draw_list + .add_line( + canvas_pos, + [canvas_pos[0] + RADIUS, canvas_pos[1] + RADIUS], + RED, + ) + .thickness(5.0) + .build(); - channels.set_current(0); - let center = [canvas_pos[0] + RADIUS, canvas_pos[1] + RADIUS]; - draw_list - .add_circle(center, RADIUS, WHITE) - .thickness(10.0) - .num_segments(50) - .build(); - }); - true - }, - ); + channels.set_current(0); + let center = [canvas_pos[0] + RADIUS, canvas_pos[1] + RADIUS]; + draw_list + .add_circle(center, RADIUS, WHITE) + .thickness(10.0) + .num_segments(50) + .build(); + }); + }); } diff --git a/imgui-examples/examples/test_window.rs b/imgui-examples/examples/test_window.rs index 2e8d79273..72d1bab4c 100644 --- a/imgui-examples/examples/test_window.rs +++ b/imgui-examples/examples/test_window.rs @@ -1,11 +1,6 @@ mod support; -const CLEAR_COLOR: [f32; 4] = [0.2, 0.2, 0.2, 1.0]; - fn main() { - support::run("test_window.rs".to_owned(), CLEAR_COLOR, |ui, _, _| { - let mut open = true; - ui.show_demo_window(&mut open); - open - }); + let system = support::init(file!()); + system.main_loop(|run, ui| ui.show_demo_window(run)); } diff --git a/imgui-examples/examples/test_window_impl.rs b/imgui-examples/examples/test_window_impl.rs index 5189bb22d..cf33bcc37 100644 --- a/imgui-examples/examples/test_window_impl.rs +++ b/imgui-examples/examples/test_window_impl.rs @@ -182,16 +182,11 @@ impl Default for CustomRenderingState { } } -const CLEAR_COLOR: [f32; 4] = [114.0 / 255.0, 144.0 / 255.0, 154.0 / 255.0, 1.0]; - fn main() { let mut state = State::default(); - support::run("test_window.rs".to_owned(), CLEAR_COLOR, |ui, _, _| { - let mut open = true; - show_test_window(ui, &mut state, &mut open); - open - }); + let system = support::init(file!()); + system.main_loop(|run, ui| show_test_window(ui, &mut state, run)); } fn show_help_marker(ui: &Ui, desc: &str) { diff --git a/imgui-gfx-examples/examples/LICENSE.mplus b/imgui-gfx-examples/examples/LICENSE.mplus deleted file mode 100644 index e8fa893f9..000000000 --- a/imgui-gfx-examples/examples/LICENSE.mplus +++ /dev/null @@ -1,16 +0,0 @@ -M+ FONTS Copyright (C) 2002-2017 M+ FONTS PROJECT - -- - -LICENSE_E - - - - -These fonts are free software. -Unlimited permission is granted to use, copy, and distribute them, with -or without modification, either commercially or noncommercially. -THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY. - - -http://mplus-fonts.osdn.jp diff --git a/imgui-gfx-examples/examples/support_gfx/mod.rs b/imgui-gfx-examples/examples/support_gfx/mod.rs index 01e78a933..8272fa5e1 100644 --- a/imgui-gfx-examples/examples/support_gfx/mod.rs +++ b/imgui-gfx-examples/examples/support_gfx/mod.rs @@ -1,15 +1,15 @@ use gfx::Device; use glutin::{Event, WindowEvent}; -use imgui::{FontGlyphRange, ImFontConfig, Context, Ui}; +use imgui::{FontGlyphRanges, FontConfig, FontSource, Context, Ui}; use imgui_gfx_renderer::{GfxRenderer, Shaders}; use imgui_winit_support::{WinitPlatform, HiDpiMode}; use std::time::Instant; +type ColorFormat = gfx::format::Rgba8; +type DepthFormat = gfx::format::DepthStencil; + #[cfg(feature = "opengl")] pub fn run bool>(title: String, clear_color: [f32; 4], mut run_ui: F) { - type ColorFormat = gfx::format::Rgba8; - type DepthFormat = gfx::format::DepthStencil; - let mut events_loop = glutin::EventsLoop::new(); let context = glutin::ContextBuilder::new().with_vsync(true); let builder = glutin::WindowBuilder::new() @@ -17,7 +17,7 @@ pub fn run bool>(title: String, clear_color: [f32; 4], mut run_ .with_dimensions(glutin::dpi::LogicalSize::new(1024f64, 768f64)); let (windowed_context, mut device, mut factory, mut main_color, mut main_depth) = gfx_window_glutin::init::(builder, context, &events_loop) - .expect("Failed to initalize graphics"); + .expect("Failed to initialize graphics"); let mut encoder: gfx::Encoder<_, _> = factory.create_command_buffer().into(); let shaders = { @@ -64,24 +64,23 @@ pub fn run bool>(title: String, clear_color: [f32; 4], mut run_ let hidpi_factor = platform.hidpi_factor(); let font_size = (13.0 * hidpi_factor) as f32; - - imgui.fonts().add_default_font_with_config( - ImFontConfig::new() - .oversample_h(1) - .pixel_snap_h(true) - .size_pixels(font_size), - ); - - imgui.fonts().add_font_with_config( - include_bytes!("../../../resources/mplus-1p-regular.ttf"), - ImFontConfig::new() - .merge_mode(true) - .oversample_h(1) - .pixel_snap_h(true) - .size_pixels(font_size) - .rasterizer_multiply(1.75), - &FontGlyphRange::japanese(), - ); + imgui.fonts().add_font(&[ + FontSource::DefaultFontData { + config: Some(FontConfig { + size_pixels: font_size, + ..FontConfig::default() + }), + }, + FontSource::TtfData { + data: include_bytes!("../../../resources/mplus-1p-regular.ttf"), + size_pixels: font_size, + config: Some(FontConfig { + rasterizer_multiply: 1.75, + glyph_ranges: FontGlyphRanges::japanese(), + ..FontConfig::default() + }), + }, + ]); imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; @@ -134,9 +133,6 @@ pub fn run bool>(title: String, clear_color: [f32; 4], mut run_ use gfx_window_dxgi; use glutin; - type ColorFormat = gfx::format::Rgba8; - type DepthFormat = gfx::format::DepthStencil; - let mut events_loop = glutin::EventsLoop::new(); let window = glutin::WindowBuilder::new() .with_title(title) diff --git a/imgui-gfx-renderer/src/lib.rs b/imgui-gfx-renderer/src/lib.rs index 7edaf5c2e..b01cbab5b 100644 --- a/imgui-gfx-renderer/src/lib.rs +++ b/imgui-gfx-renderer/src/lib.rs @@ -136,21 +136,7 @@ where gfx::memory::Usage::Dynamic, Bind::empty(), )?; - let (_, texture) = ctx.prepare_texture(|handle| { - factory.create_texture_immutable_u8::( - gfx::texture::Kind::D2( - handle.width as u16, - handle.height as u16, - gfx::texture::AaMode::Single, - ), - gfx::texture::Mipmap::Provided, - &[handle.pixels], - ) - })?; - let sampler = - factory.create_sampler(SamplerInfo::new(FilterMethod::Trilinear, WrapMode::Clamp)); - ctx.set_font_texture_id(TextureId::from(usize::MAX)); - + let font_texture = upload_font_texture(ctx.fonts(), factory)?; let slice = Slice { start: 0, end: 0, @@ -167,12 +153,20 @@ where index_buffer, slice, pso, - font_texture: (texture, sampler), + font_texture, textures: Textures::new(), #[cfg(feature = "directx")] constants: factory.create_constant_buffer(1), }) } + pub fn reload_font_texture>( + &mut self, + ctx: &mut imgui::Context, + factory: &mut F, + ) -> Result<(), GfxRendererError> { + self.font_texture = upload_font_texture(ctx.fonts(), factory)?; + Ok(()) + } pub fn textures(&mut self) -> &mut Textures> { &mut self.textures } @@ -314,6 +308,26 @@ where } } +fn upload_font_texture>( + mut fonts: imgui::FontAtlasRefMut, + factory: &mut F, +) -> Result, GfxRendererError> { + let texture = fonts.build_rgba32_texture(); + let (_, texture_view) = factory.create_texture_immutable_u8::( + gfx::texture::Kind::D2( + texture.width as u16, + texture.height as u16, + gfx::texture::AaMode::Single, + ), + gfx::texture::Mipmap::Provided, + &[texture.data], + )?; + fonts.tex_id = TextureId::from(usize::MAX); + let sampler = factory.create_sampler(SamplerInfo::new(FilterMethod::Bilinear, WrapMode::Tile)); + let font_texture = (texture_view, sampler); + Ok(font_texture) +} + #[cfg(feature = "directx")] mod constants { use gfx::gfx_constant_struct_meta; diff --git a/imgui-glium-renderer/src/lib.rs b/imgui-glium-renderer/src/lib.rs index 9104ef90d..8fa41975a 100644 --- a/imgui-glium-renderer/src/lib.rs +++ b/imgui-glium-renderer/src/lib.rs @@ -1,7 +1,7 @@ use glium::backend::{Context, Facade}; use glium::index::{self, PrimitiveType}; use glium::program::ProgramChooserCreationError; -use glium::texture::{ClientFormat, RawImage2d, TextureCreationError}; +use glium::texture::{ClientFormat, MipmapsOption, RawImage2d, TextureCreationError}; use glium::uniforms::{MagnifySamplerFilter, MinifySamplerFilter}; use glium::{ program, uniform, vertex, Blend, DrawError, DrawParameters, IndexBuffer, Program, Rect, @@ -81,16 +81,7 @@ impl GliumRenderer { facade: &F, ) -> Result { let program = compile_default_program(facade)?; - let font_texture = ctx.prepare_texture(|handle| { - let data = RawImage2d { - data: Cow::Borrowed(handle.pixels), - width: handle.width, - height: handle.height, - format: ClientFormat::U8U8U8U8, - }; - Texture2d::new(facade, data) - })?; - ctx.set_font_texture_id(TextureId::from(usize::MAX)); + let font_texture = upload_font_texture(ctx.fonts(), facade.get_context())?; ctx.set_renderer_name(Some(ImString::from(format!( "imgui-glium-renderer {}", env!("CARGO_PKG_VERSION") @@ -102,6 +93,13 @@ impl GliumRenderer { textures: Textures::new(), }) } + pub fn reload_font_texture( + &mut self, + ctx: &mut imgui::Context, + ) -> Result<(), GliumRendererError> { + self.font_texture = upload_font_texture(ctx.fonts(), &self.ctx)?; + Ok(()) + } pub fn textures(&mut self) -> &mut Textures> { &mut self.textures } @@ -212,6 +210,22 @@ impl GliumRenderer { } } +fn upload_font_texture( + mut fonts: imgui::FontAtlasRefMut, + ctx: &Rc, +) -> Result { + let texture = fonts.build_rgba32_texture(); + let data = RawImage2d { + data: Cow::Borrowed(texture.data), + width: texture.width, + height: texture.height, + format: ClientFormat::U8U8U8U8, + }; + let font_texture = Texture2d::with_mipmaps(ctx, data, MipmapsOption::NoMipmap)?; + fonts.tex_id = TextureId::from(usize::MAX); + Ok(font_texture) +} + fn compile_default_program(facade: &F) -> Result { program!( facade, diff --git a/src/context.rs b/src/context.rs index d7720991a..d36fd12b1 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,8 +1,11 @@ use parking_lot::ReentrantMutex; +use std::cell::RefCell; use std::ffi::CStr; use std::ops::Drop; use std::ptr; +use std::rc::Rc; +use crate::fonts::atlas::{FontAtlas, FontAtlasRefMut, FontId, SharedFontAtlas}; use crate::io::Io; use crate::string::{ImStr, ImString}; use crate::style::Style; @@ -46,6 +49,7 @@ use crate::Ui; #[derive(Debug)] pub struct Context { raw: *mut sys::ImGuiContext, + shared_font_atlas: Option>>, ini_filename: Option, log_filename: Option, platform_name: Option, @@ -75,7 +79,15 @@ impl Context { /// /// Panics if an active context already exists pub fn create() -> Self { - Self::create_internal() + Self::create_internal(None) + } + /// Creates a new active imgui-rs context with a shared font atlas. + /// + /// # Panics + /// + /// Panics if an active context already exists + pub fn create_with_shared_font_atlas(shared_font_atlas: Rc>) -> Self { + Self::create_internal(Some(shared_font_atlas)) } /// Suspends this context so another context can be the active context. pub fn suspend(self) -> SuspendedContext { @@ -158,7 +170,7 @@ impl Context { let data = unsafe { CStr::from_ptr(sys::igSaveIniSettingsToMemory(ptr::null_mut())) }; buf.push_str(&data.to_string_lossy()); } - fn create_internal() -> Self { + fn create_internal(shared_font_atlas: Option>>) -> Self { let _guard = CTX_MUTEX.lock(); assert!( no_current_context(), @@ -169,6 +181,7 @@ impl Context { let raw = unsafe { sys::igCreateContext(ptr::null_mut()) }; Context { raw, + shared_font_atlas, ini_filename: None, log_filename: None, platform_name: None, @@ -217,7 +230,11 @@ pub struct SuspendedContext(Context); impl SuspendedContext { /// Creates a new suspended imgui-rs context. pub fn create() -> Self { - Self::create_internal() + Self::create_internal(None) + } + /// Creates a new suspended imgui-rs context with a shared font atlas. + pub fn create_with_shared_font_atlas(shared_font_atlas: Rc>) -> Self { + Self::create_internal(Some(shared_font_atlas)) } /// Attempts to activate this suspended context. /// @@ -236,11 +253,12 @@ impl SuspendedContext { Err(self) } } - fn create_internal() -> Self { + fn create_internal(shared_font_atlas: Option>>) -> Self { let _guard = CTX_MUTEX.lock(); let raw = unsafe { sys::igCreateContext(ptr::null_mut()) }; let ctx = Context { raw, + shared_font_atlas, ini_filename: None, log_filename: None, platform_name: None, @@ -323,6 +341,31 @@ fn test_suspend_failure() { assert!(suspended.activate().is_err()); } +#[test] +fn test_shared_font_atlas() { + let _guard = crate::test::TEST_MUTEX.lock(); + let atlas = Rc::new(RefCell::new(SharedFontAtlas::create())); + let suspended1 = SuspendedContext::create_with_shared_font_atlas(atlas.clone()); + let mut ctx2 = Context::create_with_shared_font_atlas(atlas.clone()); + { + let _borrow = ctx2.fonts(); + } + let _suspended2 = ctx2.suspend(); + let mut ctx = suspended1.activate().unwrap(); + let _borrow = ctx.fonts(); +} + +#[test] +#[should_panic] +fn test_shared_font_atlas_borrow_panic() { + let _guard = crate::test::TEST_MUTEX.lock(); + let atlas = Rc::new(RefCell::new(SharedFontAtlas::create())); + let _suspended = SuspendedContext::create_with_shared_font_atlas(atlas.clone()); + let mut ctx = Context::create_with_shared_font_atlas(atlas.clone()); + let _borrow1 = atlas.borrow(); + let _borrow2 = ctx.fonts(); +} + #[test] fn test_ini_load_save() { let (_guard, mut ctx) = crate::test::test_ctx(); @@ -365,10 +408,42 @@ impl Context { &mut *(sys::igGetStyle() as *mut Style) } } + /// Returns a mutable reference to the font atlas. + /// + /// # Panics + /// + /// Panics if the context uses a shared font atlas that is already borrowed + pub fn fonts(&mut self) -> FontAtlasRefMut { + match self.shared_font_atlas { + Some(ref font_atlas) => FontAtlasRefMut::Shared(font_atlas.borrow_mut()), + None => unsafe { + // safe because FontAtlas is a transparent wrapper around sys::ImFontAtlas + let fonts = &mut *(self.io_mut().fonts as *mut FontAtlas); + FontAtlasRefMut::Owned(fonts) + }, + } + } + /// # Panics + /// + /// Panics if the context uses a shared font atlas that is already borrowed pub fn frame<'ui, 'a: 'ui>(&'a mut self) -> Ui<'ui> { + // Clear default font if it no longer exists. This could be an error in the future + let default_font = self.io().font_default; + if !default_font.is_null() && self.fonts().get_font(FontId(default_font)).is_none() { + self.io_mut().font_default = ptr::null_mut(); + } + // NewFrame/Render/EndFrame mutate the font atlas so we need exclusive access to it + let font_atlas = self + .shared_font_atlas + .as_ref() + .map(|font_atlas| font_atlas.borrow_mut()); + // TODO: precondition checks unsafe { sys::igNewFrame(); } - Ui { ctx: self } + Ui { + ctx: self, + font_atlas, + } } } diff --git a/src/fonts.rs b/src/fonts.rs deleted file mode 100644 index 7a912f1db..000000000 --- a/src/fonts.rs +++ /dev/null @@ -1,399 +0,0 @@ -use std::f32; -use std::marker::PhantomData; -use std::mem; -use std::os::raw::{c_float, c_int, c_void}; -use std::ptr; - -use crate::internal::ImVector; -use crate::sys; - -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -enum FontGlyphRangeData { - ChineseSimplifiedCommon, - ChineseFull, - Cyrillic, - Default, - Japanese, - Korean, - Thai, - Custom(*const sys::ImWchar), -} - -/// A set of 16-bit Unicode codepoints -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -pub struct FontGlyphRange(FontGlyphRangeData); -impl FontGlyphRange { - /// The default set of glyph ranges used by imgui. - pub fn default() -> FontGlyphRange { - FontGlyphRange(FontGlyphRangeData::Default) - } - - /// A set of glyph ranges appropriate for use with simplified common Chinese text. - pub fn chinese_simplified_common() -> FontGlyphRange { - FontGlyphRange(FontGlyphRangeData::ChineseSimplifiedCommon) - } - /// A set of glyph ranges appropriate for use with Chinese text. - pub fn chinese_full() -> FontGlyphRange { - FontGlyphRange(FontGlyphRangeData::ChineseFull) - } - /// A set of glyph ranges appropriate for use with Cyrillic text. - pub fn cyrillic() -> FontGlyphRange { - FontGlyphRange(FontGlyphRangeData::Cyrillic) - } - /// A set of glyph ranges appropriate for use with Japanese text. - pub fn japanese() -> FontGlyphRange { - FontGlyphRange(FontGlyphRangeData::Japanese) - } - /// A set of glyph ranges appropriate for use with Korean text. - pub fn korean() -> FontGlyphRange { - FontGlyphRange(FontGlyphRangeData::Korean) - } - /// A set of glyph ranges appropriate for use with Thai text. - pub fn thai() -> FontGlyphRange { - FontGlyphRange(FontGlyphRangeData::Thai) - } - - /// Creates a glyph range from a static slice. The expected format is a series of pairs of - /// non-zero shorts, each representing an inclusive range of codepoints, followed by a single - /// zero terminating the range. The ranges must not overlap. - /// - /// As the slice is expected to last as long as a font is used, and is written into global - /// state, it must be `'static`. - /// - /// Panics - /// ====== - /// - /// This function will panic if the given slice is not a valid font range. - pub fn from_slice(slice: &'static [sys::ImWchar]) -> FontGlyphRange { - assert_eq!( - slice.len() % 2, - 1, - "The length of a glyph range must be odd." - ); - assert_eq!( - slice.last(), - Some(&0), - "A glyph range must be zero-terminated." - ); - - for (i, &glyph) in slice.iter().enumerate().take(slice.len() - 1) { - assert_ne!( - glyph, 0, - "A glyph in a range cannot be zero. \ - (Glyph is zero at index {})", - i - ) - } - - let mut ranges = Vec::new(); - for i in 0..slice.len() / 2 { - let (start, end) = (slice[i * 2], slice[i * 2 + 1]); - assert!( - start <= end, - "The start of a range cannot be larger than its end. \ - (At index {}, {} > {})", - i * 2, - start, - end - ); - ranges.push((start, end)); - } - ranges.sort_unstable_by_key(|x| x.0); - for i in 0..ranges.len() - 1 { - let (range_a, range_b) = (ranges[i], ranges[i + 1]); - if range_a.1 >= range_b.0 { - panic!( - "The glyph ranges {:?} and {:?} overlap between {:?}.", - range_a, - range_b, - (range_a.1, range_b.0) - ); - } - } - - unsafe { FontGlyphRange::from_slice_unchecked(slice) } - } - - /// Creates a glyph range from a static slice without checking its validity. - /// - /// See [`FontRangeGlyph::from_slice`] for more information. - pub unsafe fn from_slice_unchecked(slice: &'static [sys::ImWchar]) -> FontGlyphRange { - FontGlyphRange::from_ptr(slice.as_ptr()) - } - - /// Creates a glyph range from a pointer, without checking its validity or enforcing its - /// lifetime. The memory the pointer points to must be valid for as long as the font is - /// in use. - pub unsafe fn from_ptr(ptr: *const sys::ImWchar) -> FontGlyphRange { - FontGlyphRange(FontGlyphRangeData::Custom(ptr)) - } - - unsafe fn to_ptr(&self, atlas: *mut sys::ImFontAtlas) -> *const sys::ImWchar { - match self.0 { - FontGlyphRangeData::ChineseFull => sys::ImFontAtlas_GetGlyphRangesChineseFull(atlas), - FontGlyphRangeData::ChineseSimplifiedCommon => { - sys::ImFontAtlas_GetGlyphRangesChineseSimplifiedCommon(atlas) - } - FontGlyphRangeData::Cyrillic => sys::ImFontAtlas_GetGlyphRangesCyrillic(atlas), - FontGlyphRangeData::Default => sys::ImFontAtlas_GetGlyphRangesDefault(atlas), - FontGlyphRangeData::Japanese => sys::ImFontAtlas_GetGlyphRangesJapanese(atlas), - FontGlyphRangeData::Korean => sys::ImFontAtlas_GetGlyphRangesKorean(atlas), - FontGlyphRangeData::Thai => sys::ImFontAtlas_GetGlyphRangesThai(atlas), - - FontGlyphRangeData::Custom(ptr) => ptr, - } - } -} - -/// A builder for the configuration for a font. -#[derive(Copy, Clone, PartialEq, Debug)] -pub struct ImFontConfig { - size_pixels: f32, - oversample_h: u32, - oversample_v: u32, - pixel_snap_h: bool, - glyph_extra_spacing: sys::ImVec2, - glyph_offset: sys::ImVec2, - merge_mode: bool, - rasterizer_multiply: f32, -} -impl ImFontConfig { - pub fn new() -> ImFontConfig { - ImFontConfig { - size_pixels: 0.0, - oversample_h: 3, - oversample_v: 1, - pixel_snap_h: false, - glyph_extra_spacing: sys::ImVec2::zero(), - glyph_offset: sys::ImVec2::zero(), - merge_mode: false, - rasterizer_multiply: 1.0, - } - } - - pub fn size_pixels(mut self, size_pixels: f32) -> ImFontConfig { - self.size_pixels = size_pixels; - self - } - pub fn oversample_h(mut self, oversample_h: u32) -> ImFontConfig { - self.oversample_h = oversample_h; - self - } - pub fn oversample_v(mut self, oversample_v: u32) -> ImFontConfig { - self.oversample_v = oversample_v; - self - } - pub fn pixel_snap_h(mut self, pixel_snap_h: bool) -> ImFontConfig { - self.pixel_snap_h = pixel_snap_h; - self - } - pub fn glyph_extra_spacing>(mut self, extra_spacing: I) -> ImFontConfig { - self.glyph_extra_spacing = extra_spacing.into(); - self - } - pub fn glyph_offset>(mut self, glyph_offset: I) -> ImFontConfig { - self.glyph_offset = glyph_offset.into(); - self - } - pub fn merge_mode(mut self, merge_mode: bool) -> ImFontConfig { - self.merge_mode = merge_mode; - self - } - pub fn rasterizer_multiply(mut self, rasterizer_multiply: f32) -> ImFontConfig { - self.rasterizer_multiply = rasterizer_multiply; - self - } - - fn make_config(self) -> sys::ImFontConfig { - let mut config = unsafe { - let mut config = mem::zeroed::(); - config.FontDataOwnedByAtlas = true; - config.GlyphMaxAdvanceX = f32::MAX as c_float; - config - }; - config.SizePixels = self.size_pixels; - config.OversampleH = self.oversample_h as c_int; - config.OversampleV = self.oversample_v as c_int; - config.PixelSnapH = self.pixel_snap_h; - config.GlyphExtraSpacing = self.glyph_extra_spacing; - config.GlyphOffset = self.glyph_offset; - config.MergeMode = self.merge_mode; - config.RasterizerMultiply = self.rasterizer_multiply; - config - } - - /// Adds a custom font to the font set with the given configuration. A font size must be set - /// in the configuration. - /// - /// Panics - /// ====== - /// - /// If no font size is set for the configuration. - pub fn add_font<'a>( - self, - atlas: &'a mut ImFontAtlas<'a>, - data: &[u8], - range: &FontGlyphRange, - ) -> ImFont<'a> { - atlas.add_font_with_config(data, self, range) - } - - /// Adds the default font to a given atlas using this configuration. - pub fn add_default_font<'a>(self, atlas: &'a mut ImFontAtlas<'a>) -> ImFont<'a> { - atlas.add_default_font_with_config(self) - } -} -impl Default for ImFontConfig { - fn default() -> Self { - ImFontConfig::new() - } -} - -/// A handle to an imgui font. -pub struct ImFont<'a> { - font: *mut sys::ImFont, - _phantom: PhantomData<&'a mut sys::ImFont>, -} -impl<'a> ImFont<'a> { - unsafe fn from_ptr(font: *mut sys::ImFont) -> ImFont<'a> { - ImFont { - font, - _phantom: PhantomData, - } - } - - fn chain(&mut self) -> ImFont { - ImFont { - font: self.font, - _phantom: PhantomData, - } - } - - pub fn font_size(&self) -> f32 { - unsafe { (*self.font).FontSize } - } - pub fn set_font_size(&mut self, size: f32) -> ImFont { - unsafe { - (*self.font).FontSize = size; - } - self.chain() - } - - pub fn scale(&self) -> f32 { - unsafe { (*self.font).Scale } - } - pub fn set_scale(&mut self, size: f32) -> ImFont { - unsafe { - (*self.font).Scale = size; - } - self.chain() - } - - pub fn display_offset(&self) -> (f32, f32) { - unsafe { (*self.font).DisplayOffset.into() } - } -} - -/// A handle to imgui's font manager. -#[repr(C)] -pub struct ImFontAtlas<'a> { - atlas: *mut sys::ImFontAtlas, - _phantom: PhantomData<&'a mut sys::ImFontAtlas>, -} -impl<'a> ImFontAtlas<'a> { - pub(crate) unsafe fn from_ptr(atlas: *mut sys::ImFontAtlas) -> ImFontAtlas<'a> { - ImFontAtlas { - atlas, - _phantom: PhantomData, - } - } - - /// Adds the default font to the font set. - pub fn add_default_font(&mut self) -> ImFont { - unsafe { ImFont::from_ptr(sys::ImFontAtlas_AddFontDefault(self.atlas, ptr::null_mut())) } - } - - /// Adds the default fnt to the font set with the given configuration. - pub fn add_default_font_with_config(&mut self, config: ImFontConfig) -> ImFont { - let config = config.make_config(); - unsafe { ImFont::from_ptr(sys::ImFontAtlas_AddFontDefault(self.atlas, &config)) } - } - - fn raw_add_font( - &mut self, - data: &[u8], - config: ImFontConfig, - range: &FontGlyphRange, - ) -> ImFont { - assert!( - (data.len() as u64) < (c_int::max_value() as u64), - "Font data is too long." - ); - unsafe { - let mut config = config.make_config(); - assert!(config.SizePixels > 0.0, "Font size cannot be zero."); - config.FontData = data.as_ptr() as *mut c_void; - config.FontDataSize = data.len() as c_int; - config.GlyphRanges = range.to_ptr(self.atlas); - config.FontDataOwnedByAtlas = false; - - ImFont::from_ptr(sys::ImFontAtlas_AddFont(self.atlas, &config)) - } - } - - /// Adds a custom font to the font set. - pub fn add_font(&mut self, data: &[u8], size: f32, range: &FontGlyphRange) -> ImFont { - self.raw_add_font(data, ImFontConfig::new().size_pixels(size), range) - } - - /// Adds a custom font to the font set with the given configuration. A font size must be set - /// in the configuration. - /// - /// Panics - /// ====== - /// - /// If no font size is set for the configuration. - pub fn add_font_with_config( - &mut self, - data: &[u8], - config: ImFontConfig, - range: &FontGlyphRange, - ) -> ImFont { - self.raw_add_font(data, config, range) - } - - /// The number of fonts currently registered in the atlas. - pub fn font_count(&self) -> usize { - unsafe { (*self.atlas).Fonts.Size as usize } - } - - /// Gets a font from the atlas. - /// - /// Panics - /// ====== - /// - /// Panics if the index is out of range. - pub fn index_font(&mut self, index: usize) -> ImFont { - let fonts = unsafe { - let fonts: &sys::ImVector_ImFontPtr = &(*self.atlas).Fonts; - let fonts: &ImVector<*mut sys::ImFont> = ::std::mem::transmute(fonts); - fonts.as_slice() - }; - assert!(index < fonts.len(), "Font index is out of range."); - unsafe { ImFont::from_ptr(fonts[index]) } - } - - /// Clears all fonts associated with this texture atlas. - pub fn clear(&mut self) { - unsafe { sys::ImFontAtlas_Clear(self.atlas) } - } - - pub fn texture_id(&self) -> usize { - unsafe { (*self.atlas).TexID as usize } - } - pub fn set_texture_id(&mut self, value: usize) { - unsafe { - (*self.atlas).TexID = value as *mut c_void; - } - } -} diff --git a/src/fonts/atlas.rs b/src/fonts/atlas.rs new file mode 100644 index 000000000..723035be6 --- /dev/null +++ b/src/fonts/atlas.rs @@ -0,0 +1,442 @@ +use bitflags::bitflags; +use std::cell; +use std::f32; +use std::ops::{Deref, DerefMut}; +use std::os::raw::{c_int, c_uchar, c_void}; +use std::ptr; +use std::slice; + +use crate::fonts::font::Font; +use crate::fonts::glyph_ranges::FontGlyphRanges; +use crate::internal::{ImVector, RawCast}; +use crate::sys; +use crate::TextureId; + +bitflags! { + /// Font atlas configuration flags + #[repr(transparent)] + pub struct FontAtlasFlags: u32 { + const NO_POWER_OF_TWO_HEIGHT = sys::ImFontAtlasFlags_NoPowerOfTwoHeight; + const NO_MOUSE_CURSORS = sys::ImFontAtlasFlags_NoMouseCursors; + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct FontId(pub(crate) *const Font); + +/// A font atlas that builds a single texture +#[repr(C)] +pub struct FontAtlas { + locked: bool, + /// Configuration flags + pub flags: FontAtlasFlags, + /// Texture identifier + pub tex_id: TextureId, + /// Texture width desired by user before building the atlas. + /// + /// Must be a power-of-two. If you have many glyphs and your graphics API has texture size + /// restrictions, you may want to increase texture width to decrease the height. + pub tex_desired_width: i32, + /// Padding between glyphs within texture in pixels. + /// + /// Defaults to 1. If your rendering method doesn't rely on bilinear filtering, you may set + /// this to 0. + pub tex_glyph_padding: i32, + + tex_pixels_alpha8: *mut u8, + tex_pixels_rgba32: *mut u32, + tex_width: i32, + tex_height: i32, + tex_uv_scale: [f32; 2], + tex_uv_white_pixel: [f32; 2], + fonts: ImVector<*mut Font>, + custom_rects: sys::ImVector_CustomRect, + config_data: sys::ImVector_ImFontConfig, + custom_rect_ids: [i32; 1], +} + +unsafe impl RawCast for FontAtlas {} + +impl FontAtlas { + pub fn add_font(&mut self, font_sources: &[FontSource]) -> FontId { + let (head, tail) = font_sources.split_first().unwrap(); + let font_id = self.add_font_internal(head, false); + for font in tail { + self.add_font_internal(font, true); + } + font_id + } + fn add_font_internal(&mut self, font_source: &FontSource, merge_mode: bool) -> FontId { + let mut raw_config = sys_font_config_default(); + raw_config.MergeMode = merge_mode; + let raw_font = match font_source { + FontSource::DefaultFontData { config } => unsafe { + if let Some(config) = config { + config.apply_to_raw_config(&mut raw_config, self.raw_mut()); + } + sys::ImFontAtlas_AddFontDefault(self.raw_mut(), &raw_config) + }, + FontSource::TtfData { + data, + size_pixels, + config, + } => { + if let Some(config) = config { + unsafe { + config.apply_to_raw_config(&mut raw_config, self.raw_mut()); + } + } + // We can't guarantee `data` is alive when the font atlas is built, so + // make a copy and move ownership of the data to the atlas + let data_copy = unsafe { + let ptr = sys::igMemAlloc(data.len()) as *mut u8; + assert!(!ptr.is_null()); + slice::from_raw_parts_mut(ptr, data.len()) + }; + data_copy.copy_from_slice(data); + raw_config.FontData = data_copy.as_mut_ptr() as *mut c_void; + raw_config.FontDataSize = data_copy.len() as i32; + raw_config.FontDataOwnedByAtlas = true; + raw_config.SizePixels = *size_pixels; + unsafe { sys::ImFontAtlas_AddFont(self.raw_mut(), &raw_config) } + } + }; + FontId(raw_font as *const _) + } + pub fn fonts(&self) -> Vec { + let mut result = Vec::new(); + unsafe { + for &font in self.fonts.as_slice() { + result.push((*font).id()); + } + } + result + } + pub fn get_font(&self, id: FontId) -> Option<&Font> { + unsafe { + for &font in self.fonts.as_slice() { + if id == FontId(font) { + return Some(&*(font as *const Font)); + } + } + } + None + } + /// Returns true if the font atlas has been built + pub fn is_built(&self) -> bool { + unsafe { sys::ImFontAtlas_IsBuilt(self.raw() as *const sys::ImFontAtlas as *mut _) } + } + /// Builds a 1 byte per-pixel font atlas texture + pub fn build_alpha8_texture(&mut self) -> FontAtlasTexture { + let mut pixels: *mut c_uchar = ptr::null_mut(); + let mut width: c_int = 0; + let mut height: c_int = 0; + let mut bytes_per_pixel: c_int = 0; + unsafe { + sys::ImFontAtlas_GetTexDataAsAlpha8( + self.raw_mut(), + &mut pixels, + &mut width, + &mut height, + &mut bytes_per_pixel, + ); + assert!(width >= 0, "font texture width must be positive"); + assert!(height >= 0, "font texture height must be positive"); + assert!( + bytes_per_pixel >= 0, + "font texture bytes per pixel must be positive" + ); + let height = height as usize; + // Check multiplication to avoid constructing an invalid slice in case of overflow + let pitch = width + .checked_mul(bytes_per_pixel) + .expect("Overflow in font texture pitch calculation") + as usize; + FontAtlasTexture { + width: width as u32, + height: height as u32, + data: slice::from_raw_parts(pixels, pitch * height), + } + } + } + /// Builds a 4 byte per-pixel font atlas texture + pub fn build_rgba32_texture(&mut self) -> FontAtlasTexture { + let mut pixels: *mut c_uchar = ptr::null_mut(); + let mut width: c_int = 0; + let mut height: c_int = 0; + let mut bytes_per_pixel: c_int = 0; + unsafe { + sys::ImFontAtlas_GetTexDataAsRGBA32( + self.raw_mut(), + &mut pixels, + &mut width, + &mut height, + &mut bytes_per_pixel, + ); + assert!(width >= 0, "font texture width must be positive"); + assert!(height >= 0, "font texture height must be positive"); + assert!( + bytes_per_pixel >= 0, + "font texture bytes per pixel must be positive" + ); + let height = height as usize; + // Check multiplication to avoid constructing an invalid slice in case of overflow + let pitch = width + .checked_mul(bytes_per_pixel) + .expect("Overflow in font texture pitch calculation") + as usize; + FontAtlasTexture { + width: width as u32, + height: height as u32, + data: slice::from_raw_parts(pixels, pitch * height), + } + } + } + /// Clears the font atlas completely (both input and output data) + pub fn clear(&mut self) { + unsafe { + sys::ImFontAtlas_Clear(self.raw_mut()); + } + } + /// Clears output font data (glyph storage, UV coordinates) + pub fn clear_fonts(&mut self) { + unsafe { + sys::ImFontAtlas_ClearFonts(self.raw_mut()); + } + } + /// Clears output texture data. + /// + /// Can be used to save RAM once the texture has been transferred to the GPU. + pub fn clear_tex_data(&mut self) { + unsafe { + sys::ImFontAtlas_ClearTexData(self.raw_mut()); + } + } + /// Clears all the data used to build the textures and fonts + pub fn clear_input_data(&mut self) { + unsafe { + sys::ImFontAtlas_ClearInputData(self.raw_mut()); + } + } +} + +#[test] +fn test_font_atlas_memory_layout() { + use std::mem; + assert_eq!( + mem::size_of::(), + mem::size_of::() + ); + assert_eq!( + mem::align_of::(), + mem::align_of::() + ); + use memoffset::offset_of; + macro_rules! assert_field_offset { + ($l:ident, $r:ident) => { + assert_eq!(offset_of!(FontAtlas, $l), offset_of!(sys::ImFontAtlas, $r)); + }; + }; + assert_field_offset!(locked, Locked); + assert_field_offset!(flags, Flags); + assert_field_offset!(tex_id, TexID); + assert_field_offset!(tex_desired_width, TexDesiredWidth); + assert_field_offset!(tex_glyph_padding, TexGlyphPadding); + assert_field_offset!(tex_pixels_alpha8, TexPixelsAlpha8); + assert_field_offset!(tex_pixels_rgba32, TexPixelsRGBA32); + assert_field_offset!(tex_width, TexWidth); + assert_field_offset!(tex_height, TexHeight); + assert_field_offset!(tex_uv_scale, TexUvScale); + assert_field_offset!(tex_uv_white_pixel, TexUvWhitePixel); + assert_field_offset!(fonts, Fonts); + assert_field_offset!(custom_rects, CustomRects); + assert_field_offset!(config_data, ConfigData); + assert_field_offset!(custom_rect_ids, CustomRectIds); +} + +#[derive(Clone, Debug)] +pub enum FontSource<'a> { + DefaultFontData { + config: Option, + }, + TtfData { + data: &'a [u8], + size_pixels: f32, + config: Option, + }, +} + +#[derive(Clone, Debug)] +pub struct FontConfig { + pub size_pixels: f32, + pub oversample_h: i32, + pub oversample_v: i32, + pub pixel_snap_h: bool, + pub glyph_extra_spacing: [f32; 2], + pub glyph_offset: [f32; 2], + pub glyph_ranges: FontGlyphRanges, + pub glyph_min_advance_x: f32, + pub glyph_max_advance_x: f32, + pub rasterizer_flags: u32, + pub rasterizer_multiply: f32, +} + +impl Default for FontConfig { + fn default() -> FontConfig { + FontConfig { + size_pixels: 0.0, + oversample_h: 3, + oversample_v: 1, + pixel_snap_h: false, + glyph_extra_spacing: [0.0, 0.0], + glyph_offset: [0.0, 0.0], + glyph_ranges: FontGlyphRanges::default(), + glyph_min_advance_x: 0.0, + glyph_max_advance_x: f32::MAX, + rasterizer_flags: 0, + rasterizer_multiply: 1.0, + } + } +} + +impl FontConfig { + fn apply_to_raw_config(&self, raw: &mut sys::ImFontConfig, atlas: *mut sys::ImFontAtlas) { + raw.SizePixels = self.size_pixels; + raw.OversampleH = self.oversample_h; + raw.OversampleV = self.oversample_v; + raw.PixelSnapH = self.pixel_snap_h; + raw.GlyphExtraSpacing = self.glyph_extra_spacing.into(); + raw.GlyphOffset = self.glyph_offset.into(); + raw.GlyphRanges = unsafe { self.glyph_ranges.to_ptr(atlas) }; + raw.GlyphMinAdvanceX = self.glyph_min_advance_x; + raw.GlyphMaxAdvanceX = self.glyph_max_advance_x; + raw.RasterizerFlags = self.rasterizer_flags; + raw.RasterizerMultiply = self.rasterizer_multiply; + } +} + +fn sys_font_config_default() -> sys::ImFontConfig { + unsafe { + let heap_allocated = sys::ImFontConfig_ImFontConfig(); + let copy = *heap_allocated; + sys::ImFontConfig_destroy(heap_allocated); + copy + } +} + +#[test] +fn test_font_config_default() { + let sys_font_config = sys_font_config_default(); + let font_config = FontConfig::default(); + assert_eq!(font_config.size_pixels, sys_font_config.SizePixels); + assert_eq!(font_config.oversample_h, sys_font_config.OversampleH); + assert_eq!(font_config.oversample_v, sys_font_config.OversampleV); + assert_eq!(font_config.pixel_snap_h, sys_font_config.PixelSnapH); + assert_eq!( + font_config.glyph_extra_spacing[0], + sys_font_config.GlyphExtraSpacing.x + ); + assert_eq!( + font_config.glyph_extra_spacing[1], + sys_font_config.GlyphExtraSpacing.y + ); + assert_eq!(font_config.glyph_offset[0], sys_font_config.GlyphOffset.x); + assert_eq!(font_config.glyph_offset[1], sys_font_config.GlyphOffset.y); + assert_eq!( + font_config.glyph_min_advance_x, + sys_font_config.GlyphMinAdvanceX + ); + assert_eq!( + font_config.glyph_max_advance_x, + sys_font_config.GlyphMaxAdvanceX + ); + assert_eq!( + font_config.rasterizer_flags, + sys_font_config.RasterizerFlags + ); + assert_eq!( + font_config.rasterizer_multiply, + sys_font_config.RasterizerMultiply + ); +} + +/// Handle to a font atlas texture +#[derive(Clone, Debug)] +pub struct FontAtlasTexture<'a> { + pub width: u32, + pub height: u32, + pub data: &'a [u8], +} + +/// A font atlas that can be shared between contexts +#[derive(Debug)] +pub struct SharedFontAtlas(*mut sys::ImFontAtlas); + +impl SharedFontAtlas { + pub fn create() -> SharedFontAtlas { + SharedFontAtlas(unsafe { sys::ImFontAtlas_ImFontAtlas() }) + } +} + +impl Drop for SharedFontAtlas { + fn drop(&mut self) { + unsafe { sys::ImFontAtlas_destroy(self.0) }; + } +} + +impl Deref for SharedFontAtlas { + type Target = FontAtlas; + fn deref(&self) -> &FontAtlas { + unsafe { &*(self.0 as *const FontAtlas) } + } +} + +impl DerefMut for SharedFontAtlas { + fn deref_mut(&mut self) -> &mut FontAtlas { + unsafe { &mut *(self.0 as *mut FontAtlas) } + } +} + +/// An immutably borrowed reference to a (possibly shared) font atlas +pub enum FontAtlasRef<'a> { + Owned(&'a FontAtlas), + Shared(&'a cell::RefMut<'a, SharedFontAtlas>), +} + +impl<'a> Deref for FontAtlasRef<'a> { + type Target = FontAtlas; + fn deref(&self) -> &FontAtlas { + use self::FontAtlasRef::*; + match self { + Owned(atlas) => atlas, + Shared(cell) => cell, + } + } +} + +/// A mutably borrowed reference to a (possibly shared) font atlas +pub enum FontAtlasRefMut<'a> { + Owned(&'a mut FontAtlas), + Shared(cell::RefMut<'a, SharedFontAtlas>), +} + +impl<'a> Deref for FontAtlasRefMut<'a> { + type Target = FontAtlas; + fn deref(&self) -> &FontAtlas { + use self::FontAtlasRefMut::*; + match self { + Owned(atlas) => atlas, + Shared(cell) => cell, + } + } +} + +impl<'a> DerefMut for FontAtlasRefMut<'a> { + fn deref_mut(&mut self) -> &mut FontAtlas { + use self::FontAtlasRefMut::*; + match self { + Owned(atlas) => atlas, + Shared(cell) => cell, + } + } +} diff --git a/src/fonts/font.rs b/src/fonts/font.rs new file mode 100644 index 000000000..42ea5be1e --- /dev/null +++ b/src/fonts/font.rs @@ -0,0 +1,63 @@ +use std::os::raw::c_int; + +use crate::fonts::atlas::{FontAtlas, FontId}; +use crate::fonts::glyph::FontGlyph; +use crate::internal::{ImVector, RawCast}; +use crate::sys; + +#[repr(C)] +pub struct Font { + index_advance_x: ImVector, + pub fallback_advance_x: f32, + pub font_size: f32, + index_lookup: ImVector, + glyphs: ImVector, + fallback_glyph: *const FontGlyph, + pub display_offset: [f32; 2], + container_atlas: *mut FontAtlas, + config_data: *const sys::ImFontConfig, + pub config_data_count: i16, + pub fallback_char: sys::ImWchar, + pub scale: f32, + pub ascent: f32, + pub descent: f32, + pub metrics_total_surface: c_int, + pub dirty_lookup_tables: bool, +} + +unsafe impl RawCast for Font {} + +impl Font { + pub fn id(&self) -> FontId { + FontId(self as *const _) + } +} + +#[test] +fn test_font_memory_layout() { + use std::mem; + assert_eq!(mem::size_of::(), mem::size_of::()); + assert_eq!(mem::align_of::(), mem::align_of::()); + use memoffset::offset_of; + macro_rules! assert_field_offset { + ($l:ident, $r:ident) => { + assert_eq!(offset_of!(Font, $l), offset_of!(sys::ImFont, $r)); + }; + }; + assert_field_offset!(index_advance_x, IndexAdvanceX); + assert_field_offset!(fallback_advance_x, FallbackAdvanceX); + assert_field_offset!(font_size, FontSize); + assert_field_offset!(index_lookup, IndexLookup); + assert_field_offset!(glyphs, Glyphs); + assert_field_offset!(fallback_glyph, FallbackGlyph); + assert_field_offset!(display_offset, DisplayOffset); + assert_field_offset!(container_atlas, ContainerAtlas); + assert_field_offset!(config_data, ConfigData); + assert_field_offset!(config_data_count, ConfigDataCount); + assert_field_offset!(fallback_char, FallbackChar); + assert_field_offset!(scale, Scale); + assert_field_offset!(ascent, Ascent); + assert_field_offset!(descent, Descent); + assert_field_offset!(metrics_total_surface, MetricsTotalSurface); + assert_field_offset!(dirty_lookup_tables, DirtyLookupTables); +} diff --git a/src/fonts/glyph.rs b/src/fonts/glyph.rs new file mode 100644 index 000000000..08582f4d0 --- /dev/null +++ b/src/fonts/glyph.rs @@ -0,0 +1,48 @@ +use crate::internal::RawCast; +use crate::sys; + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(C)] +pub struct FontGlyph { + pub codepoint: u16, + pub advance_x: f32, + pub x0: f32, + pub y0: f32, + pub x1: f32, + pub y1: f32, + pub u0: f32, + pub v0: f32, + pub u1: f32, + pub v1: f32, +} + +unsafe impl RawCast for FontGlyph {} + +#[test] +fn test_font_glyph_memory_layout() { + use std::mem; + assert_eq!( + mem::size_of::(), + mem::size_of::() + ); + assert_eq!( + mem::align_of::(), + mem::align_of::() + ); + use memoffset::offset_of; + macro_rules! assert_field_offset { + ($l:ident, $r:ident) => { + assert_eq!(offset_of!(FontGlyph, $l), offset_of!(sys::ImFontGlyph, $r)); + }; + }; + assert_field_offset!(codepoint, Codepoint); + assert_field_offset!(advance_x, AdvanceX); + assert_field_offset!(x0, X0); + assert_field_offset!(y0, Y0); + assert_field_offset!(x1, X1); + assert_field_offset!(y1, Y1); + assert_field_offset!(u0, U0); + assert_field_offset!(v0, V0); + assert_field_offset!(u1, U1); + assert_field_offset!(v1, V1); +} diff --git a/src/fonts/glyph_ranges.rs b/src/fonts/glyph_ranges.rs new file mode 100644 index 000000000..5149f97e9 --- /dev/null +++ b/src/fonts/glyph_ranges.rs @@ -0,0 +1,143 @@ +use crate::sys; + +#[derive(Clone, Eq, PartialEq, Debug)] +enum FontGlyphRangeData { + ChineseSimplifiedCommon, + ChineseFull, + Cyrillic, + Default, + Japanese, + Korean, + Thai, + Vietnamese, + Custom(*const sys::ImWchar), +} + +/// A set of 16-bit Unicode codepoints +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct FontGlyphRanges(FontGlyphRangeData); +impl FontGlyphRanges { + /// The default set of glyph ranges used by imgui. + pub fn default() -> FontGlyphRanges { + FontGlyphRanges(FontGlyphRangeData::Default) + } + /// A set of glyph ranges appropriate for use with simplified common Chinese text. + pub fn chinese_simplified_common() -> FontGlyphRanges { + FontGlyphRanges(FontGlyphRangeData::ChineseSimplifiedCommon) + } + /// A set of glyph ranges appropriate for use with Chinese text. + pub fn chinese_full() -> FontGlyphRanges { + FontGlyphRanges(FontGlyphRangeData::ChineseFull) + } + /// A set of glyph ranges appropriate for use with Cyrillic text. + pub fn cyrillic() -> FontGlyphRanges { + FontGlyphRanges(FontGlyphRangeData::Cyrillic) + } + /// A set of glyph ranges appropriate for use with Japanese text. + pub fn japanese() -> FontGlyphRanges { + FontGlyphRanges(FontGlyphRangeData::Japanese) + } + /// A set of glyph ranges appropriate for use with Korean text. + pub fn korean() -> FontGlyphRanges { + FontGlyphRanges(FontGlyphRangeData::Korean) + } + /// A set of glyph ranges appropriate for use with Thai text. + pub fn thai() -> FontGlyphRanges { + FontGlyphRanges(FontGlyphRangeData::Thai) + } + /// A set of glyph ranges appropriate for use with Vietnamese text. + pub fn vietnamese() -> FontGlyphRanges { + FontGlyphRanges(FontGlyphRangeData::Vietnamese) + } + + /// Creates a glyph range from a static slice. The expected format is a series of pairs of + /// non-zero shorts, each representing an inclusive range of codepoints, followed by a single + /// zero terminating the range. The ranges must not overlap. + /// + /// As the slice is expected to last as long as a font is used, and is written into global + /// state, it must be `'static`. + /// + /// Panics + /// ====== + /// + /// This function will panic if the given slice is not a valid font range. + pub fn from_slice(slice: &'static [u16]) -> FontGlyphRanges { + assert_eq!( + slice.len() % 2, + 1, + "The length of a glyph range must be odd." + ); + assert_eq!( + slice.last(), + Some(&0), + "A glyph range must be zero-terminated." + ); + + for (i, &glyph) in slice.iter().enumerate().take(slice.len() - 1) { + assert_ne!( + glyph, 0, + "A glyph in a range cannot be zero. \ + (Glyph is zero at index {})", + i + ) + } + + let mut ranges = Vec::new(); + for i in 0..slice.len() / 2 { + let (start, end) = (slice[i * 2], slice[i * 2 + 1]); + assert!( + start <= end, + "The start of a range cannot be larger than its end. \ + (At index {}, {} > {})", + i * 2, + start, + end + ); + ranges.push((start, end)); + } + ranges.sort_unstable_by_key(|x| x.0); + for i in 0..ranges.len() - 1 { + let (range_a, range_b) = (ranges[i], ranges[i + 1]); + if range_a.1 >= range_b.0 { + panic!( + "The glyph ranges {:?} and {:?} overlap between {:?}.", + range_a, + range_b, + (range_a.1, range_b.0) + ); + } + } + + unsafe { FontGlyphRanges::from_slice_unchecked(slice) } + } + + /// Creates a glyph range from a static slice without checking its validity. + /// + /// See [`FontRangeGlyph::from_slice`] for more information. + pub unsafe fn from_slice_unchecked(slice: &'static [u16]) -> FontGlyphRanges { + FontGlyphRanges::from_ptr(slice.as_ptr()) + } + + /// Creates a glyph range from a pointer, without checking its validity or enforcing its + /// lifetime. The memory the pointer points to must be valid for as long as the font is + /// in use. + pub unsafe fn from_ptr(ptr: *const u16) -> FontGlyphRanges { + FontGlyphRanges(FontGlyphRangeData::Custom(ptr)) + } + + pub(crate) unsafe fn to_ptr(&self, atlas: *mut sys::ImFontAtlas) -> *const sys::ImWchar { + match self.0 { + FontGlyphRangeData::ChineseFull => sys::ImFontAtlas_GetGlyphRangesChineseFull(atlas), + FontGlyphRangeData::ChineseSimplifiedCommon => { + sys::ImFontAtlas_GetGlyphRangesChineseSimplifiedCommon(atlas) + } + FontGlyphRangeData::Cyrillic => sys::ImFontAtlas_GetGlyphRangesCyrillic(atlas), + FontGlyphRangeData::Default => sys::ImFontAtlas_GetGlyphRangesDefault(atlas), + FontGlyphRangeData::Japanese => sys::ImFontAtlas_GetGlyphRangesJapanese(atlas), + FontGlyphRangeData::Korean => sys::ImFontAtlas_GetGlyphRangesKorean(atlas), + FontGlyphRangeData::Thai => sys::ImFontAtlas_GetGlyphRangesThai(atlas), + FontGlyphRangeData::Vietnamese => sys::ImFontAtlas_GetGlyphRangesVietnamese(atlas), + FontGlyphRangeData::Custom(ptr) => ptr, + } + } +} diff --git a/src/fonts/mod.rs b/src/fonts/mod.rs new file mode 100644 index 000000000..fa8cda7f6 --- /dev/null +++ b/src/fonts/mod.rs @@ -0,0 +1,29 @@ +use crate::fonts::font::Font; +use crate::internal::RawCast; +use crate::Ui; + +pub mod atlas; +pub mod font; +pub mod glyph; +pub mod glyph_ranges; + +impl<'ui> Ui<'ui> { + /// Returns the current font + pub fn current_font(&self) -> &Font { + unsafe { Font::from_raw(&*sys::igGetFont()) } + } + /// Returns the current font size (= height in pixels) with font scale applied + pub fn current_font_size(&self) -> f32 { + unsafe { sys::igGetFontSize() } + } + /// Returns the UV coordinate for a white pixel. + /// + /// Useful for drawing custom shapes with the draw list API. + pub fn font_tex_uv_white_pixel(&self) -> [f32; 2] { + unsafe { sys::igGetFontTexUvWhitePixel_nonUDT2().into() } + } + /// Set the font scale of the current window + pub fn set_window_font_scale(&self, scale: f32) { + unsafe { sys::igSetWindowFontScale(scale) } + } +} diff --git a/src/input/mouse.rs b/src/input/mouse.rs index 5fa45eed3..594d0973f 100644 --- a/src/input/mouse.rs +++ b/src/input/mouse.rs @@ -170,3 +170,254 @@ impl<'ui> Ui<'ui> { } } } + +#[test] +fn test_mouse_down_clicked_released() { + for &button in MouseButton::VARIANTS.iter() { + let (_guard, mut ctx) = crate::test::test_ctx_initialized(); + { + ctx.io_mut().mouse_down = [false; 5]; + let ui = ctx.frame(); + assert!(!ui.is_mouse_down(button)); + assert!(!ui.is_any_mouse_down()); + assert!(!ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_released(button)); + } + { + ctx.io_mut()[button] = true; + let ui = ctx.frame(); + assert!(ui.is_mouse_down(button)); + assert!(ui.is_any_mouse_down()); + assert!(ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_released(button)); + } + { + let ui = ctx.frame(); + assert!(ui.is_mouse_down(button)); + assert!(ui.is_any_mouse_down()); + assert!(!ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_released(button)); + } + { + ctx.io_mut()[button] = false; + let ui = ctx.frame(); + assert!(!ui.is_mouse_down(button)); + assert!(!ui.is_any_mouse_down()); + assert!(!ui.is_mouse_clicked(button)); + assert!(ui.is_mouse_released(button)); + } + { + let ui = ctx.frame(); + assert!(!ui.is_mouse_down(button)); + assert!(!ui.is_any_mouse_down()); + assert!(!ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_released(button)); + } + } +} + +#[test] +fn test_mouse_double_click() { + let (_guard, mut ctx) = crate::test::test_ctx_initialized(); + // Workaround for dear imgui bug/feature: + // If a button is clicked before io.mouse_double_click_time seconds has passed after the + // context is initialized, the single click is interpreted as a double-click. This happens + // because internally g.IO.MouseClickedTime is set to 0.0, so the context creation is + // considered a "click". + { + // Pass one second of time + ctx.io_mut().delta_time = 1.0; + let _ = ctx.frame(); + } + // Fast clicks + ctx.io_mut().delta_time = 1.0 / 60.0; + for &button in MouseButton::VARIANTS.iter() { + { + ctx.io_mut().mouse_down = [false; 5]; + let ui = ctx.frame(); + assert!(!ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_double_clicked(button)); + } + { + ctx.io_mut()[button] = true; + let ui = ctx.frame(); + assert!(ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_double_clicked(button)); + } + { + let ui = ctx.frame(); + assert!(!ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_double_clicked(button)); + } + { + ctx.io_mut()[button] = false; + let ui = ctx.frame(); + assert!(!ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_double_clicked(button)); + } + { + ctx.io_mut()[button] = true; + let ui = ctx.frame(); + assert!(ui.is_mouse_clicked(button)); + assert!(ui.is_mouse_double_clicked(button)); + } + { + let ui = ctx.frame(); + assert!(!ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_double_clicked(button)); + } + } + // Slow clicks + ctx.io_mut().delta_time = 1.0; + for &button in MouseButton::VARIANTS.iter() { + { + ctx.io_mut().mouse_down = [false; 5]; + let ui = ctx.frame(); + assert!(!ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_double_clicked(button)); + } + { + ctx.io_mut()[button] = true; + let ui = ctx.frame(); + assert!(ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_double_clicked(button)); + } + { + let ui = ctx.frame(); + assert!(!ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_double_clicked(button)); + } + { + ctx.io_mut()[button] = false; + let ui = ctx.frame(); + assert!(!ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_double_clicked(button)); + } + { + ctx.io_mut()[button] = true; + let ui = ctx.frame(); + assert!(ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_double_clicked(button)); + } + { + let ui = ctx.frame(); + assert!(!ui.is_mouse_clicked(button)); + assert!(!ui.is_mouse_double_clicked(button)); + } + } +} + +#[test] +fn test_set_get_mouse_cursor() { + let (_guard, mut ctx) = crate::test::test_ctx_initialized(); + let ui = ctx.frame(); + ui.set_mouse_cursor(None); + assert_eq!(None, ui.mouse_cursor()); + ui.set_mouse_cursor(Some(MouseCursor::Hand)); + assert_eq!(Some(MouseCursor::Hand), ui.mouse_cursor()); +} + +#[test] +fn test_mouse_drags() { + for &button in MouseButton::VARIANTS.iter() { + let (_guard, mut ctx) = crate::test::test_ctx_initialized(); + { + ctx.io_mut().mouse_pos = [0.0, 0.0]; + ctx.io_mut().mouse_down = [false; 5]; + let ui = ctx.frame(); + assert!(!ui.is_mouse_dragging(button)); + assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0)); + assert_eq!(ui.mouse_drag_delta(button), [0.0, 0.0]); + assert_eq!( + ui.mouse_drag_delta_with_threshold(button, 200.0), + [0.0, 0.0] + ); + } + { + ctx.io_mut()[button] = true; + let ui = ctx.frame(); + assert!(!ui.is_mouse_dragging(button)); + assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0)); + assert_eq!(ui.mouse_drag_delta(button), [0.0, 0.0]); + assert_eq!( + ui.mouse_drag_delta_with_threshold(button, 200.0), + [0.0, 0.0] + ); + } + { + ctx.io_mut().mouse_pos = [0.0, 100.0]; + let ui = ctx.frame(); + assert!(ui.is_mouse_dragging(button)); + assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0)); + assert_eq!(ui.mouse_drag_delta(button), [0.0, 100.0]); + assert_eq!( + ui.mouse_drag_delta_with_threshold(button, 200.0), + [0.0, 0.0] + ); + } + { + ctx.io_mut().mouse_pos = [0.0, 200.0]; + let ui = ctx.frame(); + assert!(ui.is_mouse_dragging(button)); + assert!(ui.is_mouse_dragging_with_threshold(button, 200.0)); + assert_eq!(ui.mouse_drag_delta(button), [0.0, 200.0]); + assert_eq!( + ui.mouse_drag_delta_with_threshold(button, 200.0), + [0.0, 200.0] + ); + } + { + ctx.io_mut().mouse_pos = [10.0, 10.0]; + ctx.io_mut()[button] = false; + let ui = ctx.frame(); + assert!(!ui.is_mouse_dragging(button)); + assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0)); + assert_eq!(ui.mouse_drag_delta(button), [10.0, 10.0]); + assert_eq!( + ui.mouse_drag_delta_with_threshold(button, 200.0), + [10.0, 10.0] + ); + } + { + ctx.io_mut()[button] = true; + let ui = ctx.frame(); + assert!(!ui.is_mouse_dragging(button)); + assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0)); + assert_eq!(ui.mouse_drag_delta(button), [0.0, 0.0]); + assert_eq!( + ui.mouse_drag_delta_with_threshold(button, 200.0), + [0.0, 0.0] + ); + } + { + ctx.io_mut().mouse_pos = [180.0, 180.0]; + let ui = ctx.frame(); + assert!(ui.is_mouse_dragging(button)); + assert!(ui.is_mouse_dragging_with_threshold(button, 200.0)); + assert_eq!(ui.mouse_drag_delta(button), [170.0, 170.0]); + assert_eq!( + ui.mouse_drag_delta_with_threshold(button, 200.0), + [170.0, 170.0] + ); + ui.reset_mouse_drag_delta(button); + assert!(ui.is_mouse_dragging(button)); + assert!(ui.is_mouse_dragging_with_threshold(button, 200.0)); + assert_eq!(ui.mouse_drag_delta(button), [0.0, 0.0]); + assert_eq!( + ui.mouse_drag_delta_with_threshold(button, 200.0), + [0.0, 0.0] + ); + } + { + ctx.io_mut().mouse_pos = [200.0, 200.0]; + let ui = ctx.frame(); + assert!(ui.is_mouse_dragging(button)); + assert!(ui.is_mouse_dragging_with_threshold(button, 200.0)); + assert_eq!(ui.mouse_drag_delta(button), [20.0, 20.0]); + assert_eq!( + ui.mouse_drag_delta_with_threshold(button, 200.0), + [20.0, 20.0] + ); + } + } +} diff --git a/src/io.rs b/src/io.rs index eb7fd4744..fe0132414 100644 --- a/src/io.rs +++ b/src/io.rs @@ -4,14 +4,13 @@ use std::ops::{Index, IndexMut}; use std::os::raw::{c_char, c_int, c_void}; use std::time::Instant; +use crate::fonts::atlas::FontAtlas; +use crate::fonts::font::Font; use crate::input::keyboard::Key; use crate::input::mouse::MouseButton; use crate::internal::{ImVector, RawCast}; use crate::sys; -type FontAtlas = sys::ImFontAtlas; -type Font = sys::ImFont; - bitflags! { /// Configuration flags #[repr(transparent)] diff --git a/src/legacy.rs b/src/legacy.rs index dfbe70d14..bb6929db4 100644 --- a/src/legacy.rs +++ b/src/legacy.rs @@ -1,6 +1,8 @@ #![allow(non_upper_case_globals)] use bitflags::bitflags; +use std::ffi::CStr; use std::os::raw::c_int; +use std::str; use crate::input::keyboard::Key; use crate::input::mouse::MouseButton; @@ -173,17 +175,6 @@ bitflags!( } ); -bitflags!( - /// Flags for font atlases - #[repr(C)] - pub struct ImFontAtlasFlags: c_int { - /// Don't round the height to next power of two - const NoPowerOfTwoHeight = 1; - /// Don't build software mouse cursors into the atlas - const NoMouseCursors = 1 << 1; - } -); - bitflags!( /// Flags for hover checks #[repr(C)] @@ -667,3 +658,11 @@ impl<'ui> Ui<'ui> { io.metrics_active_windows } } + +#[deprecated(since = "0.1.0", note = "Use dear_imgui_version instead")] +pub fn get_version() -> &'static str { + unsafe { + let bytes = CStr::from_ptr(sys::igGetVersion()).to_bytes(); + str::from_utf8_unchecked(bytes) + } +} diff --git a/src/lib.rs b/src/lib.rs index b412b5b50..43df9ebc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,10 +2,10 @@ pub extern crate imgui_sys as sys; #[macro_use] extern crate lazy_static; +use std::cell; use std::ffi::CStr; -use std::os::raw::{c_char, c_int, c_uchar, c_void}; +use std::os::raw::{c_char, c_void}; use std::ptr; -use std::slice; use std::str; use std::thread; @@ -19,7 +19,10 @@ pub use self::drag::{ DragFloat, DragFloat2, DragFloat3, DragFloat4, DragFloatRange2, DragInt, DragInt2, DragInt3, DragInt4, DragIntRange2, }; -pub use self::fonts::{FontGlyphRange, ImFont, ImFontAtlas, ImFontConfig}; +pub use self::fonts::atlas::*; +pub use self::fonts::font::*; +pub use self::fonts::glyph::*; +pub use self::fonts::glyph_ranges::*; pub use self::image::{Image, ImageButton}; pub use self::input::keyboard::*; pub use self::input::mouse::*; @@ -86,50 +89,7 @@ fn test_version() { assert_eq!(dear_imgui_version(), "1.71"); } -pub struct TextureHandle<'a> { - pub width: u32, - pub height: u32, - pub pixels: &'a [c_uchar], -} - -pub fn get_version() -> &'static str { - unsafe { - let bytes = CStr::from_ptr(sys::igGetVersion()).to_bytes(); - str::from_utf8_unchecked(bytes) - } -} - impl Context { - pub fn fonts(&mut self) -> ImFontAtlas { - unsafe { ImFontAtlas::from_ptr(self.io_mut().fonts) } - } - pub fn prepare_texture<'a, F, T>(&mut self, f: F) -> T - where - F: FnOnce(TextureHandle<'a>) -> T, - { - let io = self.io(); - let mut pixels: *mut c_uchar = ptr::null_mut(); - let mut width: c_int = 0; - let mut height: c_int = 0; - let mut bytes_per_pixel: c_int = 0; - unsafe { - sys::ImFontAtlas_GetTexDataAsRGBA32( - io.fonts, - &mut pixels, - &mut width, - &mut height, - &mut bytes_per_pixel, - ); - f(TextureHandle { - width: width as u32, - height: height as u32, - pixels: slice::from_raw_parts(pixels, (width * height * bytes_per_pixel) as usize), - }) - } - } - pub fn set_font_texture_id(&mut self, value: TextureId) { - self.fonts().set_texture_id(value.id()); - } pub fn get_time(&self) -> f64 { unsafe { sys::igGetTime() } } @@ -140,6 +100,7 @@ impl Context { pub struct Ui<'ui> { ctx: &'ui Context, + font_atlas: Option>, } static FMT: &'static [u8] = b"%s\0"; @@ -152,6 +113,15 @@ impl<'ui> Ui<'ui> { pub fn io(&self) -> &Io { unsafe { &*(sys::igGetIO() as *const Io) } } + pub fn fonts(&self) -> FontAtlasRef { + match self.font_atlas { + Some(ref font_atlas) => FontAtlasRef::Shared(font_atlas), + None => unsafe { + let fonts = &*(self.io().fonts as *const FontAtlas); + FontAtlasRef::Owned(fonts) + }, + } + } pub fn get_time(&self) -> f64 { unsafe { sys::igGetTime() } } diff --git a/src/test.rs b/src/test.rs index 25525ae7e..9b1ad35c9 100644 --- a/src/test.rs +++ b/src/test.rs @@ -13,3 +13,13 @@ pub fn test_ctx() -> (ReentrantMutexGuard<'static, ()>, Context) { ctx.io_mut().ini_filename = ptr::null(); (guard, ctx) } + +pub fn test_ctx_initialized() -> (ReentrantMutexGuard<'static, ()>, Context) { + let (guard, mut ctx) = test_ctx(); + let io = ctx.io_mut(); + io.display_size = [1024.0, 768.0]; + io.delta_time = 1.0 / 60.0; + io.mouse_pos = [0.0, 0.0]; + ctx.fonts().build_rgba32_texture(); + (guard, ctx) +}