diff --git a/Cargo.toml b/Cargo.toml index 6af0439..9918da5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,11 @@ version = "0.1.0" edition = "2021" [dependencies] +battery = "0.7.8" +chrono = "0.4.38" eframe = "0.28" egui = "0.28" +os_info = "3.8.2" rand = "0.8.5" serde = "1.0.214" serde_json = "1.0.132" diff --git a/src/configs.rs b/src/configs.rs index 92b65a8..eed2d5b 100644 --- a/src/configs.rs +++ b/src/configs.rs @@ -1,11 +1,33 @@ use crate::structs; -use egui::{Color32, Stroke}; +use egui::{Color32, FontId, Stroke}; use std::f32::consts::PI; // pub const windows: PaneSplit = // Pane::new(SplitHorisontal).new_hsplit(Pane::new(Temp1), Pane::new(Temp2)); +pub const SCREEN_WIDTH: f32 = 1920.; +pub const SCREEN_HEIGHT: f32 = 1080.; + +pub const TITLE_FONT: FontId = FontId::monospace(25.); +pub const GRAPH_STROKE: Stroke = Stroke { + width: 0.5, + color: Color32::from_rgba_premultiplied(255, 255, 255, 1), +}; + +pub const TEXT_FONT: FontId = FontId::monospace(16.); + +pub const UP_GRAPH_STROKE: Stroke = Stroke { + width: 1.5, + color: Color32::from_rgba_premultiplied(0, 255, 255, 1), +}; + +pub const DOWN_GRAPH_STROKE: Stroke = Stroke { + width: 1.5, + color: Color32::from_rgba_premultiplied(255, 64, 4, 1), +}; + pub const TEXT_COLOR: Color32 = Color32::from_rgb(255, 255, 255); +pub const BACKGROUND: Color32 = Color32::BLACK; pub const BACKGROUND_2: Color32 = Color32::from_rgba_premultiplied(10, 10, 10, 230); pub const LOGIN_CIRCLE_RADIUS: f32 = 50.; @@ -40,65 +62,3 @@ pub const DOTS_COLOR: Color32 = Color32::from_rgb(255, 255, 255); pub const CORNER_CUT: f32 = LOGIN_CIRCLE_RADIUS * 1.41421356237; pub const PANE_GAP: f32 = 6.; - -// Example JSON configuration: -pub const EXAMPLE_CONFIG: &str = r#" -{ - "root": { - "id": "root", - "split": { - "direction": "Horizontal", - "ratio": 0.9, - "children": [ - { - "id": "left", - "pane_type": { - "type": "Solid", - "config": { - "color": [255, 255, 255] - } - } - }, - { - "id": "right", - "split": { - "direction": "Vertical", - "ratio": 0.6, - "children": [ - { - "id": "right_top", - "pane_type": { - "type": "Text", - "config": { - "text": "Hello World", - "font_size": 24.0, - "color": [255, 255, 255], - "background_color": [50, 50, 150] - } - } - }, - { - "id": "right_bottom", - "pane_type": { - "type": "Gradient", - "config": { - "start_color": [200, 200, 200], - "end_color": [100, 200, 100], - "horizontal": true - } - } - } - ] - } - } - ] - } - }, - "default_pane_type": { - "type": "Solid", - "config": { - "color": [200, 200, 200] - } - } -} -"#; diff --git a/src/cpugraph.rs b/src/cpugraph.rs new file mode 100644 index 0000000..7fb7a9d --- /dev/null +++ b/src/cpugraph.rs @@ -0,0 +1,56 @@ +use egui::{Color32, Pos2, Rect, Stroke}; +use std::collections::VecDeque; +use std::time::Instant; +use sysinfo::System; + +use crate::configs; +use crate::graph::*; + +pub struct CpuGraph { + sys: System, + cpu_graph: ResourceGraph, + last_update: Instant, +} + +impl CpuGraph { + pub fn new() -> Self { + let mut sys = System::new(); + sys.refresh_cpu_all(); + + let mut cpu_graph = + ResourceGraph::new("CPU Usage".to_string(), "%".to_string(), 0.0, 100.0); + + let cpu_count = sys.cpus().len(); + for i in 0..cpu_count { + let hue = (i as f32 / cpu_count as f32) * 360.0; + cpu_graph.add_line(format!("CPU {}", i), configs::GRAPH_STROKE); + } + + Self { + sys, + cpu_graph, + last_update: Instant::now(), + } + } + + pub fn update(&mut self) { + let now = Instant::now(); + if now.duration_since(self.last_update).as_secs_f32() < 1.0 / UPDATES_PER_SECOND { + return; + } + + self.sys.refresh_cpu_all(); + // self.sys.refresh_memory(); + + // Update CPU usage + for (i, cpu) in self.sys.cpus().iter().enumerate() { + self.cpu_graph.update_line(i, cpu.cpu_usage()); + } + + self.last_update = now; + } + + pub fn render(&mut self, painter: &egui::Painter, rect: Rect) { + self.cpu_graph.render(painter, rect); + } +} diff --git a/src/diskgraph.rs b/src/diskgraph.rs new file mode 100644 index 0000000..3597765 --- /dev/null +++ b/src/diskgraph.rs @@ -0,0 +1,80 @@ +use egui::Rect; +use std::time::Instant; +use sysinfo::ProcessRefreshKind; +use sysinfo::RefreshKind; +use sysinfo::System; + +use crate::configs; +use crate::graph::*; +use crate::table::ProcessTable; + +pub struct DiskGraph { + sys: sysinfo::System, + mem_graph: ResourceGraph, + last_update: Instant, + + proc_refresh: ProcessRefreshKind, +} + +impl DiskGraph { + pub fn new() -> Self { + let proc_refresh = ProcessRefreshKind::new().with_disk_usage(); + + let mut sys = + sysinfo::System::new_with_specifics(RefreshKind::new().with_processes(proc_refresh)); + + let mut memory_graph = + ResourceGraph::new("Net Usage".to_string(), " MB/s".to_string(), 0.0, 10.0); + + // Add memory lines + memory_graph.add_line("U".to_string(), configs::UP_GRAPH_STROKE); + memory_graph.add_line("D".to_string(), configs::DOWN_GRAPH_STROKE); + + Self { + sys, + mem_graph: memory_graph, + last_update: Instant::now(), + proc_refresh, + } + } + + pub fn update(&mut self) { + let now = Instant::now(); + if now.duration_since(self.last_update).as_secs_f32() < 1.0 / UPDATES_PER_SECOND { + return; + } + + // self.mem_graph. + + // self.networks.refresh_cpu_all(); + + let mut transmitted: f32 = 0.; + let mut recieved: f32 = 0.; + + for (_, proc) in self.sys.processes() { + // println!("in: {} B", network.received()); + let disk = proc.disk_usage(); + recieved += (disk.read_bytes as f32 / 1024. / 1024.) * UPDATES_PER_SECOND; + + transmitted += (disk.written_bytes as f32 / 1024. / 1024.) * UPDATES_PER_SECOND; + } + + self.sys.refresh_processes_specifics( + sysinfo::ProcessesToUpdate::All, + false, + self.proc_refresh, + ); + + self.mem_graph.update_line(0, transmitted); + self.mem_graph.update_line(1, recieved); + + self.mem_graph.redo_max(); + + self.last_update = now; + } + + pub fn render(&mut self, painter: &egui::Painter, rect: Rect) { + // self.cpu_graph.render(painter, cpu_rect); + self.mem_graph.render(painter, rect); + } +} diff --git a/src/graph.rs b/src/graph.rs index 5dace52..51a06a7 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -1,11 +1,14 @@ -use egui::{Color32, Pos2, Rect, Stroke, Vec2}; +use egui::{Color32, Pos2, Rect, Stroke}; +use std::borrow::BorrowMut; use std::collections::VecDeque; use std::time::Instant; use sysinfo::System; -const HISTORY_SIZE: usize = 100; -const ANIMATION_DURATION: f32 = 0.2; // seconds -const UPDATES_PER_SECOND: f32 = 2.0; +use crate::configs; + +pub const HISTORY_SIZE: usize = 25; +pub const ANIMATION_DURATION: f32 = 0.2; // seconds +pub const UPDATES_PER_SECOND: f32 = 1.5; #[derive(Clone)] struct DataPoint { @@ -41,130 +44,158 @@ impl AnimatedValue { self.current } } -// #[derive(Clone)] -// -// #[derive(Debug)] -pub struct CpuGraph { - sys: System, - history: Vec>, - animated_values: Vec, - last_update: Instant, - colors: Vec, + +#[derive(Clone)] +pub struct GraphLine { + label: String, + stroke: Stroke, + history: VecDeque, + animated_value: AnimatedValue, } -impl CpuGraph { - pub fn new() -> Self { - let mut sys = System::new(); - sys.refresh_cpu_all(); - - let cpu_count = sys.cpus().len(); - let history = vec![VecDeque::with_capacity(HISTORY_SIZE); cpu_count]; - let animated_values = vec![AnimatedValue::new(0.0); cpu_count]; - - // Generate distinct colors for each CPU core - let colors = (0..cpu_count) - .map(|i| { - let hue = (i as f32 / cpu_count as f32) * 360.0; - Color32::from_rgb( - ((1.0 + (hue.sin())) * 127.5) as u8, - ((1.0 + ((hue + 120.0).sin())) * 127.5) as u8, - ((1.0 + ((hue + 240.0).sin())) * 127.5) as u8, - ) - }) - .collect(); - +impl GraphLine { + fn new(label: String, stroke: Stroke) -> Self { Self { - sys, - history, - animated_values, - last_update: Instant::now(), - colors, + label, + stroke, + history: VecDeque::with_capacity(HISTORY_SIZE), + animated_value: AnimatedValue::new(0.0), } } - pub fn update(&mut self) { - let now = Instant::now(); - if now.duration_since(self.last_update).as_secs_f32() < 1.0 / UPDATES_PER_SECOND { - return; + fn update(&mut self, value: f32) { + self.animated_value.update(value); + self.history.push_back(DataPoint { + value, + timestamp: Instant::now(), + }); + + while self.history.len() > HISTORY_SIZE { + self.history.pop_front(); } + } +} - self.sys.refresh_cpu_all(); +pub struct ResourceGraph { + lines: Vec, + // last_update: Instant, + title: String, + y_axis_label: String, + min_value: f32, + max_value: f32, +} - for (i, cpu) in self.sys.cpus().iter().enumerate() { - let usage = cpu.cpu_usage() / 100.0; +impl ResourceGraph { + pub fn new(title: String, y_axis_label: String, min_value: f32, max_value: f32) -> Self { + Self { + lines: Vec::new(), + // last_update: Instant::now(), + title, + y_axis_label, + min_value, + max_value, + } + } - self.animated_values[i].update(usage); + pub fn redo_max(&mut self) { + let mut max = 0.5; + for i in 0..self.lines.len() { + let history = self.lines.get_mut(i).unwrap(); - self.history[i].push_back(DataPoint { - value: usage, - timestamp: now, - }); - - while self.history[i].len() > HISTORY_SIZE { - self.history[i].pop_front(); + for j in 0..history.history.len() { + let val = history.history.get(j).unwrap(); + if val.value > max { + max = val.value; + } } } + self.max_value = max; + } - self.last_update = now; + pub fn add_line(&mut self, label: String, stroke: Stroke) { + self.lines.push(GraphLine::new(label, stroke)); + } + + pub fn update_line(&mut self, index: usize, value: f32) { + if let Some(line) = self.lines.get_mut(index) { + line.update(value); + } } pub fn render(&mut self, painter: &egui::Painter, rect: Rect) { - let stroke_width = 2.0; - + // let now = Instant::now(); // Draw background - painter.rect_filled(rect, 0.0, Color32::from_gray(20)); + // painter.rect_filled(rect, 0.0, Color32::from_gray(20)); - // Draw grid lines + // Draw title + // painter.text( + // Pos2::new(rect.min.x + 5.0, rect.min.y + 5.0), + // egui::Align2::LEFT_TOP, + // &self.title, + // egui::FontId::proportional(16.0), + // Color32::WHITE, + // ); + + // Calculate graph area (leaving space for labels) + let label_width = 65.0; + let graph_rect = Rect::from_min_max( + Pos2::new(rect.min.x + label_width, rect.min.y + 30.0), + rect.max, + ); + + // Draw grid lines and labels for i in 0..=4 { - let y = rect.min.y + rect.height() * (i as f32 / 4.0); + let y = graph_rect.min.y + graph_rect.height() * (i as f32 / 4.0); painter.line_segment( - [Pos2::new(rect.min.x, y), Pos2::new(rect.max.x, y)], + [ + Pos2::new(graph_rect.min.x, y), + Pos2::new(graph_rect.max.x, y), + ], Stroke::new(1.0, Color32::from_gray(40)), ); + + let value = self.max_value - (i as f32 / 4.0) * (self.max_value - self.min_value); + painter.text( + Pos2::new(rect.min.x + 5.0, y - 8.0), + egui::Align2::LEFT_CENTER, + format!("{:.1}{}", value, self.y_axis_label), + egui::FontId::proportional(12.0), + Color32::LIGHT_GRAY, + ); } - // Draw CPU usage lines - for (core_idx, (history, color)) in self.history.iter().zip(self.colors.iter()).enumerate() - { - if history.is_empty() { + // Draw lines + for (_, line) in self.lines.iter_mut().enumerate() { + if line.history.is_empty() { continue; } - let points: Vec = history + let points: Vec = line + .history .iter() .enumerate() .map(|(i, point)| { - let x = rect.min.x + rect.width() * (i as f32 / (HISTORY_SIZE - 1) as f32); - let y = rect.max.y - rect.height() * point.value; + let x = graph_rect.min.x + + graph_rect.width() * (i as f32 / (HISTORY_SIZE - 1) as f32); + + let normalized_value = + (point.value - self.min_value) / (self.max_value - self.min_value); + let y = graph_rect.max.y - graph_rect.height() * normalized_value; Pos2::new(x, y) }) .collect(); // Get the current animated value for the last point - let current_value = self.animated_values[core_idx].get_current_value(); + let current_value = line.animated_value.get_current_value(); let mut animated_points = points; if let Some(last) = animated_points.last_mut() { - last.y = rect.max.y - rect.height() * current_value; + let normalized_value = + (current_value - self.min_value) / (self.max_value - self.min_value); + last.y = graph_rect.max.y - graph_rect.height() * normalized_value; } - // Draw the line - painter.add(egui::Shape::line( - animated_points, - Stroke::new(stroke_width, *color), - )); - } - - // Draw labels - for (i, color) in self.colors.iter().enumerate() { - let text = format!("CPU {}", i); - let pos = Pos2::new(rect.min.x + 5.0, rect.min.y + 15.0 + (i as f32 * 20.0)); - painter.text( - pos, - egui::Align2::LEFT_TOP, - text, - egui::FontId::proportional(14.0), - *color, - ); + painter.add(egui::Shape::line(animated_points, line.stroke)); } + // self.last_update = now; } } diff --git a/src/infopane.rs b/src/infopane.rs new file mode 100644 index 0000000..4238a07 --- /dev/null +++ b/src/infopane.rs @@ -0,0 +1,157 @@ +use battery::units::electric_potential::volt; +use battery::units::energy::joule; +use battery::units::power::watt; +use battery::units::time::minute; +use battery::units::time::second; +use battery::Manager; +use chrono::Local; +use egui::Align2; +use egui::Galley; +use egui::Pos2; +use egui::Rect; +use std::env; +use std::time::Instant; + +use crate::configs::*; +use crate::graph::*; + +pub struct InfoPane { + manager: Manager, + unamea: String, + state: BatteryState, + last_update: Instant, +} + +struct BatteryState { + percent: f32, + rate: f32, + time: f32, + discharging: bool, +} + +impl InfoPane { + pub fn new() -> Self { + Self { + manager: Manager::new().unwrap(), + unamea: os_info::get().to_string(), + state: BatteryState { + percent: 0., + rate: 0., + time: 0., + discharging: false, + }, + last_update: Instant::now(), + } + } + + // fn get_unamea() -> + + fn get_percent(&mut self) -> Result { + // let mut percent = 1.; + // let mut rate = 0.; + // let mut min_time; + + for (idx, unsafe_battery) in self.manager.batteries()?.enumerate() { + let battery = unsafe_battery.unwrap(); + let percent = battery.energy().get::() / battery.energy_full().get::(); + let rate = battery.energy_rate().get::(); + + let time_to_empty = battery.time_to_empty(); + + let time: f32 = if !time_to_empty.is_none() { + time_to_empty.unwrap().get::() + } else if !battery.time_to_full().is_none() { + battery.time_to_full().unwrap().get::() + } else { + 0. + }; + + return Ok((BatteryState { + percent: percent * 100.0, + rate: rate, + time: time, + discharging: !time_to_empty.is_none(), + })); + + // percent *= battery + // .unwrap() + // .state_of_charge() + // .get::(); + } + + Ok((BatteryState { + percent: 0., + rate: 0., + time: 0., + discharging: false, + })) + } + + pub fn update(&mut self) { + let now = Instant::now(); + if now.duration_since(self.last_update).as_secs_f32() < 1.0 / UPDATES_PER_SECOND { + return; + } + + self.state = self.get_percent().unwrap(); + self.last_update = Instant::now(); + } + + pub fn render(&mut self, painter: &egui::Painter, rect: Rect) { + let now = Local::now(); + + let mut y = 0.; + + painter.text( + Pos2 { + x: rect.min.x, + y: rect.min.y, + }, + Align2::LEFT_TOP, + format!("TIME: {}", now.format("UTC%Z %H:%M:%S%.f (%Y-%d-%m)")), + TEXT_FONT, + TEXT_COLOR, + ); + + y += TEXT_FONT.size; + + // let galley = painter.layout_no_wrap(format!("{}%", self.percent), TITLE_FONT, TEXT_COLOR); + // + + painter.text( + Pos2 { + x: rect.min.x, + y: rect.min.y + y, + }, + Align2::LEFT_TOP, + format!( + "BAT: {}% {} ({} min) ({} W)", + self.state.percent, + { + if self.state.discharging { + "discharging" + } else { + "charging" + } + }, + self.state.time, + self.state.rate, + ), + TEXT_FONT, + TEXT_COLOR, + ); + + y += TEXT_FONT.size; + + painter.text( + Pos2 { + x: rect.min.x, + y: rect.min.y + y, + }, + Align2::LEFT_TOP, + format!("OS: {}", self.unamea,), + TEXT_FONT, + TEXT_COLOR, + ); + } +} diff --git a/src/main.rs b/src/main.rs index 5735b6a..421de1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,20 @@ +use configs::SCREEN_HEIGHT; +use eframe::Error; use eframe::{egui, App}; use egui::FontFamily; use egui::Key; -use graph::CpuGraph; +// use graph::CpuGraph; use input::is_locked; -use panes::{PaneConfig, PaneRenderer, SplitDirection}; +use panes::Pane; +// use serde::de::Error; +// use rand::Error; +// use panes::{PaneConfig, PaneRenderer, SplitDirection}; use std::collections::BTreeMap; use std::collections::HashMap; use std::process::{Command, Stdio}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; - // use egui::epaint::text::{FontInsert, InsertFontFamily}; use eframe::CreationContext; @@ -23,49 +27,27 @@ use egui::Stroke; use egui::ecolor::HexColor; mod configs; + +mod cpugraph; +mod diskgraph; mod graph; +mod memgraph; +mod netgraph; + +mod table; + +mod infopane; + mod input; mod panes; mod structs; mod ui; -// Demonstrates how to add a font to the existing ones -// fn add_font(ctx: &egui::Context) { -// // Start with the default fonts (we will be adding to them rather than replacing them). -// let mut fonts = egui::FontDefinitions::default(); +use panes::{load_pane_config, EXAMPLE_CONFIG}; -// // Install my own font (maybe supporting non-latin characters). -// // .ttf and .otf files supported. -// fonts.font_data.insert( -// "my_font".to_owned(), -// egui::FontData::from_static(include_bytes!( -// "/home/astatin3/.fonts/UbuntuMono/UbuntuMonoNerdFontMono-Regular.ttf" -// )), -// ); - -// // Put my font first (highest priority) for proportional text: -// // fonts -// // .families -// // .entry(egui::FontFamily::Proportional) -// // .or_default() -// // .insert(0, "my_font".to_owned()); - -// // Put my font as last fallback for monospace: -// fonts -// .families -// .entry(egui::FontFamily::Monospace) -// .or_default() -// .push("my_font".to_owned()); - -// // Tell egui to use these fonts: -// ctx.set_fonts(fonts); -// } - -// #[derive(Default)] struct ExampleApp { auth_state: Arc>, - cpu_graph: CpuGraph, - // terminals: HashMap, + root_pane: panes::PaneInstance, } impl ExampleApp { @@ -80,34 +62,7 @@ impl eframe::App for ExampleApp { } fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - // let s1 = self.terminals.get_mut("0").unwrap(); - // s1.style() - // s1.cursor_trail = false; - // s1.cursor_trail_color = Some(HexColor::Hex8(Color32::LIGHT_BLUE.gamma_multiply(0.5))); - - // s1.default_focus_cursor = CursorType::OpenBlock(HexColor::Hex8(Color32::RED)); - // s1.default_unfocus_cursor = CursorType::None; - // for str in egui::FontDefinitions::builtin_font_names() { - // println!("{}", str); - // } - // s1.cursor_stroke = Stroke::new(1., Color32::WHITE); - - // add_font(ctx); - - // let font = FontId { - // size: 9., - // family: FontFamily::Name("my_font".into()), - // }; - - // s1.font = font; - let mut state = self.auth_state.lock().unwrap(); - //ctx.set_pixels_per_point(1.5); - // let config = ui::winconfig(); - - let config: panes::LayoutConfig = serde_json::from_str(configs::EXAMPLE_CONFIG).unwrap(); - let mut pane_renderer = PaneRenderer::new(config); - if ctx.input(|i| i.events.len() > 0) { ctx.input(|i| { for event in &i.events { @@ -134,67 +89,18 @@ impl eframe::App for ExampleApp { state.password += str; } } - //let mod_str = if modifiers.is_empty() { - // String::new() - //} else { - // format!(" + {:?}", modifiers) - //}; } } } }); } - // let sine_points = PlotPoints::from_explicit_callback(|x| x.sin(), .., 5000); - // let plot_a_lines = [Line::new(sine_points).name("Sine")]; - - // let sine_points2 = PlotPoints::from_explicit_callback(|x| x.sin(), .., 5000); - // let plot_b_lines = [Line::new(sine_points2).name("Sine")]; - - // self.cpu_graph.update(); - + // let mut pane = ; egui::CentralPanel::default() .frame(egui::Frame::none()) .show(ctx, |ui| { - // let ht = ui.available_height(); - // for (_idx, (_id, term)) in self.terminals.iter_mut().enumerate() { - // ui.terminal_sized(term, egui::vec2(ui.available_width(), ht)); - // } - // ui.add_space(200.0); - // ui.heading("*".repeat(state.password.clone().len())); - // ui.add_space(20.0); - // - - // ui.terminal_sized( - // self.terminals.get_mut("0").unwrap(), - // egui::vec2(ui.available_width(), ui.available_height()), - // ); - // Splitter::new("some_unique_id", splitter::SplitterAxis::Horizontal).show( - // ui, - // |ui_a, ui_b| { - // ui_a.terminal_sized( - // self.terminals.get_mut("0").unwrap(), - // egui::vec2(ui_a.available_width(), ui_a.available_height()), - // ); - - // ui_b.terminal_sized( - // self.terminals.get_mut("1h-1").unwrap(), - // egui::vec2(ui_a.available_width(), ui_a.available_height()), - // ); - - // }, - // ); - - ui::update_password_viewer(state, ctx, _frame, ui, &mut pane_renderer); - - // self.cpu_graph.render( - // ui.painter(), - // egui::Rect { - // min: egui::Pos2 { x: 0., y: 0. }, - // max: egui::Pos2 { x: 500., y: 500. }, - // }, - // ); - + std::thread::sleep(Duration::from_millis(50)); + ui::update(state, ctx, _frame, ui, &mut self.root_pane); ctx.request_repaint(); }); } @@ -206,10 +112,6 @@ fn main() -> eframe::Result<()> { ..eframe::NativeOptions::default() }; - // let system_shell = std::env::var("SHELL") - // .expect("SHELL variable is not defined") - // .to_string(); - let state = Arc::new(Mutex::new(structs::AuthState { password: String::new(), to_be_submitted: false, @@ -218,7 +120,6 @@ fn main() -> eframe::Result<()> { let auth_state_clone = state.clone(); - // Spawn authentication thread thread::spawn(move || loop { let mut state = auth_state_clone.lock().unwrap(); @@ -246,40 +147,43 @@ fn main() -> eframe::Result<()> { thread::sleep(Duration::from_millis(100)); }); - // let mut map = HashMap::new(); - // map.insert(String::from("0"), TermHandler::new_from_str("btop")); - // map.insert(String::from("1h-0"), TermHandler::new_from_str("nvitop")); - // map.insert(String::from("1h-1"), TermHandler::new_from_str("neofetch")); - input::sway_lock_input(); - if input::is_locked() { println!("Raylock is already running!"); std::process::exit(1); } - let test = [1, 2, 3]; - + input::sway_lock_input(); input::create_lock(); + let default_panic = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + input::sway_unlock_input(); + input::remove_lock(); + default_panic(info); + })); + + let mut pane_config = load_pane_config(EXAMPLE_CONFIG).unwrap(); + pane_config.precalc(egui::Rect { + min: egui::Pos2 { x: 0., y: 0. }, + max: egui::Pos2 { + x: configs::SCREEN_WIDTH, + y: configs::SCREEN_HEIGHT, + }, + }); + eframe::run_native( ExampleApp::name(), native_options, Box::new(|_| { Ok(Box::::new(ExampleApp { auth_state: state, - cpu_graph: CpuGraph::new(), - // terminals: map, + root_pane: pane_config, + // cpu_graph: CpuGraph::new(), })) }), ) } -fn test_test(test: &mut [i32]) { - for i in 0..test.len() { - test[i] += 1; - } -} - fn try_sudo(password: &str) -> Result { let mut child = Command::new("sudo") .args(["-kS", "true"]) // Use -S to read password from stdin diff --git a/src/memgraph.rs b/src/memgraph.rs new file mode 100644 index 0000000..5655cf5 --- /dev/null +++ b/src/memgraph.rs @@ -0,0 +1,64 @@ +use egui::Rect; +use std::time::Instant; +use sysinfo::System; + +use crate::configs; +use crate::graph::*; + +pub struct MemGraph { + sys: System, + mem_graph: ResourceGraph, + last_update: Instant, +} + +impl MemGraph { + pub fn new() -> Self { + let mut sys = System::new(); + sys.refresh_memory(); + + let mut memory_graph = ResourceGraph::new( + "Memory Usage".to_string(), + "GB".to_string(), + 0.0, + sys.total_memory() as f32 / 1024.0 / 1024.0 / 1024.0, + ); + + // Add memory lines + memory_graph.add_line("Used".to_string(), configs::GRAPH_STROKE); + memory_graph.add_line("Cached".to_string(), configs::GRAPH_STROKE); + + Self { + sys, + mem_graph: memory_graph, + last_update: Instant::now(), + } + } + + pub fn update(&mut self) { + let now = Instant::now(); + if now.duration_since(self.last_update).as_secs_f32() < 1.0 / UPDATES_PER_SECOND { + return; + } + + // self.sys.refresh_cpu_all(); + self.sys.refresh_memory(); + + // Update memory usage + let total_gb = self.sys.total_memory() as f32 / 1024.0 / 1024.0 / 1024.0; + let used_gb = self.sys.used_memory() as f32 / 1024.0 / 1024.0 / 1024.0; + let cached_gb = (self.sys.total_memory() - self.sys.available_memory()) as f32 + / 1024.0 + / 1024.0 + / 1024.0; + + self.mem_graph.update_line(0, used_gb); + self.mem_graph.update_line(1, cached_gb); + + self.last_update = now; + } + + pub fn render(&mut self, painter: &egui::Painter, rect: Rect) { + // self.cpu_graph.render(painter, cpu_rect); + self.mem_graph.render(painter, rect); + } +} diff --git a/src/netgraph.rs b/src/netgraph.rs new file mode 100644 index 0000000..ceb48f2 --- /dev/null +++ b/src/netgraph.rs @@ -0,0 +1,70 @@ +use egui::Rect; +use std::time::Instant; +use sysinfo::Process; +use sysinfo::ProcessRefreshKind; +use sysinfo::RefreshKind; +use sysinfo::System; + +use crate::configs; +use crate::graph::*; + +pub struct NetGraph { + net: sysinfo::Networks, + + mem_graph: ResourceGraph, + last_update: Instant, +} + +impl NetGraph { + pub fn new() -> Self { + let mut net = sysinfo::Networks::new_with_refreshed_list(); + + let mut memory_graph = + ResourceGraph::new("Disk Usage".to_string(), " KB/s".to_string(), 0.0, 10.0); + + // Add memory lines + memory_graph.add_line("U".to_string(), configs::UP_GRAPH_STROKE); + memory_graph.add_line("D".to_string(), configs::DOWN_GRAPH_STROKE); + + Self { + net, + mem_graph: memory_graph, + last_update: Instant::now(), + } + } + + pub fn update(&mut self) { + let now = Instant::now(); + if now.duration_since(self.last_update).as_secs_f32() < 1.0 / UPDATES_PER_SECOND { + return; + } + + // self.mem_graph. + + // self.networks.refresh_cpu_all(); + + let mut transmitted: f32 = 0.; + let mut recieved: f32 = 0.; + + for (_, network) in &self.net { + // println!("in: {} B", network.received()); + recieved += (network.received() as f32 / 1024.); + transmitted += (network.transmitted() as f32 / 1024.); + } + + self.net.refresh(); + + self.mem_graph + .update_line(0, transmitted * UPDATES_PER_SECOND); + self.mem_graph.update_line(1, recieved * UPDATES_PER_SECOND); + + self.mem_graph.redo_max(); + + self.last_update = now; + } + + pub fn render(&mut self, painter: &egui::Painter, rect: Rect) { + // self.cpu_graph.render(painter, cpu_rect); + self.mem_graph.render(painter, rect); + } +} diff --git a/src/panes.rs b/src/panes.rs index 8347386..f7f184b 100644 --- a/src/panes.rs +++ b/src/panes.rs @@ -1,8 +1,24 @@ -use egui::{Color32, Painter, Pos2, Rect}; +use egui::ecolor::linear_u8_from_linear_f32; +use egui::emath::Rot2; +use egui::epaint::TextShape; +use egui::{Color32, FontId, Galley, Image, Painter, Pos2, Rect, Rounding}; use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::borrow::BorrowMut; use std::collections::HashMap; +use std::default::Default; +use std::f32::consts::PI; -use crate::{graph::CpuGraph, ui}; +use crate::cpugraph::CpuGraph; +use crate::diskgraph::DiskGraph; +use crate::infopane::InfoPane; +use crate::memgraph::MemGraph; +use crate::netgraph::NetGraph; +use crate::table::{ProcessTable, BAR_HEIGHT, ROW_HEIGHT}; +use crate::ui::get_corners; +use crate::{configs::*, ui}; + +// use crate::{graph::CpuGraph, ui}; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum CornerTypes { @@ -12,387 +28,474 @@ pub enum CornerTypes { Ang60, } -// Serializable configuration types -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub enum SplitDirection { - Horizontal, - Vertical, +#[derive(Clone)] +pub enum TitleFormats { + TOP { text: Option }, + SIDE { text: Option }, } -#[derive(Clone, Serialize, Deserialize)] -#[serde(tag = "type", content = "config")] -pub enum PaneTypeConfig { - Solid(SolidPaneConfig), - Text(TextPaneConfig), - Gradient(GradientPaneConfig), +impl Default for TitleFormats { + fn default() -> Self { + TitleFormats::TOP { text: None } + } } -// Runtime-only pane type that includes non-serializable data -// #[derive(Clone)] +const DEFAULT_CORNERS: [CornerTypes; 4] = [ + CornerTypes::Ang30, + CornerTypes::Ang60, + CornerTypes::Ang60, + CornerTypes::Ang30, +]; + +// Enum to represent different types of panes +#[derive(Deserialize, Serialize, Clone)] +// #[serde(tag = "type")] pub enum PaneType { - Solid(SolidPane), - Text(TextPane), - Gradient(GradientPane), + Info, + CpuGraph, + MemGraph, + NetGraph, + DiskGraph, + ProcTable, + No, } -// Serializable configs -#[derive(Clone, Serialize, Deserialize)] -pub struct SolidPaneConfig { - pub color: [u8; 3], +#[derive(Deserialize, Serialize, Clone, PartialEq)] +pub enum SplitType { + H, + V, } -#[derive(Clone, Serialize, Deserialize)] -pub struct TextPaneConfig { - pub text: String, - pub font_size: f32, - pub color: [u8; 3], - pub background_color: Option<[u8; 3]>, +// Structure to store runtime data for different pane types +pub enum PaneData { + Info { info_man: InfoPane }, + CpuGraph { cpu_graph: CpuGraph }, + MemGraph { mem_graph: MemGraph }, + NetGraph { net_graph: NetGraph }, + DiskGraph { disk_graph: DiskGraph }, + ProcTable { proc_table: ProcessTable }, + No {}, } -#[derive(Clone, Serialize, Deserialize)] -pub struct GradientPaneConfig { - pub start_color: [u8; 3], - pub end_color: [u8; 3], - pub horizontal: bool, +impl Default for PaneData { + fn default() -> Self { + PaneData::No {} + } } -// Runtime types with additional non-serializable data -#[derive(Clone)] -pub struct SolidPane { - config: SolidPaneConfig, - // Runtime-only fields - cached_color: Color32, -} +impl PaneData { + pub fn new(pane: &Pane) -> Self { + match pane { + Pane::Split { .. } => PaneData::No {}, -#[derive(Clone)] -pub struct TextPane { - config: TextPaneConfig, - // Runtime-only fields - cached_color: Color32, - cached_bg_color: Option, - cached_font: egui::FontId, -} - -// #[derive(Clone)] -pub struct GradientPane { - config: GradientPaneConfig, - // Runtime-only fields - cached_start_color: Color32, - cached_end_color: Color32, -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct PaneConfig { - pub id: String, - #[serde(default)] - pub split: Option, - #[serde(default)] - pub pane_type: Option, -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct SplitConfig { - pub direction: SplitDirection, - pub ratio: f32, - pub children: Vec, -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct LayoutConfig { - pub root: PaneConfig, - #[serde(default)] - pub default_pane_type: Option, -} - -// Runtime representation of the layout -pub struct RuntimePane { - pub id: String, - pub split: Option, - pub pane_type: Option, -} - -pub struct RuntimeSplit { - pub direction: SplitDirection, - pub ratio: f32, - pub children: Vec, -} - -// Conversion implementations -impl From for PaneType { - fn from(config: PaneTypeConfig) -> Self { - match config { - PaneTypeConfig::Solid(config) => PaneType::Solid(SolidPane { - cached_color: Color32::from_rgb(config.color[0], config.color[1], config.color[2]), - config, - }), - PaneTypeConfig::Text(config) => PaneType::Text(TextPane { - cached_color: Color32::from_rgb(config.color[0], config.color[1], config.color[2]), - cached_bg_color: config - .background_color - .map(|c| Color32::from_rgb(c[0], c[1], c[2])), - cached_font: egui::FontId::proportional(config.font_size), - config, - }), - PaneTypeConfig::Gradient(config) => PaneType::Gradient(GradientPane { - cached_start_color: Color32::from_rgb( - config.start_color[0], - config.start_color[1], - config.start_color[2], - ), - cached_end_color: Color32::from_rgb( - config.end_color[0], - config.end_color[1], - config.end_color[2], - ), - - config, - }), + Pane::Leaf { pane_type, .. } => match pane_type { + PaneType::Info => PaneData::Info { + info_man: InfoPane::new(), + }, + PaneType::CpuGraph => PaneData::CpuGraph { + cpu_graph: CpuGraph::new(), + }, + PaneType::MemGraph => PaneData::MemGraph { + mem_graph: MemGraph::new(), + }, + PaneType::NetGraph => PaneData::NetGraph { + net_graph: NetGraph::new(), + }, + PaneType::DiskGraph => PaneData::DiskGraph { + disk_graph: DiskGraph::new(), + }, + PaneType::ProcTable => PaneData::ProcTable { + proc_table: ProcessTable::new(30, 50, 0), + }, + PaneType::No {} => PaneData::No {}, + }, } } } -impl PaneType { - fn render(&self, painter: &Painter, rect: Rect) { - ui::background_render( - painter, - rect, - [ - CornerTypes::Ang30, - CornerTypes::Ang60, - CornerTypes::Ang30, - CornerTypes::Ang60, - ], - ); - match self { - PaneType::Solid(pane) => { - // painter.rect_filled(rect, 0.0, pane.cached_color); - // painter.rect_stroke(rect, 0.0, (1.0, Color32::BLACK)); +// Main pane structure that represents either a split or leaf node +#[derive(Deserialize, Serialize)] +// #[default] +#[serde(tag = "kind")] +pub enum Pane { + Leaf { + pane_type: PaneType, + corners: [CornerTypes; 4], + #[serde(skip, default = "get_default_rect")] + rect: Rect, + #[serde(skip, default = "get_default_rect")] + inner_rect: Rect, + #[serde(skip)] + container_points: Vec, + #[serde(skip)] + title_type: TitleFormats, + }, + Split { + direction: SplitType, + bias: f32, + #[serde(skip)] + first: Box, + #[serde(skip)] + second: Box, + a: Box, + b: Box, + }, +} + +const DEFAULT_RECT: Rect = Rect { + min: Pos2 { x: 0., y: 0. }, + max: Pos2 { x: 0., y: 0. }, +}; +const DEFAULT_POINTS: Vec = Vec::new(); + +fn get_default_rect() -> Rect { + DEFAULT_RECT +} + +impl Default for Pane { + fn default() -> Self { + Pane::Leaf { + pane_type: PaneType::No {}, + corners: DEFAULT_CORNERS, + rect: DEFAULT_RECT, + inner_rect: DEFAULT_RECT, + container_points: DEFAULT_POINTS, + title_type: TitleFormats::default(), + } + } +} +impl Default for PaneInstance { + fn default() -> Self { + PaneInstance { + config: Pane::default(), + runtime_data: PaneData::default(), + } + } +} + +// impl Pane { +// fn +// } +// Runtime instance of a pane that includes both config and runtime data +#[derive(Deserialize, Serialize)] +pub struct PaneInstance { + config: Pane, + #[serde(skip)] + runtime_data: PaneData, +} + +impl PaneInstance { + // Create a new instance from configuration + fn from_config(config: Pane) -> Self { + PaneInstance { + runtime_data: PaneData::new(&config), + config: config, + } + } + + pub fn precalc(&mut self, rect: Rect) { + match &mut self.config { + Pane::Split { + direction, + bias, + first, + second, + .. + } => { + let (first_rect, second_rect) = if *direction == SplitType::V { + let split_x = rect.min.x + rect.width() * bias.clone(); + ( + Rect::from_min_max(rect.min, egui::pos2(split_x, rect.max.y)), + Rect::from_min_max(egui::pos2(split_x, rect.min.y), rect.max), + ) + } else { + let mut split_y = rect.min.y + rect.height() * bias.clone(); + ( + Rect::from_min_max(rect.min, egui::pos2(rect.max.x, split_y)), + Rect::from_min_max(egui::pos2(rect.min.x, split_y), rect.max), + ) + }; + + first.precalc(first_rect); + second.precalc(second_rect); } - PaneType::Text(pane) => { - // if let Some(bg_color) = pane.cached_bg_color { - // painter.rect_filled(rect, 0.0, bg_color); - // } + Pane::Leaf { + pane_type, + corners, + rect: rect2, + inner_rect, + container_points, + title_type: title_type, + } => { + container_points.clone_from(&get_corners(rect, corners.to_owned())); + rect2.clone_from(&rect); + inner_rect.clone_from(&ui::find_largest_rectangle(container_points).unwrap()); - // painter.text( - // rect.center(), - // egui::Align2::CENTER_CENTER, - // &pane.config.text, - // pane.cached_font.clone(), - // pane.cached_color, - // ); - } - PaneType::Gradient(pane) => { - let mut pane2 = pane; - // pane.cpu_graph.update(); - // pane.cpu_graph.render(painter, rect); - // if pane.config.horizontal { - // painter.rect_filled(rect, 0.0, pane.cached_start_color); // Simplified - // } else { - // painter.rect_filled(rect, 0.0, pane.cached_end_color); // Simplified - // } - } - } - } -} + let text = Some( + match pane_type { + PaneType::Info => "INFO", + PaneType::CpuGraph => "CPU", + PaneType::MemGraph => "MEM", + PaneType::ProcTable => "PROC", + PaneType::NetGraph => "NET", + PaneType::DiskGraph => "DISK", + _ => "ERR", + } + .to_string(), + ); -pub struct PaneRenderer { - runtime_config: RuntimePane, - default_pane_type: Option, -} + title_type.clone_from(&if ((inner_rect.left() - rect2.left()) + + (rect2.right() - inner_rect.right())) + > ((inner_rect.top() - rect2.top()) + (rect2.bottom() - inner_rect.bottom())) + { + TitleFormats::SIDE { text: text } + } else { + TitleFormats::TOP { text: text } + }); -impl PaneRenderer { - pub fn new(config: LayoutConfig) -> Self { - Self { - runtime_config: Self::convert_config(&config.root), - default_pane_type: config.default_pane_type.map(Into::into), - } - } - - fn convert_config(config: &PaneConfig) -> RuntimePane { - RuntimePane { - id: config.id.clone(), - split: config.split.as_ref().map(|split| RuntimeSplit { - direction: split.direction, - ratio: split.ratio, - children: split.children.iter().map(Self::convert_config).collect(), - }), - pane_type: config.pane_type.clone().map(Into::into), - } - } - - pub fn render(&self, painter: &Painter, rect: &Rect) { - self.render_pane(painter, &rect, &self.runtime_config); - } - - fn render_pane(&self, painter: &Painter, rect: &Rect, pane: &RuntimePane) { - if let Some(split) = &pane.split { - if !split.children.is_empty() { - let rects = - self.split_rect(rect, split.direction, split.ratio, split.children.len()); - - for (child, child_rect) in split.children.iter().zip(&rects) { - self.render_pane(painter, child_rect, child); - } - - // Draw split lines - let split_line_color = Color32::from_gray(128); - match split.direction { - SplitDirection::Horizontal => { - for rect in rects.windows(2) { - let x = rect[0].max.x; - painter.line_segment( - [Pos2::new(x, rect[0].min.y), Pos2::new(x, rect[0].max.y)], - (1.0, split_line_color), - ); - } - } - SplitDirection::Vertical => { - for rect in rects.windows(2) { - let y = rect[0].max.y; - painter.line_segment( - [Pos2::new(rect[0].min.x, y), Pos2::new(rect[0].max.x, y)], - (1.0, split_line_color), - ); + match pane_type { + PaneType::ProcTable { .. } => { + if let PaneData::ProcTable { proc_table } = self.runtime_data.borrow_mut() { + proc_table.row_count = + ((inner_rect.height() - BAR_HEIGHT) / ROW_HEIGHT).floor() as usize; + // cpu_proc_table.row_count = 10; } } + _ => {} } - } - } else { - // Render leaf pane - let pane_type = &mut pane.pane_type.as_ref(); - - if let Some(pane_type) = pane_type { - pane_type.render(painter, *rect); + // let adj_rect = background_render(painter, rect, *corners); } } } - fn split_rect( - &self, - rect: &Rect, - direction: SplitDirection, - ratio: f32, - count: usize, - ) -> Vec { - let mut rects = Vec::with_capacity(count); - let size = match direction { - SplitDirection::Horizontal => rect.width(), - SplitDirection::Vertical => rect.height(), - }; + // Render the pane and its children + pub fn render(&mut self, painter: &Painter) { + match &mut self.config { + Pane::Split { first, second, .. } => { + first.render(painter); + second.render(painter); + } + Pane::Leaf { + pane_type, + rect, + inner_rect, + container_points, + title_type, + .. + } => { + painter.add(egui::Shape::convex_polygon( + container_points.to_owned(), + BACKGROUND_2, + egui::Stroke::new(0.5, TEXT_COLOR), + )); - let first_size = size * ratio; - let remaining_size = size - first_size; - let size_per_remaining = if count > 1 { - remaining_size / (count as f32 - 1.0) - } else { - 0.0 - }; + match title_type { + TitleFormats::SIDE { text } => { + let galley = painter.layout_no_wrap( + text.as_ref().unwrap().to_owned(), + TITLE_FONT, + TEXT_COLOR, + ); + let size = galley.size(); + let mid_x = rect.min.x + (f32::abs(rect.min.x - inner_rect.min.x) / 2.) + - (size.y / 2.) + + PANE_GAP; + let mid_y = (inner_rect.min.y + + f32::abs(inner_rect.min.y - inner_rect.max.y) / 2.) + + size.x / 2.; + painter.add( + TextShape::new(Pos2 { x: mid_x, y: mid_y }, galley, Color32::WHITE) + .with_angle(-PI / 2.), + ); + } + TitleFormats::TOP { text } => { + let galley = painter.layout_no_wrap( + text.as_ref().unwrap().to_owned(), + TITLE_FONT, + TEXT_COLOR, + ); + let size = galley.size(); + let mid_x = rect.min.x + + (f32::abs(inner_rect.min.x - inner_rect.max.x) / 2.) + - (size.x / 2.); + let mid_y = (rect.min.y + f32::abs(rect.min.y - inner_rect.min.y) / 2.) + - (size.y / 2.) + + PANE_GAP; + painter.add(TextShape::new( + Pos2 { x: mid_x, y: mid_y }, + galley, + Color32::WHITE, + )); + } + } - for i in 0..count { - let (start, end) = match direction { - SplitDirection::Horizontal => { - let start = if i == 0 { - rect.min.x - } else { - rect.min.x + first_size + size_per_remaining * (i as f32 - 1.0) - }; - let end = if i == 0 { - rect.min.x + first_size - } else { - start + size_per_remaining - }; - (Pos2::new(start, rect.min.y), Pos2::new(end, rect.max.y)) + painter.rect_stroke( + inner_rect.to_owned(), + 0.0, + egui::Stroke::new(0.25, TEXT_COLOR), + ); + + match pane_type { + PaneType::Info => { + render_info(painter, inner_rect.to_owned(), &mut self.runtime_data); + } + PaneType::CpuGraph => { + render_cpu_graph(painter, inner_rect.to_owned(), &mut self.runtime_data); + } + PaneType::MemGraph => { + render_mem_graph(painter, inner_rect.to_owned(), &mut self.runtime_data); + } + PaneType::NetGraph => { + render_net_graph(painter, inner_rect.to_owned(), &mut self.runtime_data); + } + PaneType::DiskGraph => { + render_disk_graph(painter, inner_rect.to_owned(), &mut self.runtime_data); + } + PaneType::ProcTable => { + render_proc_table(painter, inner_rect.to_owned(), &mut self.runtime_data); + } + PaneType::No => {} } - SplitDirection::Vertical => { - let start = if i == 0 { - rect.min.y - } else { - rect.min.y + first_size + size_per_remaining * (i as f32 - 1.0) - }; - let end = if i == 0 { - rect.min.y + first_size - } else { - start + size_per_remaining - }; - (Pos2::new(rect.min.x, start), Pos2::new(rect.max.x, end)) - } - }; - rects.push(Rect::from_min_max(start, end)); + } } - rects } } -// // Example JSON configuration: -// const EXAMPLE_CONFIG: &str = r#" -// { -// "root": { -// "id": "root", -// "split": { -// "direction": "Horizontal", -// "ratio": 0.3, -// "children": [ -// { -// "id": "left", -// "pane_type": { -// "type": "Solid", -// "config": { -// "color": [100, 150, 200] -// } -// } -// }, -// { -// "id": "right", -// "split": { -// "direction": "Vertical", -// "ratio": 0.6, -// "children": [ -// { -// "id": "right_top", -// "pane_type": { -// "type": "Text", -// "config": { -// "text": "Hello World", -// "font_size": 24.0, -// "color": [255, 255, 255], -// "background_color": [50, 50, 150] -// } -// } -// }, -// { -// "id": "right_bottom", -// "pane_type": { -// "type": "Gradient", -// "config": { -// "start_color": [200, 100, 100], -// "end_color": [100, 200, 100], -// "horizontal": true -// } -// } -// } -// ] -// } -// } -// ] -// } -// }, -// "default_pane_type": { -// "type": "Solid", -// "config": { -// "color": [200, 200, 200] -// } -// } -// } -// "#; +pub fn render_info(painter: &Painter, rect: Rect, data: &mut PaneData) { + if let PaneData::Info { info_man } = data { + info_man.update(); + info_man.render(painter, rect); + } +} -// // Example usage -// pub fn example_usage(ctx: &egui::Context) { -// // Parse configuration -// let config: LayoutConfig = serde_json::from_str(EXAMPLE_CONFIG).unwrap(); -// let pane_renderer = PaneRenderer::new(config); +// Example rendering functions for different pane types +pub fn render_cpu_graph(painter: &Painter, rect: Rect, data: &mut PaneData) { + if let PaneData::CpuGraph { cpu_graph } = data { + cpu_graph.update(); + cpu_graph.render(painter, rect); + } +} -// // egui::CentralPanel::default().show(ctx, |ui| { -// // let painter = ui.painter(); +pub fn render_mem_graph(painter: &Painter, rect: Rect, data: &mut PaneData) { + if let PaneData::MemGraph { mem_graph } = data { + mem_graph.update(); + mem_graph.render(painter, rect); + } +} -// // }); -// } +pub fn render_net_graph(painter: &Painter, rect: Rect, data: &mut PaneData) { + if let PaneData::NetGraph { net_graph } = data { + net_graph.update(); + net_graph.render(painter, rect); + } +} + +pub fn render_disk_graph(painter: &Painter, rect: Rect, data: &mut PaneData) { + if let PaneData::DiskGraph { disk_graph } = data { + disk_graph.update(); + disk_graph.render(painter, rect); + } +} + +pub fn render_proc_table(painter: &Painter, rect: Rect, data: &mut PaneData) { + if let PaneData::ProcTable { proc_table } = data { + proc_table.update(); + proc_table.render(painter, rect); + } +} + +// Function to load pane configuration from JSON +pub fn load_pane_config(json: &str) -> Result { + let config: Pane = serde_json::from_str(json)?; + + // Create the pane hierarchy with runtime data + Ok(create_pane_instance(config)) +} + +// Helper function to create the pane hierarchy +pub fn create_pane_instance(config: Pane) -> PaneInstance { + match config { + Pane::Split { + direction, + bias, + a, + b, + .. + } => { + let first = Box::new(create_pane_instance(*a)); + let second = Box::new(create_pane_instance(*b)); + + PaneInstance { + config: Pane::Split { + direction, + bias, + first, + second, + a: Box::new(Pane::default()), + b: Box::new(Pane::default()), + }, + runtime_data: PaneData::default(), + } + } + leaf => PaneInstance::from_config(leaf), + } +} + +// Example JSON configuration +pub const EXAMPLE_CONFIG: &str = r#" +{ + "kind": "Split", + "direction": "V", + "bias": 0.5, + "a": { + "kind": "Split", + "direction": "H", + "bias": 0.2, + "a": { + "kind": "Leaf", + "corners": ["Ang60", "Ang30", "Ang60", "Ang30"], + "pane_type": "Info" + }, + "b": { + "kind": "Leaf", + "corners": ["Ang30", "Ang60", "SQUARE", "SQUARE"], + "pane_type": "ProcTable" + } + }, + "b": { + "kind": "Split", + "direction": "H", + "bias": 0.5, + "a": { + "kind": "Split", + "direction": "H", + "bias": 0.5, + "a": { + "kind": "Leaf", + "corners": ["Ang60", "SQUARE", "SQUARE", "Ang30"], + "pane_type": "CpuGraph" + }, + "b": { + "kind": "Leaf", + "corners": ["Ang60", "SQUARE", "SQUARE", "Ang30"], + "pane_type": "MemGraph" + } + }, + "b": { + "kind": "Split", + "direction": "H", + "bias": 0.5, + "a": { + "kind": "Leaf", + "corners": ["Ang60", "SQUARE", "SQUARE", "Ang30"], + "pane_type": "NetGraph" + }, + "b": { + "kind": "Leaf", + "corners": ["Ang60", "SQUARE", "SQUARE", "Ang30"], + "pane_type": "DiskGraph" + } + } + } +} +"#; diff --git a/src/table.rs b/src/table.rs new file mode 100644 index 0000000..f877ac7 --- /dev/null +++ b/src/table.rs @@ -0,0 +1,297 @@ +use egui::{Color32, Pos2, Rect, Response, Stroke, Vec2}; +use std::time::Instant; +use sysinfo::{Pid, System}; + +const UPDATE_INTERVAL: f32 = 0.5; // seconds +pub const BAR_HEIGHT: f32 = 16.0; +pub const ROW_HEIGHT: f32 = 24.0; +const COLUMN_PADDING: f32 = 10.0; +const SWAP_INTERVAL: f32 = 5.; // seconds + +#[derive(Clone, Copy)] +pub enum SortColumn { + Cpu, + Memory, +} + +struct ProcessInfo { + pid: Pid, + name: String, + command: String, + cpu_usage: f32, + memory_bytes: u64, + memory_percent: f32, +} + +pub struct ProcessTable { + sys: System, + last_update: Instant, + last_swap: Instant, + processes: Vec, + sort_by: SortColumn, + name_width: usize, + command_width: usize, + pub row_count: usize, +} + +fn rem_first_and_last(value: String, num: usize) -> String { + let mut chars = value.chars(); + chars.next(); + chars.next_back(); + chars.take(num).collect::() +} + +impl ProcessTable { + pub fn new(name_width: usize, command_width: usize, row_count: usize) -> Self { + Self { + sys: System::new_all(), + last_update: Instant::now(), + last_swap: Instant::now(), + processes: Vec::new(), + sort_by: SortColumn::Cpu, + name_width, + command_width, + row_count, + } + } + + pub fn set_sort(&mut self, sort_by: SortColumn) { + self.sort_by = sort_by; + } + + pub fn update(&mut self) { + let now = Instant::now(); + if now.duration_since(self.last_update).as_secs_f32() < UPDATE_INTERVAL { + return; + } + if now.duration_since(self.last_swap).as_secs_f32() > SWAP_INTERVAL { + self.sort_by = match self.sort_by { + SortColumn::Cpu => SortColumn::Memory, + SortColumn::Memory => SortColumn::Cpu, + }; + self.last_swap = now; + } + + self.sys.refresh_all(); + + let total_memory = self.sys.total_memory() as f64; + + // Collect process information + self.processes = self + .sys + .processes() + .iter() + .map(|(pid, proc)| ProcessInfo { + pid: *pid, + name: proc.name().to_str().unwrap().to_string(), + command: format!("{:?}", proc.cmd()), + cpu_usage: proc.cpu_usage(), + memory_bytes: proc.memory(), + memory_percent: (proc.memory() as f64 / total_memory * 100.0) as f32, + }) + .collect(); + + // Sort processes based on selected criterion + match self.sort_by { + SortColumn::Cpu => { + self.processes + .sort_by(|a, b| b.cpu_usage.partial_cmp(&a.cpu_usage).unwrap()); + } + SortColumn::Memory => { + self.processes + .sort_by_key(|p| std::cmp::Reverse(p.memory_bytes)); + } + } + + // Keep only top N processes + self.processes.truncate(self.row_count); + + self.last_update = now; + } + + fn draw_bar(&self, painter: &egui::Painter, rect: Rect, percentage: f32, color: Color32) { + // Background + painter.rect_filled(rect, 0.0, Color32::from_gray(40)); + + // Foreground bar + let bar_width = rect.width() * (percentage / 100.0).min(1.0); + let bar_rect = Rect::from_min_max(rect.min, Pos2::new(rect.min.x + bar_width, rect.max.y)); + painter.rect_filled(bar_rect, 0.0, color); + + // Percentage text + let text = format!("{:.1}%", percentage); + painter.text( + rect.center(), + egui::Align2::CENTER_CENTER, + text, + egui::FontId::proportional(12.0), + Color32::WHITE, + ); + } + + fn draw_header(&self, painter: &egui::Painter, rect: Rect) -> f32 { + let text_color = Color32::LIGHT_GRAY; + let header_height = 24.0; + let header_rect = + Rect::from_min_max(rect.min, Pos2::new(rect.max.x, rect.min.y + header_height)); + + // Background + painter.rect_filled(header_rect, 0.0, Color32::from_gray(30)); + + let mut x = rect.min.x + COLUMN_PADDING; + + // PID header + painter.text( + Pos2::new(x, header_rect.center().y), + egui::Align2::LEFT_CENTER, + "PID", + egui::FontId::proportional(14.0), + text_color, + ); + x += rect.width() * 0.1; + + // Name header + painter.text( + Pos2::new(x, header_rect.center().y), + egui::Align2::LEFT_CENTER, + "Name", + egui::FontId::proportional(14.0), + text_color, + ); + x += rect.width() * 0.2; + + // Command header + painter.text( + Pos2::new(x, header_rect.center().y), + egui::Align2::LEFT_CENTER, + "Command", + egui::FontId::proportional(14.0), + text_color, + ); + x += rect.width() * 0.5; + + // CPU header + painter.text( + Pos2::new(x, header_rect.center().y), + egui::Align2::LEFT_CENTER, + "CPU %", + egui::FontId::proportional(14.0), + text_color, + ); + x += rect.width() * 0.1; + + // Memory header + painter.text( + Pos2::new(x, header_rect.center().y), + egui::Align2::LEFT_CENTER, + "Memory %", + egui::FontId::proportional(14.0), + text_color, + ); + + header_height + } + + pub fn render(&mut self, painter: &egui::Painter, rect: Rect) { + let header_height = self.draw_header(painter, rect); + let content_rect = + Rect::from_min_max(Pos2::new(rect.min.x, rect.min.y + header_height), rect.max); + + for (i, process) in self.processes.iter().enumerate() { + let row_min_y = content_rect.min.y + (i as f32 * ROW_HEIGHT); + let row_rect = Rect::from_min_max( + Pos2::new(content_rect.min.x, row_min_y), + Pos2::new(content_rect.max.x, row_min_y + ROW_HEIGHT), + ); + + // Background (alternating) + painter.rect_filled( + row_rect, + 0.0, + if i % 2 == 0 { + Color32::from_gray(25) + } else { + Color32::from_gray(20) + }, + ); + + let mut x = row_rect.min.x + COLUMN_PADDING; + let text_y = row_rect.center().y; + + // PID + painter.text( + Pos2::new(x, text_y), + egui::Align2::LEFT_CENTER, + process.pid.to_string(), + egui::FontId::proportional(14.0), + Color32::WHITE, + ); + x += rect.width() * 0.1; + + // Name (truncated) + painter.text( + Pos2::new(x, text_y), + egui::Align2::LEFT_CENTER, + process + .name + .chars() + .take(self.name_width) + .collect::(), + egui::FontId::proportional(14.0), + Color32::WHITE, + ); + x += rect.width() * 0.2; + + // let cmd = rem_first_and_last( + // process.command.split("\", \"").collect(), + // self.command_width, + // ); + + // Command (truncated) + painter.text( + Pos2::new(x, text_y), + egui::Align2::LEFT_CENTER, + process + .command + .chars() + .take(self.row_count) + .collect::(), + // .chars() + // .take(self.command_width).collect::(), + egui::FontId::proportional(14.0), + Color32::WHITE, + ); + x += rect.width() * 0.5; + + let cpu_bar_rect = Rect::from_min_max( + Pos2::new(x, row_rect.min.y + (ROW_HEIGHT - BAR_HEIGHT) / 2.0), + Pos2::new( + x + rect.width() * 0.08, + row_rect.min.y + (ROW_HEIGHT + BAR_HEIGHT) / 2.0, + ), + ); + self.draw_bar( + painter, + cpu_bar_rect, + process.cpu_usage, + Color32::from_rgb(46, 194, 126), + ); + x += rect.width() * 0.1; + + // Memory bar + let mem_bar_rect = Rect::from_min_max( + Pos2::new(x, row_rect.min.y + (ROW_HEIGHT - BAR_HEIGHT) / 2.0), + Pos2::new( + x + rect.width() * 0.08, + row_rect.min.y + (ROW_HEIGHT + BAR_HEIGHT) / 2.0, + ), + ); + self.draw_bar( + painter, + mem_bar_rect, + process.memory_percent, + Color32::from_rgb(194, 137, 46), + ); + } + } +} diff --git a/src/ui.rs b/src/ui.rs index 869dfdb..4d92c40 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,16 +1,16 @@ use crate::configs::*; -use crate::graph::CpuGraph; use crate::panes; +use crate::panes::PaneInstance; use crate::structs; -use panes::PaneRenderer; +// use panes::PaneRenderer; // use crate::structs::cur_context; // use crate::structs::windowTypes; use eframe::egui; use egui::Color32; use egui::Shape; -use egui::{Pos2, Vec2}; -use panes::{PaneConfig, SplitDirection}; +use egui::{Pos2, Rect, Vec2}; +use panes::Pane; use std::f32::consts::PI; use std::ops::Deref; @@ -24,14 +24,16 @@ fn rot_circle(i: i16, center: Pos2, rad: f32, offset_ang: f32, ang_per_num: f32) }) } -fn add_Pos2(add: Vec2, points: &mut Vec) -> &mut Vec { +fn add_Pos2(add: Vec2, points: Vec) -> Vec { + let mut points = points; for i in 0..points.len() { points[i] = points[i] + add; } points } -fn mult_Pos2(mult: f32, points: &mut Vec) -> &mut Vec { +fn mult_Pos2(mult: f32, points: Vec) -> Vec { + let mut points = points; let vec = Vec2 { x: mult, y: mult }; for i in 0..points.len() { points[i] = points[i] * mult; @@ -39,7 +41,8 @@ fn mult_Pos2(mult: f32, points: &mut Vec) -> &mut Vec { points } -fn rotate_90(points: &mut Vec) -> &mut Vec { +fn rotate_90(points: Vec) -> Vec { + let mut points = points; for i in 0..points.len() { points[i] = Pos2 { x: -points[i].y, @@ -48,7 +51,8 @@ fn rotate_90(points: &mut Vec) -> &mut Vec { } points } -fn rotate_180(points: &mut Vec) -> &mut Vec { +fn rotate_180(points: Vec) -> Vec { + let mut points = points; for i in 0..points.len() { points[i] = Pos2 { x: -points[i].x, @@ -57,7 +61,8 @@ fn rotate_180(points: &mut Vec) -> &mut Vec { } points } -fn rotate_270(points: &mut Vec) -> &mut Vec { +fn rotate_270(points: Vec) -> Vec { + let mut points = points; for i in 0..points.len() { points[i] = Pos2 { x: points[i].y, @@ -82,65 +87,66 @@ fn get_corner_points(ctype: panes::CornerTypes) -> Vec { } } -pub fn background_render( - painter: &egui::Painter, - rect: egui::Rect, - corners: [panes::CornerTypes; 4], -) { +pub fn get_corners(rect: egui::Rect, corners: [panes::CornerTypes; 4]) -> Vec { let mut points: Vec = Vec::new(); let left = rect.left() + PANE_GAP; let right = rect.right() - PANE_GAP; let bottom = rect.bottom() - PANE_GAP; let top = rect.top() + PANE_GAP; - // let x1 = left + CORNER_CUT - PANE_GAP; - // let x2 = right - CORNER_CUT + PANE_GAP; - // let y1 = top + CORNER_CUT - PANE_GAP; - // let y2 = bottom - CORNER_CUT + PANE_GAP; - points.append(add_Pos2( - Vec2 { x: left, y: top }, - mult_Pos2(CORNER_CUT, &mut get_corner_points(corners[0]).to_vec()), - )); + let rot_point_tl = get_corner_points(corners[0]).to_vec(); + let rot_point_tr = rotate_90(get_corner_points(corners[1]).to_vec()); + let rot_point_bl = rotate_180(get_corner_points(corners[2]).to_vec()); + let rot_point_br = rotate_270(get_corner_points(corners[3]).to_vec()); - points.append(add_Pos2( - Vec2 { x: right, y: top }, - mult_Pos2( - CORNER_CUT, - rotate_90(&mut get_corner_points(corners[1]).to_vec()), - ), - )); + points.append( + add_Pos2( + Vec2 { x: left, y: top }, + mult_Pos2(CORNER_CUT, rot_point_tl), + ) + .as_mut(), + ); - points.append(add_Pos2( - Vec2 { - x: right, - y: bottom, - }, - mult_Pos2( - CORNER_CUT, - rotate_180(&mut get_corner_points(corners[2]).to_vec()), - ), - )); + points.append( + add_Pos2( + Vec2 { x: right, y: top }, + mult_Pos2(CORNER_CUT, rot_point_tr), + ) + .as_mut(), + ); - points.append(add_Pos2( - Vec2 { x: left, y: bottom }, - mult_Pos2( - CORNER_CUT, - rotate_270(&mut get_corner_points(corners[3]).to_vec()), - ), - )); + points.append( + add_Pos2( + Vec2 { + x: right, + y: bottom, + }, + mult_Pos2(CORNER_CUT, rot_point_bl), + ) + .as_mut(), + ); - let filled_polygon = - Shape::convex_polygon(points, BACKGROUND_2, egui::Stroke::new(0.5, TEXT_COLOR)); + points.append( + add_Pos2( + Vec2 { x: left, y: bottom }, + mult_Pos2(CORNER_CUT, rot_point_br), + ) + .as_mut(), + ); - painter.add(filled_polygon); + // let largest_rect = find_largest_rectangle(&points).unwrap(); + + // let filled_polygon = + + return points; } -pub fn update_password_viewer( +pub fn update( wstate: MutexGuard<'_, structs::AuthState>, ctx: &egui::Context, frame: &mut eframe::Frame, ui: &mut egui::Ui, - winconfig: &mut PaneRenderer, + root_pane: &mut PaneInstance, ) { let rect: egui::Rect = ui.available_rect_before_wrap(); // let ctx: cur_context = cur_context { @@ -152,17 +158,17 @@ pub fn update_password_viewer( }; let painter: &egui::Painter = ui.painter(); - paint_windows( - painter, - rect.width() as f32, - rect.height() as f32, - 0., - 0., - // windows, - ); + // paint_windows( + // painter, + // rect.width() as f32, + // rect.height() as f32, + // 0., + // 0., + // // windows, + // ); - dots(painter, ui.clip_rect()); - winconfig.render(painter, &rect); + // dots(painter, ui.clip_rect()); + root_pane.render(ui.painter()); paint_password_circle(state, ctx, frame, ui, center, painter); } @@ -272,19 +278,144 @@ fn paint_password_circle( ctx.request_repaint(); } -fn paint_windows( - painter: &egui::Painter, +pub fn find_largest_rectangle(points: &[Pos2]) -> Option { + if points.len() < 4 { + return None; + } - width: f32, - height: f32, - x: f32, - y: f32, - // pane: structs::PaneSplit, -) { - // match pane.wintype { - // windowTypes::SplitHorisontal => { - // paint_windows(painter, width, height / 2., x, y, pane.sub_a); - // paint_windows(painter, width, height / 2., x, y + height / 2., pane.sub_b); - // } - // } + // Helper function to calculate area of a rectangle + fn calculate_area(rect: &Rect) -> f32 { + rect.width() * rect.height() + } + + // Helper function to determine if a point is inside a polygon using ray casting + fn is_point_in_polygon(point: &Pos2, polygon: &[Pos2]) -> bool { + let mut inside = false; + let mut j = polygon.len() - 1; + + for i in 0..polygon.len() { + if ((polygon[i].y >= point.y) != (polygon[j].y >= point.y)) + && (point.x + <= (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y) + / (polygon[j].y - polygon[i].y) + + polygon[i].x) + { + inside = !inside; + } + j = i; + } + + inside + } + + // Helper function to check if a rectangle is completely inside the polygon + fn is_rect_in_polygon(rect: &Rect, polygon: &[Pos2]) -> bool { + // Check all four corners of the rectangle + let corners = [ + rect.min, + Pos2::new(rect.max.x, rect.min.y), + rect.max, + Pos2::new(rect.min.x, rect.max.y), + ]; + + corners + .iter() + .all(|corner| is_point_in_polygon(corner, polygon)) + } + + // Helper function to check if a set of points forms a valid rectangle + fn is_valid_rectangle(p1: &Pos2, p2: &Pos2, p3: &Pos2, p4: &Pos2) -> bool { + // Sort points by x coordinate + let mut pts = vec![p1, p2, p3, p4]; + pts.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap()); + + // For a valid rectangle: + // - The first two points should have the same x-coordinate + // - The last two points should have the same x-coordinate + // - Two points should have the min y-coordinate + // - Two points should have the max y-coordinate + let x_eps = 0.; + let y_eps = 0.; + + // // Check x-coordinates are properly paired + // if (pts[0].x - pts[1].x).abs() >= x_eps || (pts[2].x - pts[3].x).abs() >= x_eps { + // return false; + // } + + // // Sort points by y coordinate + // pts.sort_by(|a, b| a.y.partial_cmp(&b.y).unwrap()); + + // // Check y-coordinates are properly paired + // if (pts[0].y - pts[1].y).abs() >= y_eps || (pts[2].y - pts[3].y).abs() >= y_eps { + // return false; + // } + + true + } + + // Sort points to ensure they form a proper polygon + let mut polygon_points = points.to_vec(); + let center = Pos2::new( + polygon_points.iter().map(|p| p.x).sum::() / polygon_points.len() as f32, + polygon_points.iter().map(|p| p.y).sum::() / polygon_points.len() as f32, + ); + + // Sort points clockwise around the center + polygon_points.sort_by(|a, b| { + let a_angle = (a.y - center.y).atan2(a.x - center.x); + let b_angle = (b.y - center.y).atan2(b.x - center.x); + b_angle.partial_cmp(&a_angle).unwrap() + }); + + let mut largest_rect = None; + let mut max_area = 0.0; + + // Try all combinations of 4 points + for i in 0..points.len() { + for j in i + 1..points.len() { + for k in j + 1..points.len() { + for l in k + 1..points.len() { + let p1 = points[i]; + let p2 = points[j]; + let p3 = points[k]; + let p4 = points[l]; + + // Skip if these points don't form a valid rectangle + if !is_valid_rectangle(&p1, &p2, &p3, &p4) { + continue; + } + + // Create rectangle from these points + let min_x = p1.x.min(p2.x).min(p3.x).min(p4.x); + let max_x = p1.x.max(p2.x).max(p3.x).max(p4.x); + let min_y = p1.y.min(p2.y).min(p3.y).min(p4.y); + let max_y = p1.y.max(p2.y).max(p3.y).max(p4.y); + + let rect = Rect { + min: Pos2 { + x: min_x + 1., + y: min_y + 1., + }, + max: Pos2 { + x: max_x - 1., + y: max_y - 1., + }, + }; + + // Verify the rectangle is inside the polygon + if !is_rect_in_polygon(&rect, &polygon_points) { + continue; + } + + let area = calculate_area(&rect); + if area > max_area { + max_area = area; + largest_rect = Some(rect); + } + } + } + } + } + + largest_rect }