diff --git a/frontend/src/controls_panel.rs b/frontend/src/controls_panel.rs index 1eabc14..2e8f723 100644 --- a/frontend/src/controls_panel.rs +++ b/frontend/src/controls_panel.rs @@ -81,10 +81,12 @@ impl ControlsPanel { .map(|layout| matches!(layout, Layout::Columns)) .map_true(|| Width::fill()), )) - .s(Height::with_signal_self(layout.signal().map(move |layout| match layout { - Layout::Tree => Height::fill(), - Layout::Columns => Height::fill().max(MILLER_COLUMN_MAX_HEIGHT), - }))) + .s(Height::with_signal_self(layout.signal().map( + move |layout| match layout { + Layout::Tree => Height::fill(), + Layout::Columns => Height::fill().max(MILLER_COLUMN_MAX_HEIGHT), + }, + ))) .s(Scrollbars::both()) .s(Padding::all(20)) .s(Gap::new().y(40)) @@ -400,7 +402,7 @@ impl ControlsPanel { ) .on_hovered_change(move |is_hovered| hovered.set_neq(is_hovered)) .on_press(move || match layout.get() { - Layout::Tree => { + Layout::Tree => { if scope_for_ui.expanded.get() { scope_for_ui.selected_scope_in_level.set(None); } else { diff --git a/frontend/src/waveform_panel.rs b/frontend/src/waveform_panel.rs index 08b6873..ad090a0 100644 --- a/frontend/src/waveform_panel.rs +++ b/frontend/src/waveform_panel.rs @@ -1,6 +1,6 @@ use crate::{tauri_bridge, HierarchyAndTimeTable}; use wellen::GetItem; -use zoon::*; +use zoon::{eprintln, *}; mod pixi_canvas; use pixi_canvas::{PixiCanvas, PixiController}; @@ -29,7 +29,7 @@ impl WaveformPanel { fn root(&self) -> impl Element { let selected_vars_panel_height_getter: Mutable = <_>::default(); Row::new() - .s(Padding::all(20).left(0)) + .s(Padding::all(20)) .s(Scrollbars::y_and_clip_x()) .s(Width::growable()) .s(Height::fill()) @@ -67,7 +67,7 @@ impl WaveformPanel { })).for_each(clone!((controller, hierarchy_and_time_table) move |vec_diff| { clone!((controller, hierarchy_and_time_table) async move { match vec_diff { - VecDiff::Replace { values } => { + VecDiff::Replace { values } => { let controller = controller.wait_for_some_cloned().await; controller.clear_vars(); for var_ref in values { @@ -104,23 +104,38 @@ impl WaveformPanel { } async fn push_var( - controller: &PixiController, + controller: &PixiController, hierarchy_and_time_table: &Mutable>, var_ref: wellen::VarRef, ) { let (hierarchy, time_table) = hierarchy_and_time_table.get_cloned().unwrap(); + if time_table.is_empty() { + eprintln!("timetable is empty"); + return; + } + let last_time = time_table.last().copied().unwrap_throw(); + let var = hierarchy.get(var_ref); let signal_ref = var.signal_ref(); let signal = tauri_bridge::load_and_get_signal(signal_ref).await; let timescale = hierarchy.timescale(); + // @TODO remove zoon::println!("{timescale:?}"); - // Note: Sync `timeline`'s type with the `Timeline` in `frontend/typescript/pixi_canvas/pixi_canvas.ts' - let mut timeline: Vec<(wellen::Time, Option)> = time_table.iter().map(|time| (*time, None)).collect(); - for (time_index, signal_value) in signal.iter_changes() { - timeline[time_index as usize].1 = Some(signal_value.to_string()); + let mut timeline: Vec<(wellen::Time, String)> = signal + .iter_changes() + .map(|(time_index, signal_value)| { + (time_table[time_index as usize], signal_value.to_string()) + }) + .collect(); + if timeline.is_empty() { + eprintln!("timeline is empty"); + return; } + timeline.push((last_time, timeline.last().cloned().unwrap_throw().1)); + + // Note: Sync `timeline`'s type with the `Timeline` in `frontend/typescript/pixi_canvas/pixi_canvas.ts' controller.push_var(serde_wasm_bindgen::to_value(&timeline).unwrap_throw()); } diff --git a/frontend/src/waveform_panel/pixi_canvas.rs b/frontend/src/waveform_panel/pixi_canvas.rs index 6ffcd2a..52a4c5a 100644 --- a/frontend/src/waveform_panel/pixi_canvas.rs +++ b/frontend/src/waveform_panel/pixi_canvas.rs @@ -1,5 +1,5 @@ -use zoon::*; pub use js_bridge::PixiController; +use zoon::*; pub struct PixiCanvas { raw_el: RawHtmlEl, diff --git a/frontend/typescript/bundles/pixi_canvas.js b/frontend/typescript/bundles/pixi_canvas.js index 114a541..5ab8f18 100644 --- a/frontend/typescript/bundles/pixi_canvas.js +++ b/frontend/typescript/bundles/pixi_canvas.js @@ -35183,22 +35183,30 @@ var PixiController = class { } }; var VarSignalRow = class { - timeline; app; + timeline; + last_time; + formatter; + timeline_for_ui; owner; index_in_owner; rows_container; row_height; row_gap; row_height_with_gap; - renderer_resize_callback = () => this.draw(); + renderer_resize_callback = () => this.redraw_on_canvas_resize(); // -- elements -- row_container = new Container(); signal_blocks_container = new Container(); constructor(timeline, app, owner, rows_container, row_height, row_gap) { - console.log("VarSignalRow timeline:", timeline); - this.timeline = timeline; this.app = app; + this.timeline = timeline; + this.last_time = timeline[timeline.length - 1][0]; + this.formatter = (signal_value) => parseInt(signal_value, 2).toString(16); + this.timeline_for_ui = this.timeline.map(([time, value]) => { + const x2 = time / this.last_time * this.app.screen.width; + return [x2, this.formatter(value)]; + }); this.row_height = row_height; this.row_gap = row_gap; this.row_height_with_gap = row_height + row_gap; @@ -35206,46 +35214,59 @@ var VarSignalRow = class { this.owner = owner; this.owner.push(this); this.rows_container = rows_container; - this.create_element_tree(); this.draw(); this.app.renderer.on("resize", this.renderer_resize_callback); } - create_element_tree() { + draw() { this.row_container.y = this.index_in_owner * this.row_height_with_gap; this.rows_container.addChild(this.row_container); this.row_container.addChild(this.signal_blocks_container); + const label_style = new TextStyle({ + align: "center", + fill: "White", + fontSize: 16, + fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"' + }); + this.timeline_for_ui.forEach(([x2, value], index) => { + if (index == this.timeline_for_ui.length - 1) { + return; + } + const block_width = this.timeline_for_ui[index + 1][0] - x2; + const block_height = this.row_height; + const signal_block = new Container(); + signal_block.x = x2; + this.signal_blocks_container.addChild(signal_block); + const background = new Graphics().roundRect(0, 0, block_width, block_height, 15).fill("SlateBlue"); + background.label = "background"; + signal_block.addChild(background); + const label = new Text({ text: value, style: label_style }); + label.x = (block_width - label.width) / 2; + label.y = (block_height - label.height) / 2; + label.visible = label.width < block_width; + label.label = "label"; + signal_block.addChild(label); + }); } - draw() { - if (this.timeline.length > 0) { - const last_time = this.timeline[this.timeline.length - 1][0]; - const formatter = (signal_value) => parseInt(signal_value, 2).toString(16); - const timeline = this.timeline.map(([time, value]) => { - const x2 = time / last_time * this.app.screen.width; - const formatted_value = typeof value === "string" ? formatter(value) : void 0; - return [x2, formatted_value]; - }); - this.signal_blocks_container.removeChildren(); - timeline.forEach(([x2, value], index) => { - if (typeof value === "string") { - const block_width = timeline[index + 1][0] - x2; - const block_height = this.row_height; - const signal_block = new Container({ x: x2 }); - this.signal_blocks_container.addChild(signal_block); - let background = new Graphics().roundRect(0, 0, block_width, block_height, 15).fill("SlateBlue"); - signal_block.addChild(background); - let style = new TextStyle({ - align: "center", - fill: "White", - fontSize: 16, - fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"' - }); - let label = new Text({ text: value, style }); - label.x = (block_width - label.width) / 2; - label.y = (block_height - label.height) / 2; - signal_block.addChild(label); - } - }); + redraw_on_canvas_resize() { + for (let index = 0; index < this.timeline_for_ui.length; index++) { + const x2 = this.timeline[index][0] / this.last_time * this.app.screen.width; + this.timeline_for_ui[index][0] = x2; } + this.timeline_for_ui.forEach(([x2, _value], index) => { + if (index == this.timeline_for_ui.length - 1) { + return; + } + const block_width = this.timeline_for_ui[index + 1][0] - x2; + const block_height = this.row_height; + const signal_block = this.signal_blocks_container.getChildAt(index); + signal_block.x = x2; + const background = signal_block.getChildByLabel("background"); + background.width = block_width; + const label = signal_block.getChildByLabel("label"); + label.x = (block_width - label.width) / 2; + label.y = (block_height - label.height) / 2; + label.visible = label.width < block_width; + }); } decrement_index() { this.index_in_owner--; diff --git a/frontend/typescript/pixi_canvas/pixi_canvas.ts b/frontend/typescript/pixi_canvas/pixi_canvas.ts index e23feda..97bd6fc 100644 --- a/frontend/typescript/pixi_canvas/pixi_canvas.ts +++ b/frontend/typescript/pixi_canvas/pixi_canvas.ts @@ -2,7 +2,10 @@ import { Application, Text, Graphics, Container, TextStyle } from "pixi.js"; type Time = number; type BitString = string; -type Timeline = Array<[Time, BitString | undefined]>; +type Timeline = Array<[Time, BitString]>; + +type X = number; +type TimelineForUI = Array<[X, string]>; export class PixiController { app: Application @@ -72,15 +75,18 @@ export class PixiController { } class VarSignalRow { - timeline: Timeline; app: Application; + timeline: Timeline; + last_time: Time; + formatter: (signal_value: BitString) => string; + timeline_for_ui: TimelineForUI; owner: Array; index_in_owner: number; rows_container: Container; row_height: number; row_gap: number; row_height_with_gap: number; - renderer_resize_callback = () => this.draw(); + renderer_resize_callback = () => this.redraw_on_canvas_resize(); // -- elements -- row_container = new Container(); signal_blocks_container = new Container(); @@ -93,11 +99,17 @@ class VarSignalRow { row_height: number, row_gap: number, ) { - console.log("VarSignalRow timeline:", timeline); - this.timeline = timeline; - this.app = app; + this.timeline = timeline; + this.last_time = timeline[timeline.length - 1][0]; + this.formatter = signal_value => parseInt(signal_value, 2).toString(16); + + this.timeline_for_ui = this.timeline.map(([time, value]) => { + const x = time / this.last_time * this.app.screen.width; + return [x, this.formatter(value)] + }); + this.row_height = row_height; this.row_gap = row_gap; this.row_height_with_gap = row_height + row_gap; @@ -107,64 +119,82 @@ class VarSignalRow { this.owner.push(this); this.rows_container = rows_container; - this.create_element_tree(); this.draw(); this.app.renderer.on("resize", this.renderer_resize_callback); } - create_element_tree() { + draw() { // row_container this.row_container.y = this.index_in_owner * this.row_height_with_gap; this.rows_container.addChild(this.row_container); // signal_block_container this.row_container.addChild(this.signal_blocks_container); + + const label_style = new TextStyle({ + align: "center", + fill: "White", + fontSize: 16, + fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"', + }); + + this.timeline_for_ui.forEach(([x, value], index) => { + if (index == this.timeline_for_ui.length - 1) { + return; + } + const block_width = this.timeline_for_ui[index+1][0] - x; + const block_height = this.row_height; + + // signal_block + const signal_block = new Container(); + signal_block.x = x; + this.signal_blocks_container.addChild(signal_block); + + // background + const background = new Graphics() + .roundRect(0, 0, block_width, block_height, 15) + .fill("SlateBlue"); + background.label = "background"; + signal_block.addChild(background); + + // label + const label = new Text({ text: value, style: label_style }); + label.x = (block_width - label.width) / 2; + label.y = (block_height - label.height) / 2; + label.visible = label.width < block_width; + label.label = "label"; + signal_block.addChild(label); + }) } - draw() { - if (this.timeline.length > 0) { - const last_time = this.timeline[this.timeline.length - 1][0]; - // @TODO make formatter configurable - const formatter: (signal_value: BitString) => string = signal_value => parseInt(signal_value, 2).toString(16); - // @TODO optimize - one pass, partly in Rust, partly outside of `draw()`, etc. - const timeline: Array<[number, string | undefined]> = this.timeline.map(([time, value]) => { - const x = time / last_time * this.app.screen.width; - const formatted_value = typeof value === 'string' ? formatter(value) : undefined; - return [x, formatted_value] - }); - // @TODO optimize - don't recreate all on every draw - this.signal_blocks_container.removeChildren(); - timeline.forEach(([x, value], index) => { - if (typeof value === 'string') { - const block_width = timeline[index+1][0] - x; - const block_height = this.row_height; - - // signal_block - const signal_block = new Container({x}); - this.signal_blocks_container.addChild(signal_block); - - // background - let background = new Graphics() - .roundRect(0, 0, block_width, block_height, 15) - .fill("SlateBlue"); - signal_block.addChild(background); - - // label - let style = new TextStyle({ - align: "center", - fill: "White", - fontSize: 16, - fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"', - }); - // @TODO don't show when the label is wider/higher than the block - let label = new Text({ text: value, style }); - label.x = (block_width - label.width) / 2; - label.y = (block_height - label.height) / 2; - signal_block.addChild(label); - } - }) + redraw_on_canvas_resize() { + for (let index = 0; index < this.timeline_for_ui.length; index++) { + const x = this.timeline[index][0] / this.last_time * this.app.screen.width; + this.timeline_for_ui[index][0] = x; } + this.timeline_for_ui.forEach(([x, _value], index) => { + if (index == this.timeline_for_ui.length - 1) { + return; + } + + const block_width = this.timeline_for_ui[index+1][0] - x; + const block_height = this.row_height; + + // signal_block + const signal_block = this.signal_blocks_container.getChildAt(index); + signal_block.x = x; + + // background + const background = signal_block.getChildByLabel("background")!; + background.width = block_width; + + // label + const label = signal_block.getChildByLabel("label")!; + label.x = (block_width - label.width) / 2; + label.y = (block_height - label.height) / 2; + label.visible = label.width < block_width; + }) } decrement_index() {