diff --git a/Cargo.lock b/Cargo.lock index 6b867a2..6e6c42b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,11 +575,12 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "eframe" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeeebd536f1a2737661be42819ed58a7aac69a4bf061610b373467f886574e6" +checksum = "3a084390b90aa223d5fb6ee3d2ac3a2ded0df212f684f91fbb5f0f45ab9e0724" dependencies = [ "egui", + "egui-winit", "egui_glium", "egui_web", "epi", @@ -587,31 +588,45 @@ dependencies = [ [[package]] name = "egui" -version = "0.14.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b403f29d592b5c2267279bfdf2cd3fe3ba0e7a8738d03203a6f1536e8e9d26bd" +checksum = "1c8d416a3343cbfc6f4d17bb1cba46b4d7efecb9ee541967763e0b5e04e5fae7" dependencies = [ + "ahash", "epaint", + "nohash-hasher", ] [[package]] -name = "egui_glium" -version = "0.14.0" +name = "egui-winit" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "972b1882ae98f7dd2f5dcbf5efeb35448c158a8f6ba80d6cc3b8073eae1fae4f" +checksum = "bc403e91d1bd693239f1c734193cdf0eb38c8682bbfb9990c4b6cd2db5ee368e" dependencies = [ "copypasta", "egui", "epi", - "glium", "webbrowser", + "winit", +] + +[[package]] +name = "egui_glium" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26478ec89b8c9c41a45687a90f9c8fc18106e3ffd8a08559285d625185a2ac92" +dependencies = [ + "egui", + "egui-winit", + "epi", + "glium", ] [[package]] name = "egui_web" -version = "0.14.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a70a7eee03a31589660cbfefa5ac529b9740b008d94075c46fd29f80abfad9de" +checksum = "43f2af8984a1c9ecaaaf7f11424c78185c89b5cfe8dab3bd0fac641db81c5763" dependencies = [ "egui", "epi", @@ -638,9 +653,9 @@ dependencies = [ [[package]] name = "emath" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca5179aa9d15128cebb79bb56dda73a79cc66b402056ff19a992e54b365e15c" +checksum = "24a1aaa922d55da6a2bf32957c3d153e7fb9d52ed8d69777a75092240172eb6e" [[package]] name = "emlx" @@ -654,22 +669,22 @@ dependencies = [ [[package]] name = "epaint" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136e7d88da926182bcfdb8217137dd347dfe5dc03b8988eaba3ef8becf83394a" +checksum = "16bb4d3b8bbbd132c99d2a5efec8567e8b6d09b742f758ae6cf1e4b104fe0231" dependencies = [ "ab_glyph", "ahash", "atomic_refcell", "emath", - "ordered-float", + "nohash-hasher", ] [[package]] name = "epi" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c033c9b4d921566a98b8831b63af5ba2a0ad7ffa62d22b897698f36c22a28a" +checksum = "3f5e4e08127f9b86e2c450c96a3032764b63546eb170c2fc54684dc70ff3fc82" dependencies = [ "egui", ] @@ -1085,9 +1100,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.22.2" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290b64917f8b0cb885d9de0f9959fe1f775d7fa12f1da2db9001c1c8ab60f89d" +checksum = "abd5850c449b40bacb498b2bbdfaff648b1b055630073ba8db499caf2d0ea9f2" dependencies = [ "pkg-config", "vcpkg", @@ -1324,6 +1339,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.0.0" @@ -1471,15 +1492,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" -[[package]] -name = "ordered-float" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97c9d06878b3a851e8026ef94bf7fef9ba93062cd412601da4d9cf369b1cc62d" -dependencies = [ - "num-traits", -] - [[package]] name = "osmesa-sys" version = "0.1.2" @@ -1659,8 +1671,8 @@ dependencies = [ "serde", "serde_json", "shellexpand", - "strum", - "strum_macros", + "strum 0.22.0", + "strum_macros 0.22.0", "thiserror", "tracing", "tracing-subscriber", @@ -1862,9 +1874,9 @@ dependencies = [ [[package]] name = "rusqlite" -version = "0.25.3" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57adcf67c8faaf96f3248c2a7b419a0dbc52ebe36ba83dd57fe83827c1ea4eb3" +checksum = "8a82b0b91fad72160c56bf8da7a549b25d7c31109f52cc1437eac4c0ad2550a7" dependencies = [ "bitflags", "chrono", @@ -1948,9 +1960,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.68" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" +checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3" dependencies = [ "itoa", "ryu", @@ -2055,6 +2067,12 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" +[[package]] +name = "strum" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e" + [[package]] name = "strum_macros" version = "0.21.1" @@ -2067,6 +2085,18 @@ dependencies = [ "syn", ] +[[package]] +name = "strum_macros" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "1.0.77" @@ -2089,8 +2119,8 @@ dependencies = [ "heck", "itertools", "pkg-config", - "strum", - "strum_macros", + "strum 0.21.0", + "strum_macros 0.21.1", "thiserror", "toml", "version-compare", diff --git a/Cargo.toml b/Cargo.toml index e6502d1..30177b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,29 +25,29 @@ eyre = "0.6.5" thiserror = "1.0.29" tracing = "0.1.29" tracing-subscriber = "0.3.0" -rusqlite = {version = "0.25.3", features = ["chrono", "trace", "serde_json"]} +rusqlite = {version = "0.26.1", features = ["chrono", "trace", "serde_json"]} regex = "1.5.3" flate2 = "1.0.22" once_cell = "1.8.0" email-parser = { git = "https://github.com/terhechte/email-parser", features = ["sender", "to", "in-reply-to", "date", "subject", "mime", "allow-duplicate-headers", "compatibility-fixes"]} rayon = "1.5.1" chrono = "0.4.19" -serde_json = "*" -serde = { version = "*", features = ["derive"]} +serde_json = "1.0.70" +serde = { version = "1.0.130", features = ["derive"]} crossbeam-channel = "0.5.1" -eframe = { version = "*", optional = true} +eframe = { version = "0.15.0", optional = true} rsql_builder = "0.1.2" treemap = "0.3.2" num-format = "0.4.0" -strum = "0.21" -strum_macros = "0.21" +strum = "0.22.0" +strum_macros = "0.22.0" lru = { version = "0.7.0", optional = true } emlx = { git = "https://github.com/terhechte/emlx", features = []} -walkdir = "*" +walkdir = "2.3.2" mbox-reader = "0.2.0" rfd = "0.5.1" rand = "0.8.4" -shellexpand = "*" +shellexpand = "2.1.0" image = { version = "0.23", default-features = false, features = ["png"] } [features] diff --git a/src/gui/app_state/error.rs b/src/gui/app_state/error.rs index b36907a..b49414f 100644 --- a/src/gui/app_state/error.rs +++ b/src/gui/app_state/error.rs @@ -75,7 +75,7 @@ impl ErrorUI { } ui.add_space(30.0); if self.show_details { - egui::containers::ScrollArea::auto_sized().show(ui, |ui| { + egui::containers::ScrollArea::vertical().show(ui, |ui| { ui.add( egui::widgets::Label::new("Error Chain") .text_style(egui::TextStyle::Heading), diff --git a/src/gui/app_state/startup.rs b/src/gui/app_state/startup.rs index efdc95f..a72c6e9 100644 --- a/src/gui/app_state/startup.rs +++ b/src/gui/app_state/startup.rs @@ -214,21 +214,22 @@ impl StartupUI { let button_size1: Vec2 = ((center.width() / 2.0) - 25.0, 25.0).into(); let button_size2: Vec2 = ((center.width() / 2.0) - 25.0, 25.0).into(); ui.horizontal(|ui| { - let response = ui.add_sized( - button_size1, - egui::Button::new("Start") - .enabled( + let enabled = { // if we have an email folder, // and - if we want to save to disk - // if we have a database path self.email_folder.is_some() && (self.save_to_disk == self.database_path.is_some()) - ) - .text_color(colors.text_primary), - ); - if response.clicked() { - self.action_start(); - } + }; + ui.add_enabled_ui(enabled, |ui| { + let response = ui.add_sized( + button_size1, + egui::Button::new("Start").text_color(colors.text_primary), + ); + if response.clicked() { + self.action_start(); + } + }); let response = ui.add_sized(button_size2, egui::Button::new("Or Open Database")); if response.clicked() { self.action_open_database(); diff --git a/src/gui/navigation_bar.rs b/src/gui/navigation_bar.rs index 2f6c448..1373ac3 100644 --- a/src/gui/navigation_bar.rs +++ b/src/gui/navigation_bar.rs @@ -1,5 +1,5 @@ use crate::model::Engine; -use eframe::egui::{self, Widget}; +use eframe::egui::{self, Color32, Widget}; use eyre::Report; use super::app_state::UIState; @@ -64,17 +64,21 @@ impl<'a> Widget for NavigationBar<'a> { let mut w = ui.available_width(); let mail_text = "\u{1F4E7} Mails"; - let mail_galley = ui - .painter() - .layout_no_wrap(egui::TextStyle::Button, mail_text.to_owned()); + let mail_galley = ui.painter().layout_no_wrap( + mail_text.to_owned(), + egui::TextStyle::Button, + Color32::WHITE, + ); let filter_text = "\u{1F5B9} Export"; - let filter_galley = ui - .painter() - .layout_no_wrap(egui::TextStyle::Button, filter_text.to_owned()); + let filter_galley = ui.painter().layout_no_wrap( + filter_text.to_owned(), + egui::TextStyle::Button, + Color32::WHITE, + ); - w -= mail_galley.size.x + ui.spacing().button_padding.x * 4.0; - w -= filter_galley.size.x + ui.spacing().button_padding.x * 4.0; + w -= mail_galley.size().x + ui.spacing().button_padding.x * 4.0; + w -= filter_galley.size().x + ui.spacing().button_padding.x * 4.0; ui.add_space(w); ui.add(navigation_button(filter_text)); diff --git a/src/gui/segmentation_bar.rs b/src/gui/segmentation_bar.rs index 3ebbcdf..2e5b0dc 100644 --- a/src/gui/segmentation_bar.rs +++ b/src/gui/segmentation_bar.rs @@ -25,10 +25,11 @@ impl<'a> Widget for SegmentationBar<'a> { for (id_index, group) in groupings.iter().enumerate() { let alternatives = segmentations::aggregation_fields(self.engine, group); if let Some(value) = group.value() { - let button = egui::Button::new(format!("{} {}", group.name(), value)) - .enabled(false) - .text_color(egui::Color32::WHITE); - ui.add(button); + ui.add_enabled( + false, + egui::Button::new(format!("{} {}", group.name(), value)) + .text_color(egui::Color32::WHITE), + ); } else if let Some(mut selected) = group.index(&alternatives) { let p = egui::ComboBox::from_id_source(&id_index).show_index( ui, diff --git a/src/gui/widgets/background.rs b/src/gui/widgets/background.rs index c4e06dc..884469e 100644 --- a/src/gui/widgets/background.rs +++ b/src/gui/widgets/background.rs @@ -1,6 +1,8 @@ //! Various background utilities use eframe::egui::{ - self, epaint::Shadow, vec2, Color32, Painter, Pos2, Rect, Response, Shape, Stroke, Ui, Vec2, + self, + epaint::{RectShape, Shadow}, + vec2, Color32, Painter, Pos2, Rect, Response, Shape, Stroke, Ui, Vec2, }; use std::ops::Rem; @@ -29,12 +31,12 @@ pub fn color_background( ui.painter().set( where_to_put_background, - egui::epaint::Shape::Rect { + egui::epaint::Shape::Rect(RectShape { corner_radius: 0.0, fill, stroke, rect, - }, + }), ); ret } @@ -48,12 +50,12 @@ pub fn shadow_background( corner_radius: f32, shadow: Shadow, ) { - let frame_shape = Shape::Rect { + let frame_shape = Shape::Rect(RectShape { rect: paint_rect, corner_radius, fill, stroke, - }; + }); let shadow = shadow.tessellate(paint_rect, corner_radius); let shadow = Shape::Mesh(shadow); diff --git a/src/gui/widgets/filter_panel.rs b/src/gui/widgets/filter_panel.rs index 2501bff..5a48456 100644 --- a/src/gui/widgets/filter_panel.rs +++ b/src/gui/widgets/filter_panel.rs @@ -14,7 +14,7 @@ pub struct FilterState { is_reply: Option, is_seen: Option, subject_contains: Option, - tags_contains: Option, + tags_contains: Option>, } impl FilterState { @@ -41,6 +41,21 @@ impl<'a> Widget for FilterPanel<'a> { let Self { engine, state } = self; egui::Frame::none() .margin(vec2(15.0, 10.5)) + .show(ui, |ui| { + FilterPanel::filter_panel_contents(ui, engine, state) + }) + .response + } +} + +impl FilterPanel<'_> { + fn filter_panel_contents( + ui: &mut egui::Ui, + engine: &mut Engine, + state: &mut FilterState, + ) -> Response { + egui::ScrollArea::vertical() + .max_height(330.0) .show(ui, |ui| { egui::Grid::new("filter_grid") .spacing(vec2(15.0, 15.0)) @@ -67,7 +82,12 @@ impl<'a> Widget for FilterPanel<'a> { ui.end_row(); if engine.format_has_tags() { - input_block(ui, "Tags", &mut state.tags_contains); + input_tags( + ui, + "Tags", + &mut state.tags_contains, + engine.known_tags(), + ); ui.end_row(); } }); @@ -135,3 +155,19 @@ fn input_block(ui: &mut egui::Ui, title: &str, value: &mut Option) { _ => *value = Some(text_value), } } + +fn input_tags( + ui: &mut egui::Ui, + title: &str, + selected: &mut Option>, + available: &[String], +) { + let mut selected: String = "".to_owned(); + ui.horizontal_wrapped(|ui| { + for tag in available { + //ui.label(tag); + ui.selectable_value(&mut selected, tag.clone(), tag); + } + }); + ui.button("Clear"); +} diff --git a/src/gui/widgets/rectangles.rs b/src/gui/widgets/rectangles.rs index 1469d68..44dfa74 100644 --- a/src/gui/widgets/rectangles.rs +++ b/src/gui/widgets/rectangles.rs @@ -1,7 +1,10 @@ use std::collections::hash_map::DefaultHasher; use crate::model::{segmentations, Engine, Segment}; -use eframe::egui::{self, epaint::Galley, Color32, Pos2, Rect, Rgba, Stroke, TextStyle, Widget}; +use eframe::{ + egui::{self, epaint::Galley, Color32, Pos2, Rect, Rgba, Stroke, TextStyle, Widget}, + epi::Frame, +}; use eyre::Report; use num_format::{Locale, ToFormattedString}; @@ -53,26 +56,30 @@ impl<'a> Widget for Rectangles<'a> { response.mark_changed(); } if item_response.hovered() { - hovered = Some(format!("#{}: {}", item.count, item.field.to_string())); + hovered = Some(format!("{}: #{}", item.field.to_string(), item.count)); } } if let Some(h) = hovered { // Calculate the size let text = format!("{}", h); - let galley = ui.painter().layout_no_wrap(TextStyle::Body, text.clone()); + let galley = ui + .painter() + .layout_no_wrap(text.clone(), TextStyle::Body, Color32::WHITE); // keep spacing in mind + let size = galley.size(); let size: Pos2 = ( - galley.size.x + ui.spacing().button_padding.x * 2.0, - galley.size.y + ui.spacing().button_padding.y * 2.0, + size.x + ui.spacing().button_padding.x * 2.0, + size.y + ui.spacing().button_padding.y * 2.0, ) .into(); // we build a disabled for easy rounded corners - let label_button = egui::widgets::Button::new(text) - .enabled(false) - .text_color(Color32::WHITE); + + let label = egui::widgets::Label::new(text) + .background_color(colors.window_background) + .text_color(colors.text_primary); // we want to be a wee bit in the rectangle system let offset = -2.0; @@ -85,7 +92,7 @@ impl<'a> Widget for Rectangles<'a> { .into(), (size.x + 10.0, size.y + 10.0).into(), ), - label_button, + label, ); } @@ -127,10 +134,11 @@ fn rectangle_ui( let align_bottom = |galley: &std::sync::Arc, center: &mut Pos2, spacing: f32| { #[allow(clippy::clone_on_copy)] let mut position = center.clone(); - position.x -= galley.size.x / 2.0; - position.y -= galley.size.y / 2.0; - center.y += galley.size.y + spacing; - if galley.size.x < rect.width() && galley.size.y < rect.height() { + let size = galley.size(); + position.x -= size.x / 2.0; + position.y -= size.y / 2.0; + center.y += size.y + spacing; + if size.x < rect.width() && size.y < rect.height() { Some(position) } else { None @@ -142,10 +150,10 @@ fn rectangle_ui( // Take the max width - some spacing to fit into the rectangle let width = rect.width() - ui.spacing().button_padding.x * 2.0; let text = segment.field.to_string(); - let galley = painter.layout_multiline(TextStyle::Body, text, width); + let galley = painter.layout(text, TextStyle::Body, Rgba::BLACK.into(), width); let previous_center = center; if let Some(center) = align_bottom(&galley, &mut center, 2.0) { - painter.galley(center, galley, Rgba::BLACK.into()); + painter.galley(center, galley); } else { // If the name doesn't fit, reverse the changes to center the count center = previous_center; @@ -153,9 +161,9 @@ fn rectangle_ui( } { let text = segment.count.to_formatted_string(&Locale::en); - let galley = painter.layout_no_wrap(TextStyle::Small, text); + let galley = painter.layout_no_wrap(text, TextStyle::Small, Rgba::BLACK.into()); if let Some(center) = align_bottom(&galley, &mut center, 5.0) { - painter.galley(center, galley, Rgba::BLACK.into()); + painter.galley(center, galley); } } response diff --git a/src/gui/widgets/spinner.rs b/src/gui/widgets/spinner.rs index 4ca66ee..fe2627f 100644 --- a/src/gui/widgets/spinner.rs +++ b/src/gui/widgets/spinner.rs @@ -1,4 +1,6 @@ -use eframe::egui::{self, lerp, vec2, Color32, Pos2, Response, Shape, Stroke, Vec2, Widget}; +use eframe::egui::{ + self, epaint::PathShape, lerp, vec2, Color32, Pos2, Response, Shape, Stroke, Vec2, Widget, +}; /// A simple spinner pub struct Spinner(Vec2); @@ -31,12 +33,13 @@ impl Widget for Spinner { + vec2(-corner_radius, 0.0) }) .collect(); - ui.painter().add(Shape::Path { + let shape = Shape::Path(PathShape { points, closed: false, fill: Color32::TRANSPARENT, stroke: Stroke::new(8.0, visuals.strong_text_color()), }); + ui.painter().add(shape); ui.ctx().request_repaint(); diff --git a/src/gui/widgets/table.rs b/src/gui/widgets/table.rs index 66ee323..69fe0a8 100644 --- a/src/gui/widgets/table.rs +++ b/src/gui/widgets/table.rs @@ -129,9 +129,6 @@ impl< let column_id = self.id_source.with("_column_").with(i); let desired_column_width = state.column_width(i); - let galley = ui - .fonts() - .layout_single_line(header_text_style, column.name.clone()); let mut column_rect = rect; column_rect.min.x += column_offset; @@ -143,6 +140,16 @@ impl< let response = ui.interact(column_rect, column_id, Sense::hover()); + let color = if response.hovered() { + ui.style().visuals.widgets.hovered.fg_stroke.color + } else { + ui.style().visuals.widgets.inactive.fg_stroke.color + }; + + let galley = ui + .fonts() + .layout_no_wrap(column.name.clone(), header_text_style, color); + if response.hovered() { ui.painter().rect_stroke( column_rect, @@ -153,16 +160,8 @@ impl< let mut text_pos = column_rect.left_center(); text_pos.x += self.cell_padding.x; - text_pos.y -= galley.size.y / 2.0; - ui.painter_at(column_rect).galley( - text_pos, - galley, - if response.hovered() { - ui.style().visuals.widgets.hovered.fg_stroke.color - } else { - ui.style().visuals.widgets.inactive.fg_stroke.color - }, - ); + text_pos.y -= galley.size().y / 2.0; + ui.painter_at(column_rect).galley(text_pos, galley); column_offset += column_rect.width(); } @@ -198,14 +197,14 @@ impl< // First step: compute some sizes used during rendering. Since this is a // homogenous table, we can figure out its exact sizes based on the // number of rows and columns. - let table_rect = ui.available_rect_before_wrap_finite(); + let table_rect = ui.available_rect_before_wrap(); let response = ui.interact(table_rect, self.id_source, Sense::hover()); self.header_ui(ui, &mut state); // Now render the table body, which is inside an independently // scrollable area. - ScrollArea::auto_sized().show_rows(ui, self.row_height, self.num_rows, |ui, row_range| { + ScrollArea::vertical().show_rows(ui, self.row_height, self.num_rows, |ui, row_range| { ui.scope(|ui| { let maker = &mut self.row_maker; let rows = maker(row_range); @@ -272,12 +271,14 @@ impl< let painter = ui.painter_at(column_rect); - let galley = ui.fonts().layout_single_line(cell_text_style, cell_text); + let galley = + ui.fonts() + .layout_no_wrap(cell_text, cell_text_style, cell_text_color); let mut text_pos = column_rect.left_center(); text_pos.x += self.cell_padding.x; - text_pos.y -= galley.size.y / 2.0; - painter.galley(text_pos, galley, cell_text_color); + text_pos.y -= galley.size().y / 2.0; + painter.galley(text_pos, galley); column_offset += column_rect.width(); }