diff --git a/frontend/src/main.rs b/frontend/src/main.rs index b0fbfdf..0db9ce7 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -63,17 +63,19 @@ fn root() -> impl Element { hierarchy.clone(), selected_var_refs.clone(), layout.clone(), - loaded_filename, + loaded_filename.clone(), )) .item_signal({ let hierarchy = hierarchy.clone(); let selected_var_refs = selected_var_refs.clone(); + let loaded_filename = loaded_filename.clone(); map_ref!{ let layout = layout.signal(), let hierarchy_is_some = hierarchy.signal_ref(Option::is_some) => { - (*hierarchy_is_some && matches!(layout, Layout::Tree)).then(clone!((hierarchy, selected_var_refs) move || WaveformPanel::new( + (*hierarchy_is_some && matches!(layout, Layout::Tree)).then(clone!((hierarchy, selected_var_refs, loaded_filename) move || WaveformPanel::new( hierarchy.clone(), selected_var_refs.clone(), + loaded_filename.clone(), ))) } } @@ -83,9 +85,10 @@ fn root() -> impl Element { map_ref!{ let layout = layout.signal(), let hierarchy_is_some = hierarchy.signal_ref(Option::is_some) => { - (*hierarchy_is_some && matches!(layout, Layout::Columns)).then(clone!((hierarchy, selected_var_refs) move || WaveformPanel::new( + (*hierarchy_is_some && matches!(layout, Layout::Columns)).then(clone!((hierarchy, selected_var_refs, loaded_filename) move || WaveformPanel::new( hierarchy.clone(), selected_var_refs.clone(), + loaded_filename.clone(), ))) } } diff --git a/frontend/src/waveform_panel.rs b/frontend/src/waveform_panel.rs index c0e6920..f9f42e6 100644 --- a/frontend/src/waveform_panel.rs +++ b/frontend/src/waveform_panel.rs @@ -1,4 +1,4 @@ -use crate::{platform, script_bridge}; +use crate::{platform, script_bridge, Filename}; use std::sync::Arc; use wellen::GetItem; use zoon::*; @@ -13,6 +13,7 @@ const ROW_GAP: u32 = 4; pub struct WaveformPanel { selected_var_refs: MutableVec, hierarchy: Mutable>>, + loaded_filename: Mutable>, canvas_controller: Mutable>>, } @@ -20,10 +21,12 @@ impl WaveformPanel { pub fn new( hierarchy: Mutable>>, selected_var_refs: MutableVec, + loaded_filename: Mutable>, ) -> impl Element { Self { selected_var_refs, hierarchy, + loaded_filename, canvas_controller: Mutable::new(Mutable::default().read_only()), } .root() @@ -167,6 +170,9 @@ impl WaveformPanel { fn save_selected_vars_button(&self) -> impl Element { let (hovered, hovered_signal) = Mutable::new_and_signal(false); + let loaded_filename = self.loaded_filename.clone(); + let selected_var_refs = self.selected_var_refs.clone(); + let hierarchy = self.hierarchy.clone(); Button::new() .s(Padding::new().x(20).y(10)) .s(Background::new().color_signal( @@ -175,7 +181,34 @@ impl WaveformPanel { .s(RoundedCorners::all(15)) .label("Save") .on_hovered_change(move |is_hovered| hovered.set_neq(is_hovered)) - .on_press(move || zoon::println!("SAVE!")) + .on_press(move || { + let loaded_filename = loaded_filename.get_cloned().unwrap_throw(); + let file_name = format!("{}_vars.fw.js", loaded_filename.replace('.', "_")); + + let hierarchy = hierarchy.get_cloned().unwrap_throw(); + let mut full_var_names = Vec::new(); + for var_ref in selected_var_refs.lock_ref().as_slice() { + let var = hierarchy.get(*var_ref); + let var_name = var.full_name(&hierarchy); + full_var_names.push(format!("\"{var_name}\"")); + } + let full_var_names_string = full_var_names.join(",\n\t\t"); + let file_content = include_str!("waveform_panel/template_vars.px.js") + .replacen("{LOADED_FILENAME}", &loaded_filename, 1) + .replacen("{FULL_VAR_NAMES}", &full_var_names_string, 1); + + // @TODO we need to use ugly code with temp anchor element until (if ever) + // `showSaveFilePicker` is supported in Safari and Firefox (https://caniuse.com/?search=showSaveFilePicker) + let file = gloo_file::File::new(&file_name, file_content.as_str()); + let file_object_url = gloo_file::ObjectUrl::from(file); + let a = document().create_element("a").unwrap_throw(); + a.set_attribute("href", &file_object_url).unwrap_throw(); + a.set_attribute("download", &file_name).unwrap_throw(); + a.set_attribute("style", "display: none;").unwrap_throw(); + dom::body().append_child(&a).unwrap_throw(); + a.unchecked_ref::().click(); + a.remove(); + }) } // @TODO autoscroll down diff --git a/frontend/src/waveform_panel/template_vars.px.js b/frontend/src/waveform_panel/template_vars.px.js new file mode 100644 index 0000000..784460c --- /dev/null +++ b/frontend/src/waveform_panel/template_vars.px.js @@ -0,0 +1,5 @@ +if (FW.loaded_filename() === "{LOADED_FILENAME}") { + FW.select_vars([ + {FULL_VAR_NAMES} + ]) +} diff --git a/test_files/simple_vcd.fw.js b/test_files/simple_vcd_vars.fw.js similarity index 67% rename from test_files/simple_vcd.fw.js rename to test_files/simple_vcd_vars.fw.js index 999095c..4552b94 100644 --- a/test_files/simple_vcd.fw.js +++ b/test_files/simple_vcd_vars.fw.js @@ -1,7 +1,7 @@ if (FW.loaded_filename() === "simple.vcd") { FW.select_vars([ "simple_tb.s.A", - "simple_tb.s.A", - "simple_tb.s.B" + "simple_tb.s.A", + "simple_tb.s.B" ]) } diff --git a/test_files/wave_27_fst_vars.fw.js b/test_files/wave_27_fst_vars.fw.js new file mode 100644 index 0000000..fde21b8 --- /dev/null +++ b/test_files/wave_27_fst_vars.fw.js @@ -0,0 +1,8 @@ +if (FW.loaded_filename() === "wave_27.fst") { + FW.select_vars([ + "TOP.LsuPlugin_logic_bus_rsp_payload_error", + "TOP.LsuPlugin_logic_bus_rsp_payload_data", + "TOP.VexiiRiscv.integer_RegFilePlugin_logic_regfile_fpga.ramAsyncMwMux_1.io_writes_0_payload_data", + "TOP.VexiiRiscv.EmbeddedRiscvJtag_logic_onDebugCd_dmiDirect_logic.logic_jtagLogic_dmiStat_value_string" + ]) +}