Sling GUI
This commit is contained in:
+5
-2
@@ -10,6 +10,9 @@ bench = false
|
||||
|
||||
[dependencies]
|
||||
libsvld = { path = "../libsvld" }
|
||||
native-windows-gui = { version = "1.0.13", features = ["file-dialog"] }
|
||||
native-windows-derive = "1.0.5"
|
||||
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;
|
||||
extern crate native_windows_derive as nwd;
|
||||
|
||||
use std::{cell::RefCell, ffi::OsStr, fs, path::PathBuf};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fs,
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
thread,
|
||||
};
|
||||
|
||||
use can_dbc::Dbc;
|
||||
use nwd::NwgUi;
|
||||
use nwg::NativeUi;
|
||||
use slint::ComponentHandle;
|
||||
|
||||
#[derive(Default, NwgUi)]
|
||||
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,
|
||||
slint::include_modules!();
|
||||
|
||||
#[nwg_layout(parent: window, spacing: 1)]
|
||||
grid: nwg::GridLayout,
|
||||
|
||||
#[nwg_resource(title: "Select DBC", action: nwg::FileDialogAction::Open, filters: "DBC(*.dbc)|Any(*.*)")]
|
||||
dbc_dialog: nwg::FileDialog,
|
||||
|
||||
#[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();
|
||||
}
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
dbc_path: Option<PathBuf>,
|
||||
asc_paths: Vec<PathBuf>,
|
||||
output_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
nwg::init().expect("Failed to init Native Windows GUI");
|
||||
nwg::Font::set_global_family("Segoe UI").expect("Failed to set default font");
|
||||
let _app = BasicApp::build_ui(Default::default()).expect("Failed to build UI");
|
||||
nwg::dispatch_thread_events();
|
||||
}
|
||||
let window = MainWindow::new().unwrap();
|
||||
let state = Rc::new(RefCell::new(State::default()));
|
||||
|
||||
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