file picker + show_open_file_picker

This commit is contained in:
Martin Kavík 2024-06-03 19:11:22 +02:00
parent 6cdccb72b2
commit ce36cd914f
13 changed files with 702 additions and 110 deletions

View file

@ -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"] }

View file

@ -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))))
}
})
})
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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>;
}
}

View file

@ -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
};

View file

@ -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> {