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) -> bool {
self.draw_with_size(0.0)
}
pub fn draw_with_size(&mut self, width: f32) -> bool {
let label_ptr = crate::string::tls_scratch_txt(&self.label);
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())
);
}
}