diff --git a/imgui-examples/examples/test_window_impl.rs b/imgui-examples/examples/test_window_impl.rs index 8a3fa8c..d2c35f6 100644 --- a/imgui-examples/examples/test_window_impl.rs +++ b/imgui-examples/examples/test_window_impl.rs @@ -49,6 +49,7 @@ struct State { dont_ask_me_next_time: bool, stacked_modals_item: usize, stacked_modals_color: [f32; 4], + app_log: Vec, tabs: TabState, } @@ -108,6 +109,7 @@ impl Default for State { dont_ask_me_next_time: false, stacked_modals_item: 0, stacked_modals_color: [0.4, 0.7, 0.0, 0.5], + app_log: Vec::new(), tabs: TabState::default(), } } @@ -312,6 +314,10 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { ); } + if state.show_app_log { + show_app_log(ui, &mut state.app_log); + } + let mut window = Window::new(im_str!("ImGui Demo")) .title_bar(!state.no_titlebar) .resizable(!state.no_resize) @@ -1232,3 +1238,56 @@ fn show_example_app_custom_rendering(ui: &Ui, state: &mut CustomRenderingState, } }); } + +fn show_app_log(ui: &Ui, app_log: &mut Vec) { + Window::new(im_str!("Example: Log")) + .size([500.0, 400.0], Condition::FirstUseEver) + .build(ui, || { + if ui.small_button(im_str!("[Debug] Add 5 entries")) { + let categories = ["info", "warn", "error"]; + let words = [ + "Bumfuzzled", + "Cattywampus", + "Snickersnee", + "Abibliophobia", + "Absquatulate", + "Nincompoop", + "Pauciloquent", + ]; + for _ in 0..5 { + let category = categories[app_log.len() % categories.len()]; + let word = words[app_log.len() % words.len()]; + let frame = ui.frame_count(); + let time = ui.time(); + let text = format!( + "{:05} {} Hello, current time is {:.1}, here's a word: {}", + frame, category, time, word + ); + app_log.push(text); + } + } + if ui.button(im_str!("Clear"), [0.0, 0.0]) { + app_log.clear(); + } + ui.same_line(0.0); + if ui.button(im_str!("Copy"), [0.0, 0.0]) { + ui.set_clipboard_text(&ImString::from(app_log.join("\n"))); + } + ui.separator(); + ChildWindow::new("logwindow") + .flags(WindowFlags::HORIZONTAL_SCROLLBAR) + .build(ui, || { + let mut clipper = ListClipper::new() + .items_count(app_log.len() as i32) + .begin(ui); + while clipper.step() { + for line in clipper.display_start()..clipper.display_end() { + ui.text(&app_log[line as usize]); + } + } + if ui.scroll_y() >= ui.scroll_max_y() { + ui.set_scroll_here_y(); + } + }); + }); +} diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index bb8b990..038946c 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -23,6 +23,7 @@ pub use self::input_widget::{ pub use self::io::*; pub use self::layout::*; pub use self::legacy::*; +pub use self::list_clipper::ListClipper; pub use self::plothistogram::PlotHistogram; pub use self::plotlines::PlotLines; pub use self::popup_modal::PopupModal; @@ -58,6 +59,7 @@ pub mod internal; mod io; mod layout; mod legacy; +mod list_clipper; mod plothistogram; mod plotlines; mod popup_modal; diff --git a/imgui/src/list_clipper.rs b/imgui/src/list_clipper.rs new file mode 100644 index 0000000..8582bd5 --- /dev/null +++ b/imgui/src/list_clipper.rs @@ -0,0 +1,82 @@ +use std::marker::PhantomData; +use std::thread; + +use crate::sys; +use crate::Ui; + +pub struct ListClipper { + items_count: i32, + items_height: f32, +} + +impl ListClipper { + pub fn new() -> Self { + ListClipper { + items_count: -1, + items_height: -1.0, + } + } + + pub fn items_count(mut self, items_count: i32) -> Self { + self.items_count = items_count; + self + } + + pub fn items_height(mut self, items_height: f32) -> Self { + self.items_height = items_height; + self + } + + pub fn begin<'ui>(self, ui: &Ui<'ui>) -> ListClipperToken<'ui> { + let list_clipper = unsafe { + let list_clipper = sys::ImGuiListClipper_ImGuiListClipper(); + sys::ImGuiListClipper_Begin(list_clipper, self.items_count, self.items_height); + list_clipper + }; + ListClipperToken::new(ui, list_clipper) + } +} + +pub struct ListClipperToken<'ui> { + list_clipper: *mut sys::ImGuiListClipper, + _phantom: PhantomData<&'ui Ui<'ui>>, +} + +impl<'ui> ListClipperToken<'ui> { + fn new(_: &Ui<'ui>, list_clipper: *mut sys::ImGuiListClipper) -> Self { + Self { + list_clipper, + _phantom: PhantomData, + } + } + + pub fn step(&mut self) -> bool { + unsafe { sys::ImGuiListClipper_Step(self.list_clipper) } + } + + pub fn end(&mut self) { + unsafe { + sys::ImGuiListClipper_End(self.list_clipper); + } + } + + pub fn display_start(&self) -> i32 { + unsafe { (*self.list_clipper).DisplayStart } + } + + pub fn display_end(&self) -> i32 { + unsafe { (*self.list_clipper).DisplayEnd } + } +} + +impl<'ui> Drop for ListClipperToken<'ui> { + fn drop(&mut self) { + if !self.step() { + unsafe { + sys::ImGuiListClipper_destroy(self.list_clipper); + }; + } else if !thread::panicking() { + panic!("step() was not called until it returned false"); + } + } +}