selected_var_format_button

This commit is contained in:
Martin Kavík 2024-06-08 23:51:30 +02:00
parent fbdf8090a1
commit a6da2887c9
13 changed files with 191 additions and 87 deletions

3
Cargo.lock generated
View file

@ -1420,7 +1420,6 @@ checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
name = "fastwave" name = "fastwave"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"convert-base",
"serde", "serde",
"serde_json", "serde_json",
"shared", "shared",
@ -1506,7 +1505,6 @@ dependencies = [
name = "frontend" name = "frontend"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"convert-base",
"gloo-file", "gloo-file",
"shared", "shared",
"wasm-bindgen-test", "wasm-bindgen-test",
@ -4132,6 +4130,7 @@ dependencies = [
name = "shared" name = "shared"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"convert-base",
"futures-util", "futures-util",
"moonlight", "moonlight",
"wellen", "wellen",

View file

@ -16,7 +16,6 @@ readme = "../README.md"
publish = false publish = false
[workspace.dependencies] [workspace.dependencies]
convert-base = "1.1.2"
# wellen = { version = "0.9.9", features = ["serde1"] } # wellen = { version = "0.9.9", features = ["serde1"] }
# wellen = { path = "../wellen/wellen", features = ["serde1"] } # wellen = { path = "../wellen/wellen", features = ["serde1"] }
wellen = { git = "https://github.com/MartinKavik/wellen", features = ["serde1"], branch = "new_pub_types" } wellen = { git = "https://github.com/MartinKavik/wellen", features = ["serde1"], branch = "new_pub_types" }

View file

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

View file

@ -170,7 +170,8 @@ impl ControlsPanel {
Label::new() Label::new()
.s(Padding::new().x(20).y(10)) .s(Padding::new().x(20).y(10))
.s(Background::new().color_signal( .s(Background::new().color_signal(
hovered_signal.map_bool(|| color!("MediumSlateBlue"), || color!("SlateBlue")), hovered_signal
.map_bool(|| color!("MediumSlateBlue"), || color!("SlateBlue")),
)) ))
.s(Align::new().left()) .s(Align::new().left())
.s(RoundedCorners::all(15)) .s(RoundedCorners::all(15))
@ -183,7 +184,9 @@ impl ControlsPanel {
)) ))
.on_hovered_change(move |is_hovered| hovered.set_neq(is_hovered)) .on_hovered_change(move |is_hovered| hovered.set_neq(is_hovered))
.for_input(file_input_id) .for_input(file_input_id)
.on_click_event_with_options(EventOptions::new().preventable(), clone!((hierarchy) move |event| { .on_click_event_with_options(
EventOptions::new().preventable(),
clone!((hierarchy) move |event| {
let mut hierarchy_lock = hierarchy.lock_mut(); let mut hierarchy_lock = hierarchy.lock_mut();
if hierarchy_lock.is_some() { if hierarchy_lock.is_some() {
*hierarchy_lock = None; *hierarchy_lock = None;
@ -193,20 +196,21 @@ impl ControlsPanel {
} }
return; return;
} }
})) }),
),
) )
.item( .item(
// @TODO https://github.com/MoonZoon/MoonZoon/issues/39 // @TODO https://github.com/MoonZoon/MoonZoon/issues/39
// + https://developer.mozilla.org/en-US/docs/Web/API/File_API/Using_files_from_web_applications#using_hidden_file_input_elements_using_the_click_method // + https://developer.mozilla.org/en-US/docs/Web/API/File_API/Using_files_from_web_applications#using_hidden_file_input_elements_using_the_click_method
TextInput::new() TextInput::new().id(file_input_id).update_raw_el(|raw_el| {
.id(file_input_id)
.update_raw_el(|raw_el| {
let dom_element = raw_el.dom_element(); let dom_element = raw_el.dom_element();
raw_el raw_el
.style("display", "none") .style("display", "none")
.attr("type", "file") .attr("type", "file")
.event_handler(move |_: events::Input| { .event_handler(move |_: events::Input| {
let Some(file_list) = dom_element.files().map(gloo_file::FileList::from) else { let Some(file_list) =
dom_element.files().map(gloo_file::FileList::from)
else {
zoon::println!("file list is `None`"); zoon::println!("file list is `None`");
return; return;
}; };
@ -217,13 +221,15 @@ impl ControlsPanel {
let hierarchy = hierarchy.clone(); let hierarchy = hierarchy.clone();
let loaded_filename = loaded_filename.clone(); let loaded_filename = loaded_filename.clone();
Task::start(async move { Task::start(async move {
if let Some(filename) = platform::pick_and_load_waveform(Some(file)).await { if let Some(filename) =
platform::pick_and_load_waveform(Some(file)).await
{
loaded_filename.set_neq(Some(filename)); loaded_filename.set_neq(Some(filename));
hierarchy.set(Some(Rc::new(platform::get_hierarchy().await))) hierarchy.set(Some(Rc::new(platform::get_hierarchy().await)))
} }
}) })
}) })
}) }),
) )
} }

View file

@ -1,5 +1,5 @@
use zoon::*;
use std::rc::Rc; use std::rc::Rc;
use zoon::*;
mod platform; mod platform;
@ -42,13 +42,22 @@ fn root() -> impl Element {
selected_var_refs.clone(), selected_var_refs.clone(),
layout.clone(), layout.clone(),
)) ))
.item_signal(layout.signal().map(|layout| matches!(layout, Layout::Tree)).map_true(clone!((hierarchy, selected_var_refs) move || WaveformPanel::new( .item_signal(
layout
.signal()
.map(|layout| matches!(layout, Layout::Tree))
.map_true(
clone!((hierarchy, selected_var_refs) move || WaveformPanel::new(
hierarchy.clone(), hierarchy.clone(),
selected_var_refs.clone(), selected_var_refs.clone(),
)))) )),
),
),
)
.item_signal(
layout
.signal()
.map(|layout| matches!(layout, Layout::Columns))
.map_true(move || WaveformPanel::new(hierarchy.clone(), selected_var_refs.clone())),
) )
.item_signal(layout.signal().map(|layout| matches!(layout, Layout::Columns)).map_true(move || WaveformPanel::new(
hierarchy.clone(),
selected_var_refs.clone(),
)))
} }

View file

@ -137,13 +137,7 @@ fn signal_to_timeline(
continue; continue;
} }
// @TODO dynamic formatter let value = shared::VarFormat::default().format(value);
// @TODO optimize it by not using `.to_string` if possible
let value = value.to_string();
let ones_and_zeros = value.chars().rev().map(|char| char.to_digit(2).unwrap()).collect::<Vec<_>>();
let mut base = convert_base::Convert::new(2, 16);
let output = base.convert::<u32, u32>(&ones_and_zeros);
let value: String = output.into_iter().map(|number| char::from_digit(number, 16).unwrap()).collect();
let value_width = value.chars().count() as u32 * LETTER_WIDTH; let value_width = value.chars().count() as u32 * LETTER_WIDTH;
let label = if (value_width + (2 * LABEL_X_PADDING)) <= block_width { let label = if (value_width + (2 * LABEL_X_PADDING)) <= block_width {

View file

@ -140,23 +140,56 @@ impl WaveformPanel {
None? None?
}; };
let var = hierarchy.get(var_ref); let var = hierarchy.get(var_ref);
let name: &str = var.name(&hierarchy); Row::new()
.item(self.selected_var_name_button(var.name(&hierarchy), index))
.item(self.selected_var_format_button())
.apply(Some)
}
fn selected_var_name_button(
&self,
name: &str,
index: ReadOnlyMutable<Option<usize>>,
) -> impl Element {
let selected_var_refs = self.selected_var_refs.clone(); let selected_var_refs = self.selected_var_refs.clone();
let (hovered, hovered_signal) = Mutable::new_and_signal(false);
Button::new() Button::new()
.s(Height::exact(ROW_HEIGHT)) .s(Height::exact(ROW_HEIGHT))
.s(Background::new().color(color!("SlateBlue", 0.8))) .s(Background::new().color_signal(
.s(RoundedCorners::new().left(15)) hovered_signal.map_bool(|| color!("SlateBlue"), || color!("SlateBlue", 0.8)),
))
.s(RoundedCorners::new().left(15).right(5))
.label( .label(
El::new() El::new()
.s(Align::center()) .s(Align::center())
.s(Padding::new().left(20).right(17).y(10)) .s(Padding::new().left(20).right(17).y(10))
.child(name), .child(name),
) )
.on_hovered_change(move |is_hovered| hovered.set_neq(is_hovered))
.on_press(move || { .on_press(move || {
if let Some(index) = index.get() { if let Some(index) = index.get() {
selected_var_refs.lock_mut().remove(index); selected_var_refs.lock_mut().remove(index);
} }
}) })
.apply(Some) }
fn selected_var_format_button(&self) -> impl Element {
let var_format = Mutable::new(shared::VarFormat::default());
let (hovered, hovered_signal) = Mutable::new_and_signal(false);
Button::new()
.s(Height::exact(ROW_HEIGHT))
.s(Width::exact(70))
.s(Background::new().color_signal(
hovered_signal.map_bool(|| color!("SlateBlue"), || color!("SlateBlue", 0.8)),
))
.s(RoundedCorners::new().left(5))
.label(
El::new()
.s(Align::center())
.s(Padding::new().left(20).right(17).y(10))
.child_signal(var_format.signal().map(|format| format.as_static_str())),
)
.on_hovered_change(move |is_hovered| hovered.set_neq(is_hovered))
.on_press(move || var_format.update(|format| format.next()))
} }
} }

View file

@ -1,7 +1,7 @@
pub use js_bridge::PixiController;
use zoon::*;
use std::rc::Rc;
use crate::platform; use crate::platform;
pub use js_bridge::PixiController;
use std::rc::Rc;
use zoon::*;
pub struct PixiCanvas { pub struct PixiCanvas {
raw_el: RawHtmlEl<web_sys::HtmlElement>, raw_el: RawHtmlEl<web_sys::HtmlElement>,
@ -41,22 +41,31 @@ impl PixiCanvas {
let height = height.signal() => (*width, *height) let height = height.signal() => (*width, *height)
} }
.throttle(|| Timer::sleep(50)) .throttle(|| Timer::sleep(50))
.for_each(clone!((controller) move |(width, height)| clone!((controller) async move { .for_each(
clone!((controller) move |(width, height)| clone!((controller) async move {
if let Some(controller) = controller.lock_ref().as_ref() { if let Some(controller) = controller.lock_ref().as_ref() {
controller.resize(width, height).await controller.resize(width, height).await
} }
}))), })),
),
); );
let task_with_controller = Mutable::new(None); let task_with_controller = Mutable::new(None);
// -- FastWave-specific -- // -- FastWave-specific --
let timeline_getter = Rc::new(Closure::new(|signal_ref_index, screen_width, row_height| { let timeline_getter = Rc::new(Closure::new(
|signal_ref_index, screen_width, row_height| {
future_to_promise(async move { future_to_promise(async move {
let signal_ref = wellen::SignalRef::from_index(signal_ref_index).unwrap_throw(); let signal_ref = wellen::SignalRef::from_index(signal_ref_index).unwrap_throw();
let timeline = platform::load_signal_and_get_timeline(signal_ref, screen_width, row_height).await; let timeline = platform::load_signal_and_get_timeline(
signal_ref,
screen_width,
row_height,
)
.await;
let timeline = serde_wasm_bindgen::to_value(&timeline).unwrap_throw(); let timeline = serde_wasm_bindgen::to_value(&timeline).unwrap_throw();
Ok(timeline) Ok(timeline)
}) })
})); },
));
// -- // -- // -- // --
Self { Self {
controller: controller.read_only(), controller: controller.read_only(),
@ -105,7 +114,8 @@ mod js_bridge {
type SignalRefIndex = usize; type SignalRefIndex = usize;
type ScreenWidth = u32; type ScreenWidth = u32;
type RowHeight = u32; type RowHeight = u32;
type TimelineGetter = Closure<dyn FnMut(SignalRefIndex, ScreenWidth, RowHeight) -> TimelinePromise>; type TimelineGetter =
Closure<dyn FnMut(SignalRefIndex, ScreenWidth, RowHeight) -> TimelinePromise>;
// Note: Add all corresponding methods to `frontend/typescript/pixi_canvas/pixi_canvas.ts` // Note: Add all corresponding methods to `frontend/typescript/pixi_canvas/pixi_canvas.ts`
#[wasm_bindgen(module = "/typescript/bundles/pixi_canvas.js")] #[wasm_bindgen(module = "/typescript/bundles/pixi_canvas.js")]
@ -115,7 +125,11 @@ mod js_bridge {
// @TODO `row_height` and `row_gap` is FastWave-specific // @TODO `row_height` and `row_gap` is FastWave-specific
#[wasm_bindgen(constructor)] #[wasm_bindgen(constructor)]
pub fn new(row_height: u32, row_gap: u32, timeline_getter: &TimelineGetter) -> PixiController; pub fn new(
row_height: u32,
row_gap: u32,
timeline_getter: &TimelineGetter,
) -> PixiController;
#[wasm_bindgen(method)] #[wasm_bindgen(method)]
pub async fn init(this: &PixiController, parent_element: &JsValue); pub async fn init(this: &PixiController, parent_element: &JsValue);

View file

@ -10,6 +10,7 @@ publish.workspace = true
[dependencies] [dependencies]
wellen.workspace = true wellen.workspace = true
moonlight.workspace = true moonlight.workspace = true
convert-base = "1.1.2"
# @TODO update `futures_util_ext` - add feature `sink`, set exact `futures-util` version # @TODO update `futures_util_ext` - add feature `sink`, set exact `futures-util` version
futures-util = { version = "0.3.30", features = ["sink"] } futures-util = { version = "0.3.30", features = ["sink"] }

View file

@ -1,5 +1,8 @@
use moonlight::*; use moonlight::*;
mod var_format;
pub use var_format::VarFormat;
pub mod wellen_helpers; pub mod wellen_helpers;
#[derive(Serialize, Deserialize, Debug, Default)] #[derive(Serialize, Deserialize, Debug, Default)]

54
shared/src/var_format.rs Normal file
View file

@ -0,0 +1,54 @@
#[derive(Default, Clone, Copy, Debug)]
pub enum VarFormat {
ASCII,
Binary,
BinaryWithGroups,
#[default]
Hexadecimal,
Octal,
Signed,
Unsigned,
}
impl VarFormat {
pub fn as_static_str(&self) -> &'static str {
match self {
VarFormat::ASCII => "Text",
VarFormat::Binary => "Bin",
VarFormat::BinaryWithGroups => "Bins",
VarFormat::Hexadecimal => "Hex",
VarFormat::Octal => "Oct",
VarFormat::Signed => "i32",
VarFormat::Unsigned => "u32",
}
}
pub fn next(&self) -> Self {
match self {
VarFormat::ASCII => VarFormat::Binary,
VarFormat::Binary => VarFormat::BinaryWithGroups,
VarFormat::BinaryWithGroups => VarFormat::Hexadecimal,
VarFormat::Hexadecimal => VarFormat::Octal,
VarFormat::Octal => VarFormat::Signed,
VarFormat::Signed => VarFormat::Unsigned,
VarFormat::Unsigned => VarFormat::ASCII,
}
}
pub fn format(&self, value: wellen::SignalValue) -> String {
// @TODO optimize it by not using `.to_string` if possible
let value = value.to_string();
let ones_and_zeros = value
.chars()
.rev()
.map(|char| char.to_digit(2).unwrap())
.collect::<Vec<_>>();
let mut base = convert_base::Convert::new(2, 16);
let output = base.convert::<u32, u32>(&ones_and_zeros);
let value: String = output
.into_iter()
.map(|number| char::from_digit(number, 16).unwrap())
.collect();
value
}
}

View file

@ -17,7 +17,6 @@ tauri-build = { version = "=2.0.0-beta.17", features = [] }
[dependencies] [dependencies]
wellen.workspace = true wellen.workspace = true
convert-base.workspace = true
shared = { path = "../shared", features = ["backend"] } shared = { path = "../shared", features = ["backend"] }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View file

@ -131,13 +131,7 @@ fn signal_to_timeline(
continue; continue;
} }
// @TODO dynamic formatter let value = shared::VarFormat::default().format(value);
// @TODO optimize it by not using `.to_string` if possible
let value = value.to_string();
let ones_and_zeros = value.chars().rev().map(|char| char.to_digit(2).unwrap()).collect::<Vec<_>>();
let mut base = convert_base::Convert::new(2, 16);
let output = base.convert::<u32, u32>(&ones_and_zeros);
let value: String = output.into_iter().map(|number| char::from_digit(number, 16).unwrap()).collect();
let value_width = value.chars().count() as u32 * LETTER_WIDTH; let value_width = value.chars().count() as u32 * LETTER_WIDTH;
let label = if (value_width + (2 * LABEL_X_PADDING)) <= block_width { let label = if (value_width + (2 * LABEL_X_PADDING)) <= block_width {