use crate::{Ui, sys};
use std::ops::Range;
use std::os::raw::c_char;
use std::ptr;
pub struct TextFilter {
label: String,
raw: *mut sys::ImGuiTextFilter,
}
impl TextFilter {
pub fn new(label: impl Into<String>) -> Self {
Self::new_with_filter(label, "")
}
pub fn new_with_filter(label: impl Into<String>, filter: impl AsRef<str>) -> Self {
let label = label.into();
let filter_ptr = crate::string::tls_scratch_txt(filter);
unsafe {
let raw = sys::ImGuiTextFilter_ImGuiTextFilter(filter_ptr);
if raw.is_null() {
panic!("ImGuiTextFilter_ImGuiTextFilter() returned null");
}
Self { label, raw }
}
}
pub fn build(&mut self) {
unsafe {
sys::ImGuiTextFilter_Build(self.raw);
}
}
pub fn draw(&mut self, ui: &Ui) -> bool {
self.draw_with_size(ui, 0.0)
}
pub fn draw_with_size(&mut self, ui: &Ui, width: f32) -> bool {
assert!(
width.is_finite(),
"TextFilter::draw_with_size() width must be finite"
);
let label_ptr = ui.scratch_txt(&self.label);
ui.run_with_bound_context(|| unsafe {
sys::ImGuiTextFilter_Draw(self.raw, label_ptr, width)
})
}
pub fn is_active(&self) -> bool {
unsafe { (*self.raw).Filters.Size > 0 }
}
pub fn pass_filter(&self, text: &str) -> bool {
let text_ptr = crate::string::tls_scratch_txt(text);
unsafe { sys::ImGuiTextFilter_PassFilter(self.raw, text_ptr, ptr::null()) }
}
pub fn pass_filter_range(&self, text: &str, range: Range<usize>) -> bool {
if range.start > range.end || range.end > text.len() {
return false;
}
if !text.is_char_boundary(range.start) || !text.is_char_boundary(range.end) {
return false;
}
let start_ptr = unsafe { text.as_ptr().add(range.start) as *const c_char };
let end_ptr = unsafe { text.as_ptr().add(range.end) as *const c_char };
unsafe { sys::ImGuiTextFilter_PassFilter(self.raw, start_ptr, end_ptr) }
}
pub fn clear(&mut self) {
unsafe {
(*self.raw).InputBuf[0] = 0;
sys::ImGuiTextFilter_Build(self.raw);
}
}
}
impl Drop for TextFilter {
fn drop(&mut self) {
unsafe { sys::ImGuiTextFilter_destroy(self.raw) }
}
}
impl Ui {
pub fn text_filter(&self, label: impl Into<String>) -> TextFilter {
TextFilter::new(label)
}
pub fn text_filter_with_filter(
&self,
label: impl Into<String>,
filter: impl AsRef<str>,
) -> TextFilter {
TextFilter::new_with_filter(label, filter)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static TEST_CTX_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn text_filter_build_and_pass_filter_work() {
let _lock = TEST_CTX_LOCK.lock().unwrap();
let _ctx = crate::Context::create();
let mut filter = TextFilter::new("Search");
filter.build();
assert!(filter.pass_filter("anything"));
let mut filter = TextFilter::new_with_filter("Search", "abc");
filter.build();
assert!(filter.pass_filter("xxabcxx"));
assert!(!filter.pass_filter("xxdefxx"));
}
#[test]
fn pass_filter_range_validates_bounds_and_char_boundaries() {
let _lock = TEST_CTX_LOCK.lock().unwrap();
let _ctx = crate::Context::create();
let mut filter = TextFilter::new_with_filter("Search", "test");
filter.build();
let start = 2usize;
let end = 1usize;
assert!(!filter.pass_filter_range("abc", start..end));
assert!(!filter.pass_filter_range("abc", 0..4));
assert!(!filter.pass_filter_range("é", 1..2));
}
#[test]
fn pass_filter_range_matches_full_string() {
let _lock = TEST_CTX_LOCK.lock().unwrap();
let _ctx = crate::Context::create();
let mut filter = TextFilter::new_with_filter("Search", "test");
filter.build();
let text = "hello test world";
assert_eq!(
filter.pass_filter(text),
filter.pass_filter_range(text, 0..text.len())
);
}
#[test]
fn draw_uses_owner_ui_context_and_restores_previous_current_context() {
let _lock = TEST_CTX_LOCK.lock().unwrap();
let mut ctx_a = crate::Context::create();
let raw_a = unsafe { crate::sys::igGetCurrentContext() };
let raw_b = unsafe { crate::sys::igCreateContext(std::ptr::null_mut()) };
assert!(!raw_b.is_null());
unsafe { crate::sys::igSetCurrentContext(raw_a) };
let _ = ctx_a.font_atlas_mut().build();
ctx_a.io_mut().set_display_size([128.0, 128.0]);
ctx_a.io_mut().set_delta_time(1.0 / 60.0);
{
let ui_a = ctx_a.frame();
let _ = ui_a.window("TextFilter owner context").build(|| {
let mut filter = TextFilter::new("Search");
unsafe { crate::sys::igSetCurrentContext(raw_b) };
assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, raw_b);
let _ = filter.draw(&ui_a);
assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, raw_b);
});
}
unsafe { crate::sys::igSetCurrentContext(raw_a) };
let _ = ctx_a.render();
unsafe { crate::sys::igDestroyContext(raw_b) };
drop(ctx_a);
}
}