Add info pane

This commit is contained in:
Astatin3
2024-11-04 17:14:12 -07:00
parent 68ccc7164b
commit 10d538df16
12 changed files with 1566 additions and 710 deletions
+3
View File
@@ -4,8 +4,11 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
battery = "0.7.8"
chrono = "0.4.38"
eframe = "0.28" eframe = "0.28"
egui = "0.28" egui = "0.28"
os_info = "3.8.2"
rand = "0.8.5" rand = "0.8.5"
serde = "1.0.214" serde = "1.0.214"
serde_json = "1.0.132" serde_json = "1.0.132"
+23 -63
View File
@@ -1,11 +1,33 @@
use crate::structs; use crate::structs;
use egui::{Color32, Stroke}; use egui::{Color32, FontId, Stroke};
use std::f32::consts::PI; use std::f32::consts::PI;
// pub const windows: PaneSplit = // pub const windows: PaneSplit =
// Pane::new(SplitHorisontal).new_hsplit(Pane::new(Temp1), Pane::new(Temp2)); // 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 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 BACKGROUND_2: Color32 = Color32::from_rgba_premultiplied(10, 10, 10, 230);
pub const LOGIN_CIRCLE_RADIUS: f32 = 50.; 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 CORNER_CUT: f32 = LOGIN_CIRCLE_RADIUS * 1.41421356237;
pub const PANE_GAP: f32 = 6.; 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]
}
}
}
"#;
+56
View File
@@ -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);
}
}
+80
View File
@@ -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);
}
}
+122 -91
View File
@@ -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::collections::VecDeque;
use std::time::Instant; use std::time::Instant;
use sysinfo::System; use sysinfo::System;
const HISTORY_SIZE: usize = 100; use crate::configs;
const ANIMATION_DURATION: f32 = 0.2; // seconds
const UPDATES_PER_SECOND: f32 = 2.0; pub const HISTORY_SIZE: usize = 25;
pub const ANIMATION_DURATION: f32 = 0.2; // seconds
pub const UPDATES_PER_SECOND: f32 = 1.5;
#[derive(Clone)] #[derive(Clone)]
struct DataPoint { struct DataPoint {
@@ -41,130 +44,158 @@ impl AnimatedValue {
self.current self.current
} }
} }
// #[derive(Clone)]
// #[derive(Clone)]
// #[derive(Debug)] pub struct GraphLine {
pub struct CpuGraph { label: String,
sys: System, stroke: Stroke,
history: Vec<VecDeque<DataPoint>>, history: VecDeque<DataPoint>,
animated_values: Vec<AnimatedValue>, animated_value: AnimatedValue,
last_update: Instant,
colors: Vec<Color32>,
} }
impl CpuGraph { impl GraphLine {
pub fn new() -> Self { fn new(label: String, stroke: Stroke) -> 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();
Self { Self {
sys, label,
history, stroke,
animated_values, history: VecDeque::with_capacity(HISTORY_SIZE),
last_update: Instant::now(), animated_value: AnimatedValue::new(0.0),
colors,
} }
} }
pub fn update(&mut self) { fn update(&mut self, value: f32) {
let now = Instant::now(); self.animated_value.update(value);
if now.duration_since(self.last_update).as_secs_f32() < 1.0 / UPDATES_PER_SECOND { self.history.push_back(DataPoint {
return; value,
} timestamp: Instant::now(),
self.sys.refresh_cpu_all();
for (i, cpu) in self.sys.cpus().iter().enumerate() {
let usage = cpu.cpu_usage() / 100.0;
self.animated_values[i].update(usage);
self.history[i].push_back(DataPoint {
value: usage,
timestamp: now,
}); });
while self.history[i].len() > HISTORY_SIZE { while self.history.len() > HISTORY_SIZE {
self.history[i].pop_front(); self.history.pop_front();
}
}
}
pub struct ResourceGraph {
lines: Vec<GraphLine>,
// last_update: Instant,
title: String,
y_axis_label: String,
min_value: f32,
max_value: f32,
}
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.last_update = now; 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();
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;
}
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) { pub fn render(&mut self, painter: &egui::Painter, rect: Rect) {
let stroke_width = 2.0; // let now = Instant::now();
// Draw background // 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 { 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( 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)), 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 // Draw lines
for (core_idx, (history, color)) in self.history.iter().zip(self.colors.iter()).enumerate() for (_, line) in self.lines.iter_mut().enumerate() {
{ if line.history.is_empty() {
if history.is_empty() {
continue; continue;
} }
let points: Vec<Pos2> = history let points: Vec<Pos2> = line
.history
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, point)| { .map(|(i, point)| {
let x = rect.min.x + rect.width() * (i as f32 / (HISTORY_SIZE - 1) as f32); let x = graph_rect.min.x
let y = rect.max.y - rect.height() * point.value; + 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) Pos2::new(x, y)
}) })
.collect(); .collect();
// Get the current animated value for the last point // 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; let mut animated_points = points;
if let Some(last) = animated_points.last_mut() { 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, line.stroke));
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,
);
} }
// self.last_update = now;
} }
} }
+157
View File
@@ -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<BatteryState, battery::errors::Error> {
// 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::<joule>() / battery.energy_full().get::<joule>();
let rate = battery.energy_rate().get::<watt>();
let time_to_empty = battery.time_to_empty();
let time: f32 = if !time_to_empty.is_none() {
time_to_empty.unwrap().get::<minute>()
} else if !battery.time_to_full().is_none() {
battery.time_to_full().unwrap().get::<minute>()
} 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::<battery::units::ratio::percent>();
}
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,
);
}
}
+41 -137
View File
@@ -1,16 +1,20 @@
use configs::SCREEN_HEIGHT;
use eframe::Error;
use eframe::{egui, App}; use eframe::{egui, App};
use egui::FontFamily; use egui::FontFamily;
use egui::Key; use egui::Key;
use graph::CpuGraph; // use graph::CpuGraph;
use input::is_locked; 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::BTreeMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
// use egui::epaint::text::{FontInsert, InsertFontFamily}; // use egui::epaint::text::{FontInsert, InsertFontFamily};
use eframe::CreationContext; use eframe::CreationContext;
@@ -23,49 +27,27 @@ use egui::Stroke;
use egui::ecolor::HexColor; use egui::ecolor::HexColor;
mod configs; mod configs;
mod cpugraph;
mod diskgraph;
mod graph; mod graph;
mod memgraph;
mod netgraph;
mod table;
mod infopane;
mod input; mod input;
mod panes; mod panes;
mod structs; mod structs;
mod ui; mod ui;
// Demonstrates how to add a font to the existing ones use panes::{load_pane_config, EXAMPLE_CONFIG};
// 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();
// // 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 { struct ExampleApp {
auth_state: Arc<Mutex<structs::AuthState>>, auth_state: Arc<Mutex<structs::AuthState>>,
cpu_graph: CpuGraph, root_pane: panes::PaneInstance,
// terminals: HashMap<String, TermHandler>,
} }
impl ExampleApp { impl ExampleApp {
@@ -80,34 +62,7 @@ impl eframe::App for ExampleApp {
} }
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { 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(); 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) { if ctx.input(|i| i.events.len() > 0) {
ctx.input(|i| { ctx.input(|i| {
for event in &i.events { for event in &i.events {
@@ -134,67 +89,18 @@ impl eframe::App for ExampleApp {
state.password += str; 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 mut pane = ;
// 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();
egui::CentralPanel::default() egui::CentralPanel::default()
.frame(egui::Frame::none()) .frame(egui::Frame::none())
.show(ctx, |ui| { .show(ctx, |ui| {
// let ht = ui.available_height(); std::thread::sleep(Duration::from_millis(50));
// for (_idx, (_id, term)) in self.terminals.iter_mut().enumerate() { ui::update(state, ctx, _frame, ui, &mut self.root_pane);
// 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. },
// },
// );
ctx.request_repaint(); ctx.request_repaint();
}); });
} }
@@ -206,10 +112,6 @@ fn main() -> eframe::Result<()> {
..eframe::NativeOptions::default() ..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 { let state = Arc::new(Mutex::new(structs::AuthState {
password: String::new(), password: String::new(),
to_be_submitted: false, to_be_submitted: false,
@@ -218,7 +120,6 @@ fn main() -> eframe::Result<()> {
let auth_state_clone = state.clone(); let auth_state_clone = state.clone();
// Spawn authentication thread
thread::spawn(move || loop { thread::spawn(move || loop {
let mut state = auth_state_clone.lock().unwrap(); let mut state = auth_state_clone.lock().unwrap();
@@ -246,40 +147,43 @@ fn main() -> eframe::Result<()> {
thread::sleep(Duration::from_millis(100)); 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() { if input::is_locked() {
println!("Raylock is already running!"); println!("Raylock is already running!");
std::process::exit(1); std::process::exit(1);
} }
let test = [1, 2, 3]; input::sway_lock_input();
input::create_lock(); 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( eframe::run_native(
ExampleApp::name(), ExampleApp::name(),
native_options, native_options,
Box::new(|_| { Box::new(|_| {
Ok(Box::<ExampleApp>::new(ExampleApp { Ok(Box::<ExampleApp>::new(ExampleApp {
auth_state: state, auth_state: state,
cpu_graph: CpuGraph::new(), root_pane: pane_config,
// terminals: map, // 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<bool, std::io::Error> { fn try_sudo(password: &str) -> Result<bool, std::io::Error> {
let mut child = Command::new("sudo") let mut child = Command::new("sudo")
.args(["-kS", "true"]) // Use -S to read password from stdin .args(["-kS", "true"]) // Use -S to read password from stdin
+64
View File
@@ -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);
}
}
+70
View File
@@ -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);
}
}
+457 -354
View File
@@ -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::{Deserialize, Serialize};
use serde_json::Value;
use std::borrow::BorrowMut;
use std::collections::HashMap; 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)] #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum CornerTypes { pub enum CornerTypes {
@@ -12,387 +28,474 @@ pub enum CornerTypes {
Ang60, Ang60,
} }
// Serializable configuration types #[derive(Clone)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum TitleFormats {
pub enum SplitDirection { TOP { text: Option<String> },
Horizontal, SIDE { text: Option<String> },
Vertical,
} }
#[derive(Clone, Serialize, Deserialize)] impl Default for TitleFormats {
#[serde(tag = "type", content = "config")] fn default() -> Self {
pub enum PaneTypeConfig { TitleFormats::TOP { text: None }
Solid(SolidPaneConfig), }
Text(TextPaneConfig),
Gradient(GradientPaneConfig),
} }
// Runtime-only pane type that includes non-serializable data const DEFAULT_CORNERS: [CornerTypes; 4] = [
// #[derive(Clone)] 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 { pub enum PaneType {
Solid(SolidPane), Info,
Text(TextPane), CpuGraph,
Gradient(GradientPane), MemGraph,
NetGraph,
DiskGraph,
ProcTable,
No,
} }
// Serializable configs #[derive(Deserialize, Serialize, Clone, PartialEq)]
#[derive(Clone, Serialize, Deserialize)] pub enum SplitType {
pub struct SolidPaneConfig { H,
pub color: [u8; 3], V,
} }
#[derive(Clone, Serialize, Deserialize)] // Structure to store runtime data for different pane types
pub struct TextPaneConfig { pub enum PaneData {
pub text: String, Info { info_man: InfoPane },
pub font_size: f32, CpuGraph { cpu_graph: CpuGraph },
pub color: [u8; 3], MemGraph { mem_graph: MemGraph },
pub background_color: Option<[u8; 3]>, NetGraph { net_graph: NetGraph },
DiskGraph { disk_graph: DiskGraph },
ProcTable { proc_table: ProcessTable },
No {},
} }
#[derive(Clone, Serialize, Deserialize)] impl Default for PaneData {
pub struct GradientPaneConfig { fn default() -> Self {
pub start_color: [u8; 3], PaneData::No {}
pub end_color: [u8; 3], }
pub horizontal: bool,
} }
// Runtime types with additional non-serializable data impl PaneData {
#[derive(Clone)] pub fn new(pane: &Pane) -> Self {
pub struct SolidPane { match pane {
config: SolidPaneConfig, Pane::Split { .. } => PaneData::No {},
// Runtime-only fields
cached_color: Color32,
}
#[derive(Clone)] Pane::Leaf { pane_type, .. } => match pane_type {
pub struct TextPane { PaneType::Info => PaneData::Info {
config: TextPaneConfig, info_man: InfoPane::new(),
// Runtime-only fields },
cached_color: Color32, PaneType::CpuGraph => PaneData::CpuGraph {
cached_bg_color: Option<Color32>, cpu_graph: CpuGraph::new(),
cached_font: egui::FontId, },
} PaneType::MemGraph => PaneData::MemGraph {
mem_graph: MemGraph::new(),
// #[derive(Clone)] },
pub struct GradientPane { PaneType::NetGraph => PaneData::NetGraph {
config: GradientPaneConfig, net_graph: NetGraph::new(),
// Runtime-only fields },
cached_start_color: Color32, PaneType::DiskGraph => PaneData::DiskGraph {
cached_end_color: Color32, disk_graph: DiskGraph::new(),
} },
PaneType::ProcTable => PaneData::ProcTable {
#[derive(Clone, Serialize, Deserialize)] proc_table: ProcessTable::new(30, 50, 0),
pub struct PaneConfig { },
pub id: String, PaneType::No {} => PaneData::No {},
#[serde(default)] },
pub split: Option<SplitConfig>,
#[serde(default)]
pub pane_type: Option<PaneTypeConfig>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct SplitConfig {
pub direction: SplitDirection,
pub ratio: f32,
pub children: Vec<PaneConfig>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct LayoutConfig {
pub root: PaneConfig,
#[serde(default)]
pub default_pane_type: Option<PaneTypeConfig>,
}
// Runtime representation of the layout
pub struct RuntimePane {
pub id: String,
pub split: Option<RuntimeSplit>,
pub pane_type: Option<PaneType>,
}
pub struct RuntimeSplit {
pub direction: SplitDirection,
pub ratio: f32,
pub children: Vec<RuntimePane>,
}
// Conversion implementations
impl From<PaneTypeConfig> 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,
}),
} }
} }
} }
impl PaneType { // Main pane structure that represents either a split or leaf node
fn render(&self, painter: &Painter, rect: Rect) { #[derive(Deserialize, Serialize)]
ui::background_render( // #[default]
painter, #[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<Pos2>,
#[serde(skip)]
title_type: TitleFormats,
},
Split {
direction: SplitType,
bias: f32,
#[serde(skip)]
first: Box<PaneInstance>,
#[serde(skip)]
second: Box<PaneInstance>,
a: Box<Pane>,
b: Box<Pane>,
},
}
const DEFAULT_RECT: Rect = Rect {
min: Pos2 { x: 0., y: 0. },
max: Pos2 { x: 0., y: 0. },
};
const DEFAULT_POINTS: Vec<Pos2> = 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);
}
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());
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(),
);
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 }
});
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;
}
}
_ => {}
}
// let adj_rect = background_render(painter, rect, *corners);
}
}
}
// 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, rect,
[ inner_rect,
CornerTypes::Ang30, container_points,
CornerTypes::Ang60, title_type,
CornerTypes::Ang30, ..
CornerTypes::Ang60, } => {
], painter.add(egui::Shape::convex_polygon(
); container_points.to_owned(),
match self { BACKGROUND_2,
PaneType::Solid(pane) => { egui::Stroke::new(0.5, TEXT_COLOR),
// painter.rect_filled(rect, 0.0, pane.cached_color); ));
// painter.rect_stroke(rect, 0.0, (1.0, Color32::BLACK));
}
PaneType::Text(pane) => {
// if let Some(bg_color) = pane.cached_bg_color {
// painter.rect_filled(rect, 0.0, bg_color);
// }
// painter.text( match title_type {
// rect.center(), TitleFormats::SIDE { text } => {
// egui::Align2::CENTER_CENTER, let galley = painter.layout_no_wrap(
// &pane.config.text, text.as_ref().unwrap().to_owned(),
// pane.cached_font.clone(), TITLE_FONT,
// pane.cached_color, 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,
));
}
}
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 => {}
} }
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
// }
} }
} }
} }
} }
pub struct PaneRenderer { pub fn render_info(painter: &Painter, rect: Rect, data: &mut PaneData) {
runtime_config: RuntimePane, if let PaneData::Info { info_man } = data {
default_pane_type: Option<PaneType>, info_man.update();
} info_man.render(painter, rect);
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),
);
}
}
}
}
} 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);
}
}
}
fn split_rect(
&self,
rect: &Rect,
direction: SplitDirection,
ratio: f32,
count: usize,
) -> Vec<Rect> {
let mut rects = Vec::with_capacity(count);
let size = match direction {
SplitDirection::Horizontal => rect.width(),
SplitDirection::Vertical => rect.height(),
};
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
};
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))
}
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: // Example rendering functions for different pane types
// const EXAMPLE_CONFIG: &str = r#" pub fn render_cpu_graph(painter: &Painter, rect: Rect, data: &mut PaneData) {
// { if let PaneData::CpuGraph { cpu_graph } = data {
// "root": { cpu_graph.update();
// "id": "root", cpu_graph.render(painter, rect);
// "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]
// }
// }
// }
// "#;
// // Example usage pub fn render_mem_graph(painter: &Painter, rect: Rect, data: &mut PaneData) {
// pub fn example_usage(ctx: &egui::Context) { if let PaneData::MemGraph { mem_graph } = data {
// // Parse configuration mem_graph.update();
// let config: LayoutConfig = serde_json::from_str(EXAMPLE_CONFIG).unwrap(); mem_graph.render(painter, rect);
// let pane_renderer = PaneRenderer::new(config); }
}
// // egui::CentralPanel::default().show(ctx, |ui| { pub fn render_net_graph(painter: &Painter, rect: Rect, data: &mut PaneData) {
// // let painter = ui.painter(); 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<PaneInstance, serde_json::Error> {
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"
}
}
}
}
"#;
+297
View File
@@ -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<ProcessInfo>,
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::<String>()
}
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::<String>(),
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::<String>(),
// .chars()
// .take(self.command_width).collect::<String>(),
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),
);
}
}
}
+197 -66
View File
@@ -1,16 +1,16 @@
use crate::configs::*; use crate::configs::*;
use crate::graph::CpuGraph;
use crate::panes; use crate::panes;
use crate::panes::PaneInstance;
use crate::structs; use crate::structs;
use panes::PaneRenderer; // use panes::PaneRenderer;
// use crate::structs::cur_context; // use crate::structs::cur_context;
// use crate::structs::windowTypes; // use crate::structs::windowTypes;
use eframe::egui; use eframe::egui;
use egui::Color32; use egui::Color32;
use egui::Shape; use egui::Shape;
use egui::{Pos2, Vec2}; use egui::{Pos2, Rect, Vec2};
use panes::{PaneConfig, SplitDirection}; use panes::Pane;
use std::f32::consts::PI; use std::f32::consts::PI;
use std::ops::Deref; 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<Pos2>) -> &mut Vec<Pos2> { fn add_Pos2(add: Vec2, points: Vec<Pos2>) -> Vec<Pos2> {
let mut points = points;
for i in 0..points.len() { for i in 0..points.len() {
points[i] = points[i] + add; points[i] = points[i] + add;
} }
points points
} }
fn mult_Pos2(mult: f32, points: &mut Vec<Pos2>) -> &mut Vec<Pos2> { fn mult_Pos2(mult: f32, points: Vec<Pos2>) -> Vec<Pos2> {
let mut points = points;
let vec = Vec2 { x: mult, y: mult }; let vec = Vec2 { x: mult, y: mult };
for i in 0..points.len() { for i in 0..points.len() {
points[i] = points[i] * mult; points[i] = points[i] * mult;
@@ -39,7 +41,8 @@ fn mult_Pos2(mult: f32, points: &mut Vec<Pos2>) -> &mut Vec<Pos2> {
points points
} }
fn rotate_90(points: &mut Vec<Pos2>) -> &mut Vec<Pos2> { fn rotate_90(points: Vec<Pos2>) -> Vec<Pos2> {
let mut points = points;
for i in 0..points.len() { for i in 0..points.len() {
points[i] = Pos2 { points[i] = Pos2 {
x: -points[i].y, x: -points[i].y,
@@ -48,7 +51,8 @@ fn rotate_90(points: &mut Vec<Pos2>) -> &mut Vec<Pos2> {
} }
points points
} }
fn rotate_180(points: &mut Vec<Pos2>) -> &mut Vec<Pos2> { fn rotate_180(points: Vec<Pos2>) -> Vec<Pos2> {
let mut points = points;
for i in 0..points.len() { for i in 0..points.len() {
points[i] = Pos2 { points[i] = Pos2 {
x: -points[i].x, x: -points[i].x,
@@ -57,7 +61,8 @@ fn rotate_180(points: &mut Vec<Pos2>) -> &mut Vec<Pos2> {
} }
points points
} }
fn rotate_270(points: &mut Vec<Pos2>) -> &mut Vec<Pos2> { fn rotate_270(points: Vec<Pos2>) -> Vec<Pos2> {
let mut points = points;
for i in 0..points.len() { for i in 0..points.len() {
points[i] = Pos2 { points[i] = Pos2 {
x: points[i].y, x: points[i].y,
@@ -82,65 +87,66 @@ fn get_corner_points(ctype: panes::CornerTypes) -> Vec<Pos2> {
} }
} }
pub fn background_render( pub fn get_corners(rect: egui::Rect, corners: [panes::CornerTypes; 4]) -> Vec<Pos2> {
painter: &egui::Painter,
rect: egui::Rect,
corners: [panes::CornerTypes; 4],
) {
let mut points: Vec<Pos2> = Vec::new(); let mut points: Vec<Pos2> = Vec::new();
let left = rect.left() + PANE_GAP; let left = rect.left() + PANE_GAP;
let right = rect.right() - PANE_GAP; let right = rect.right() - PANE_GAP;
let bottom = rect.bottom() - PANE_GAP; let bottom = rect.bottom() - PANE_GAP;
let top = rect.top() + 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( 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: left, y: top }, Vec2 { x: left, y: top },
mult_Pos2(CORNER_CUT, &mut get_corner_points(corners[0]).to_vec()), mult_Pos2(CORNER_CUT, rot_point_tl),
)); )
.as_mut(),
);
points.append(add_Pos2( points.append(
add_Pos2(
Vec2 { x: right, y: top }, Vec2 { x: right, y: top },
mult_Pos2( mult_Pos2(CORNER_CUT, rot_point_tr),
CORNER_CUT, )
rotate_90(&mut get_corner_points(corners[1]).to_vec()), .as_mut(),
), );
));
points.append(add_Pos2( points.append(
add_Pos2(
Vec2 { Vec2 {
x: right, x: right,
y: bottom, y: bottom,
}, },
mult_Pos2( mult_Pos2(CORNER_CUT, rot_point_bl),
CORNER_CUT, )
rotate_180(&mut get_corner_points(corners[2]).to_vec()), .as_mut(),
), );
));
points.append(add_Pos2( points.append(
add_Pos2(
Vec2 { x: left, y: bottom }, Vec2 { x: left, y: bottom },
mult_Pos2( mult_Pos2(CORNER_CUT, rot_point_br),
CORNER_CUT, )
rotate_270(&mut get_corner_points(corners[3]).to_vec()), .as_mut(),
), );
));
let filled_polygon = // let largest_rect = find_largest_rectangle(&points).unwrap();
Shape::convex_polygon(points, BACKGROUND_2, egui::Stroke::new(0.5, TEXT_COLOR));
painter.add(filled_polygon); // let filled_polygon =
return points;
} }
pub fn update_password_viewer( pub fn update(
wstate: MutexGuard<'_, structs::AuthState>, wstate: MutexGuard<'_, structs::AuthState>,
ctx: &egui::Context, ctx: &egui::Context,
frame: &mut eframe::Frame, frame: &mut eframe::Frame,
ui: &mut egui::Ui, ui: &mut egui::Ui,
winconfig: &mut PaneRenderer, root_pane: &mut PaneInstance,
) { ) {
let rect: egui::Rect = ui.available_rect_before_wrap(); let rect: egui::Rect = ui.available_rect_before_wrap();
// let ctx: cur_context = cur_context { // let ctx: cur_context = cur_context {
@@ -152,17 +158,17 @@ pub fn update_password_viewer(
}; };
let painter: &egui::Painter = ui.painter(); let painter: &egui::Painter = ui.painter();
paint_windows( // paint_windows(
painter, // painter,
rect.width() as f32, // rect.width() as f32,
rect.height() as f32, // rect.height() as f32,
0., // 0.,
0., // 0.,
// windows, // // windows,
); // );
dots(painter, ui.clip_rect()); // dots(painter, ui.clip_rect());
winconfig.render(painter, &rect); root_pane.render(ui.painter());
paint_password_circle(state, ctx, frame, ui, center, painter); paint_password_circle(state, ctx, frame, ui, center, painter);
} }
@@ -272,19 +278,144 @@ fn paint_password_circle(
ctx.request_repaint(); ctx.request_repaint();
} }
fn paint_windows( pub fn find_largest_rectangle(points: &[Pos2]) -> Option<Rect> {
painter: &egui::Painter, if points.len() < 4 {
return None;
}
width: f32, // Helper function to calculate area of a rectangle
height: f32, fn calculate_area(rect: &Rect) -> f32 {
x: f32, rect.width() * rect.height()
y: f32, }
// pane: structs::PaneSplit,
) { // Helper function to determine if a point is inside a polygon using ray casting
// match pane.wintype { fn is_point_in_polygon(point: &Pos2, polygon: &[Pos2]) -> bool {
// windowTypes::SplitHorisontal => { let mut inside = false;
// paint_windows(painter, width, height / 2., x, y, pane.sub_a); let mut j = polygon.len() - 1;
// paint_windows(painter, width, height / 2., x, y + height / 2., pane.sub_b);
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::<f32>() / polygon_points.len() as f32,
polygon_points.iter().map(|p| p.y).sum::<f32>() / 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
} }