This page is unfinished.

The following page is a Work In Progress and will suffer changes in the future.

Tech used: TauriRustVueTypeScript

I wanted to explore a framework for desktop (and possibly mobile) applications.

Java and Rust are preferred but I am no stranger to JavaScript (or TypeScript for that matter).

Warning: I’m developing all of this in Ubuntu 22.04

Choosing a framework

JavaFX

Electron

Flutter

Qt

Tauri

Tauri basics

Installation is done following the website’s instructions. https://tauri.app/

Project structure is composed of a rust backend and the chosen frontend, Vue in this case.

invoke(<method>, <args>) primitive to call Rust functions in the backend from the frontend.

Event structure shared between backend and frontend, with emit and listen primitives.

Vue basics

Vue’s way of programming is based in components.

<script setup> procedure

Initializing components’s variables is done with ref(<inital value>).

Avoiding await in the setup step and opting with .then(..). It is doable using the Suspense feature but it is still experimental. https://vuejs.org/guide/built-ins/suspense.html

Variables initialized with ref() are accessed with .value our .values within the setup, but everywhere else it is accessed directly (trust me, this is something to watch out as it may lead to hours of useless debugging).

A fix for pinch zoom

The Tauri app window allows for pinch zoom, i.e. when you do a zoom gesture in a trackpad.

It doesn’t work like a normal page zoom where contents are scaled, only the view is scaled.

After many hours of search I found how to do it based on these links: https://github.com/tauri-apps/tauri/discussions/3843 https://docs.rs/tauri/latest/tauri/window/struct.Window.html

Warning: you need to add webkit2gtk to your Cargo.toml

[dependencies]
...
webkit2gtk = "0.18"

And came up with this:

pub fn fix_pinch_zoom(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
    let main_window = app.get_window("main").unwrap();
    main_window.with_webview(|webview| {
        #[cfg(target_os = "linux")]
        {
            unsafe {
                if let Some(data) = webview.inner().data::<>("wk-view-zoom-gesture") {
                    gobject_ffi::g_signal_handlers_destroy(data.as_ptr());
                }
            }
        }
    }).expect("Something went wrong disabling pinch zoom.");

    Ok(())
}

Applied with setup() during the app creation:

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

A solution for zooming in and out

In rust we add two menu options Zoom In and Zoom Out.

We can add key shortcuts such as Ctrl++ and Ctrl+- for each.

pub fn create_menu() -> Menu {
    let options_menu = 
        Menu::new().add_item(CustomMenuItem::new("zoom_in".to_string(), "Zoom In").accelerator("CommandOrControl+-"))
                   .add_item(CustomMenuItem::new("zoom_out".to_string(), "Zoom Out").accelerator("CommandOrControl+Plus"));

    let menu = Menu::new().add_submenu(Submenu::new("Options", options_menu));
    
    return menu;
}

Warning: these do not seem to be working with Wayland, only with X11. https://github.com/tauri-apps/tauri/issues/3578

We add a listener to each menu item to get notified when they get pressed. When they get pressed they emit an event.

pub fn handle_menu_event(event: WindowMenuEvent) {
    match event.menu_item_id() {
        "zoom_in" => {
            event.window().emit("zoom_in", {}).unwrap();
        }
        "zoom_out" => {
            event.window().emit("zoom_out", {}).unwrap();
        }
        _ => {}
      }
}

On the other side, the frontend, we listen for these events and resize the window as needed.

const curr_zoom = ref(1.0);

async function resize(zoom:number) {
  const total_height = window.outerHeight;
  const total_width = window.outerWidth;

  const vertical_padding   = 25;
  const horizontal_padding = 0;
  
  // calculate new app size
  // const new_app_height =  zoom > 1 ? (total_height - 16) * zoom + 16 : (total_height + 16) * zoom - 16;
  const new_app_height = (total_height - vertical_padding) * zoom + vertical_padding;
  const new_app_width  = total_width  * zoom + horizontal_padding;
  
  // create new logical size
  const new_size = new LogicalSize(new_app_width, new_app_height);
  
  // set size
  await getCurrent().setSize(new_size);
  
  // scale contents through rust
  const new_zoom = curr_zoom.value * zoom;
  // document.body.style.scale = new_zoom + "%";
  curr_zoom.value = new_zoom;
}

listen('zoom_in', (_) => {
  resize(0.625);
});

listen('zoom_out', (_) => {
  resize(1.6);
});

curr_zoom is a variable used to determine the container’s zoom CSS style value.

References

https://scythe-studio.com/en/blog/4-best-frameworks-for-cross-platform-desktop-app-development https://medium.com/@maxel333/comparing-desktop-application-development-frameworks-electron-flutter-tauri-react-native-and-fd2712765377