radar-g/src/components/monitor/sidebar/sidebar.rs
2024-03-20 18:38:24 +08:00

378 lines
12 KiB
Rust

use crate::actions::*;
use crate::widgets::AssoElement;
use abi_stable::type_level::trait_marker::Hash;
use chrono::{DateTime, Utc};
use glib_macros::clone;
use gtk::glib;
use gtk::prelude::WidgetExt;
use gtk::prelude::*;
use relm4::actions::{AccelsPlus, RelmAction};
use relm4::{
binding::{Binding, U8Binding},
factory::{DynamicIndex, FactoryComponent, FactorySender, FactoryVecDeque},
gtk::gio,
prelude::*,
typed_view::{
column::TypedColumnView,
list::{RelmListItem, TypedListView},
},
RelmObjectExt,
};
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use crate::components::app::AppMsg;
use crate::{
chart::Chart,
data::Npz,
widgets::render::{predefined::color_mapper::BoundaryNorm, Layer},
};
use super::{
bottom_bar::BottomBarModel,
meta_data_list::{InfoColumn, MyListItem, TagColumn},
};
pub struct SideBarModel {
layers: Rc<RefCell<Vec<Layer>>>,
selected_layer_idx: usize,
counter: u8,
list_view_wrapper: TypedListView<LayerItem, gtk::MultiSelection>,
bottom_bar_vec: FactoryVecDeque<BottomBarModel>,
meta_list_view: TypedColumnView<MyListItem, gtk::NoSelection>,
}
#[derive(Debug)]
pub enum SideBarInputMsg {
AddMetaItems(HashMap<String, String>),
ClearMetaItems,
RefreshList,
None,
}
#[derive(Debug)]
pub enum SideBarOutputMsg {
SelectLayer(usize),
NewLayer(Layer),
SwitchToTimeSeries(usize),
}
#[relm4::component(pub)]
impl SimpleComponent for SideBarModel {
type Init = Rc<RefCell<Vec<Layer>>>;
type Output = SideBarOutputMsg;
type Input = SideBarInputMsg;
view! {
#[root]
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 5,
set_margin_all: 5,
gtk::Paned{
set_orientation: gtk::Orientation::Vertical,
set_position: 200,
#[wrap(Some)]
set_start_child = &gtk::Box{
set_orientation: gtk::Orientation::Vertical,
set_spacing: 5,
gtk::Frame{
add_css_class: "rb",
#[name="meta_panel"]
gtk::Notebook::builder().vexpand(true).hexpand(true).build() -> gtk::Notebook{}
},
},
#[wrap(Some)]
set_end_child=&gtk::Box{
set_orientation: gtk::Orientation::Vertical,
set_vexpand: true,
set_hexpand: true,
#[name="bottom_panel"]
gtk::Notebook::builder().vexpand(true).build() -> gtk::Notebook{
set_margin_top: 10,
set_margin_bottom: 5
},
#[local_ref]
counter_box -> gtk::Box{
set_spacing: 5,
}
}
}
},
layer_page = gtk::ScrolledWindow::builder()
.vexpand(true)
.hexpand(true)
.build() -> gtk::ScrolledWindow{
#[wrap(Some)]
#[local_ref]
set_child=my_view -> gtk::ListView{
},
set_margin_horizontal:5,
set_margin_vertical:3
},
#[local_ref]
meta_view -> gtk::ColumnView{
set_hexpand:true,
set_vexpand:true,
set_show_column_separators: true,
set_show_row_separators: true,
set_enable_rubberband:true,
set_reorderable:false,
},
bottom_panel.append_page(&layer_page, Some(&gtk::Label::new(Some("Layers")))),
meta_panel.append_page(meta_view, Some(&gtk::Label::new(Some("Meta")))),
meta_panel.append_page(&Chart::new(), Some(&gtk::Label::new(Some("Chart")))),
#[local_ref]
info_c -> gtk::ColumnViewColumn{
set_expand: true
}
}
fn init(
init: Self::Init,
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
// Initialize the ListView wrapper
let mut list_view_wrapper: TypedListView<LayerItem, gtk::MultiSelection> =
TypedListView::with_sorting();
list_view_wrapper.selection_model.connect_selection_changed(
clone!(@strong sender => move |s,_, _| {
let selection = s.selection();
println!("selection changed: {:?}", selection);
}),
);
// let mut bottom_bar_vec = FactoryVecDeque::new(gtk::Box::default(), sender.input_sender());
let mut bottom_bar_vec =
FactoryVecDeque::builder()
.launch_default()
.forward(sender.input_sender(), |msg| match msg {
_ => SideBarInputMsg::None,
});
let app = relm4::main_application();
{
let mut bottom_bar_vec_guard = bottom_bar_vec.guard();
bottom_bar_vec_guard.push_back(BottomBarModel::new("add-filled".to_string()));
bottom_bar_vec_guard.push_back(BottomBarModel::new("delete-filled".to_string()));
bottom_bar_vec_guard.push_back(BottomBarModel::new("chevron-up-filled".to_string()));
bottom_bar_vec_guard.push_back(BottomBarModel::new("chevron-down-filled".to_string()));
}
let mut meta_list_view = TypedColumnView::new();
meta_list_view.append_column::<TagColumn>();
meta_list_view.append_column::<InfoColumn>();
let mut model = SideBarModel {
meta_list_view,
layers: init,
selected_layer_idx: 0,
counter: 0,
list_view_wrapper,
bottom_bar_vec,
};
let my_view = &model.list_view_wrapper.view;
let counter_box = model.bottom_bar_vec.widget();
let meta_view = &model.meta_list_view.view;
let columns = model.meta_list_view.get_columns();
let info_c = columns.get("info").unwrap();
let widgets = view_output!();
{
let mut list = model
.layers
.borrow()
.iter()
.enumerate()
.map(|(idx, v)| {
LayerItem::new(
idx as u32,
v.name.clone(),
v.visiable,
v.get_thumbnail(),
match v.get_associated_element() {
AssoElement::TimeSeries(_) => LayerStatus::BindToTime(Utc::now()),
AssoElement::Instant(_) => LayerStatus::Instance,
_ => LayerStatus::Instance,
},
)
})
.collect::<Vec<_>>();
model.list_view_wrapper.extend_from_iter(list);
}
ComponentParts { model, widgets }
}
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
match message {
SideBarInputMsg::RefreshList => {
let mut list = self
.layers
.borrow()
.iter()
.enumerate()
.map(|(idx, v)| {
LayerItem::new(
idx as u32,
v.name.clone(),
v.visiable,
v.get_thumbnail(),
match v.get_associated_element() {
AssoElement::TimeSeries(_) => LayerStatus::BindToTime(Utc::now()),
AssoElement::Instant(_) => LayerStatus::Instance,
_ => LayerStatus::Instance,
},
)
})
.collect::<Vec<_>>();
self.list_view_wrapper.clear();
self.list_view_wrapper.extend_from_iter(list);
}
SideBarInputMsg::AddMetaItems(hs) => {
for (k, v) in hs {
self.meta_list_view.append(MyListItem::new(k, v));
}
}
SideBarInputMsg::ClearMetaItems => {
self.meta_list_view.clear();
}
_ => {}
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum LayerStatus {
BindToTime(DateTime<Utc>),
Instance,
BindToOtherLayer(usize),
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct LayerItem {
key: u32,
layer_name: String,
visiable: bool,
status: LayerStatus,
img: Option<gtk::gdk::Texture>,
}
impl LayerItem {
fn new(
key: u32,
name: String,
visiable: bool,
img: Option<gtk::gdk::Texture>,
status: LayerStatus,
) -> Self {
Self {
key,
layer_name: name,
visiable,
status,
img,
}
}
}
struct Widgets {
label: gtk::Label,
screen_shot: gtk::Image,
status: gtk::Label,
button: gtk::CheckButton,
menu: gtk::PopoverMenu,
}
impl RelmListItem for LayerItem {
type Root = gtk::Box;
type Widgets = Widgets;
fn setup(_item: &gtk::ListItem) -> (gtk::Box, Widgets) {
let position = _item.position() as u8;
relm4::menu! {
main_menu: {
custom: "MyWidget",
"Remove" => RemoveLayerAction,
section!{
"test" => RemoveLayerAction,
"select" => AddLayerAction
}
}
}
relm4::view! {
my_box = gtk::Box {
gtk::Frame{
set_margin_end: 10,
#[name = "screen_shot"]
gtk::Image{
set_size_request: (65, 40),
}
},
#[name = "label"]
gtk::Label{
set_halign: gtk::Align::Start,
},
#[name = "status"]
gtk::Label{
set_halign: gtk::Align::Start
},
gtk::Label{
set_hexpand: true,
},
#[name = "button"]
gtk::CheckButton{
set_halign: gtk::Align::End,
},
#[name = "menu"]
gtk::PopoverMenu::from_model(Some(&main_menu)){}
}
}
let widgets = Widgets {
screen_shot,
label,
status,
button,
menu,
};
(my_box, widgets)
}
fn bind(&mut self, widgets: &mut Self::Widgets, _root: &mut Self::Root) {
let Widgets {
label,
button,
screen_shot,
status,
menu,
} = widgets;
status.set_label(&match self.status {
LayerStatus::BindToTime(t) => format!("Bind To Time: {}", t),
LayerStatus::Instance => "Instance".to_string(),
LayerStatus::BindToOtherLayer(idx) => format!("Bind To Layer: {}", idx),
});
let gesture_click = gtk::GestureClick::new();
gesture_click.set_button(gtk::gdk::BUTTON_SECONDARY);
screen_shot.set_paintable(self.img.as_ref());
let menu = menu.clone();
gesture_click.connect_released(clone!(@weak menu => move |gesture_click, _, x, y| {
menu.set_pointing_to(Some(&gtk::gdk::Rectangle::new(x as i32, y as i32, 1, 1)));
menu.popup();
}));
_root.add_controller(gesture_click);
label.set_label(&self.layer_name);
button.set_active(self.visiable);
}
}