diff --git a/src/app/data/mod.rs b/src/app/data/mod.rs index 056e84b..4e92ea6 100644 --- a/src/app/data/mod.rs +++ b/src/app/data/mod.rs @@ -6,6 +6,7 @@ use std::sync::{RwLock, Mutex}; use std::num::ParseFloatError; use chrono::{DateTime, Utc}; use eframe::egui::plot::{Values, Value}; +use eframe::epaint::Color32; use self::store::SQLiteDataStore; @@ -64,8 +65,9 @@ impl ApplicationState { Ok(()) } - pub fn add_source(&self, panel_id:i32, name:&str, url:&str, query_x:&str, query_y:&str) -> Result<(), FetchError> { - let source = self.storage.lock().expect("Storage Mutex poisoned").new_source(panel_id, name, url, query_x, query_y)?; + pub fn add_source(&self, panel_id:i32, name:&str, url:&str, query_x:&str, query_y:&str, color:Color32, visible:bool) -> Result<(), FetchError> { + let source = self.storage.lock().expect("Storage Mutex poisoned") + .new_source(panel_id, name, url, query_x, query_y, color, visible)?; let panels = self.panels.read().expect("Panels RwLock poisoned"); for panel in &*panels { if panel.id == panel_id { @@ -97,6 +99,8 @@ pub struct Source { pub name: String, pub url: String, pub interval: i32, + pub color: Color32, + pub visible: bool, pub(crate) last_fetch: RwLock>, pub query_x: String, // pub(crate) compiled_query_x: Arc>, diff --git a/src/app/data/store.rs b/src/app/data/store.rs index 1d1e4fa..eebee11 100644 --- a/src/app/data/store.rs +++ b/src/app/data/store.rs @@ -1,8 +1,9 @@ -use crate::app::data::{Panel, Source}; +use crate::app::{data::{Panel, Source}, util::repack_color}; use chrono::{TimeZone, Utc}; -use eframe::egui::plot::Value; +use eframe::egui::{Color32, plot::Value}; use rusqlite::{params, Connection}; use std::sync::RwLock; +use crate::app::util::unpack_color; pub trait DataStorage { fn add_panel(&self, name: &str); @@ -19,12 +20,12 @@ impl SQLiteDataStore { conn.execute( "CREATE TABLE IF NOT EXISTS panels ( id INTEGER PRIMARY KEY, - name TEXT UNIQUE, - view_scroll BOOL, - view_size INT, - timeserie BOOL, - width INT, - height INT + name TEXT UNIQUE NOT NULL, + view_scroll BOOL NOT NULL, + view_size INT NOT NULL, + timeserie BOOL NOT NULL, + width INT NOT NULL, + height INT NOT NULL );", [], )?; @@ -32,12 +33,14 @@ impl SQLiteDataStore { conn.execute( "CREATE TABLE IF NOT EXISTS sources ( id INTEGER PRIMARY KEY, - name TEXT, - url TEXT, - interval INT, - query_x TEXT, - query_y TEXT, - panel_id INT + name TEXT NOT NULL, + url TEXT NOT NULL, + interval INT NOT NULL, + query_x TEXT NOT NULL, + query_y TEXT NOT NULL, + panel_id INT NOT NULL, + color INT NULL, + visible BOOL NOT NULL );", [], )?; @@ -45,10 +48,10 @@ impl SQLiteDataStore { conn.execute( "CREATE TABLE IF NOT EXISTS points ( id INTEGER PRIMARY KEY, - panel_id INT, - source_id INT, - x FLOAT, - y FLOAT + panel_id INT NOT NULL, + source_id INT NOT NULL, + x FLOAT NOT NULL, + y FLOAT NOT NULL );", [], )?; @@ -105,6 +108,8 @@ impl SQLiteDataStore { query_y: row.get(5)?, // compiled_query_y: Arc::new(Mutex::new(jq_rs::compile(row.get::(5)?.as_str()).unwrap())), // panel_id: row.get(6)?, + color: unpack_color(row.get(7).unwrap_or(0)), + visible: row.get(8)?, data: RwLock::new(Vec::new()), }) })?; @@ -127,10 +132,13 @@ impl SQLiteDataStore { url: &str, query_x: &str, query_y: &str, + color: Color32, + visible: bool, ) -> rusqlite::Result { + let color_u32 : Option = if color == Color32::TRANSPARENT { None } else { Some(repack_color(color)) }; self.conn.execute( - "INSERT INTO sources(name, url, interval, query_x, query_y, panel_id) VALUES (?, ?, ?, ?, ?, ?)", - params![name, url, 60, query_x, query_y, panel_id], + "INSERT INTO sources(name, url, interval, query_x, query_y, panel_id, color, visible) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + params![name, url, 60i32, query_x, query_y, panel_id, color_u32, visible], )?; let mut statement = self .conn @@ -141,17 +149,19 @@ impl SQLiteDataStore { name: row.get(1)?, url: row.get(2)?, interval: row.get(3)?, - query_x: row.get(4)?, - query_y: row.get(5)?, - // panel_id: row.get(6)?, last_fetch: RwLock::new(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)), + query_x: row.get(4)?, + // compiled_query_x: Arc::new(Mutex::new(jq_rs::compile(row.get::(4)?.as_str()).unwrap())), + query_y: row.get(5)?, + // compiled_query_y: Arc::new(Mutex::new(jq_rs::compile(row.get::(5)?.as_str()).unwrap())), + // panel_id: row.get(6)?, + color: unpack_color(row.get(7).unwrap_or(0)), + visible: row.get(8)?, data: RwLock::new(Vec::new()), }) })? { if let Ok(p) = panel { return Ok(p); - } else { - println!("WTF"); } } @@ -166,10 +176,13 @@ impl SQLiteDataStore { interval: i32, query_x: &str, query_y: &str, + color: Color32, + visible: bool, ) -> rusqlite::Result { + let color_u32 : Option = if color == Color32::TRANSPARENT { None } else { Some(repack_color(color)) }; self.conn.execute( - "UPDATE sources SET name = ?, url = ?, interval = ?, query_x = ?, query_y = ? WHERE id = ?", - params![name, url, interval, query_x, query_y, source_id], + "UPDATE sources SET name = ?, url = ?, interval = ?, query_x = ?, query_y = ?, color = ?, visible = ? WHERE id = ?", + params![name, url, interval, query_x, query_y, color_u32, visible, source_id], ) } diff --git a/src/app/mod.rs b/src/app/mod.rs index d933f57..83a1bfb 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -5,7 +5,7 @@ pub mod util; use std::sync::Arc; use chrono::Utc; use eframe::egui; -use eframe::egui::{plot::{Line, Plot}}; +use eframe::egui::{RichText, plot::{Line, Plot}, Color32}; use self::data::ApplicationState; use self::worker::native_save; @@ -18,6 +18,8 @@ struct InputBuffer { interval: i32, query_x: String, query_y: String, + color: Color32, + visible: bool, panel_id: i32, } @@ -30,6 +32,8 @@ impl Default for InputBuffer { interval: 60, query_x: "".to_string(), query_y: "".to_string(), + color: Color32::TRANSPARENT, + visible: true, panel_id: 0, } } @@ -84,7 +88,9 @@ impl eframe::App for App { } } ); + ui.checkbox(&mut self.input.visible, "visible"); ui.add(egui::Slider::new(&mut self.input.interval, 1..=60)); + ui.color_edit_button_srgba(&mut self.input.color); if ui.button("add").clicked() { self.data.add_source( self.input.panel_id, @@ -92,6 +98,8 @@ impl eframe::App for App { self.input.url.as_str(), self.input.query_x.as_str(), self.input.query_y.as_str(), + self.input.color, + self.input.visible, ).unwrap(); } ui.separator(); @@ -128,11 +136,19 @@ impl eframe::App for App { ui.horizontal(|ui| { ui.heading(panel.name.as_str()); ui.separator(); - if !self.edit { - for source in &*sources { - ui.label(source.name.as_str()); - ui.separator(); + for source in &mut *sources { + if self.edit { + ui.checkbox(&mut source.visible, ""); } + if source.visible { + ui.label( + RichText::new(source.name.as_str()) + .color(if source.color == Color32::TRANSPARENT { Color32::GRAY } else { source.color }) + ); + } else { + ui.label(RichText::new(source.name.as_str()).color(Color32::BLACK)); + } + ui.separator(); } ui.checkbox(&mut panel.view_scroll, "autoscroll"); ui.checkbox(&mut panel.timeserie, "timeserie"); @@ -150,11 +166,12 @@ impl eframe::App for App { for source in &mut *sources { ui.horizontal(|ui| { ui.add(egui::Slider::new(&mut source.interval, 1..=60)); - eframe::egui::TextEdit::singleline(&mut source.url).hint_text("url").desired_width(250.0).show(ui); + eframe::egui::TextEdit::singleline(&mut source.url).hint_text("url").desired_width(300.0).show(ui); if !panel.timeserie { - eframe::egui::TextEdit::singleline(&mut source.query_x).hint_text("x").desired_width(25.0).show(ui); + eframe::egui::TextEdit::singleline(&mut source.query_x).hint_text("x").desired_width(50.0).show(ui); } - eframe::egui::TextEdit::singleline(&mut source.query_y).hint_text("y").desired_width(25.0).show(ui); + eframe::egui::TextEdit::singleline(&mut source.query_y).hint_text("y").desired_width(50.0).show(ui); + ui.color_edit_button_srgba(&mut source.color); ui.separator(); ui.label(source.name.as_str()); }); @@ -185,10 +202,13 @@ impl eframe::App for App { p.show(ui, |plot_ui| { for source in &mut *sources { - if self.filter { - plot_ui.line(Line::new(source.values_filter((Utc::now().timestamp() - (panel.view_size as i64 * 60)) as f64)).name(source.name.as_str())); - } else { - plot_ui.line(Line::new(source.values()).name(source.name.as_str())); + if source.visible { + let line = if self.filter { + Line::new(source.values_filter((Utc::now().timestamp() - (panel.view_size as i64 * 60)) as f64)).name(source.name.as_str()) + } else { + Line::new(source.values()).name(source.name.as_str()) + }; + plot_ui.line(line.color(source.color)); } } }); diff --git a/src/app/util.rs b/src/app/util.rs index 4757afe..f9721b5 100644 --- a/src/app/util.rs +++ b/src/app/util.rs @@ -1,5 +1,6 @@ // if you're handling more than terabytes of data, it's the future and you ought to update this code! use chrono::{DateTime, Utc, NaiveDateTime}; +use eframe::egui::Color32; const PREFIXES: &'static [&'static str] = &["", "k", "M", "G", "T"]; @@ -20,4 +21,22 @@ pub fn timestamp_to_str(t:i64) -> String { DateTime::::from_utc(NaiveDateTime::from_timestamp(t, 0), Utc) .format("%Y/%m/%d %H:%M:%S") ) +} + +pub fn unpack_color(c: u32) -> Color32 { + let r : u8 = (c >> 0) as u8; + let g : u8 = (c >> 8) as u8; + let b : u8 = (c >> 16) as u8; + let a : u8 = (c >> 24) as u8; + return Color32::from_rgba_unmultiplied(r, g, b, a); +} + +pub fn repack_color(c: Color32) -> u32 { + let mut out : u32 = 0; + let mut offset = 0; + for el in c.to_array() { + out |= ((el & 0xFF) as u32) << offset; + offset += 8; + } + return out; } \ No newline at end of file diff --git a/src/app/worker.rs b/src/app/worker.rs index c73af3d..065ca36 100644 --- a/src/app/worker.rs +++ b/src/app/worker.rs @@ -26,6 +26,8 @@ pub fn native_save(state:Arc) { source.interval, source.query_x.as_str(), source.query_y.as_str(), + source.color, + source.visible, ).unwrap(); } }