selected_var_format_button
This commit is contained in:
parent
fbdf8090a1
commit
a6da2887c9
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
)))
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"] }
|
||||
|
||||
|
|
|
@ -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
54
shared/src/var_format.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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"] }
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue