Practice Rust and TAURI: Make an Image Viewer #2

mar-m. nakamura
4 min readJan 29, 2022

--

I will make an image viewer and study. 😊
It is a continuation from #1.

🚧I would like to inform you in advance that I am an immature developer and an inefficient study. I’m a hobby programmer who wants to have fun learning.πŸ˜™

This time, I will try to get the drive list of the PC and switch the drive.

Since it is an internal process, the appearance does not improve from the previous template. It’s a tedious task.

This goal

For the time being, I wrote the following JavaScript.

Create : tauri_imgv/dist/main.js

class RequestToRust {
#invoke;
constructor() {
this.#invoke = window.__TAURI__.invoke;
}
/**
* Request drive change
* @param {number} drvNum Request drive number
* @returns {number} Changed drive number
*/
async changeDrive(drvNum) {
const newDrvNum =
await this.#invoke('change_drive', {
chgNum: drvNum
}).then((_newDrvNum) => {
return _newDrvNum;
}).catch(() => 0);
return newDrvNum;
}
}
class UserOperation {
#reqRust;
#drives = [];
#activeDriveNum = 0; // Key of #drives
constructor() {
this.#reqRust = new RequestToRust();
}
async init(payload) {
this.#drives = payload.drives;
document.querySelector('info').innerHTML = this.#drives;
this.#activeDriveNum = await this.#reqRust.changeDrive(this.#activeDriveNum);
}
/**
* Select drive
* @param {number} inc increment (or decrement) current drive number
*/
async selectDrive(inc) {
let n = this.#activeDriveNum += parseInt(inc);
let maxNum = this.#drives.length - 1;
n = (n < 0) ? maxNum : n;
n = (n > maxNum) ? 0 : n;
this.#activeDriveNum = await this.#reqRust.changeDrive(n);
console.log("Drv: " + this.#activeDriveNum);
}
}
// Boot event ( from Rust .on_page_load() )
window.__TAURI__.event.listen('boot', async (ev) => {
main(ev.payload);
});
/**
* Main : key operation
*/
const main = (payload) => {
const op = new UserOperation();
op.init(payload);
// Hook key down
document.addEventListener('keydown', (e) => {
// Ignore IME inputting
if (e.isComposing) {
return;
}
switch (e.key) {
case 'ArrowLeft':
op.selectDrive(-1);
break;
case 'ArrowRight':
op.selectDrive(1);
break;
default:
break;
}
});
}

The event boot is emitted by .on_page_load on the Rust side (I will make it after this.), and the main on the javascript side is started together with the payload.

I would like to include a list of PC drives in the payload. Store this in an array.

By pressing the left and right keys on the keyboard, instructions are sent to the command change_drive on the Rust side.

You can send the drive letter letter (C, D, etc...) as it is, but this time we will send the key number of the array. Therefore, the Rust side also keeps the array of drive list.

Now, prepare the Rsut side.

Edit : tauri_imgv/src-tauri/src/main.rs

#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
use serde::Serialize;
use std::{
path::{Path, PathBuf},
sync::Mutex,
};
use tauri::{api::dir::is_dir, State};#[derive(Serialize)]
struct BootPayload {
drives: Vec<String>
}
#[derive(Debug)]
struct DriveEntries(Mutex<Vec<String>>);
struct ActiveDrive(Mutex<usize>);
struct ActivePath(Mutex<PathBuf>);
#[tauri::command]
fn change_drive(
chg_num: usize,
active_drive: State<ActiveDrive>,
drives: State<DriveEntries>,
active_path: State<ActivePath>,
) -> usize {
let v_drives = drives.0.lock().unwrap().to_vec();
let new_num = if let Some(_letter) = v_drives.get(chg_num) {
chg_num
} else {
0
};
*active_drive.0.lock().unwrap() = new_num;
*active_path.0.lock().unwrap() = Path::new(dbg!(&v_drives[new_num])).to_path_buf();
new_num
}
fn main() {
tauri::Builder::default()
.manage(DriveEntries(scan_drive().into()))
.manage(ActiveDrive(0.into()))
.manage(ActivePath(Default::default()))
.invoke_handler(tauri::generate_handler![
change_drive,
])
.on_page_load(|window, _payload| {
let payload = BootPayload { drives: scan_drive() };
window
.emit("boot", Some(payload))
.expect("failed to emit event");
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
fn scan_drive() -> Vec<String> {
let mut drives: Vec<String> = Vec::new();
for a in 0..26 + b'A' {
let d = format!("{}:\\", a as char);
if let Ok(_) = is_dir(&d) {
drives.push(d);
};
}
print!("drives --> {}", &drives);
drives
}

I managed the state of DriveEntries, ActiveDrive, and ActivePath.
It manages the drive list array, the key number of the selected drive array, and the selected path, respectively. ActivePath is still only the drive root, but in the future we plan to store directories and file paths.

scan_drive() returns a list of PC drives as an array. (For Example, [β€œC:\","D:\","E:\",...].) This is the initial value of DriveEntries and the payload of the event boot that calls javascript with .on_page_load.

Let’s run it.
A list of PC drives will be displayed in the red area.

Operation example

Notice the console of the developer tools (right-clicking the window) and the running terminal.
Confirm that the drive variable is changed by operating the left and right keys on the keyboard.

I managed to finish it. πŸ˜„
It was a long article, but the visible results were modest.

Thank you for reading.
Next time I would like to get a list of directory entries.

--

--

mar-m. nakamura

CatShanty2 Author. I want to develop a modern β€œ3” ! But … My brain is still 8-bit / 32KB.πŸ˜