file picker + show_open_file_picker
This commit is contained in:
parent
6cdccb72b2
commit
ce36cd914f
13 changed files with 702 additions and 110 deletions
|
@ -14,3 +14,5 @@ wasm-bindgen-test = "0.3.19"
|
|||
shared.workspace = true
|
||||
zoon.workspace = true
|
||||
wellen.workspace = true
|
||||
web-sys = { version = "*", features = ["FileSystemFileHandle"] }
|
||||
gloo-file = { version = "0.3.0", features = ["futures"] }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{platform, HierarchyAndTimeTable, Layout};
|
||||
use futures_util::join;
|
||||
use std::cell::Cell;
|
||||
use std::mem;
|
||||
use std::ops::Not;
|
||||
use std::rc::Rc;
|
||||
|
@ -9,6 +10,8 @@ use zoon::*;
|
|||
const SCOPE_VAR_ROW_MAX_WIDTH: u32 = 480;
|
||||
const MILLER_COLUMN_MAX_HEIGHT: u32 = 500;
|
||||
|
||||
type Filename = String;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct VarForUI {
|
||||
name: Rc<String>,
|
||||
|
@ -35,6 +38,7 @@ pub struct ControlsPanel {
|
|||
hierarchy_and_time_table: Mutable<Option<HierarchyAndTimeTable>>,
|
||||
selected_var_refs: MutableVec<wellen::VarRef>,
|
||||
layout: Mutable<Layout>,
|
||||
loaded_filename: Mutable<Option<Filename>>,
|
||||
}
|
||||
|
||||
impl ControlsPanel {
|
||||
|
@ -48,19 +52,28 @@ impl ControlsPanel {
|
|||
hierarchy_and_time_table,
|
||||
selected_var_refs,
|
||||
layout,
|
||||
loaded_filename: <_>::default(),
|
||||
}
|
||||
.root()
|
||||
}
|
||||
|
||||
fn triggers(&self) -> Vec<TaskHandle> {
|
||||
vec![Task::start_droppable(
|
||||
self.hierarchy_and_time_table
|
||||
.signal_ref(Option::is_none)
|
||||
.for_each_sync(clone!((self => s) move |_| {
|
||||
s.selected_scope_ref.set(None);
|
||||
s.selected_var_refs.lock_mut().clear();
|
||||
})),
|
||||
)]
|
||||
vec![Task::start_droppable(clone!((self => s) async move {
|
||||
let was_some = Cell::new(false);
|
||||
s.hierarchy_and_time_table
|
||||
.signal_ref(Option::is_some)
|
||||
.dedupe()
|
||||
.for_each_sync(clone!((s) move |is_some| {
|
||||
if is_some {
|
||||
return was_some.set(true);
|
||||
}
|
||||
if was_some.get() {
|
||||
s.selected_scope_ref.set(None);
|
||||
s.selected_var_refs.lock_mut().clear();
|
||||
s.loaded_filename.set(None);
|
||||
}
|
||||
})).await
|
||||
}))]
|
||||
}
|
||||
|
||||
fn root(&self) -> impl Element {
|
||||
|
@ -94,8 +107,7 @@ impl ControlsPanel {
|
|||
Row::new()
|
||||
.s(Gap::both(15))
|
||||
.s(Align::new().left())
|
||||
.item(self.load_button("simple.vcd"))
|
||||
.item(self.load_button("wave_27.fst"))
|
||||
.item(self.load_button())
|
||||
.item(self.layout_switcher()),
|
||||
)
|
||||
.item_signal(
|
||||
|
@ -112,9 +124,10 @@ impl ControlsPanel {
|
|||
))
|
||||
}
|
||||
|
||||
fn load_button(&self, test_file_name: &'static str) -> impl Element {
|
||||
fn load_button(&self) -> impl Element {
|
||||
let (hovered, hovered_signal) = Mutable::new_and_signal(false);
|
||||
let hierarchy_and_time_table = self.hierarchy_and_time_table.clone();
|
||||
let loaded_filename = self.loaded_filename.clone();
|
||||
Button::new()
|
||||
.s(Padding::new().x(20).y(10))
|
||||
.s(Background::new().color_signal(
|
||||
|
@ -122,16 +135,12 @@ impl ControlsPanel {
|
|||
))
|
||||
.s(Align::new().left())
|
||||
.s(RoundedCorners::all(15))
|
||||
.label(
|
||||
El::new().s(Font::new().no_wrap()).child_signal(
|
||||
hierarchy_and_time_table
|
||||
.signal_ref(Option::is_some)
|
||||
.map_bool(
|
||||
|| format!("Unload test file"),
|
||||
move || format!("Load {test_file_name}"),
|
||||
),
|
||||
.label(El::new().s(Font::new().no_wrap()).child_signal(
|
||||
loaded_filename.signal_cloned().map_option(
|
||||
|filename| format!("Unload {filename}"),
|
||||
|| format!("Load file.."),
|
||||
),
|
||||
)
|
||||
))
|
||||
.on_hovered_change(move |is_hovered| hovered.set_neq(is_hovered))
|
||||
.on_press(move || {
|
||||
let mut hierarchy_and_time_table_lock = hierarchy_and_time_table.lock_mut();
|
||||
|
@ -141,11 +150,15 @@ impl ControlsPanel {
|
|||
}
|
||||
drop(hierarchy_and_time_table_lock);
|
||||
let hierarchy_and_time_table = hierarchy_and_time_table.clone();
|
||||
let loaded_filename = loaded_filename.clone();
|
||||
Task::start(async move {
|
||||
platform::load_waveform(test_file_name).await;
|
||||
let (hierarchy, time_table) =
|
||||
join!(platform::get_hierarchy(), platform::get_time_table());
|
||||
hierarchy_and_time_table.set(Some((Rc::new(hierarchy), Rc::new(time_table))))
|
||||
if let Some(filename) = platform::pick_and_load_waveform().await {
|
||||
loaded_filename.set_neq(Some(filename));
|
||||
let (hierarchy, time_table) =
|
||||
join!(platform::get_hierarchy(), platform::get_time_table());
|
||||
hierarchy_and_time_table
|
||||
.set(Some((Rc::new(hierarchy), Rc::new(time_table))))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,12 +13,15 @@ mod browser;
|
|||
#[cfg(FASTWAVE_PLATFORM = "BROWSER")]
|
||||
use browser as platform;
|
||||
|
||||
type Filename = String;
|
||||
|
||||
pub async fn show_window() {
|
||||
platform::show_window().await
|
||||
}
|
||||
|
||||
pub async fn load_waveform(test_file_name: &'static str) {
|
||||
platform::load_waveform(test_file_name).await
|
||||
// @TODO allow only support file types
|
||||
pub async fn pick_and_load_waveform() -> Option<Filename> {
|
||||
platform::pick_and_load_waveform().await
|
||||
}
|
||||
|
||||
pub async fn get_hierarchy() -> wellen::Hierarchy {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use shared::wellen_helpers;
|
||||
use std::sync::Mutex;
|
||||
use wellen::simple::Waveform;
|
||||
use zoon::*;
|
||||
use zoon::{println, *};
|
||||
|
||||
#[derive(Default)]
|
||||
struct Store {
|
||||
|
@ -12,19 +12,40 @@ static STORE: Lazy<Store> = lazy::default();
|
|||
|
||||
pub(super) async fn show_window() {}
|
||||
|
||||
pub(super) async fn load_waveform(test_file_name: &'static str) {
|
||||
static SIMPLE_VCD: &'static [u8; 311] = include_bytes!("../../../test_files/simple.vcd");
|
||||
// static WAVE_27_FST: &'static [u8; 28860652] = include_bytes!("../../../test_files/wave_27.fst");
|
||||
let chosen_file = match test_file_name {
|
||||
"simple.vcd" => SIMPLE_VCD.to_vec(),
|
||||
// "wave_27.fst" => WAVE_27_FST.to_vec(),
|
||||
test_file_name => todo!("add {test_file_name} to the `test_files` folder"),
|
||||
pub(super) async fn pick_and_load_waveform() -> Option<super::Filename> {
|
||||
let file_handles_promise = window().show_open_file_picker().expect_throw(
|
||||
"failed to open file picker (browser has to support `showOpenFilePicker` and use HTTPS",
|
||||
);
|
||||
let file_handles = JsFuture::from(file_handles_promise).await;
|
||||
let file_handles = match file_handles {
|
||||
Ok(file_handles) => file_handles.dyn_into::<js_sys::Array>().unwrap_throw(),
|
||||
Err(error) => {
|
||||
println!("file picker error: {error:?}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let waveform = wellen_helpers::read_from_bytes(chosen_file);
|
||||
let file_handle = file_handles
|
||||
.at(0)
|
||||
.dyn_into::<web_sys::FileSystemFileHandle>()
|
||||
.unwrap_throw();
|
||||
|
||||
let file = JsFuture::from(file_handle.get_file())
|
||||
.await
|
||||
.unwrap_throw()
|
||||
.dyn_into::<web_sys::File>()
|
||||
.unwrap_throw();
|
||||
|
||||
let file = gloo_file::File::from(file);
|
||||
let content = gloo_file::futures::read_as_bytes(&file)
|
||||
.await
|
||||
.unwrap_throw();
|
||||
|
||||
let waveform = wellen_helpers::read_from_bytes(content);
|
||||
let Ok(waveform) = waveform else {
|
||||
panic!("VCD file reading failed")
|
||||
panic!("Waveform file reading failed")
|
||||
};
|
||||
*STORE.waveform.lock().unwrap_throw() = Some(waveform);
|
||||
Some(file.name())
|
||||
}
|
||||
|
||||
pub(super) async fn get_hierarchy() -> wellen::Hierarchy {
|
||||
|
|
|
@ -1,28 +1,37 @@
|
|||
use zoon::*;
|
||||
|
||||
pub(super) async fn show_window() {
|
||||
tauri_glue::show_window().await
|
||||
tauri_glue::show_window().await.unwrap_throw()
|
||||
}
|
||||
|
||||
pub(super) async fn load_waveform(test_file_name: &'static str) {
|
||||
tauri_glue::load_waveform(test_file_name).await
|
||||
pub(super) async fn pick_and_load_waveform() -> Option<super::Filename> {
|
||||
tauri_glue::pick_and_load_waveform()
|
||||
.await
|
||||
.unwrap_throw()
|
||||
.as_string()
|
||||
}
|
||||
|
||||
pub(super) async fn get_hierarchy() -> wellen::Hierarchy {
|
||||
serde_wasm_bindgen::from_value(tauri_glue::get_hierarchy().await).unwrap_throw()
|
||||
serde_wasm_bindgen::from_value(tauri_glue::get_hierarchy().await.unwrap_throw()).unwrap_throw()
|
||||
}
|
||||
|
||||
pub(super) async fn get_time_table() -> wellen::TimeTable {
|
||||
serde_wasm_bindgen::from_value(tauri_glue::get_time_table().await).unwrap_throw()
|
||||
serde_wasm_bindgen::from_value(tauri_glue::get_time_table().await.unwrap_throw()).unwrap_throw()
|
||||
}
|
||||
|
||||
pub(super) async fn load_and_get_signal(signal_ref: wellen::SignalRef) -> wellen::Signal {
|
||||
serde_wasm_bindgen::from_value(tauri_glue::load_and_get_signal(signal_ref.index()).await)
|
||||
.unwrap_throw()
|
||||
serde_wasm_bindgen::from_value(
|
||||
tauri_glue::load_and_get_signal(signal_ref.index())
|
||||
.await
|
||||
.unwrap_throw(),
|
||||
)
|
||||
.unwrap_throw()
|
||||
}
|
||||
|
||||
pub(super) async fn unload_signal(signal_ref: wellen::SignalRef) {
|
||||
tauri_glue::unload_signal(signal_ref.index()).await
|
||||
tauri_glue::unload_signal(signal_ref.index())
|
||||
.await
|
||||
.unwrap_throw()
|
||||
}
|
||||
|
||||
mod tauri_glue {
|
||||
|
@ -31,16 +40,22 @@ mod tauri_glue {
|
|||
// Note: Add all corresponding methods to `frontend/typescript/tauri_glue/tauri_glue.ts`
|
||||
#[wasm_bindgen(module = "/typescript/bundles/tauri_glue.js")]
|
||||
extern "C" {
|
||||
pub async fn show_window();
|
||||
#[wasm_bindgen(catch)]
|
||||
pub async fn show_window() -> Result<(), JsValue>;
|
||||
|
||||
pub async fn load_waveform(test_file_name: &str);
|
||||
#[wasm_bindgen(catch)]
|
||||
pub async fn pick_and_load_waveform() -> Result<JsValue, JsValue>;
|
||||
|
||||
pub async fn get_hierarchy() -> JsValue;
|
||||
#[wasm_bindgen(catch)]
|
||||
pub async fn get_hierarchy() -> Result<JsValue, JsValue>;
|
||||
|
||||
pub async fn get_time_table() -> JsValue;
|
||||
#[wasm_bindgen(catch)]
|
||||
pub async fn get_time_table() -> Result<JsValue, JsValue>;
|
||||
|
||||
pub async fn load_and_get_signal(signal_ref_index: usize) -> JsValue;
|
||||
#[wasm_bindgen(catch)]
|
||||
pub async fn load_and_get_signal(signal_ref_index: usize) -> Result<JsValue, JsValue>;
|
||||
|
||||
pub async fn unload_signal(signal_ref_index: usize);
|
||||
#[wasm_bindgen(catch)]
|
||||
pub async fn unload_signal(signal_ref_index: usize) -> Result<(), JsValue>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2514,8 +2514,8 @@ var invoke2 = core_exports.invoke;
|
|||
async function show_window() {
|
||||
return await invoke2("show_window");
|
||||
}
|
||||
async function load_waveform(test_file_name) {
|
||||
return await invoke2("load_waveform", { test_file_name });
|
||||
async function pick_and_load_waveform() {
|
||||
return await invoke2("pick_and_load_waveform");
|
||||
}
|
||||
async function get_hierarchy() {
|
||||
return await invoke2("get_hierarchy");
|
||||
|
@ -2533,7 +2533,7 @@ export {
|
|||
get_hierarchy,
|
||||
get_time_table,
|
||||
load_and_get_signal,
|
||||
load_waveform,
|
||||
pick_and_load_waveform,
|
||||
show_window,
|
||||
unload_signal
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ import { core } from '@tauri-apps/api'
|
|||
|
||||
const invoke = core.invoke;
|
||||
|
||||
type Filename = string;
|
||||
type WellenHierarchy = unknown;
|
||||
type WellenTimeTable = unknown;
|
||||
type WellenSignal = unknown;
|
||||
|
@ -12,8 +13,8 @@ export async function show_window(): Promise<void> {
|
|||
return await invoke("show_window");
|
||||
}
|
||||
|
||||
export async function load_waveform(test_file_name: string): Promise<void> {
|
||||
return await invoke("load_waveform", { test_file_name });
|
||||
export async function pick_and_load_waveform(): Promise<Filename | undefined> {
|
||||
return await invoke("pick_and_load_waveform");
|
||||
}
|
||||
|
||||
export async function get_hierarchy(): Promise<WellenHierarchy> {
|
||||
|
|
Reference in a new issue