From a9df93fa5ab6ea945a8f874e5525d74be73d97be Mon Sep 17 00:00:00 2001 From: Stanislav Pastushenko Date: Tue, 2 Dec 2025 19:50:12 +0100 Subject: [PATCH] cleaner UI --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/main.rs | 104 ++++++++++++++++++++++++++++++++++--- src/ui/log_view.rs | 37 ++++++++----- src/ui/search_panel.rs | 115 ++++++++++++++++++++++++++--------------- 5 files changed, 194 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f19ec45..b55534b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2669,7 +2669,7 @@ dependencies = [ [[package]] name = "rlogg" -version = "0.1.0" +version = "0.2.0" dependencies = [ "eframe", "rayon", diff --git a/Cargo.toml b/Cargo.toml index 4350709..70321bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rlogg" -version = "0.1.0" +version = "0.2.0" edition = "2024" authors = ["Stanislav Pastushenko "] description = "A fast log file viewer with search, filtering, and highlighting capabilities" diff --git a/src/main.rs b/src/main.rs index a61e480..a941bc3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ mod highlight; mod line_index; mod search; mod tab_manager; +mod theme; mod types; mod ui; @@ -38,7 +39,11 @@ fn main() -> eframe::Result { eframe::run_native( "RLogg", options, - Box::new(move |_cc| Ok(Box::new(LogViewerApp::new(config)))), + Box::new(move |cc| { + // Apply the modern dark purple theme + theme::apply_theme(&cc.egui_ctx); + Ok(Box::new(LogViewerApp::new(config))) + }), ) } @@ -219,18 +224,44 @@ impl eframe::App for LogViewerApp { self.save_config(); } - // Render filtered view + // Render filtered view with improved styling let show_filtered = !self.search_panel_state.query.is_empty(); let highlight_rules = self.highlight_manager.rules.clone(); if show_filtered { if let Some(tab) = self.active_tab_mut() { if !tab.filtered_lines.is_empty() { + let palette = theme::get_palette(); + egui::TopBottomPanel::bottom("filtered_view") .resizable(true) - .default_height(200.0) + .default_height(250.0) + .min_height(150.0) .show(ctx, |ui| { - ui.heading("Filtered View"); + // Styled header + let header_frame = egui::Frame::none() + .fill(palette.bg_secondary) + .inner_margin(egui::Margin::symmetric(12.0, 8.0)); + + header_frame.show(ui, |ui| { + ui.horizontal(|ui| { + let icon = egui::RichText::new("🔍") + .size(16.0); + ui.label(icon); + + let title = egui::RichText::new("Search Results") + .size(16.0) + .color(palette.text_primary) + .strong(); + ui.label(title); + + let count = egui::RichText::new(format!("({} matches)", tab.filtered_lines.len())) + .size(14.0) + .color(palette.accent_bright); + ui.label(count); + }); + }); + ui.separator(); render_log_view( @@ -249,12 +280,49 @@ impl eframe::App for LogViewerApp { // Handle keyboard input self.handle_keyboard_input(ctx); - // Render main view + // Render main view with improved styling egui::CentralPanel::default().show(ctx, |ui| { - ui.heading("Main Log View"); - ui.separator(); + let palette = theme::get_palette(); if let Some(tab) = self.active_tab_mut() { + // Styled header + let header_frame = egui::Frame::none() + .fill(palette.bg_secondary) + .inner_margin(egui::Margin::symmetric(12.0, 8.0)); + + header_frame.show(ui, |ui| { + ui.horizontal(|ui| { + let icon = egui::RichText::new("📄") + .size(16.0); + ui.label(icon); + + let title = egui::RichText::new("Log View") + .size(16.0) + .color(palette.text_primary) + .strong(); + ui.label(title); + + // Show filename + if let Some(filename) = tab.file_path.file_name() { + ui.add_space(8.0); + let filename_text = egui::RichText::new(format!("• {}", filename.to_string_lossy())) + .size(14.0) + .color(palette.text_secondary); + ui.label(filename_text); + } + + // Show line count + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let line_count = egui::RichText::new(format!("{} lines", tab.line_index.total_lines)) + .size(13.0) + .color(palette.text_muted); + ui.label(line_count); + }); + }); + }); + + ui.separator(); + render_log_view( ui, LogViewContext { @@ -265,7 +333,27 @@ impl eframe::App for LogViewerApp { ); } else { ui.centered_and_justified(|ui| { - ui.label("Click 'Open File' to load a log file"); + ui.vertical_centered(|ui| { + ui.add_space(40.0); + + let icon = egui::RichText::new("📂") + .size(48.0); + ui.label(icon); + + ui.add_space(16.0); + + let text = egui::RichText::new("No file loaded") + .size(18.0) + .color(palette.text_secondary); + ui.label(text); + + ui.add_space(8.0); + + let hint = egui::RichText::new("Click 'Open File' in the menu to get started") + .size(14.0) + .color(palette.text_muted); + ui.label(hint); + }); }); } }); diff --git a/src/ui/log_view.rs b/src/ui/log_view.rs index ce17d2a..1a060d9 100644 --- a/src/ui/log_view.rs +++ b/src/ui/log_view.rs @@ -1,6 +1,7 @@ use eframe::egui; use crate::file_tab::FileTab; +use crate::theme; use crate::types::HighlightRule; pub struct LogViewContext<'a> { @@ -86,7 +87,7 @@ fn handle_scroll_to_line( ) { if tab.scroll_to_main { let target_row = tab.main_scroll_offset; - let adgusted_row_height = row_height + 3f32; + let adgusted_row_height = row_height + 6f32; let scroll_offset = (target_row as f32) * adgusted_row_height; eprintln!("=== SCROLL TO LINE ==="); @@ -110,7 +111,7 @@ fn handle_page_scroll( total_lines: usize, ) { if let Some(direction) = tab.page_scroll_direction.take() { - let row_height_offset = row_height + 3f32; + let row_height_offset = row_height + 6f32; let viewport_height = ui.available_height(); let rows_per_page = (viewport_height / row_height_offset).floor().max(1.0); let scroll_delta = direction * rows_per_page * row_height_offset; @@ -192,49 +193,59 @@ fn render_line( ) where F: FnOnce(bool), { + let palette = theme::get_palette(); + let highlight_color = highlight_rules .iter() .find(|rule| rule.enabled && content.contains(&rule.pattern)) .map(|rule| egui::Color32::from_rgb(rule.color[0], rule.color[1], rule.color[2])); + // Improved color scheme with better visual hierarchy let bg_color = if is_selected { - egui::Color32::from_rgb(70, 130, 180) + palette.selection } else if let Some(color) = highlight_color { color } else { egui::Color32::TRANSPARENT }; + // Better padding and margins for improved readability let frame = egui::Frame::none() .fill(bg_color) - .inner_margin(egui::Margin::symmetric(2.0, 1.0)); + .inner_margin(egui::Margin::symmetric(8.0, 3.0)); frame.show(ui, |ui| { ui.horizontal(|ui| { - let line_num_text = egui::RichText::new(format!("{:6} ", line_num + 1)) + // Line numbers with better styling + let line_num_text = egui::RichText::new(format!("{:6}", line_num + 1)) .monospace() .color(if is_selected { - egui::Color32::WHITE + palette.text_primary } else { - egui::Color32::DARK_GRAY + palette.line_number }); let line_num_response = ui.label(line_num_text); + // Separator between line number and content + ui.add_space(4.0); + ui.separator(); + ui.add_space(4.0); + + // Content text with improved styling let text = egui::RichText::new(content).monospace().color( if is_selected { - egui::Color32::WHITE + palette.text_primary } else { - ui.style().visuals.text_color() + palette.text_primary }, ); let text_response = ui .scope(|ui| { - ui.style_mut().visuals.selection.bg_fill = - egui::Color32::from_rgb(255, 180, 50); - ui.style_mut().visuals.selection.stroke.color = - egui::Color32::from_rgb(200, 140, 30); + // Better text selection colors + ui.style_mut().visuals.selection.bg_fill = palette.accent_bright; + ui.style_mut().visuals.selection.stroke.color = palette.accent_primary; ui.add(egui::Label::new(text).selectable(true)) }) .inner; diff --git a/src/ui/search_panel.rs b/src/ui/search_panel.rs index eb25ff4..1828e4f 100644 --- a/src/ui/search_panel.rs +++ b/src/ui/search_panel.rs @@ -1,6 +1,7 @@ use eframe::egui; use crate::search::SearchState; +use crate::theme; pub struct SearchPanelState { pub query: String, @@ -27,59 +28,87 @@ pub fn render_search_panel( config_changed: false, }; - egui::TopBottomPanel::bottom("search_panel").show(ctx, |ui| { - ui.vertical(|ui| { - ui.horizontal(|ui| { - ui.label("🔍 Filter:"); + let palette = theme::get_palette(); - let text_edit_width = 200.0; - let text_response = ui.add_sized( - [text_edit_width, 20.0], - egui::TextEdit::singleline(&mut state.query), - ); + egui::TopBottomPanel::bottom("search_panel") + .frame(egui::Frame::none() + .fill(palette.bg_secondary) + .inner_margin(egui::Margin::symmetric(12.0, 10.0))) + .show(ctx, |ui| { + ui.vertical(|ui| { + ui.horizontal(|ui| { + // Filter label with icon + let label = egui::RichText::new("🔍 Filter:") + .size(14.0) + .color(palette.text_primary); + ui.label(label); - let enter_pressed = - text_response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)); + ui.add_space(4.0); - if !state.history.is_empty() { - render_history_dropdown(ui, state); - } + // Text input with proper height matching buttons + let text_edit_width = 300.0; + let text_response = ui.add( + egui::TextEdit::singleline(&mut state.query) + .desired_width(text_edit_width) + .hint_text("Enter search query...") + ); - let case_changed = ui - .checkbox(&mut state.case_sensitive, "Case sensitive") - .changed(); - let regex_changed = ui.checkbox(&mut state.use_regex, "Regex").changed(); + let enter_pressed = + text_response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)); - if case_changed || regex_changed { - actions.config_changed = true; - } - - if ui.button("Search").clicked() || enter_pressed { - actions.execute_search = true; - actions.config_changed = true; - } - - if !state.query.is_empty() { - if ui.button("✖ Clear").clicked() { - actions.clear_search = true; + if !state.history.is_empty() { + render_history_dropdown(ui, state); } - ui.label(format!("({} matches)", match_count)); + ui.add_space(8.0); + + // Checkboxes + let case_changed = ui + .checkbox(&mut state.case_sensitive, "Case sensitive") + .changed(); + let regex_changed = ui.checkbox(&mut state.use_regex, "Regex").changed(); + + if case_changed || regex_changed { + actions.config_changed = true; + } + + ui.add_space(8.0); + + // Search button + if ui.button("Search").clicked() || enter_pressed { + actions.execute_search = true; + actions.config_changed = true; + } + + // Clear button and match count + if !state.query.is_empty() { + if ui.button("✖ Clear").clicked() { + actions.clear_search = true; + } + + ui.add_space(8.0); + + let count_text = egui::RichText::new(format!("{} matches", match_count)) + .color(palette.accent_bright) + .size(13.0); + ui.label(count_text); + } + }); + + // Progress bar + if search_state.is_searching() { + ui.add_space(6.0); + let progress = search_state.get_progress(); + ui.horizontal(|ui| { + ui.add( + egui::ProgressBar::new(progress) + .text(format!("Searching... {:.0}%", progress * 100.0)) + .animate(true), + ); + }); } }); - - if search_state.is_searching() { - let progress = search_state.get_progress(); - ui.horizontal(|ui| { - ui.add( - egui::ProgressBar::new(progress) - .text(format!("Searching... {:.0}%", progress * 100.0)) - .animate(true), - ); - }); - } }); - }); actions }