-- Leo's gemini proxy

-- Connecting to gemini.ctrl-c.club:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

First try to use Rust, Tauri and Dominator


There are already some choices for developing GUI programs with Rust. I don't want to rely on a C library for doing this, so a web based solution is a natural choice. Tauri provides the infrastructure to create a webview based application in Rust (something similar to Electron).


Work in progress


I got stuck with this project and implemented the application with Tauri and Yew first. Now, after learning some stuff I try to finish this.


Yew - another Rust web framework

Second try - this time with Yew


Goal


I want to create a simple application which demonstrates the most central concepts for a Tauri application. It has to show some GUI and has to demonstrate at least the use of Tauri commands, possibly also Tauri events.

I chose a stopwatch. The watch can include start/stop buttons which control the backend and it could use Tauri events which trigger the update of the timer display.


Components used in this project:


The Rust programming language

Tauri, portable GUI programs with a Rust backend

Dominator, a very fast Web framework for Rust

tauri-sys - connection between wasm and Tauri


I will not describe here how to setup Rust. You can look this up on the Rust website. Rust is able to generate code in WASM, another way to let programs run inside the browser. To connect the Javascript world and WASM generated from Rust there is the wasm-bindgen library in Rust which is also used by Dominator.

Module bundling in Dominator seems to be done by "Rollup".


wasm-bindgen documentation

Rollup module bundler


Set up the Dominator web page


There seems to be no way to set up an empty Dominator project through a command. So I will use a Dominator example as starting point to setup a new project. Ideally it would use Tauri already but I have no idea how to do this. There is a project that was meant to work as a template for using Dominator with Tauri, but it seems to be a dead project (is now archived on Github). It uses trunk as it's module bundler:


Example Dominator project for Tauri by Alve Larsson (dead?)

Trunk - WASM application bundler for Rust


By the way - the whole module bundler topic for web development is something I really hate - there seem to be thousand options and I never got to the point that I fully understood what they are doing. With Typescript the bundlers seem to also compile the source code. All really confusing when you come from a "classic" C environment.


Back to setting up the project. Tauri can be installed as an add-on to an existing web project. This worked for me for a simple React web page already. I was able to program the web frontend in React and could add Tauri later, producing a working desktop application. I will use the same approach here - first create a web page in Dominator and then add Tauri into the mix.

Is this is the best way to do it? I don't know (yet).


Dominator contains some examples, one of them is the counter example. Of course this works completely in the browser and doesn't use Tauri. But as I already mentioned I will add Tauri later. Because the counter is very similar to a stopwatch I will use it as a starting point.


First I create a project directory and add all the files of the Dominator example to it. To use Dominator you also need to have "yarn" installed. In turn to install yarn, you also need npm to be installed. You get npm when you install node.js. This is the joy of web programming :)

Btw.: for working with the Dominator repository you also need git.


Node.js homepage

Yarn homepage

Git homepage

Homebrew package manager for Mac


All these pages have instructions how to install the product. Do node.js first and then yarn. If you work on a Macintosh it is highly recommendable to use the Homebrew package manager for software installation. You find the link above.


Installation of Rust (on a Mac). See the Rust homepage for methods on other operating systems. You also need to install the wasm32-unknown-unknown target for the Rust compiler:


curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
rustup update

Now to the creation of the project:


git clone https://github.com/Pauan/rust-dominator/
mkdir stopwatch
cp -r rust-dominator/examples/counter/* stopwatch
cd stopwatch
yarn install
yarn start

The last step will take a while because it has to download the "crates" index, the needed crates and to compile them. If all goes well the web page is opened in your standard browser.


Within the stopwatch folder you find the src folder containing the file lib.rs. This file contains the whole source code implementing the HTML structure and the handling of events. The counter itself is the "model" of the web application. You can find the model in the line 9 - it gets initialized in lines 13 to 17 of the source file:


 8 struct App {
 9     counter: Mutable<i32>,
10 }
11
12 impl App {
13     fn new() -> Arc<Self> {
14         Arc::new(Self {
15             counter: Mutable::new(0),
16         })
17     }

First the counter gets renamed to "seconds":


 8 struct App {
 9     seconds: Mutable<i32>,
10 }
11
12 impl App {
13     fn new() -> Arc<Self> {
14         Arc::new(Self {
15             seconds: Mutable::new(0),
16         })
17     }

The name "counter" is also used in the lines 45, 53, 62, 71 - it has to be reamed there also.


The line 50 and 59 define the text decoration for the upper two buttons:


50    .text("Increase")
59    .text("Decrease")

I changed them to "Start" and "Stop":


50    .text("Start")
59    .text("Stop")

More can't be changed at the moment without adding Tauri into the mix. Of course it would be possible to implement the stopwatch inside the Web frontend but this is not the goal.


All changes of this paragraph are in the following git commit:


git commit with all changes of this paragraph


Application design


Tauri's approach is a sharp separation between the presentation logic in the webview on the one hand and the algorithmic solution and access to local computer resources/network resources at the other hand. This means that most of the computational work and the application data model has to be located in the backend.


Tauri provides procedure calls and events for communication between the webview and the backend. The plan for the stopwatch is the following:


The buttons initiate procedure calls to the backend, they don't expect a result.

The backend provides the functions which are called in reaction to a button press (start, stop, reset).

The backend holds the model. This includes the value of the stopwatch and the state of the stopwatch (running, stopped).

The backend implements a timer which triggers an increment of the stopwatch value when the state of the stopwatch is "running".

The value of the stopwatch is implemented in a way that it emits an event on a value change. This event is sent to the webview (frontend). If it is possible to transmit the timer value with the event then the webview uses the transmitted value for displaying it. If this is not possible the webview uses an additional function call to the backend to retrieve the value in reaction to the event.

The frontend (webview) has it's own model as already implemented in the counter example. It adapts the model in reaction to the events it gets from the backend.


Add Tauri


The following Tauri documentation has instructions how to add Tauri to an already working web application. If you work on Windows you have to pay attention to the second link which describes the prerequisites for Tauri - the important part here is to install webview:


https://tauri.app/v1/guides/getting-started/setup/integrate/

https://tauri.app/v1/guides/getting-started/prerequisites


On a Macintosh you must have the xcode commandline tools to be installed. You do this with the command "xcode-select --install" (also described at the second link).


Now install the Tauri command line interface - you have already installed yarn, so you can use yarn for installation:

yarn add -D @tauri-apps/cli

To setup Tauri the command line interface is used. It asks a series of questions. I give my answers here for reference:

yarn tauri init
-> stopwatch
-> Tauri example application
-> ../dist
-> http://localhost:10001
-> yarn start
-> yarn build

Now Tauri is set up and integrated into your project. Start the development run with the following command:

yarn tauri dev

You will most likely get the following error:

      Error failed to get project out directory: failed to get cargo metadata: cargo metadata command exited with a non zero exit code: error: current package believes it's in a workspace when it's not:
current:   /Users/fte368/stopwatch/src-tauri/Cargo.toml
workspace: /Users/fte368/stopwatch/Cargo.toml

The error message also says what to do about it: Tell Cargo that the Tauri backend is not part of a workspace. You can achieve this by adding the following line to the src-tauri/Cargo.toml file:

[workspace]

It should be followed by an empty line to indicate that there is no workspace involved.


When you repeat the "yarn tauri dev" command it should be successful. You get two results:

The web page with the buttons is opened in your browser

After that a webview (a single window) is opened which also shows the buttons - this is your running application!


Now we have the foundation for implementing the real application.


Create the Tauri backend


The sources for the Tauri backend are located in your project in the folder src/src-tauri/src. There you can find the file main.rs which (for now) contains all the code of the backend.


It's content looks most likely like this:

#![cfg_attr(
  all(not(debug_assertions), target_os = "windows"),
  windows_subsystem = "windows"
)]

fn main() {
  tauri::Builder::default()
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}

In the Application Design paragraph it was said that the backend contains the model. So the first thing to do is to implement the seconds counter and the state of the stopwatch (running, stopped) in the backend.


The definitions for the model are inserted before the main function:

  6 use std::sync::Mutex;
  7
  8 #[derive(Default)]
  9 enum WatchState {
 10   #[default]
 11   Stopped,
 12   Running,
 13 }
 14
 15 struct AppState {
 16   seconds: u32,
 17   watch_state: WatchState
 18 }
 19
 20 // #[derive(Default)]
 21 struct StateManager(Mutex<Option<AppState>>);

Mutex is required because Tauri uses multiple threads which can access the application state. The enum definition WatchState also derives "Default", which can be used to designate a default value for the enum. The struct StateManager also has to derive "Default" because it encapsulates an AppState struct which requires this.


The main function gets a call added which adds a StateManager struct (with default values) to the Tauri Manager:

 23 fn main() {
 24   tauri::Builder::default()
 25     .manage(StateManager(Default::default()))
 ...

To be continued ...


-- Response ended

-- Page fetched on Sun May 19 06:42:42 2024