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>>, selected_layer_idx: usize, counter: u8, list_view_wrapper: TypedListView, bottom_bar_vec: FactoryVecDeque, meta_list_view: TypedColumnView, } #[derive(Debug)] pub enum SideBarInputMsg { AddMetaItems(HashMap), ClearMetaItems, RefreshList, None, } #[derive(Debug)] pub enum SideBarOutputMsg { SelectLayer(usize), NewLayer(Layer), SwitchToTimeSeries(usize), } #[relm4::component(pub)] impl SimpleComponent for SideBarModel { type Init = Rc>>; 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 = >k::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=>k::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(>k::Label::new(Some("Layers")))), meta_panel.append_page(meta_view, Some(>k::Label::new(Some("Meta")))), meta_panel.append_page(&Chart::new(), Some(>k::Label::new(Some("Chart")))), #[local_ref] info_c -> gtk::ColumnViewColumn{ set_expand: true } } fn init( init: Self::Init, root: Self::Root, sender: ComponentSender, ) -> ComponentParts { // Initialize the ListView wrapper let mut list_view_wrapper: TypedListView = 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::(); meta_list_view.append_column::(); 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::>(); model.list_view_wrapper.extend_from_iter(list); } ComponentParts { model, widgets } } fn update(&mut self, message: Self::Input, sender: ComponentSender) { 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::>(); 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), Instance, BindToOtherLayer(usize), } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] struct LayerItem { key: u32, layer_name: String, visiable: bool, status: LayerStatus, img: Option, } impl LayerItem { fn new( key: u32, name: String, visiable: bool, img: Option, 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: >k::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(>k::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); } }