1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
use std::marker::PhantomData;

use crate::sys;
use crate::Ui;

/// Used to render only the visible items when displaying a
/// long list of items in a scrollable area.
///
/// For example, you can have a huge list of checkboxes.
/// Without the clipper you have to call `ui.checkbox(...)`
/// for every one, even if 99% of of them are not visible in
/// the current frame. Using the `ListClipper`, you can only
/// call `ui.checkbox(...)` for the currently visible items.
///
/// Note the efficiency of list clipper relies on the height
/// of each item being cheaply calculated. The current rust
/// bindings only works with a fixed height for all items.
pub struct ListClipper {
    items_count: i32,
    items_height: f32,
}

impl ListClipper {
    /// Begins configuring a list clipper.
    pub const fn new(items_count: i32) -> Self {
        ListClipper {
            items_count,
            items_height: -1.0,
        }
    }

    /// Manually set item height. If not set, the height of the first item is used for all subsequent rows.
    pub const fn items_height(mut self, items_height: f32) -> Self {
        self.items_height = items_height;
        self
    }

    pub fn begin(self, ui: &Ui) -> ListClipperToken<'_> {
        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)
    }
}

/// List clipper is a mechanism to efficiently implement scrolling of
/// large lists with random access.
///
/// For example you have a list of 1 million buttons, and the list
/// clipper will help you only draw the ones which are visible.
pub struct ListClipperToken<'ui> {
    list_clipper: *mut sys::ImGuiListClipper,
    _phantom: PhantomData<&'ui Ui>,

    /// In upstream imgui < 1.87, calling step too many times will
    /// cause a segfault due to null pointer. So we keep track of this
    /// and panic instead.
    ///
    /// Fixed in https://github.com/ocornut/imgui/commit/dca527b which
    /// will likely be part of imgui 1.88 - at which point this can be
    /// removed.
    consumed_workaround: bool,
}

impl<'ui> ListClipperToken<'ui> {
    fn new(_: &Ui, list_clipper: *mut sys::ImGuiListClipper) -> Self {
        Self {
            list_clipper,
            _phantom: PhantomData,
            consumed_workaround: false,
        }
    }

    /// Progress the list clipper.
    ///
    /// If this returns returns `true` then the you can loop between
    /// between `clipper.display_start() .. clipper.display_end()`.
    /// If this returns false, you must stop calling this method.
    ///
    /// Calling step again after it returns `false` will cause imgui
    /// to abort. This mirrors the C++ interface.
    ///
    /// It is recommended to use the iterator interface!
    pub fn step(&mut self) -> bool {
        let is_imgui_1_88_or_higher = false;
        if is_imgui_1_88_or_higher {
            unsafe { sys::ImGuiListClipper_Step(self.list_clipper) }
        } else {
            if self.consumed_workaround {
                panic!("ListClipperToken::step called after it has previously returned false");
            }
            let ret = unsafe { sys::ImGuiListClipper_Step(self.list_clipper) };
            if !ret {
                self.consumed_workaround = true;
            }
            ret
        }
    }

    /// This is automatically called back the final call to
    /// `step`. You can call it sooner but typically not needed.
    pub fn end(&mut self) {
        unsafe {
            sys::ImGuiListClipper_End(self.list_clipper);
        }
    }

    /// First item to call, updated each call to `step`
    pub fn display_start(&self) -> i32 {
        unsafe { (*self.list_clipper).DisplayStart }
    }

    /// End of items to call (exclusive), updated each call to `step`
    pub fn display_end(&self) -> i32 {
        unsafe { (*self.list_clipper).DisplayEnd }
    }

    /// Get an iterator which outputs all visible indexes. This is the
    /// recommended way of using the clipper.
    pub fn iter(self) -> ListClipperIterator<'ui> {
        ListClipperIterator::new(self)
    }
}

impl<'ui> Drop for ListClipperToken<'ui> {
    fn drop(&mut self) {
        unsafe {
            sys::ImGuiListClipper_destroy(self.list_clipper);
        };
    }
}

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 {
            // Currently iterating a chunk (returning all values
            // between display_start and display_end)
            let next_value = lv + 1;

            if lv >= self.list_clipper.display_end() - 1 {
                // If we reach the end of the current chunk, clear
                // last_value so we call step below
                self.last_value = None;
            } else {
                // Otherwise just increment it
                self.last_value = Some(next_value);
            }
        }

        if let Some(lv) = self.last_value {
            // Next item within current step's chunk
            Some(lv)
        } else {
            // Start iterating a new chunk

            if self.exhausted {
                // If the clipper is exhausted, don't call step again!
                None
            } else {
                // Advance the clipper
                let ret = self.list_clipper.step();
                if !ret {
                    self.exhausted = true;
                    None
                } else {
                    // Setup iteration for this step's chunk
                    let start = self.list_clipper.display_start();
                    let end = self.list_clipper.display_end();

                    if start == end {
                        // Somewhat special case: if a single item, we
                        // don't store the last_value so we call
                        // step() again next iteration
                        self.last_value = None;
                    } else {
                        self.last_value = Some(start);
                    }
                    Some(start)
                }
            }
        }
    }
}

#[test]
fn cpp_style_usage() {
    // Setup
    let (_guard, mut ctx) = crate::test::test_ctx_initialized();
    let ui = ctx.frame();

    let _window = ui
        .window("Example")
        .position([0.0, 0.0], crate::Condition::Always)
        .size([100.0, 800.0], crate::Condition::Always)
        .begin();

    // Create clipper
    let clip = ListClipper::new(1000);
    let mut tok = clip.begin(ui);

    let mut ticks = 0;

    while dbg!(tok.step()) {
        for row_num in dbg!(tok.display_start())..dbg!(tok.display_end()) {
            dbg!(row_num);
            ui.text("...");
            ticks += 1;
        }
    }

    // Check it's called an expected amount of time (only the ones
    // visible in given sized window)
    assert_eq!(ticks, 44);

    // Calling end multiple times is fine albeit redundant
    tok.end();
    tok.end();
    tok.end();
    tok.end();
    tok.end();
    tok.end();
}

#[test]
fn iterator_usage() {
    // Setup
    let (_guard, mut ctx) = crate::test::test_ctx_initialized();
    let ui = ctx.frame();

    let _window = ui
        .window("Example")
        .position([0.0, 0.0], crate::Condition::Always)
        .size([100.0, 800.0], crate::Condition::Always)
        .begin();

    // Create clipper
    let clip = ListClipper::new(1000);

    let mut ticks = 0;

    let tok = clip.begin(ui);
    for row_num in tok.iter() {
        dbg!(row_num);
        ui.text("...");
        ticks += 1;
    }

    // Should be consistent with size in `cpp_style_usage`
    assert_eq!(ticks, 44);
}