dear_imgui/list_clipper.rs
1use std::marker::PhantomData;
2
3use crate::Ui;
4use crate::sys;
5
6/// Used to render only the visible items when displaying a
7/// long list of items in a scrollable area.
8///
9/// For example, you can have a huge list of checkboxes.
10/// Without the clipper you have to call `ui.checkbox(...)`
11/// for every one, even if 99% of of them are not visible in
12/// the current frame. Using the `ListClipper`, you can only
13/// call `ui.checkbox(...)` for the currently visible items.
14///
15/// Note the efficiency of list clipper relies on the height
16/// of each item being cheaply calculated. The current rust
17/// bindings only works with a fixed height for all items.
18pub struct ListClipper {
19 items_count: i32,
20 items_height: f32,
21}
22
23impl ListClipper {
24 /// Begins configuring a list clipper.
25 pub const fn new(items_count: i32) -> Self {
26 ListClipper {
27 items_count,
28 items_height: -1.0,
29 }
30 }
31
32 /// Manually set item height. If not set, the height of the first item is used for all subsequent rows.
33 pub const fn items_height(mut self, items_height: f32) -> Self {
34 self.items_height = items_height;
35 self
36 }
37
38 pub fn begin(self, ui: &Ui) -> ListClipperToken<'_> {
39 unsafe {
40 let ptr = sys::ImGuiListClipper_ImGuiListClipper();
41 sys::ImGuiListClipper_Begin(ptr, self.items_count, self.items_height);
42 ListClipperToken::new(ui, ptr)
43 }
44 }
45}
46
47/// List clipper is a mechanism to efficiently implement scrolling of
48/// large lists with random access.
49///
50/// For example you have a list of 1 million buttons, and the list
51/// clipper will help you only draw the ones which are visible.
52pub struct ListClipperToken<'ui> {
53 list_clipper: *mut sys::ImGuiListClipper,
54 _phantom: PhantomData<&'ui Ui>,
55
56 /// In upstream imgui < 1.87, calling step too many times will
57 /// cause a segfault due to null pointer. So we keep track of this
58 /// and panic instead.
59 ///
60 /// Fixed in https://github.com/ocornut/imgui/commit/dca527b which
61 /// will likely be part of imgui 1.88 - at which point this can be
62 /// removed.
63 consumed_workaround: bool,
64}
65
66impl<'ui> ListClipperToken<'ui> {
67 fn new(_: &Ui, list_clipper: *mut sys::ImGuiListClipper) -> Self {
68 Self {
69 list_clipper,
70 _phantom: PhantomData,
71 consumed_workaround: false,
72 }
73 }
74
75 /// Progress the list clipper.
76 ///
77 /// If this returns returns `true` then the you can loop between
78 /// between `clipper.display_start() .. clipper.display_end()`.
79 /// If this returns false, you must stop calling this method.
80 ///
81 /// Calling step again after it returns `false` will cause imgui
82 /// to abort. This mirrors the C++ interface.
83 ///
84 /// It is recommended to use the iterator interface!
85 pub fn step(&mut self) -> bool {
86 let is_imgui_1_88_or_higher = false;
87 if is_imgui_1_88_or_higher {
88 unsafe { sys::ImGuiListClipper_Step(self.list_clipper) }
89 } else {
90 if self.consumed_workaround {
91 panic!("ListClipperToken::step called after it has previously returned false");
92 }
93 let ret = unsafe { sys::ImGuiListClipper_Step(self.list_clipper) };
94 if !ret {
95 self.consumed_workaround = true;
96 }
97 ret
98 }
99 }
100
101 /// This is automatically called back the final call to
102 /// `step`. You can call it sooner but typically not needed.
103 pub fn end(&mut self) {
104 unsafe {
105 sys::ImGuiListClipper_End(self.list_clipper);
106 }
107 }
108
109 /// First item to call, updated each call to `step`
110 pub fn display_start(&self) -> i32 {
111 unsafe { (*self.list_clipper).DisplayStart }
112 }
113
114 /// End of items to call (exclusive), updated each call to `step`
115 pub fn display_end(&self) -> i32 {
116 unsafe { (*self.list_clipper).DisplayEnd }
117 }
118
119 /// Get an iterator which outputs all visible indexes. This is the
120 /// recommended way of using the clipper.
121 pub fn iter(self) -> ListClipperIterator<'ui> {
122 ListClipperIterator::new(self)
123 }
124}
125
126impl Drop for ListClipperToken<'_> {
127 fn drop(&mut self) {
128 unsafe {
129 sys::ImGuiListClipper_destroy(self.list_clipper);
130 };
131 }
132}
133
134pub struct ListClipperIterator<'ui> {
135 list_clipper: ListClipperToken<'ui>,
136 exhausted: bool,
137 last_value: Option<i32>,
138}
139
140impl<'ui> ListClipperIterator<'ui> {
141 fn new(list_clipper: ListClipperToken<'ui>) -> Self {
142 Self {
143 list_clipper,
144 exhausted: false,
145 last_value: None,
146 }
147 }
148}
149
150impl Iterator for ListClipperIterator<'_> {
151 type Item = i32;
152
153 fn next(&mut self) -> Option<Self::Item> {
154 if let Some(lv) = self.last_value {
155 // Currently iterating a chunk (returning all values
156 // between display_start and display_end)
157 let next_value = lv + 1;
158
159 if lv >= self.list_clipper.display_end() - 1 {
160 // If we reach the end of the current chunk, clear
161 // last_value so we call step below
162 self.last_value = None;
163 } else {
164 // Otherwise just increment it
165 self.last_value = Some(next_value);
166 }
167 }
168
169 if let Some(lv) = self.last_value {
170 // Next item within current step's chunk
171 Some(lv)
172 } else {
173 // Start iterating a new chunk
174
175 if self.exhausted {
176 // If the clipper is exhausted, don't call step again!
177 None
178 } else {
179 // Advance the clipper
180 let ret = self.list_clipper.step();
181 if !ret {
182 self.exhausted = true;
183 None
184 } else {
185 // Setup iteration for this step's chunk
186 let start = self.list_clipper.display_start();
187 let end = self.list_clipper.display_end();
188
189 if start == end {
190 // Somewhat special case: if a single item, we
191 // don't store the last_value so we call
192 // step() again next iteration
193 self.last_value = None;
194 } else {
195 self.last_value = Some(start);
196 }
197 Some(start)
198 }
199 }
200 }
201 }
202}