use std::marker::PhantomData;
use crate::Ui;
use crate::sys;
pub struct ListClipper {
items_count: i32,
items_height: f32,
}
impl ListClipper {
pub const fn new(items_count: i32) -> Self {
ListClipper {
items_count,
items_height: -1.0,
}
}
pub const fn items_height(mut self, items_height: f32) -> Self {
self.items_height = items_height;
self
}
pub fn begin(self, ui: &Ui) -> ListClipperToken<'_> {
assert!(
self.items_count >= 0,
"ListClipper::begin() items_count must be non-negative"
);
assert!(
self.items_height.is_finite(),
"ListClipper::begin() items_height must be finite"
);
unsafe {
let ptr = sys::ImGuiListClipper_ImGuiListClipper();
if ptr.is_null() {
panic!("ImGuiListClipper_ImGuiListClipper() returned null");
}
sys::ImGuiListClipper_Begin(ptr, self.items_count, self.items_height);
ListClipperToken::new(ui, ptr)
}
}
}
pub struct ListClipperToken<'ui> {
list_clipper: *mut sys::ImGuiListClipper,
_phantom: PhantomData<&'ui Ui>,
ended: bool,
}
impl<'ui> ListClipperToken<'ui> {
fn new(_: &Ui, list_clipper: *mut sys::ImGuiListClipper) -> Self {
Self {
list_clipper,
_phantom: PhantomData,
ended: false,
}
}
pub fn step(&mut self) -> bool {
if self.ended {
panic!("ListClipperToken::step() called after the clipper has ended");
}
let ret = unsafe { sys::ImGuiListClipper_Step(self.list_clipper) };
if !ret {
self.ended = true;
}
ret
}
pub fn end(&mut self) {
if !self.ended {
unsafe {
sys::ImGuiListClipper_End(self.list_clipper);
}
self.ended = true;
}
}
pub fn display_start(&self) -> i32 {
unsafe { (*self.list_clipper).DisplayStart }
}
pub fn display_end(&self) -> i32 {
unsafe { (*self.list_clipper).DisplayEnd }
}
pub fn iter(self) -> ListClipperIterator<'ui> {
ListClipperIterator::new(self)
}
}
impl Drop for ListClipperToken<'_> {
fn drop(&mut self) {
unsafe {
sys::ImGuiListClipper_destroy(self.list_clipper);
};
}
}
#[cfg(test)]
mod tests {
use super::*;
fn setup_context() -> crate::Context {
let mut ctx = crate::Context::create();
let _ = ctx.font_atlas_mut().build();
ctx.io_mut().set_display_size([128.0, 128.0]);
ctx.io_mut().set_delta_time(1.0 / 60.0);
ctx
}
#[test]
fn step_after_end_panics_before_ffi() {
let mut ctx = setup_context();
let ui = ctx.frame();
ui.window("list_clipper_step_after_end").build(|| {
let mut clipper = ListClipper::new(0).begin(ui);
clipper.end();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = clipper.step();
}));
assert!(result.is_err());
});
}
#[test]
fn end_after_step_false_is_a_noop() {
let mut ctx = setup_context();
let ui = ctx.frame();
ui.window("list_clipper_end_after_step_false").build(|| {
let mut clipper = ListClipper::new(0).begin(ui);
assert!(!clipper.step());
clipper.end();
});
}
#[test]
fn begin_rejects_invalid_inputs_before_ffi() {
let mut ctx = setup_context();
let ui = ctx.frame();
ui.window("list_clipper_invalid_inputs").build(|| {
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _clipper = ListClipper::new(-1).begin(ui);
}))
.is_err()
);
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _clipper = ListClipper::new(1).items_height(f32::NAN).begin(ui);
}))
.is_err()
);
});
}
}
pub struct ListClipperIterator<'ui> {
list_clipper: ListClipperToken<'ui>,
exhausted: bool,
last_value: Option<i32>,
}
impl<'ui> ListClipperIterator<'ui> {
fn new(list_clipper: ListClipperToken<'ui>) -> Self {
Self {
list_clipper,
exhausted: false,
last_value: None,
}
}
}
impl Iterator for ListClipperIterator<'_> {
type Item = i32;
fn next(&mut self) -> Option<Self::Item> {
if let Some(lv) = self.last_value {
let next_value = lv + 1;
if lv >= self.list_clipper.display_end() - 1 {
self.last_value = None;
} else {
self.last_value = Some(next_value);
}
}
if let Some(lv) = self.last_value {
Some(lv)
} else {
if self.exhausted {
None
} else {
let ret = self.list_clipper.step();
if !ret {
self.exhausted = true;
None
} else {
let start = self.list_clipper.display_start();
let end = self.list_clipper.display_end();
if start == end {
self.last_value = None;
} else {
self.last_value = Some(start);
}
Some(start)
}
}
}
}
}