2025-03-29 12:08:38 +02:00
|
|
|
use crate::app::graphs_window::DynamicIndexValue;
|
|
|
|
|
|
|
|
|
|
use super::{
|
|
|
|
|
plot::Plot,
|
|
|
|
|
stat::{StatType, StatsData},
|
|
|
|
|
GraphsWindowMsg,
|
|
|
|
|
};
|
|
|
|
|
use gtk::{
|
|
|
|
|
gdk,
|
|
|
|
|
glib::{subclass::types::ObjectSubclassIsExt, types::StaticType, value::ToValue},
|
|
|
|
|
prelude::{
|
|
|
|
|
AdjustmentExt, BoxExt, ButtonExt, CheckButtonExt, OrientableExt, PopoverExt, WidgetExt,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
use relm4::{
|
|
|
|
|
binding::{BoolBinding, F64Binding},
|
|
|
|
|
factory::positions::GridPosition,
|
|
|
|
|
prelude::{DynamicIndex, FactoryVecDeque},
|
|
|
|
|
RelmObjectExt, RelmWidgetExt,
|
|
|
|
|
};
|
|
|
|
|
use std::sync::{Arc, RwLock};
|
|
|
|
|
|
|
|
|
|
pub struct PlotComponent {
|
|
|
|
|
stats: FactoryVecDeque<StatTypeRow>,
|
|
|
|
|
plots_per_row: F64Binding,
|
|
|
|
|
data: Arc<RwLock<StatsData>>,
|
|
|
|
|
edit_mode: BoolBinding,
|
|
|
|
|
time_period: gtk::Adjustment,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct PlotComponentConfig {
|
|
|
|
|
pub selected_stats: Vec<StatType>,
|
|
|
|
|
pub data: Arc<RwLock<StatsData>>,
|
|
|
|
|
pub edit_mode: BoolBinding,
|
|
|
|
|
pub plots_per_row: F64Binding,
|
|
|
|
|
pub time_period: gtk::Adjustment,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum PlotComponentMsg {
|
|
|
|
|
Redraw,
|
|
|
|
|
UpdatedSelection,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[relm4::factory(pub)]
|
|
|
|
|
impl relm4::factory::FactoryComponent for PlotComponent {
|
|
|
|
|
type ParentWidget = gtk::Grid;
|
|
|
|
|
type Input = PlotComponentMsg;
|
|
|
|
|
type Output = GraphsWindowMsg;
|
|
|
|
|
type Init = PlotComponentConfig;
|
|
|
|
|
type Index = DynamicIndex;
|
|
|
|
|
type CommandOutput = ();
|
|
|
|
|
|
|
|
|
|
view! {
|
|
|
|
|
gtk::Frame {
|
|
|
|
|
gtk::Box {
|
|
|
|
|
set_orientation: gtk::Orientation::Vertical,
|
|
|
|
|
set_spacing: 5,
|
|
|
|
|
|
|
|
|
|
#[name = "plot"]
|
|
|
|
|
append = &Plot {
|
|
|
|
|
set_data: self.data.clone(),
|
|
|
|
|
set_margin_all: 5,
|
|
|
|
|
|
|
|
|
|
#[watch]
|
|
|
|
|
set_cursor: self.get_cursor().as_ref(),
|
|
|
|
|
#[watch]
|
|
|
|
|
set_time_period_seconds: self.time_period.value() as i64,
|
|
|
|
|
|
|
|
|
|
#[wrap(Clone::clone)]
|
|
|
|
|
add_controller = >k::DragSource {
|
|
|
|
|
#[watch]
|
|
|
|
|
set_actions: if self.edit_mode.value() { gdk::DragAction::MOVE } else { gdk::DragAction::empty() },
|
|
|
|
|
|
|
|
|
|
connect_prepare[plot, index, edit_mode = self.edit_mode.clone()] => move |drag_source, _, _| {
|
|
|
|
|
if edit_mode.value() {
|
|
|
|
|
if let Some(texture) = plot.imp().get_last_texture() {
|
|
|
|
|
drag_source.set_icon(Some(&texture), 0, 0);
|
|
|
|
|
}
|
|
|
|
|
Some(gdk::ContentProvider::for_value(&DynamicIndexValue(index.clone()).to_value()))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
add_controller = gtk::DropTarget {
|
|
|
|
|
set_actions: gdk::DragAction::MOVE,
|
|
|
|
|
set_types: &[DynamicIndexValue::static_type()],
|
|
|
|
|
|
|
|
|
|
connect_enter[root] => move |_, _, _| {
|
|
|
|
|
root.set_opacity(0.5);
|
|
|
|
|
gdk::DragAction::MOVE
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
connect_leave[root] => move |_| {
|
|
|
|
|
root.set_opacity(1.0);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
connect_drop[root, index, sender] => move |_, value, _, _| {
|
|
|
|
|
root.set_opacity(1.0);
|
|
|
|
|
|
|
|
|
|
if let Ok(DynamicIndexValue(source_index)) = value.get::<DynamicIndexValue>() {
|
|
|
|
|
sender.output(GraphsWindowMsg::SwapPlots(index.clone(), source_index)).unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
true
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
gtk::Box {
|
|
|
|
|
set_orientation: gtk::Orientation::Horizontal,
|
|
|
|
|
set_spacing: 5,
|
|
|
|
|
#[watch]
|
|
|
|
|
set_visible: self.edit_mode.value(),
|
|
|
|
|
set_align: gtk::Align::End,
|
|
|
|
|
|
|
|
|
|
append = >k::MenuButton {
|
|
|
|
|
set_icon_name: "view-list-symbolic",
|
|
|
|
|
set_tooltip: "Edit graph sensors",
|
|
|
|
|
|
|
|
|
|
#[wrap(Some)]
|
|
|
|
|
set_popover = >k::Popover {
|
|
|
|
|
#[wrap(Some)]
|
|
|
|
|
set_child = >k::ScrolledWindow {
|
|
|
|
|
set_policy: (gtk::PolicyType::Never, gtk::PolicyType::Automatic),
|
|
|
|
|
set_propagate_natural_height: true,
|
|
|
|
|
set_max_content_height: 200,
|
|
|
|
|
|
|
|
|
|
self.stats.widget() {
|
|
|
|
|
set_orientation: gtk::Orientation::Vertical,
|
|
|
|
|
set_margin_all: 10,
|
|
|
|
|
}
|
|
|
|
|
} ,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
append = >k::Button {
|
|
|
|
|
set_icon_name: "edit-delete-symbolic",
|
|
|
|
|
set_tooltip: "Delete graph",
|
|
|
|
|
|
|
|
|
|
connect_clicked[sender, index] => move |_| {
|
|
|
|
|
sender.output(GraphsWindowMsg::RemovePlot(index.clone())).unwrap();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn init_model(
|
|
|
|
|
init: Self::Init,
|
|
|
|
|
_index: &Self::Index,
|
|
|
|
|
sender: relm4::FactorySender<Self>,
|
|
|
|
|
) -> Self {
|
|
|
|
|
let mut stats = FactoryVecDeque::builder()
|
|
|
|
|
.launch_default()
|
|
|
|
|
.forward(sender.input_sender(), |msg| msg);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let mut stats_guard = stats.guard();
|
|
|
|
|
let data_guard = init.data.read().unwrap();
|
|
|
|
|
|
|
|
|
|
for stat in data_guard.list_stats() {
|
|
|
|
|
let enabled = init.selected_stats.contains(stat);
|
|
|
|
|
stats_guard.push_back((stat.clone(), enabled));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sender.input(PlotComponentMsg::UpdatedSelection);
|
|
|
|
|
|
|
|
|
|
Self {
|
|
|
|
|
stats,
|
|
|
|
|
plots_per_row: init.plots_per_row,
|
|
|
|
|
data: init.data,
|
|
|
|
|
edit_mode: init.edit_mode,
|
|
|
|
|
time_period: init.time_period,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn update_with_view(
|
|
|
|
|
&mut self,
|
|
|
|
|
widgets: &mut Self::Widgets,
|
|
|
|
|
msg: Self::Input,
|
|
|
|
|
sender: relm4::FactorySender<Self>,
|
|
|
|
|
) {
|
|
|
|
|
match msg {
|
|
|
|
|
PlotComponentMsg::Redraw => {
|
|
|
|
|
widgets.plot.set_dirty(true);
|
|
|
|
|
widgets.plot.queue_draw();
|
|
|
|
|
}
|
|
|
|
|
PlotComponentMsg::UpdatedSelection => {
|
|
|
|
|
widgets.plot.set_stats(self.selected_stats());
|
|
|
|
|
sender.input(PlotComponentMsg::Redraw);
|
2025-04-04 22:46:41 +03:00
|
|
|
sender.output(GraphsWindowMsg::SaveConfig).unwrap();
|
2025-03-29 12:08:38 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.update_view(widgets, sender);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl relm4::factory::Position<GridPosition, DynamicIndex> for PlotComponent {
|
|
|
|
|
fn position(&self, index: &DynamicIndex) -> GridPosition {
|
|
|
|
|
let i = index.current_index() as i32;
|
|
|
|
|
let plots_per_row = self.plots_per_row.value() as i32;
|
|
|
|
|
GridPosition {
|
|
|
|
|
column: i % plots_per_row,
|
|
|
|
|
row: i / plots_per_row,
|
|
|
|
|
width: 1,
|
|
|
|
|
height: 1,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PlotComponent {
|
|
|
|
|
fn get_cursor(&self) -> Option<gdk::Cursor> {
|
|
|
|
|
if self.edit_mode.value() {
|
|
|
|
|
gdk::Cursor::from_name("move", None)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn selected_stats(&self) -> Vec<StatType> {
|
|
|
|
|
self.stats
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|row| row.enabled.value())
|
|
|
|
|
.map(|row| row.stat.clone())
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn into_config(self) -> PlotComponentConfig {
|
|
|
|
|
PlotComponentConfig {
|
|
|
|
|
selected_stats: self.selected_stats(),
|
|
|
|
|
data: self.data,
|
|
|
|
|
edit_mode: self.edit_mode,
|
|
|
|
|
plots_per_row: self.plots_per_row,
|
|
|
|
|
time_period: self.time_period,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct StatTypeRow {
|
|
|
|
|
stat: StatType,
|
|
|
|
|
enabled: BoolBinding,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[relm4::factory]
|
|
|
|
|
impl relm4::factory::FactoryComponent for StatTypeRow {
|
|
|
|
|
type ParentWidget = gtk::Box;
|
|
|
|
|
type Init = (StatType, bool);
|
|
|
|
|
type Input = ();
|
|
|
|
|
type Output = PlotComponentMsg;
|
|
|
|
|
type CommandOutput = ();
|
|
|
|
|
|
|
|
|
|
view! {
|
|
|
|
|
gtk::CheckButton {
|
|
|
|
|
add_binding: (&self.enabled, "active"),
|
|
|
|
|
set_label: Some(&self.stat.display()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn init_model(
|
|
|
|
|
(stat, enabled): Self::Init,
|
|
|
|
|
_index: &Self::Index,
|
|
|
|
|
sender: relm4::FactorySender<Self>,
|
|
|
|
|
) -> Self {
|
|
|
|
|
let enabled = BoolBinding::new(enabled);
|
|
|
|
|
|
|
|
|
|
enabled.connect_value_notify(move |_| {
|
|
|
|
|
sender.output(PlotComponentMsg::UpdatedSelection).unwrap();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Self { stat, enabled }
|
|
|
|
|
}
|
|
|
|
|
}
|