platform::timeline

This commit is contained in:
Martin Kavík 2024-06-06 22:04:57 +02:00
parent c0de520811
commit 6e85b7fa35
16 changed files with 157 additions and 217 deletions

10
Cargo.lock generated
View file

@ -1538,9 +1538,9 @@ dependencies = [
[[package]]
name = "futures"
version = "0.3.30"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
dependencies = [
"futures-channel",
"futures-core",
@ -1568,9 +1568,9 @@ checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-executor"
version = "0.3.30"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
dependencies = [
"futures-core",
"futures-task",
@ -4124,6 +4124,8 @@ dependencies = [
name = "shared"
version = "0.1.0"
dependencies = [
"futures-util",
"moonlight",
"wellen",
]

View file

@ -16,11 +16,12 @@ readme = "../README.md"
publish = false
[workspace.dependencies]
shared = { path = "./shared" }
# wellen = { version = "0.9.9", features = ["serde1"] }
# wellen = { path = "../wellen/wellen", features = ["serde1"] }
wellen = { git = "https://github.com/MartinKavik/wellen", features = ["serde1"], branch = "new_pub_types" }
# moon = { path = "../../crates/moon" }
# zoon = { path = "../../crates/zoon" }
# moonlight = { path = "../../crates/zoon" }
zoon = { git = "https://github.com/MoonZoon/MoonZoon", rev = "fc73b0d90bf39be72e70fdcab4f319ea5b8e6cfc" }
moon = { git = "https://github.com/MoonZoon/MoonZoon", rev = "fc73b0d90bf39be72e70fdcab4f319ea5b8e6cfc" }
moonlight = { git = "https://github.com/MoonZoon/MoonZoon", rev = "fc73b0d90bf39be72e70fdcab4f319ea5b8e6cfc" }

View file

@ -11,8 +11,8 @@ publish.workspace = true
wasm-bindgen-test = "0.3.19"
[dependencies]
shared.workspace = true
zoon.workspace = true
wellen.workspace = true
shared = { path = "../shared", features = ["frontend"] }
web-sys = { version = "*", features = ["FileSystemFileHandle"] }
gloo-file = { version = "0.3.0", features = ["futures"] }

View file

@ -37,6 +37,10 @@ pub async fn load_and_get_signal(signal_ref: wellen::SignalRef) -> wellen::Signa
platform::load_and_get_signal(signal_ref).await
}
pub async fn timeline(signal_ref: wellen::SignalRef, screen_width: u32) -> shared::Timeline {
platform::timeline(signal_ref, screen_width).await
}
pub async fn unload_signal(signal_ref: wellen::SignalRef) {
platform::unload_signal(signal_ref).await
}

View file

@ -91,6 +91,10 @@ pub(super) async fn load_and_get_signal(signal_ref: wellen::SignalRef) -> wellen
serde_json::from_value(serde_json::to_value(signal).unwrap_throw()).unwrap_throw()
}
pub(super) async fn timeline(signal_ref: wellen::SignalRef, screen_width: u32) -> shared::Timeline {
shared::Timeline { blocks: Vec::new() }
}
pub(super) async fn unload_signal(signal_ref: wellen::SignalRef) {
let mut waveform_lock = STORE.waveform.lock().unwrap_throw();
let waveform = waveform_lock.as_mut().unwrap_throw();

View file

@ -30,6 +30,15 @@ pub(super) async fn load_and_get_signal(signal_ref: wellen::SignalRef) -> wellen
.unwrap_throw()
}
pub(super) async fn timeline(signal_ref: wellen::SignalRef, screen_width: u32) -> shared::Timeline {
serde_wasm_bindgen::from_value(
tauri_glue::timeline(signal_ref.index(), screen_width)
.await
.unwrap_throw(),
)
.unwrap_throw()
}
pub(super) async fn unload_signal(signal_ref: wellen::SignalRef) {
tauri_glue::unload_signal(signal_ref.index())
.await
@ -57,6 +66,9 @@ mod tauri_glue {
#[wasm_bindgen(catch)]
pub async fn load_and_get_signal(signal_ref_index: usize) -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn timeline(signal_ref_index: usize, screen_width: u32) -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn unload_signal(signal_ref_index: usize) -> Result<(), JsValue>;
}

View file

@ -117,24 +117,19 @@ impl WaveformPanel {
let var = hierarchy.get(var_ref);
let signal_ref = var.signal_ref();
let signal = platform::load_and_get_signal(signal_ref).await;
let timeline = platform::timeline(signal_ref, controller.screen_width()).await;
// @TODO remove
zoon::println!("Timeline in Rust: {timeline:#?}");
let timescale = hierarchy.timescale();
// @TODO remove
zoon::println!("{timescale:?}");
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() {
if timeline.blocks.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());
}

View file

@ -100,11 +100,14 @@ mod js_bridge {
#[wasm_bindgen(method)]
pub async fn init(this: &PixiController, parent_element: &JsValue);
#[wasm_bindgen(method)]
pub fn queue_resize(this: &PixiController);
#[wasm_bindgen(method)]
pub fn destroy(this: &PixiController);
#[wasm_bindgen(method)]
pub fn queue_resize(this: &PixiController);
pub fn screen_width(this: &PixiController) -> u32;
// -- FastWave-specific --

View file

@ -35126,7 +35126,6 @@ var import_earcut2 = __toESM(require_earcut(), 1);
extensions.add(browserExt, webworkerExt);
// pixi_canvas.ts
var MIN_BLOCK_WIDTH = 1;
var PixiController = class {
app;
// -- FastWave-specific --
@ -35160,6 +35159,9 @@ var PixiController = class {
};
this.app.destroy(rendererDestroyOptions, options);
}
screen_width() {
return this.app.screen.width;
}
// -- FastWave-specific --
remove_var(index) {
if (typeof this.var_signal_rows[index] !== "undefined") {
@ -35167,6 +35169,7 @@ var PixiController = class {
}
}
push_var(timeline) {
console.log("Timline in Typescript:", timeline);
new VarSignalRow(
timeline,
this.app,
@ -35186,17 +35189,13 @@ var PixiController = class {
var VarSignalRow = class {
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.redraw_on_canvas_resize();
// -- elements --
renderer_resize_callback = () => this.draw();
row_container = new Container();
signal_blocks_container = new Container();
label_style = new TextStyle({
@ -35208,12 +35207,6 @@ var VarSignalRow = class {
constructor(timeline, app, owner, rows_container, row_height, row_gap) {
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;
@ -35221,90 +35214,26 @@ var VarSignalRow = class {
this.owner = owner;
this.owner.push(this);
this.rows_container = rows_container;
this.draw();
this.app.renderer.on("resize", this.renderer_resize_callback);
}
async 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);
for (let index = 0; index < this.timeline_for_ui.length; index++) {
if (index == this.timeline_for_ui.length - 1) {
return;
}
await new Promise((resolve) => setTimeout(resolve, 0));
const [x2, value] = this.timeline_for_ui[index];
const block_width = this.timeline_for_ui[index + 1][0] - x2;
const block_height = this.row_height;
if (block_width < MIN_BLOCK_WIDTH) {
return;
this.draw();
}
draw() {
this.signal_blocks_container.removeChildren();
this.timeline.blocks.forEach((timeline_block) => {
const signal_block = new Container();
signal_block.x = x2;
signal_block.x = timeline_block.x;
this.signal_blocks_container.addChild(signal_block);
const background = new Graphics().roundRect(0, 0, block_width, block_height, 15).fill("SlateBlue");
background.label = "background";
const background = new Graphics().roundRect(0, 0, timeline_block.width, this.row_height, 15).fill("SlateBlue");
signal_block.addChild(background);
const label = new Text({ text: value, style: this.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";
if (timeline_block.label !== void 0) {
const label = new Text({ text: timeline_block.label.text, style: this.label_style });
label.x = timeline_block.label.x;
label.y = timeline_block.label.y;
signal_block.addChild(label);
}
}
async 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;
}
for (let index = 0; index < this.timeline_for_ui.length; index++) {
if (index == this.timeline_for_ui.length - 1) {
return;
}
await new Promise((resolve) => setTimeout(resolve, 0));
const [x2, value] = this.timeline_for_ui[index];
const block_width = this.timeline_for_ui[index + 1][0] - x2;
const block_height = this.row_height;
const block_visible = block_width >= MIN_BLOCK_WIDTH;
let signal_block = this.signal_blocks_container.children[index];
if (signal_block === void 0 && !block_visible) {
return;
}
if (signal_block !== void 0 && !block_visible) {
signal_block.visible = false;
return;
}
if (signal_block === void 0 && block_visible) {
signal_block = new Container();
signal_block.x = x2;
this.signal_blocks_container.addChild(signal_block);
} else if (signal_block !== void 0 && block_visible) {
signal_block.visible = true;
signal_block.x = x2;
}
let background = signal_block.getChildByLabel("background");
if (background === null) {
background = new Graphics().roundRect(0, 0, block_width, block_height, 15).fill("SlateBlue");
background.label = "background";
signal_block.addChild(background);
} else {
background.width = block_width;
}
const label = signal_block.getChildByLabel("label");
if (label === null) {
const label2 = new Text({ text: value, style: this.label_style });
label2.x = (block_width - label2.width) / 2;
label2.y = (block_height - label2.height) / 2;
label2.visible = label2.width < block_width;
label2.label = "label";
signal_block.addChild(label2);
} else {
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--;

View file

@ -2526,6 +2526,9 @@ async function get_time_table() {
async function load_and_get_signal(signal_ref_index) {
return await invoke2("load_and_get_signal", { signal_ref_index });
}
async function timeline(signal_ref_index, screen_width) {
return await invoke2("timeline", { signal_ref_index, screen_width });
}
async function unload_signal(signal_ref_index) {
return await invoke2("unload_signal", { signal_ref_index });
}
@ -2535,5 +2538,6 @@ export {
load_and_get_signal,
pick_and_load_waveform,
show_window,
timeline,
unload_signal
};

View file

@ -1,13 +1,19 @@
import { Application, Text, Graphics, Container, TextStyle, ContainerChild } from "pixi.js";
type Time = number;
type BitString = string;
type Timeline = Array<[Time, BitString]>;
type X = number;
type TimelineForUI = Array<[X, string]>;
const MIN_BLOCK_WIDTH = 1;
// @TODO sync with Rust and `tauri_glue.ts`
type Timeline = {
blocks: Array<TimelineBlock>
}
type TimelineBlock = {
x: number,
width: number,
label: TimeLineBlockLabel | undefined,
}
type TimeLineBlockLabel = {
text: string,
x: number,
y: number,
}
export class PixiController {
app: Application
@ -48,6 +54,10 @@ export class PixiController {
this.app.destroy(rendererDestroyOptions, options);
}
screen_width() {
return this.app.screen.width;
}
// -- FastWave-specific --
remove_var(index: number) {
@ -57,6 +67,7 @@ export class PixiController {
}
push_var(timeline: Timeline) {
console.log("Timline in Typescript:", timeline);
new VarSignalRow(
timeline,
this.app,
@ -79,17 +90,13 @@ export class PixiController {
class VarSignalRow {
app: Application;
timeline: Timeline;
last_time: Time;
formatter: (signal_value: BitString) => string;
timeline_for_ui: TimelineForUI;
owner: Array<VarSignalRow>;
index_in_owner: number;
rows_container: Container;
row_height: number;
row_gap: number;
row_height_with_gap: number;
renderer_resize_callback = () => this.redraw_on_canvas_resize();
// -- elements --
renderer_resize_callback = () => this.draw();
row_container = new Container();
signal_blocks_container = new Container();
label_style = new TextStyle({
@ -110,13 +117,6 @@ class VarSignalRow {
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;
@ -128,113 +128,43 @@ class VarSignalRow {
this.rows_container = rows_container;
this.draw();
this.app.renderer.on("resize", this.renderer_resize_callback);
}
async 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
// signal_blocks_container
this.row_container.addChild(this.signal_blocks_container);
for (let index = 0; index < this.timeline_for_ui.length; index++) {
if (index == this.timeline_for_ui.length - 1) {
return;
this.draw();
// this.app.renderer.on("resize", (width, height) => {
// // @TODO only on `width` change
// // @TODO inline `renderer_resize_callback`?
// this.draw();
// });
}
await new Promise(resolve => setTimeout(resolve, 0));
const [x, value] = this.timeline_for_ui[index];
draw() {
this.signal_blocks_container.removeChildren();
this.timeline.blocks.forEach(timeline_block => {
// signal_block
const block_width = this.timeline_for_ui[index+1][0] - x;
const block_height = this.row_height;
if (block_width < MIN_BLOCK_WIDTH) {
return;
}
const signal_block = new Container();
signal_block.x = x;
signal_block.x = timeline_block.x;
this.signal_blocks_container.addChild(signal_block);
// background
const background = new Graphics()
.roundRect(0, 0, block_width, block_height, 15)
.roundRect(0, 0, timeline_block.width, this.row_height, 15)
.fill("SlateBlue");
background.label = "background";
signal_block.addChild(background);
// label
const label = new Text({ text: value, style: this.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";
if (timeline_block.label !== undefined) {
const label = new Text({ text: timeline_block.label.text, style: this.label_style });
label.x = timeline_block.label.x;
label.y = timeline_block.label.y;
signal_block.addChild(label);
}
}
async 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;
}
for (let index = 0; index < this.timeline_for_ui.length; index++) {
if (index == this.timeline_for_ui.length - 1) {
return;
}
await new Promise(resolve => setTimeout(resolve, 0));
const [x, value] = this.timeline_for_ui[index];
// signal_block
const block_width = this.timeline_for_ui[index+1][0] - x;
const block_height = this.row_height;
const block_visible = block_width >= MIN_BLOCK_WIDTH;
let signal_block: ContainerChild | undefined = this.signal_blocks_container.children[index];
if (signal_block === undefined && !block_visible) {
return;
}
if (signal_block !== undefined && !block_visible) {
signal_block.visible = false;
return;
}
if (signal_block === undefined && block_visible) {
signal_block = new Container();
signal_block.x = x;
this.signal_blocks_container.addChild(signal_block);
} else if (signal_block !== undefined && block_visible) {
signal_block.visible = true;
signal_block.x = x;
}
// background
let background = signal_block.getChildByLabel("background");
if (background === null) {
background = new Graphics()
.roundRect(0, 0, block_width, block_height, 15)
.fill("SlateBlue");
background.label = "background";
signal_block.addChild(background);
} else {
background.width = block_width;
}
// label
const label = signal_block.getChildByLabel("label");
if (label === null ) {
const label = new Text({ text: value, style: this.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);
} else {
label.x = (block_width - label.width) / 2;
label.y = (block_height - label.height) / 2;
label.visible = label.width < block_width;
}
}
});
}
decrement_index() {

View file

@ -8,6 +8,7 @@ type Filename = string;
type WellenHierarchy = unknown;
type WellenTimeTable = unknown;
type WellenSignal = unknown;
type Timeline = unknown;
export async function show_window(): Promise<void> {
return await invoke("show_window");
@ -29,6 +30,10 @@ export async function load_and_get_signal(signal_ref_index: number): Promise<Wel
return await invoke("load_and_get_signal", { signal_ref_index });
}
export async function timeline(signal_ref_index: number, screen_width: number): Promise<Timeline> {
return await invoke("timeline", { signal_ref_index, screen_width });
}
export async function unload_signal(signal_ref_index: number): Promise<void> {
return await invoke("unload_signal", { signal_ref_index });
}

View file

@ -9,3 +9,10 @@ publish.workspace = true
[dependencies]
wellen.workspace = true
moonlight.workspace = true
# @TODO update `futures_util_ext` - add feature `sink`, set exact `futures-util` version
futures-util = { version = "0.3.30", features = ["sink"] }
[features]
frontend = ["moonlight/frontend"]
backend = ["moonlight/backend"]

View file

@ -1 +1,25 @@
use moonlight::*;
pub mod wellen_helpers;
#[derive(Serialize, Deserialize, Debug)]
#[serde(crate = "serde")]
pub struct Timeline {
pub blocks: Vec<TimelineBlock>
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(crate = "serde")]
pub struct TimelineBlock {
pub x: u32,
pub width: u32,
pub label: Option<TimeLineBlockLabel>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(crate = "serde")]
pub struct TimeLineBlockLabel {
pub text: String,
pub x: u32,
pub y: u32,
}

View file

@ -16,8 +16,8 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "=2.0.0-beta.17", features = [] }
[dependencies]
shared.workspace = true
wellen.workspace = true
shared = { path = "../shared", features = ["backend"] }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "=2.0.0-beta.22", features = ["macos-private-api", "linux-ipc-protocol"] }

View file

@ -61,6 +61,25 @@ async fn load_and_get_signal(
Ok(serde_json::to_value(signal).unwrap())
}
#[tauri::command(rename_all = "snake_case")]
async fn timeline(
signal_ref_index: usize,
screen_width: u32,
store: tauri::State<'_, Store>,
) -> Result<serde_json::Value, ()> {
let signal_ref = wellen::SignalRef::from_index(signal_ref_index).unwrap();
let mut waveform_lock = store.waveform.lock().unwrap();
let waveform = waveform_lock.as_mut().unwrap();
// @TODO maybe run it in a thread to not block the main one or return the result through a Tauri channel
waveform.load_signals_multi_threaded(&[signal_ref]);
let signal = waveform.get_signal(signal_ref).unwrap();
// @TODO create Timeline
let timeline = shared::Timeline { blocks: Vec::new() };
Ok(serde_json::to_value(timeline).unwrap())
}
#[tauri::command(rename_all = "snake_case")]
async fn unload_signal(signal_ref_index: usize, store: tauri::State<'_, Store>) -> Result<(), ()> {
let signal_ref = wellen::SignalRef::from_index(signal_ref_index).unwrap();
@ -87,6 +106,7 @@ pub fn run() {
get_hierarchy,
get_time_table,
load_and_get_signal,
timeline,
unload_signal,
])
.run(tauri::generate_context!())