From 76709ad04032571f0929ad132610493f7be9001d Mon Sep 17 00:00:00 2001 From: Rusty Pickle Date: Tue, 5 Aug 2025 16:50:03 +0600 Subject: [PATCH 1/2] Add nucleo fuzzy matcher --- Cargo.toml | 8 ++- README.md | 15 +++- src/fuzzy_matcher.rs | 160 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 45 +++++++++++- src/row_selection.rs | 2 +- 5 files changed, 225 insertions(+), 5 deletions(-) create mode 100644 src/fuzzy_matcher.rs diff --git a/Cargo.toml b/Cargo.toml index 12d2a06..5b7cf9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "egui-selectable-table" -version = "0.3.0" -edition = "2021" +version = "0.4.0" +edition = "2024" authors = ["TheRustyPickle "] readme = "README.md" description = """ @@ -17,6 +17,7 @@ exclude = ["/demo", "/.github"] [dependencies] egui = { version = "0.32.0", default-features = false, features = ["rayon"] } egui_extras = { version = "0.32.0", default-features = false } +nucleo-matcher = { version = "0.3.1", optional = true } rayon = "1.10.0" [lints.rust] @@ -34,3 +35,6 @@ unwrap_used = { level = "deny", priority = 5 } expect_used = { level = "allow", priority = 6 } missing_panics_doc = { level = "allow", priority = 7 } struct_excessive_bools = { level = "allow", priority = 8 } + +[features] +fuzzy-matching = ["dep:nucleo-matcher"] diff --git a/README.md b/README.md index 3922588..8fc641f 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,18 @@ A library for [egui](https://github.com/emilk/egui) to create tables with dragga - Customizable rows and header UI - Built-in select all (Ctrl+A) and copy (Ctrl+C) functionality - Capable of handling a substantial amount of rows (1M+) with proper settings +- Optional fuzzy matching for searching rows (see below) + +## Optional Features + +This crate includes an optional `fuzzy-matching` feature that enables fuzzy row search using the [nucleo-matcher](https://crates.io/crates/nucleo-matcher) crate. + +To enable fuzzy matching: + +```toml +[dependencies] +egui-selectable-table = { version = "0.4.0", features = ["fuzzy-matching"] } +``` ## Usage @@ -43,12 +55,13 @@ enum Column { // Implement both traits for row and column impl ColumnOperations for Column { // The text of a row based on the column - fn column_text(&self, row: &WhiteListRowData) -> String {} + fn column_text(&self, row: &MyRow) -> String {} // Create your own header or no header fn create_header(&self, ui: &mut Ui, sort_order: Option, table: &mut SelectableTable) -> Option {} //Create your own table row UI fn create_table_row(&self, ui: &mut Ui, row: &SelectableRow, selected: bool, table: &mut SelectableTable,) -> Response {} } + impl ColumnOrdering for Column { fn order_by(&self, row_1: &MyRow, row_2: &MyRow) -> std::cmp::Ordering { match self { diff --git a/src/fuzzy_matcher.rs b/src/fuzzy_matcher.rs new file mode 100644 index 0000000..c59496a --- /dev/null +++ b/src/fuzzy_matcher.rs @@ -0,0 +1,160 @@ +use nucleo_matcher::pattern::{CaseMatching, Normalization, Pattern}; +use nucleo_matcher::{Matcher, Utf32Str}; +use rayon::prelude::*; +use std::hash::Hash; + +use crate::{ColumnOperations, ColumnOrdering, SelectableRow, SelectableTable, SortOrder}; + +impl SelectableTable +where + Row: Clone + Send + Sync, + F: Eq + + Hash + + Clone + + Ord + + Send + + Sync + + Default + + ColumnOperations + + ColumnOrdering, + Conf: Default, +{ + /// Performs a fuzzy search using specified columns across all rows and updates the displayed rows. + /// + /// This function filters the table rows based on a search `query` using `nucleo-matcher` + /// crate. + /// It checks the specified `column_list` for each row, concatenates their string representations, + /// and scores them using the provided or generated `Pattern`. Only rows with a non-`None` score + /// are retained. + /// + /// If a `limit` is provided, it will result at most `limit` rows. + /// + /// # Parameters: + /// - `column_list`: A list of columns to search across. Does nothing if empty. + /// - `query`: The search string. Does nothing if empty. + /// - `limit`: Optional limit on the number of results returned. Does nothing if `0`. Defaults + /// to no limit + /// - `pattern`: Optional precomputed fuzzy `Pattern`. Default pattern is created from the query using + /// case-insensitive matching and smart normalization. + /// + /// The search is relatively fast even with a million rows but it should not be called every + /// frame and be used sparingly. + /// + /// To reset search results, call [`recreate_rows`](SelectableTable::recreate_rows). + /// + /// # Example: + /// ```rust,ignore + /// table.search_and_show(&vec![Column::Name, Column::Username], "john", Some(10), None); + /// ``` + pub fn search_and_show( + &mut self, + column_list: &Vec, + query: &str, + limit: Option, + pattern: Option, + ) { + if query.is_empty() { + return; + } + + if column_list.is_empty() { + return; + } + + if let Some(limit) = limit + && limit == 0 + { + return; + } + + let pattern = pattern.map_or_else( + || Pattern::parse(query, CaseMatching::Ignore, Normalization::Smart), + |pattern| pattern, + ); + + let mut buf = Vec::new(); + let mut row_data: Vec> = Vec::new(); + + for val in self.rows.values() { + let mut string_val = String::new(); + + for column in column_list { + let value = column.column_text(&val.row_data); + string_val.push_str(&value); + string_val.push(' '); + } + + if pattern + .score(Utf32Str::new(&string_val, &mut buf), &mut self.matcher) + .is_some() + { + row_data.push(val.clone()); + + if let Some(max) = limit { + if row_data.len() >= max { + break; + } + } + } + } + + self.formatted_rows.clear(); + self.active_rows.clear(); + self.active_columns.clear(); + + row_data.par_sort_by(|a, b| { + let ordering = self.sorted_by.order_by(&a.row_data, &b.row_data); + match self.sort_order { + SortOrder::Ascending => ordering, + SortOrder::Descending => ordering.reverse(), + } + }); + + self.indexed_ids = row_data + .par_iter() + .enumerate() + .map(|(index, row)| (row.id, index)) + .collect(); + + self.formatted_rows = row_data; + } + + /// Sets a custom matcher to use for fuzzy searching rows + /// + /// This allows the table to use a custom `Matcher` from `nucleo-matcher` crate + /// for searching/filtering through rows based on the input text. Use this to change + /// the search algorithm or tweak scoring behavior. + /// + /// # Parameters: + /// - `matcher`: The matcher instance to use for row filtering. + /// + /// # Returns: + /// - `Self`: The modified table with the specified matcher applied. + /// + /// # Example: + /// ```rust,ignore + /// let matcher = Matcher::default(); + /// let table = SelectableTable::new(columns) + /// .matcher(matcher); + /// ``` + #[must_use] + pub fn matcher(mut self, matcher: Matcher) -> Self { + self.matcher = matcher; + self + } + + /// Replaces the current matcher with a new one. + /// + /// This method allows updating the fuzzy search matcher dynamically. + /// + /// # Parameters: + /// - `matcher`: The new matcher instance to set. + /// + /// # Example: + /// ```rust,ignore + /// table.set_matcher(new_matcher); + /// ``` + pub fn set_matcher(&mut self, matcher: Matcher) { + self.matcher = matcher; + } +} diff --git a/src/lib.rs b/src/lib.rs index 93ace6e..02426c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,9 @@ mod auto_scroll; mod row_modification; mod row_selection; +#[cfg(feature = "fuzzy-matching")] +mod fuzzy_matcher; + use auto_reload::AutoReload; pub use auto_scroll::AutoScroll; use egui::ahash::{HashMap, HashMapExt, HashSet, HashSetExt}; @@ -11,6 +14,9 @@ use egui_extras::{Column, TableBuilder, TableRow}; use std::cmp::Ordering; use std::hash::Hash; +#[cfg(feature = "fuzzy-matching")] +use nucleo_matcher::Matcher; + /// Enum representing the possible sort orders for table columns. #[derive(Default, Clone, Copy)] pub enum SortOrder { @@ -237,6 +243,11 @@ where add_serial_column: bool, /// The row height for the table, defaults to 25.0 row_height: f32, + /// The matcher used for fuzzy searching + #[cfg(feature = "fuzzy-matching")] + matcher: Matcher, + /// Whether to capture Ctrl+A for selecting all rows + no_ctrl_a_capture: bool, } impl SelectableTable @@ -295,6 +306,9 @@ where config: Conf::default(), add_serial_column: false, row_height: 25.0, + #[cfg(feature = "fuzzy-matching")] + matcher: Matcher::default(), + no_ctrl_a_capture: false, } } @@ -366,7 +380,7 @@ where if copy_initiated { self.copy_selected_cells(ui); } - if is_ctrl_pressed && key_a_pressed { + if is_ctrl_pressed && key_a_pressed && !self.no_ctrl_a_capture { self.select_all(); } @@ -736,4 +750,33 @@ where self.row_height = height; self } + + /// Disables Ctrl+A keyboard shortcut capturing for selecting all rows + /// + /// # Returns: + /// - `Self`: The modified table with Ctrl+A capturing disabled. + /// + /// # Example: + /// ```rust,ignore + /// let table = SelectableTable::new(columns) + /// .no_ctrl_a_capture(); + /// ``` + #[must_use] + pub const fn no_ctrl_a_capture(mut self) -> Self { + self.no_ctrl_a_capture = true; + self + } + + /// Enables or disables Ctrl+A keyboard shortcut capturing dynamically for selecting all rows. + /// + /// # Parameters: + /// - `status`: `true` to disable Ctrl+A capture, `false` to enable it. + /// + /// # Example: + /// ```rust,ignore + /// table.set_no_ctrl_a_capture(true); // Disable Ctrl+A capture + /// ``` + pub const fn set_no_ctrl_a_capture(&mut self, status: bool) { + self.no_ctrl_a_capture = status; + } } diff --git a/src/row_selection.rs b/src/row_selection.rs index ba56ff2..7a7999c 100644 --- a/src/row_selection.rs +++ b/src/row_selection.rs @@ -1,5 +1,5 @@ -use egui::ahash::{HashMap, HashMapExt, HashSet, HashSetExt}; use egui::Ui; +use egui::ahash::{HashMap, HashMapExt, HashSet, HashSetExt}; use std::fmt::Write as _; use std::hash::Hash; From 3986b0a48094ac084c45c6e62976687082f943da Mon Sep 17 00:00:00 2001 From: Rusty Pickle Date: Tue, 5 Aug 2025 16:57:47 +0600 Subject: [PATCH 2/2] Update demo to include searching --- demo/Cargo.lock | 204 ++++++++++++++++++++++++++--------------------- demo/Cargo.toml | 9 ++- demo/src/app.rs | 128 ++++++++++++++++++++++------- demo/src/main.rs | 2 +- 4 files changed, 214 insertions(+), 129 deletions(-) diff --git a/demo/Cargo.lock b/demo/Cargo.lock index 16f5215..6ca1ba2 100644 --- a/demo/Cargo.lock +++ b/demo/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "ab_glyph" -version = "0.2.30" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0f4f6fbdc5ee39f2ede9f5f3ec79477271a6d6a2baff22310d51736bda6cea" +checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -232,9 +232,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" dependencies = [ "async-lock", "cfg-if", @@ -243,17 +243,16 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.0.7", + "rustix 1.0.8", "slab", - "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "async-lock" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" dependencies = [ "event-listener", "event-listener-strategy", @@ -262,9 +261,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" +checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00" dependencies = [ "async-channel", "async-io", @@ -275,8 +274,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 1.0.7", - "tracing", + "rustix 1.0.8", ] [[package]] @@ -292,9 +290,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" +checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1" dependencies = [ "async-io", "async-lock", @@ -302,10 +300,10 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.0.7", + "rustix 1.0.8", "signal-hook-registry", "slab", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -456,9 +454,9 @@ dependencies = [ [[package]] name = "bytemuck_derive" -version = "1.9.3" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" +checksum = "441473f2b4b0459a68628c744bc61d23e730fb00128b841d30fa4bb3972257e4" dependencies = [ "proc-macro2", "quote", @@ -505,9 +503,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.29" +version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "jobserver", "libc", @@ -543,9 +541,9 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" dependencies = [ "error-code", ] @@ -632,9 +630,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -683,9 +681,10 @@ dependencies = [ "eframe", "egui", "egui-selectable-table", + "egui-theme-lerp", "egui_extras", - "strum 0.27.1", - "strum_macros 0.27.1", + "strum 0.27.2", + "strum_macros 0.27.2", "wasm-bindgen-futures", "web-sys", ] @@ -813,13 +812,23 @@ dependencies = [ [[package]] name = "egui-selectable-table" -version = "0.3.0" +version = "0.4.0" dependencies = [ "egui", "egui_extras", + "nucleo-matcher", "rayon", ] +[[package]] +name = "egui-theme-lerp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a80b131214aeaff2d6dee8cf7065e9cf93c3d9e559cc72ac8d02a91efc4adf6e" +dependencies = [ + "egui", +] + [[package]] name = "egui-wgpu" version = "0.32.0" @@ -1003,9 +1012,9 @@ checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -1103,9 +1112,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ "fastrand", "futures-core", @@ -1497,7 +1506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -1508,13 +1517,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.4" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags 2.9.1", "libc", - "redox_syscall 0.5.13", + "redox_syscall 0.5.17", ] [[package]] @@ -1537,9 +1546,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" @@ -1565,9 +1574,9 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" dependencies = [ "libc", ] @@ -1682,6 +1691,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nucleo-matcher" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf33f538733d1a5a3494b836ba913207f14d9d4a1d3cd67030c5061bdd2cac85" +dependencies = [ + "memchr", + "unicode-segmentation", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2012,9 +2031,9 @@ dependencies = [ [[package]] name = "owned_ttf_parser" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" dependencies = [ "ttf-parser", ] @@ -2043,7 +2062,7 @@ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.13", + "redox_syscall 0.5.17", "smallvec", "windows-targets 0.52.6", ] @@ -2162,17 +2181,16 @@ dependencies = [ [[package]] name = "polling" -version = "3.8.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.0.7", - "tracing", - "windows-sys 0.59.0", + "rustix 1.0.8", + "windows-sys 0.60.2", ] [[package]] @@ -2300,9 +2318,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags 2.9.1", ] @@ -2340,15 +2358,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2430,9 +2448,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -2544,9 +2562,9 @@ dependencies = [ [[package]] name = "strum" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" [[package]] name = "strum_macros" @@ -2563,14 +2581,13 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck", "proc-macro2", "quote", - "rustversion", "syn", ] @@ -2605,7 +2622,7 @@ dependencies = [ "fastrand", "getrandom", "once_cell", - "rustix 1.0.7", + "rustix 1.0.8", "windows-sys 0.59.0", ] @@ -2917,13 +2934,13 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.44", + "rustix 1.0.8", "scoped-tls", "smallvec", "wayland-sys", @@ -2931,12 +2948,12 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ "bitflags 2.9.1", - "rustix 0.38.44", + "rustix 1.0.8", "wayland-backend", "wayland-scanner", ] @@ -2954,20 +2971,20 @@ dependencies = [ [[package]] name = "wayland-cursor" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" +checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "rustix 0.38.44", + "rustix 1.0.8", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.32.8" +version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -2977,9 +2994,9 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" +checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -2990,9 +3007,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" +checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" dependencies = [ "bitflags 2.9.1", "wayland-backend", @@ -3003,9 +3020,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" dependencies = [ "proc-macro2", "quick-xml 0.37.5", @@ -3014,9 +3031,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" dependencies = [ "dlib", "log", @@ -3329,7 +3346,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -3380,10 +3397,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -3585,9 +3603,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winit" -version = "0.30.11" +version = "0.30.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" dependencies = [ "ahash", "android-activity", @@ -3637,9 +3655,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -3748,9 +3766,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.8.0" +version = "5.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597f45e98bc7e6f0988276012797855613cd8269e23b5be62cc4e5d28b7e515d" +checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad" dependencies = [ "async-broadcast", "async-executor", @@ -3805,9 +3823,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.8.0" +version = "5.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c8e4e14dcdd9d97a98b189cd1220f30e8394ad271e8c987da84f73693862c2" +checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -3897,9 +3915,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "bdbb9122ea75b11bf96e7492afb723e8a7fbe12c67417aa95e7e3d18144d37cd" dependencies = [ "yoke", "zerofrom", diff --git a/demo/Cargo.toml b/demo/Cargo.toml index bee7312..c67b909 100644 --- a/demo/Cargo.toml +++ b/demo/Cargo.toml @@ -1,15 +1,16 @@ [package] name = "demo" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] eframe = "0.32.0" egui = "0.32.0" -egui-selectable-table = { path = ".." } +egui-selectable-table = { path = "..", features = ["fuzzy-matching"] } egui_extras = "0.32.0" -strum = "0.27.1" -strum_macros = "0.27.1" +strum = "0.27.2" +strum_macros = "0.27.2" +egui-theme-lerp = "0.3.0" [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen-futures = "0.4" diff --git a/demo/src/app.rs b/demo/src/app.rs index 55fabd8..c2a8cd4 100644 --- a/demo/src/app.rs +++ b/demo/src/app.rs @@ -1,14 +1,15 @@ use eframe::{App, CreationContext, Frame}; +use egui::ahash::{HashSet, HashSetExt}; use egui::{ - global_theme_preference_switch, Align, Button, CentralPanel, Context, Layout, Slider, - ThemePreference, Ui, + Align, Button, CentralPanel, Context, Layout, Slider, TextEdit, ThemePreference, Ui, Visuals, }; use egui_extras::Column; use egui_selectable_table::{ AutoScroll, ColumnOperations, ColumnOrdering, SelectableRow, SelectableTable, SortOrder, }; +use egui_theme_lerp::ThemeAnimator; use strum::IntoEnumIterator; -use strum_macros::EnumIter; +use strum_macros::{Display, EnumIter}; #[derive(Default, Clone, Copy)] pub struct Config { @@ -16,6 +17,7 @@ pub struct Config { } pub struct MainWindow { + search_text: String, select_entire_row: bool, add_rows: bool, auto_scrolling: bool, @@ -26,6 +28,8 @@ pub struct MainWindow { reload_counter: u32, table: SelectableTable, conf: Config, + theme_animator: ThemeAnimator, + search_column_list: HashSet, } impl MainWindow { @@ -39,9 +43,11 @@ impl MainWindow { let table = SelectableTable::new(all_columns) .auto_reload(10_000) .auto_scroll() - .horizontal_scroll(); + .horizontal_scroll() + .no_ctrl_a_capture(); MainWindow { + search_text: String::new(), select_entire_row: false, add_rows: false, auto_scrolling: true, @@ -52,15 +58,38 @@ impl MainWindow { reload_counter: 0, table, conf: Config::default(), + theme_animator: ThemeAnimator::new(Visuals::light(), Visuals::dark()), + search_column_list: HashSet::new(), } } } impl App for MainWindow { fn update(&mut self, ctx: &Context, _frame: &mut Frame) { + let theme_emoji = if !self.theme_animator.animation_done { + if self.theme_animator.theme_1_to_2 { + "☀" + } else { + "🌙" + } + } else if self.theme_animator.theme_1_to_2 { + "🌙" + } else { + "☀" + }; + CentralPanel::default().show(ctx, |ui| { + if self.theme_animator.anim_id.is_none() { + self.theme_animator.create_id(ui); + } else { + self.theme_animator.animate(ctx); + } + ui.horizontal(|ui| { - global_theme_preference_switch(ui); + if ui.button(theme_emoji).clicked() { + self.theme_animator.start(); + } + ui.separator(); ui.label("Total Rows to add:"); ui.add(Slider::new(&mut self.row_count, 10000..=1_000_000)); @@ -87,11 +116,11 @@ impl App for MainWindow { if ui.button("Add unsorted row at the bottom").clicked() { let new_row = TableRow { field_1: self.row_num, - field_2: self.row_num as i64 * 10, - field_3: format!("I'm unsorted row with row num: {}", self.row_num), - field_4: format!("field 4 with row num: {}", self.row_num), - field_5: format!("field 5 with row num: {}", self.row_num), - field_6: format!("field 6 with row num: {}", self.row_num), + field_2: self.row_num * 3, + field_3: self.row_num * 8, + field_4: self.row_num * 11, + field_5: self.row_num * 17, + field_6: self.row_num * 21, create_count: 0, }; self.row_num += 1; @@ -134,6 +163,44 @@ impl App for MainWindow { ui.separator(); } + ui.horizontal(|ui| { + let text_edit = + TextEdit::singleline(&mut self.search_text).hint_text("Search for rows"); + + ui.add(text_edit); + + if ui.button("Search").clicked() { + let column_list: Vec = + self.search_column_list.clone().into_iter().collect(); + + self.table + .search_and_show(&column_list, &self.search_text, None, None); + }; + + if ui.button("Clear").clicked() { + self.search_text.clear(); + self.table.recreate_rows(); + }; + + ui.separator(); + + let all_columns: Vec = TableColumns::iter().collect(); + for col in all_columns.into_iter() { + if ui + .selectable_label(self.search_column_list.contains(&col), col.to_string()) + .clicked() + { + if self.search_column_list.contains(&col) { + self.search_column_list.remove(&col); + } else { + self.search_column_list.insert(col); + } + }; + } + }); + + ui.separator(); + self.table.show_ui(ui, |table| { let mut table = table .drag_to_scroll(false) @@ -156,11 +223,11 @@ impl App for MainWindow { self.table.add_modify_row(|_| { let new_row = TableRow { field_1: self.row_num, - field_2: self.row_num as i64 * 10, - field_3: format!("field 3 with row num: {}", self.row_num), - field_4: format!("field 4 with row num: {}", self.row_num), - field_5: format!("field 5 with row num: {}", self.row_num), - field_6: format!("field 6 with row num: {}", self.row_num), + field_2: self.row_num * 3, + field_3: self.row_num * 8, + field_4: self.row_num * 11, + field_5: self.row_num * 17, + field_6: self.row_num * 21, create_count: 0, }; Some(new_row) @@ -188,23 +255,30 @@ impl App for MainWindow { #[derive(Clone, Default)] struct TableRow { field_1: u64, - field_2: i64, - field_3: String, - field_4: String, - field_5: String, - field_6: String, + field_2: u64, + field_3: u64, + field_4: u64, + field_5: u64, + field_6: u64, create_count: u64, } -#[derive(Eq, PartialEq, Debug, Ord, PartialOrd, Clone, Copy, Hash, Default, EnumIter)] +#[derive(Eq, PartialEq, Debug, Ord, PartialOrd, Clone, Copy, Hash, Default, EnumIter, Display)] enum TableColumns { #[default] + #[strum(to_string = "Column 1")] Field1, + #[strum(to_string = "Column 2")] Field2, + #[strum(to_string = "Column 3")] Field3, + #[strum(to_string = "Column 4")] Field4, + #[strum(to_string = "Column 5")] Field5, + #[strum(to_string = "Column 6")] Field6, + #[strum(to_string = "Render Counter")] Field7, } @@ -226,16 +300,8 @@ impl ColumnOperations for TableColumns { sort_order: Option, _table: &mut SelectableTable, ) -> Option { - let mut text = match self { - TableColumns::Field1 => "Field 1", - TableColumns::Field2 => "Field 2", - TableColumns::Field3 => "Field 3", - TableColumns::Field4 => "Field 4", - TableColumns::Field5 => "Field 5", - TableColumns::Field6 => "Field 6", - TableColumns::Field7 => "Row Creation Count", - } - .to_string(); + let mut text = self.to_string(); + if let Some(sort) = sort_order { match sort { SortOrder::Ascending => text += "🔽", diff --git a/demo/src/main.rs b/demo/src/main.rs index a6a8ba6..8ce2c26 100644 --- a/demo/src/main.rs +++ b/demo/src/main.rs @@ -7,7 +7,7 @@ use eframe::{WebOptions, WebRunner}; #[cfg(not(target_arch = "wasm32"))] fn main() -> eframe::Result { - use egui::{vec2, ViewportBuilder}; + use egui::{ViewportBuilder, vec2}; let options = eframe::NativeOptions { centered: true,