use std::time::Duration; use iced::{ Color, Error, Event, Length, Renderer, Subscription, Task, Theme, application, event, keyboard::{self, Key}, theme, widget, }; use crate::{ app::{graph::Graph, utils::maybe_widget}, sysinfo_snapshot::{ProcessSnapshot, SysinfoSnapshot}, utils::Toggle, }; mod graph; mod info_stream; mod utils; type Element<'a> = iced::Element<'a, Message, Theme, Renderer>; enum Message { ToggleMain, ToggleCpu, ToggleMemory, ToggleProcesses, Info(SysinfoSnapshot), } #[derive(Default)] struct State { uptime: Duration, is_main_section_shown: bool, total_cpu_graph: Graph, is_cpu_section_shown: bool, cpu_graphs: Vec, is_memory_section_shown: bool, memory_graph: Graph, swap_graph: Graph, is_processes_section_shown: bool, processes: Vec, } impl State { fn new() -> (Self, Task) { let mut state = Self { is_main_section_shown: true, ..Default::default() }; state.total_cpu_graph.show_borders = false; (state, info_stream::create()) } fn update(&mut self, message: Message) -> Task { match message { Message::ToggleMain => { self.is_main_section_shown.toggle(); self.check_borders(); } Message::ToggleCpu => { self.is_cpu_section_shown.toggle(); self.check_borders(); } Message::ToggleMemory => { self.is_memory_section_shown.toggle(); self.check_borders(); } Message::ToggleProcesses => { self.is_processes_section_shown.toggle(); self.check_borders(); } Message::Info(snapshot) => { self.uptime = snapshot.uptime(); self.total_cpu_graph.update(snapshot.cpu_total); self.memory_graph .update(snapshot.memory as f32 / snapshot.memory_total as f32 * 100.); self.swap_graph .update(snapshot.swap as f32 / snapshot.swap_total as f32 * 100.); if self.cpu_graphs.len() != snapshot.cpus.len() { self.cpu_graphs = snapshot .cpus .into_iter() .map(|value| Graph::new(value)) .collect(); } else { self.cpu_graphs .iter_mut() .zip(snapshot.cpus.into_iter()) .for_each(|(graph, value)| graph.update(value)); } self.processes = snapshot.processes; self.processes .sort_by(|a, b| b.cpu_usage.total_cmp(&a.cpu_usage)); } } Task::none() } fn view(&self) -> Element<'_> { widget::column([ widget::row([ maybe_widget(self.is_main_section_shown, || { widget::canvas(&self.total_cpu_graph) .width(Length::Fill) .height(Length::Fill) .into() }), maybe_widget(self.is_cpu_section_shown, || { widget::grid( self.cpu_graphs .iter() .map(|graph| widget::Canvas::new(graph).into()), ) .columns(2) .height(Length::Fill) .into() }), ]) .into(), widget::row([ maybe_widget(self.is_memory_section_shown, || { widget::row([ widget::canvas(&self.memory_graph) .width(Length::Fill) .height(Length::Fill) .into(), widget::canvas(&self.swap_graph) .width(Length::Fill) .height(Length::Fill) .into(), ]) .into() }), maybe_widget(self.is_processes_section_shown, || { widget::scrollable( widget::grid(self.processes.iter().flat_map(|process| { [ widget::text(&process.name).into(), widget::text(process.cpu_usage).into(), widget::text(format!("{:?}", process.cpu_time())).into(), widget::text(process.pid).into(), ] })) .height(Length::Shrink) .columns(4), ) .width(Length::Fill) .height(Length::Fill) .into() }), ]) .into(), ]) .into() } fn subscription(&self) -> Subscription { event::listen().filter_map(|event| match event { Event::Keyboard(keyboard::Event::KeyPressed { modified_key, .. }) => { match modified_key.as_ref() { Key::Character("M" | "1") => Some(Message::ToggleMain), Key::Character("c" | "2") => Some(Message::ToggleCpu), Key::Character("m" | "3") => Some(Message::ToggleMemory), Key::Character("p" | "4") => Some(Message::ToggleProcesses), _ => None, } } _ => None, }) } fn title(&self) -> String { format!("itop {:?}", self.uptime) } fn check_borders(&mut self) { self.total_cpu_graph.show_borders = self.is_cpu_section_shown || self.is_memory_section_shown; } } pub fn run() -> Result<(), Error> { application(State::new, State::update, State::view) .subscription(State::subscription) .title(State::title) .theme(theme::Theme::custom( "high-contrast-dark", theme::Palette { background: Color::BLACK, text: Color::WHITE, ..theme::Palette::DARK }, )) .run() }