cleaner UI

This commit is contained in:
2025-12-02 19:50:12 +01:00
parent e60c20aefa
commit a9df93fa5a
5 changed files with 194 additions and 66 deletions

2
Cargo.lock generated
View File

@@ -2669,7 +2669,7 @@ dependencies = [
[[package]] [[package]]
name = "rlogg" name = "rlogg"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"eframe", "eframe",
"rayon", "rayon",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "rlogg" name = "rlogg"
version = "0.1.0" version = "0.2.0"
edition = "2024" edition = "2024"
authors = ["Stanislav Pastushenko <staspast1@gmail.com>"] authors = ["Stanislav Pastushenko <staspast1@gmail.com>"]
description = "A fast log file viewer with search, filtering, and highlighting capabilities" description = "A fast log file viewer with search, filtering, and highlighting capabilities"

View File

@@ -7,6 +7,7 @@ mod highlight;
mod line_index; mod line_index;
mod search; mod search;
mod tab_manager; mod tab_manager;
mod theme;
mod types; mod types;
mod ui; mod ui;
@@ -38,7 +39,11 @@ fn main() -> eframe::Result {
eframe::run_native( eframe::run_native(
"RLogg", "RLogg",
options, 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(); self.save_config();
} }
// Render filtered view // Render filtered view with improved styling
let show_filtered = !self.search_panel_state.query.is_empty(); let show_filtered = !self.search_panel_state.query.is_empty();
let highlight_rules = self.highlight_manager.rules.clone(); let highlight_rules = self.highlight_manager.rules.clone();
if show_filtered { if show_filtered {
if let Some(tab) = self.active_tab_mut() { if let Some(tab) = self.active_tab_mut() {
if !tab.filtered_lines.is_empty() { if !tab.filtered_lines.is_empty() {
let palette = theme::get_palette();
egui::TopBottomPanel::bottom("filtered_view") egui::TopBottomPanel::bottom("filtered_view")
.resizable(true) .resizable(true)
.default_height(200.0) .default_height(250.0)
.min_height(150.0)
.show(ctx, |ui| { .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(); ui.separator();
render_log_view( render_log_view(
@@ -249,12 +280,49 @@ impl eframe::App for LogViewerApp {
// Handle keyboard input // Handle keyboard input
self.handle_keyboard_input(ctx); self.handle_keyboard_input(ctx);
// Render main view // Render main view with improved styling
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Main Log View"); let palette = theme::get_palette();
ui.separator();
if let Some(tab) = self.active_tab_mut() { 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( render_log_view(
ui, ui,
LogViewContext { LogViewContext {
@@ -265,7 +333,27 @@ impl eframe::App for LogViewerApp {
); );
} else { } else {
ui.centered_and_justified(|ui| { 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);
});
}); });
} }
}); });

View File

@@ -1,6 +1,7 @@
use eframe::egui; use eframe::egui;
use crate::file_tab::FileTab; use crate::file_tab::FileTab;
use crate::theme;
use crate::types::HighlightRule; use crate::types::HighlightRule;
pub struct LogViewContext<'a> { pub struct LogViewContext<'a> {
@@ -86,7 +87,7 @@ fn handle_scroll_to_line(
) { ) {
if tab.scroll_to_main { if tab.scroll_to_main {
let target_row = tab.main_scroll_offset; 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; let scroll_offset = (target_row as f32) * adgusted_row_height;
eprintln!("=== SCROLL TO LINE ==="); eprintln!("=== SCROLL TO LINE ===");
@@ -110,7 +111,7 @@ fn handle_page_scroll(
total_lines: usize, total_lines: usize,
) { ) {
if let Some(direction) = tab.page_scroll_direction.take() { 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 viewport_height = ui.available_height();
let rows_per_page = (viewport_height / row_height_offset).floor().max(1.0); let rows_per_page = (viewport_height / row_height_offset).floor().max(1.0);
let scroll_delta = direction * rows_per_page * row_height_offset; let scroll_delta = direction * rows_per_page * row_height_offset;
@@ -192,49 +193,59 @@ fn render_line<F>(
) where ) where
F: FnOnce(bool), F: FnOnce(bool),
{ {
let palette = theme::get_palette();
let highlight_color = highlight_rules let highlight_color = highlight_rules
.iter() .iter()
.find(|rule| rule.enabled && content.contains(&rule.pattern)) .find(|rule| rule.enabled && content.contains(&rule.pattern))
.map(|rule| egui::Color32::from_rgb(rule.color[0], rule.color[1], rule.color[2])); .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 { let bg_color = if is_selected {
egui::Color32::from_rgb(70, 130, 180) palette.selection
} else if let Some(color) = highlight_color { } else if let Some(color) = highlight_color {
color color
} else { } else {
egui::Color32::TRANSPARENT egui::Color32::TRANSPARENT
}; };
// Better padding and margins for improved readability
let frame = egui::Frame::none() let frame = egui::Frame::none()
.fill(bg_color) .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| { frame.show(ui, |ui| {
ui.horizontal(|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() .monospace()
.color(if is_selected { .color(if is_selected {
egui::Color32::WHITE palette.text_primary
} else { } else {
egui::Color32::DARK_GRAY palette.line_number
}); });
let line_num_response = ui.label(line_num_text); 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( let text = egui::RichText::new(content).monospace().color(
if is_selected { if is_selected {
egui::Color32::WHITE palette.text_primary
} else { } else {
ui.style().visuals.text_color() palette.text_primary
}, },
); );
let text_response = ui let text_response = ui
.scope(|ui| { .scope(|ui| {
ui.style_mut().visuals.selection.bg_fill = // Better text selection colors
egui::Color32::from_rgb(255, 180, 50); ui.style_mut().visuals.selection.bg_fill = palette.accent_bright;
ui.style_mut().visuals.selection.stroke.color = ui.style_mut().visuals.selection.stroke.color = palette.accent_primary;
egui::Color32::from_rgb(200, 140, 30);
ui.add(egui::Label::new(text).selectable(true)) ui.add(egui::Label::new(text).selectable(true))
}) })
.inner; .inner;

View File

@@ -1,6 +1,7 @@
use eframe::egui; use eframe::egui;
use crate::search::SearchState; use crate::search::SearchState;
use crate::theme;
pub struct SearchPanelState { pub struct SearchPanelState {
pub query: String, pub query: String,
@@ -27,59 +28,87 @@ pub fn render_search_panel(
config_changed: false, config_changed: false,
}; };
egui::TopBottomPanel::bottom("search_panel").show(ctx, |ui| { let palette = theme::get_palette();
ui.vertical(|ui| {
ui.horizontal(|ui| {
ui.label("🔍 Filter:");
let text_edit_width = 200.0; egui::TopBottomPanel::bottom("search_panel")
let text_response = ui.add_sized( .frame(egui::Frame::none()
[text_edit_width, 20.0], .fill(palette.bg_secondary)
egui::TextEdit::singleline(&mut state.query), .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 = ui.add_space(4.0);
text_response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter));
if !state.history.is_empty() { // Text input with proper height matching buttons
render_history_dropdown(ui, state); 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 let enter_pressed =
.checkbox(&mut state.case_sensitive, "Case sensitive") text_response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter));
.changed();
let regex_changed = ui.checkbox(&mut state.use_regex, "Regex").changed();
if case_changed || regex_changed { if !state.history.is_empty() {
actions.config_changed = true; render_history_dropdown(ui, state);
}
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;
} }
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 actions
} }