Sling GUI
This commit is contained in:
Generated
+5846
-60
File diff suppressed because it is too large
Load Diff
+5
-2
@@ -10,6 +10,9 @@ bench = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libsvld = { path = "../libsvld" }
|
libsvld = { path = "../libsvld" }
|
||||||
native-windows-gui = { version = "1.0.13", features = ["file-dialog"] }
|
|
||||||
native-windows-derive = "1.0.5"
|
|
||||||
can-dbc = "9.1.0"
|
can-dbc = "9.1.0"
|
||||||
|
slint = "1.16.1"
|
||||||
|
rfd = "0.17.2"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
slint-build = "1.16.1"
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
slint_build::compile("ui/main.slint").unwrap();
|
||||||
|
}
|
||||||
+127
-122
@@ -1,131 +1,136 @@
|
|||||||
#![windows_subsystem = "windows"]
|
#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")]
|
||||||
|
|
||||||
extern crate native_windows_gui as nwg;
|
use std::{
|
||||||
extern crate native_windows_derive as nwd;
|
cell::RefCell,
|
||||||
|
fs,
|
||||||
use std::{cell::RefCell, ffi::OsStr, fs, path::PathBuf};
|
path::PathBuf,
|
||||||
|
rc::Rc,
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
use can_dbc::Dbc;
|
use can_dbc::Dbc;
|
||||||
use nwd::NwgUi;
|
use slint::ComponentHandle;
|
||||||
use nwg::NativeUi;
|
|
||||||
|
|
||||||
#[derive(Default, NwgUi)]
|
slint::include_modules!();
|
||||||
pub struct BasicApp {
|
|
||||||
#[nwg_control(size: (800, 200), title: "Sane Vector Log Decoder", flags: "WINDOW|VISIBLE")]
|
|
||||||
#[nwg_events( OnWindowClose: [BasicApp::say_goodbye] )]
|
|
||||||
window: nwg::Window,
|
|
||||||
|
|
||||||
#[nwg_layout(parent: window, spacing: 1)]
|
#[derive(Default)]
|
||||||
grid: nwg::GridLayout,
|
struct State {
|
||||||
|
dbc_path: Option<PathBuf>,
|
||||||
#[nwg_resource(title: "Select DBC", action: nwg::FileDialogAction::Open, filters: "DBC(*.dbc)|Any(*.*)")]
|
asc_paths: Vec<PathBuf>,
|
||||||
dbc_dialog: nwg::FileDialog,
|
output_dir: Option<PathBuf>,
|
||||||
|
|
||||||
#[nwg_resource(title: "Select ASC(s)", action: nwg::FileDialogAction::Open, multiselect: true, filters: "ASC(*.asc)|Any(*.*)")]
|
|
||||||
asc_dialog: nwg::FileDialog,
|
|
||||||
|
|
||||||
#[nwg_resource(title: "Select output directory", action: nwg::FileDialogAction::OpenDirectory)]
|
|
||||||
out_dialog: nwg::FileDialog,
|
|
||||||
|
|
||||||
#[nwg_control(readonly: true)]
|
|
||||||
#[nwg_layout_item(layout: grid, col: 1, row: 0, col_span: 2)]
|
|
||||||
dbc_file_name: nwg::TextInput,
|
|
||||||
|
|
||||||
#[nwg_control(readonly: true)]
|
|
||||||
#[nwg_layout_item(layout: grid, col: 1, row: 1, col_span: 2)]
|
|
||||||
asc_file_names: nwg::TextInput,
|
|
||||||
|
|
||||||
#[nwg_control(readonly: true)]
|
|
||||||
#[nwg_layout_item(layout: grid, col: 1, row: 2, col_span: 2)]
|
|
||||||
out_directory: nwg::TextInput,
|
|
||||||
|
|
||||||
#[nwg_control(text: "Select DBC")]
|
|
||||||
#[nwg_layout_item(layout: grid, col: 0, row: 0)]
|
|
||||||
#[nwg_events( OnButtonClick: [BasicApp::pick_dbc] )]
|
|
||||||
dbc_button: nwg::Button,
|
|
||||||
|
|
||||||
#[nwg_control(text: "Select ASC(s)")]
|
|
||||||
#[nwg_layout_item(layout: grid, col: 0, row: 1)]
|
|
||||||
#[nwg_events( OnButtonClick: [BasicApp::pick_asc] )]
|
|
||||||
asc_button: nwg::Button,
|
|
||||||
|
|
||||||
#[nwg_control(text: "Select output dir")]
|
|
||||||
#[nwg_layout_item(layout: grid, col: 0, row: 2)]
|
|
||||||
#[nwg_events( OnButtonClick: [BasicApp::pick_out] )]
|
|
||||||
outdir_button: nwg::Button,
|
|
||||||
|
|
||||||
#[nwg_control(text: "Zrób fikołka")]
|
|
||||||
#[nwg_layout_item(layout: grid, col: 0, row: 3, col_span: 3)]
|
|
||||||
#[nwg_events( OnButtonClick: [BasicApp::action] )]
|
|
||||||
action_button: nwg::Button,
|
|
||||||
|
|
||||||
dbc_path: RefCell<Option<PathBuf>>,
|
|
||||||
asc_paths: RefCell<Option<Vec<PathBuf>>>,
|
|
||||||
out_path: RefCell<Option<PathBuf>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BasicApp {
|
|
||||||
fn action(&self) {
|
|
||||||
let dbc_path = self.dbc_path.borrow();
|
|
||||||
let asc_paths = self.asc_paths.borrow();
|
|
||||||
let out_path = self.out_path.borrow();
|
|
||||||
|
|
||||||
if let Some(dbc_path) = dbc_path.as_ref() &&
|
|
||||||
let Some(asc_paths) = asc_paths.as_ref() &&
|
|
||||||
let Some(out_path) = out_path.as_ref() {
|
|
||||||
let dbc_data = fs::read_to_string(dbc_path).unwrap();
|
|
||||||
let dbc = Dbc::try_from(dbc_data.as_str()).unwrap();
|
|
||||||
for asc_path in asc_paths {
|
|
||||||
libsvld::decode(&dbc, asc_path, out_path.as_path());
|
|
||||||
}
|
|
||||||
nwg::modal_info_message(&self.window,
|
|
||||||
"Okej",
|
|
||||||
"Finished!");
|
|
||||||
} else {
|
|
||||||
nwg::modal_error_message(&self.window,
|
|
||||||
"ERROR",
|
|
||||||
"Select all the paths first, you idiot");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pick_dbc(&self) {
|
|
||||||
if self.dbc_dialog.run(Some(&self.window)) &&
|
|
||||||
let Ok(file) = self.dbc_dialog.get_selected_item() {
|
|
||||||
*self.dbc_path.borrow_mut() = Some(file.clone().try_into().unwrap());
|
|
||||||
self.dbc_file_name.set_text(&file.into_string().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pick_asc(&self) {
|
|
||||||
println!("here");
|
|
||||||
if self.asc_dialog.run(Some(&self.window)) &&
|
|
||||||
let Ok(files) = self.asc_dialog.get_selected_items() {
|
|
||||||
*self.asc_paths.borrow_mut() = Some(files
|
|
||||||
.clone()
|
|
||||||
.iter()
|
|
||||||
.map(|x| x.try_into().unwrap())
|
|
||||||
.collect());
|
|
||||||
self.asc_file_names.set_text(&files.join(OsStr::new("; ")).into_string().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pick_out(&self) {
|
|
||||||
if self.out_dialog.run(Some(&self.window)) &&
|
|
||||||
let Ok(path) = self.out_dialog.get_selected_item() {
|
|
||||||
*self.out_path.borrow_mut() = Some(path.clone().try_into().unwrap());
|
|
||||||
self.out_directory.set_text(&path.into_string().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn say_goodbye(&self) {
|
|
||||||
nwg::stop_thread_dispatch();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
nwg::init().expect("Failed to init Native Windows GUI");
|
let window = MainWindow::new().unwrap();
|
||||||
nwg::Font::set_global_family("Segoe UI").expect("Failed to set default font");
|
let state = Rc::new(RefCell::new(State::default()));
|
||||||
let _app = BasicApp::build_ui(Default::default()).expect("Failed to build UI");
|
|
||||||
nwg::dispatch_thread_events();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
window.on_browse_dbc({
|
||||||
|
let window_weak = window.as_weak();
|
||||||
|
let state = state.clone();
|
||||||
|
move || {
|
||||||
|
if let Some(path) = rfd::FileDialog::new()
|
||||||
|
.set_title("Select DBC")
|
||||||
|
.add_filter("DBC files", &["dbc"])
|
||||||
|
.pick_file()
|
||||||
|
{
|
||||||
|
let display = path.display().to_string();
|
||||||
|
state.borrow_mut().dbc_path = Some(path);
|
||||||
|
if let Some(w) = window_weak.upgrade() {
|
||||||
|
w.set_dbc_path(display.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.on_browse_asc({
|
||||||
|
let window_weak = window.as_weak();
|
||||||
|
let state = state.clone();
|
||||||
|
move || {
|
||||||
|
if let Some(paths) = rfd::FileDialog::new()
|
||||||
|
.set_title("Select ASC file(s)")
|
||||||
|
.add_filter("ASC files", &["asc"])
|
||||||
|
.pick_files()
|
||||||
|
{
|
||||||
|
let label = match paths.len() {
|
||||||
|
0 => String::new(),
|
||||||
|
1 => paths[0].display().to_string(),
|
||||||
|
n => format!("{} file(s) selected", n),
|
||||||
|
};
|
||||||
|
let has_asc = !paths.is_empty();
|
||||||
|
state.borrow_mut().asc_paths = paths;
|
||||||
|
if let Some(w) = window_weak.upgrade() {
|
||||||
|
w.set_asc_label(label.into());
|
||||||
|
w.set_has_asc(has_asc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.on_browse_output({
|
||||||
|
let window_weak = window.as_weak();
|
||||||
|
let state = state.clone();
|
||||||
|
move || {
|
||||||
|
if let Some(path) = rfd::FileDialog::new()
|
||||||
|
.set_title("Select output directory")
|
||||||
|
.pick_folder()
|
||||||
|
{
|
||||||
|
let display = path.display().to_string();
|
||||||
|
state.borrow_mut().output_dir = Some(path);
|
||||||
|
if let Some(w) = window_weak.upgrade() {
|
||||||
|
w.set_output_dir(display.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.on_decode({
|
||||||
|
let window_weak = window.as_weak();
|
||||||
|
let state = state.clone();
|
||||||
|
move || {
|
||||||
|
let s = state.borrow();
|
||||||
|
let (Some(dbc_path), Some(output_dir)) = (s.dbc_path.clone(), s.output_dir.clone())
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let asc_paths = s.asc_paths.clone();
|
||||||
|
if asc_paths.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
drop(s);
|
||||||
|
|
||||||
|
if let Some(w) = window_weak.upgrade() {
|
||||||
|
w.set_busy(true);
|
||||||
|
w.set_status("Decoding...".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let window_weak = window_weak.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let result = (|| -> Result<(), String> {
|
||||||
|
let data = fs::read_to_string(&dbc_path)
|
||||||
|
.map_err(|e| format!("Failed to read DBC: {}", e))?;
|
||||||
|
let dbc = Dbc::try_from(data.as_str())
|
||||||
|
.map_err(|e| format!("Failed to parse DBC: {:?}", e))?;
|
||||||
|
for asc in &asc_paths {
|
||||||
|
libsvld::decode(&dbc, asc, &output_dir);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})();
|
||||||
|
|
||||||
|
let message = match result {
|
||||||
|
Ok(()) => "Done.".to_string(),
|
||||||
|
Err(e) => format!("Error: {}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
slint::invoke_from_event_loop(move || {
|
||||||
|
if let Some(w) = window_weak.upgrade() {
|
||||||
|
w.set_busy(false);
|
||||||
|
w.set_status(message.into());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.run().unwrap();
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import { Button, LineEdit, CheckBox, Spinner, GridBox, VerticalBox, HorizontalBox } from "std-widgets.slint";
|
||||||
|
|
||||||
|
export component MainWindow inherits Window {
|
||||||
|
title: "Sane Vector Log Decoder";
|
||||||
|
preferred-width: 640px;
|
||||||
|
preferred-height: 320px;
|
||||||
|
|
||||||
|
in-out property <string> dbc-path;
|
||||||
|
in-out property <string> asc-label;
|
||||||
|
in-out property <bool> has-asc;
|
||||||
|
in-out property <string> output-dir;
|
||||||
|
in-out property <bool> laps;
|
||||||
|
in-out property <bool> busy;
|
||||||
|
in-out property <string> status;
|
||||||
|
|
||||||
|
callback browse-dbc();
|
||||||
|
callback browse-asc();
|
||||||
|
callback browse-output();
|
||||||
|
callback decode();
|
||||||
|
|
||||||
|
VerticalBox {
|
||||||
|
padding: 12px;
|
||||||
|
spacing: 8px;
|
||||||
|
|
||||||
|
GridBox {
|
||||||
|
spacing: 8px;
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Text {
|
||||||
|
text: "DBC file:";
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
LineEdit {
|
||||||
|
read-only: true;
|
||||||
|
text: root.dbc-path;
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: "Browse...";
|
||||||
|
clicked => { root.browse-dbc(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Text {
|
||||||
|
text: "ASC file(s):";
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
LineEdit {
|
||||||
|
read-only: true;
|
||||||
|
text: root.asc-label;
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: "Browse...";
|
||||||
|
clicked => { root.browse-asc(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Text {
|
||||||
|
text: "Output dir:";
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
LineEdit {
|
||||||
|
read-only: true;
|
||||||
|
text: root.output-dir;
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: "Browse...";
|
||||||
|
clicked => { root.browse-output(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
text: "Generate lap beacons";
|
||||||
|
checked <=> root.laps;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle { vertical-stretch: 1; }
|
||||||
|
|
||||||
|
HorizontalBox {
|
||||||
|
padding: 0px;
|
||||||
|
spacing: 8px;
|
||||||
|
|
||||||
|
if root.busy: Spinner {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
indeterminate: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: root.status;
|
||||||
|
horizontal-stretch: 1;
|
||||||
|
vertical-alignment: center;
|
||||||
|
overflow: elide;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Decode!";
|
||||||
|
enabled: !root.busy
|
||||||
|
&& root.dbc-path != ""
|
||||||
|
&& root.has-asc
|
||||||
|
&& root.output-dir != "";
|
||||||
|
clicked => { root.decode(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user