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"
version = "0.1.0"
dependencies = [
"convert-base",
"serde",
"serde_json",
"shared",
@ -1506,7 +1505,6 @@ dependencies = [
name = "frontend"
version = "0.1.0"
dependencies = [
"convert-base",
"gloo-file",
"shared",
"wasm-bindgen-test",
@ -4132,6 +4130,7 @@ dependencies = [
name = "shared"
version = "0.1.0"
dependencies = [
"convert-base",
"futures-util",
"moonlight",
"wellen",

View file

@ -16,7 +16,6 @@ readme = "../README.md"
publish = false
[workspace.dependencies]
convert-base = "1.1.2"
# 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" }

View file

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

View file

@ -170,7 +170,8 @@ impl ControlsPanel {
Label::new()
.s(Padding::new().x(20).y(10))
.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(RoundedCorners::all(15))
@ -183,47 +184,52 @@ impl ControlsPanel {
))
.on_hovered_change(move |is_hovered| hovered.set_neq(is_hovered))
.for_input(file_input_id)
.on_click_event_with_options(EventOptions::new().preventable(), clone!((hierarchy) move |event| {
let mut hierarchy_lock = hierarchy.lock_mut();
if hierarchy_lock.is_some() {
*hierarchy_lock = None;
if let RawMouseEvent::Click(raw_event) = event.raw_event {
// @TODO Move to MoonZoon as a new API
raw_event.prevent_default();
.on_click_event_with_options(
EventOptions::new().preventable(),
clone!((hierarchy) move |event| {
let mut hierarchy_lock = hierarchy.lock_mut();
if hierarchy_lock.is_some() {
*hierarchy_lock = None;
if let RawMouseEvent::Click(raw_event) = event.raw_event {
// @TODO Move to MoonZoon as a new API
raw_event.prevent_default();
}
return;
}
return;
}
}))
}),
),
)
.item(
// @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
TextInput::new()
.id(file_input_id)
.update_raw_el(|raw_el| {
let dom_element = raw_el.dom_element();
raw_el
.style("display", "none")
.attr("type", "file")
.event_handler(move |_: events::Input| {
let Some(file_list) = dom_element.files().map(gloo_file::FileList::from) else {
zoon::println!("file list is `None`");
return;
};
let Some(file) = file_list.first().cloned() else {
zoon::println!("file list is empty");
return;
};
let hierarchy = hierarchy.clone();
let loaded_filename = loaded_filename.clone();
Task::start(async move {
if let Some(filename) = platform::pick_and_load_waveform(Some(file)).await {
loaded_filename.set_neq(Some(filename));
hierarchy.set(Some(Rc::new(platform::get_hierarchy().await)))
}
})
TextInput::new().id(file_input_id).update_raw_el(|raw_el| {
let dom_element = raw_el.dom_element();
raw_el
.style("display", "none")
.attr("type", "file")
.event_handler(move |_: events::Input| {
let Some(file_list) =
dom_element.files().map(gloo_file::FileList::from)
else {
zoon::println!("file list is `None`");
return;
};
let Some(file) = file_list.first().cloned() else {
zoon::println!("file list is empty");
return;
};
let hierarchy = hierarchy.clone();
let loaded_filename = loaded_filename.clone();
Task::start(async move {
if let Some(filename) =
platform::pick_and_load_waveform(Some(file)).await
{
loaded_filename.set_neq(Some(filename));
hierarchy.set(Some(Rc::new(platform::get_hierarchy().await)))
}
})
})
})
}),
)
}

View file

@ -1,5 +1,5 @@
use zoon::*;
use std::rc::Rc;
use zoon::*;
mod platform;
@ -42,13 +42,22 @@ fn root() -> impl Element {
selected_var_refs.clone(),
layout.clone(),
))
.item_signal(layout.signal().map(|layout| matches!(layout, Layout::Tree)).map_true(clone!((hierarchy, selected_var_refs) move || WaveformPanel::new(
hierarchy.clone(),
selected_var_refs.clone(),
))))
.item_signal(
layout
.signal()
.map(|layout| matches!(layout, Layout::Tree))
.map_true(
clone!((hierarchy, selected_var_refs) 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())),
)
.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;
}
// @TODO dynamic formatter
// @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 = shared::VarFormat::default().format(value);
let value_width = value.chars().count() as u32 * LETTER_WIDTH;
let label = if (value_width + (2 * LABEL_X_PADDING)) <= block_width {

View file

@ -140,23 +140,56 @@ impl WaveformPanel {
None?
};
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 (hovered, hovered_signal) = Mutable::new_and_signal(false);
Button::new()
.s(Height::exact(ROW_HEIGHT))
.s(Background::new().color(color!("SlateBlue", 0.8)))
.s(RoundedCorners::new().left(15))
.s(Background::new().color_signal(
hovered_signal.map_bool(|| color!("SlateBlue"), || color!("SlateBlue", 0.8)),
))
.s(RoundedCorners::new().left(15).right(5))
.label(
El::new()
.s(Align::center())
.s(Padding::new().left(20).right(17).y(10))
.child(name),
)
.on_hovered_change(move |is_hovered| hovered.set_neq(is_hovered))
.on_press(move || {
if let Some(index) = index.get() {
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;
pub use js_bridge::PixiController;
use std::rc::Rc;
use zoon::*;
pub struct PixiCanvas {
raw_el: RawHtmlEl<web_sys::HtmlElement>,
@ -41,22 +41,31 @@ impl PixiCanvas {
let height = height.signal() => (*width, *height)
}
.throttle(|| Timer::sleep(50))
.for_each(clone!((controller) move |(width, height)| clone!((controller) async move {
if let Some(controller) = controller.lock_ref().as_ref() {
controller.resize(width, height).await
}
}))),
.for_each(
clone!((controller) move |(width, height)| clone!((controller) async move {
if let Some(controller) = controller.lock_ref().as_ref() {
controller.resize(width, height).await
}
})),
),
);
let task_with_controller = Mutable::new(None);
// -- FastWave-specific --
let timeline_getter = Rc::new(Closure::new(|signal_ref_index, screen_width, row_height| {
future_to_promise(async move {
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 = serde_wasm_bindgen::to_value(&timeline).unwrap_throw();
Ok(timeline)
})
}));
let timeline_getter = Rc::new(Closure::new(
|signal_ref_index, screen_width, row_height| {
future_to_promise(async move {
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 = serde_wasm_bindgen::to_value(&timeline).unwrap_throw();
Ok(timeline)
})
},
));
// -- // --
Self {
controller: controller.read_only(),
@ -105,7 +114,8 @@ mod js_bridge {
type SignalRefIndex = usize;
type ScreenWidth = 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`
#[wasm_bindgen(module = "/typescript/bundles/pixi_canvas.js")]
@ -115,7 +125,11 @@ mod js_bridge {
// @TODO `row_height` and `row_gap` is FastWave-specific
#[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)]
pub async fn init(this: &PixiController, parent_element: &JsValue);

View file

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

View file

@ -1,5 +1,8 @@
use moonlight::*;
mod var_format;
pub use var_format::VarFormat;
pub mod wellen_helpers;
#[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]
wellen.workspace = true
convert-base.workspace = true
shared = { path = "../shared", features = ["backend"] }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }

View file

@ -131,13 +131,7 @@ fn signal_to_timeline(
continue;
}
// @TODO dynamic formatter
// @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 = shared::VarFormat::default().format(value);
let value_width = value.chars().count() as u32 * LETTER_WIDTH;
let label = if (value_width + (2 * LABEL_X_PADDING)) <= block_width {