2026-10-14 08:51:38 +02:00
|
|
|
//! The desktop shell of gpx.studio. The engine of the files (`gpx-rs/engine`) runs in this
|
|
|
|
|
//! process, and the frontend talks to it through the commands of `commands`.
|
|
|
|
|
|
2026-10-14 09:22:07 +02:00
|
|
|
mod bytes;
|
2026-10-14 08:51:38 +02:00
|
|
|
mod commands;
|
|
|
|
|
mod state;
|
2026-10-14 19:15:02 +02:00
|
|
|
mod store;
|
|
|
|
|
mod workspace;
|
2026-10-14 08:51:38 +02:00
|
|
|
|
|
|
|
|
use commands::*;
|
2026-10-14 19:15:02 +02:00
|
|
|
use tauri::{Emitter, Manager};
|
|
|
|
|
|
2026-10-16 09:02:31 +02:00
|
|
|
/// The engine of the app: each file has its own history, so that undoing only concerns the
|
|
|
|
|
/// selected files, and opening or closing a file is not something to undo.
|
|
|
|
|
pub(crate) type Engine = gpx_engine::Engine<gpx_engine::PerFileHistory>;
|
|
|
|
|
|
2026-10-14 19:15:02 +02:00
|
|
|
/// The GPX files among what the system gave to the app (the arguments of the command line, when a
|
|
|
|
|
/// file is opened with it).
|
|
|
|
|
fn gpx_paths(arguments: impl IntoIterator<Item = String>) -> Vec<std::path::PathBuf> {
|
|
|
|
|
arguments
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(std::path::PathBuf::from)
|
|
|
|
|
.filter(|path| {
|
|
|
|
|
path.is_file()
|
|
|
|
|
&& path
|
|
|
|
|
.extension()
|
|
|
|
|
.is_some_and(|extension| extension.eq_ignore_ascii_case("gpx"))
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
2026-10-14 08:51:38 +02:00
|
|
|
|
|
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
|
|
|
pub fn run() {
|
2026-10-14 19:15:02 +02:00
|
|
|
let builder = tauri::Builder::default();
|
|
|
|
|
// a single instance of the app: starting it again shows the one that runs (it has to be the
|
|
|
|
|
// first plugin)
|
|
|
|
|
#[cfg(desktop)]
|
|
|
|
|
let builder = builder.plugin(tauri_plugin_single_instance::init(
|
|
|
|
|
|app, arguments, _directory| {
|
|
|
|
|
if let Some(window) = app.get_webview_window("main") {
|
|
|
|
|
let _ = window.unminimize();
|
|
|
|
|
let _ = window.show();
|
|
|
|
|
let _ = window.set_focus();
|
|
|
|
|
}
|
|
|
|
|
// a file that was opened with the app, while it runs (Windows, Linux)
|
|
|
|
|
if let Some(state) = app.try_state::<state::AppState>() {
|
|
|
|
|
state.open_external(gpx_paths(arguments.into_iter().skip(1)));
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
));
|
|
|
|
|
register_commands(builder)
|
|
|
|
|
.plugin(tauri_plugin_dialog::init())
|
|
|
|
|
.setup(|app| {
|
|
|
|
|
let handle = app.handle().clone();
|
|
|
|
|
let emit: state::Emit = std::sync::Arc::new(move |event, payload| {
|
|
|
|
|
let _ = handle.emit(event, payload);
|
|
|
|
|
});
|
|
|
|
|
// what the engine holds is kept in the data directory of the app
|
|
|
|
|
let directory = app.path().app_data_dir()?.join("store");
|
|
|
|
|
let state = state::AppState::open(directory, emit)?;
|
|
|
|
|
// a file that was opened with the app, which starts (Windows, Linux)
|
|
|
|
|
state.open_external(gpx_paths(std::env::args().skip(1)));
|
|
|
|
|
app.manage(state);
|
|
|
|
|
Ok(())
|
|
|
|
|
})
|
|
|
|
|
.build(tauri::generate_context!())
|
|
|
|
|
.expect("error while building gpx.studio")
|
|
|
|
|
.run(|_app, _event| {
|
|
|
|
|
// a file that was opened with the app (macOS)
|
|
|
|
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
|
|
|
|
if let tauri::RunEvent::Opened { urls } = _event
|
|
|
|
|
&& let Some(state) = _app.try_state::<state::AppState>()
|
|
|
|
|
{
|
|
|
|
|
let paths = urls.iter().filter_map(|url| url.to_file_path().ok());
|
|
|
|
|
state.open_external(gpx_paths(
|
|
|
|
|
paths.map(|path| path.to_string_lossy().into_owned()),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-10-14 08:51:38 +02:00
|
|
|
}
|
|
|
|
|
|
2026-10-14 19:15:02 +02:00
|
|
|
/// Gives the builder the commands that reach the engine (which it has to be given as `AppState`).
|
|
|
|
|
fn register_commands<R: tauri::Runtime>(builder: tauri::Builder<R>) -> tauri::Builder<R> {
|
|
|
|
|
builder.invoke_handler(tauri::generate_handler![
|
|
|
|
|
// selection
|
|
|
|
|
selection::select,
|
|
|
|
|
selection::select_all,
|
|
|
|
|
selection::arrow_select,
|
|
|
|
|
selection::select_tracks,
|
|
|
|
|
selection::select_segments,
|
|
|
|
|
selection::select_waypoint_group,
|
|
|
|
|
selection::select_waypoints,
|
|
|
|
|
// files
|
|
|
|
|
files::new_file,
|
|
|
|
|
files::load_files,
|
|
|
|
|
files::duplicate,
|
|
|
|
|
files::delete,
|
|
|
|
|
files::delete_all,
|
|
|
|
|
files::reorder,
|
|
|
|
|
files::copy,
|
|
|
|
|
files::cut,
|
|
|
|
|
files::paste,
|
|
|
|
|
files::move_elements,
|
|
|
|
|
files::undo,
|
|
|
|
|
files::redo,
|
|
|
|
|
// edits and tools
|
|
|
|
|
edit::metadata,
|
|
|
|
|
edit::style,
|
|
|
|
|
edit::new_track,
|
|
|
|
|
edit::new_track_segment,
|
|
|
|
|
edit::new_waypoint,
|
|
|
|
|
edit::move_waypoint,
|
|
|
|
|
edit::delete_waypoint,
|
|
|
|
|
edit::update_waypoint,
|
|
|
|
|
edit::reverse,
|
|
|
|
|
edit::round_trip,
|
|
|
|
|
edit::crop,
|
|
|
|
|
edit::reduce,
|
|
|
|
|
edit::change_timestamps,
|
|
|
|
|
edit::create_artificial_timestamps,
|
|
|
|
|
edit::merge,
|
|
|
|
|
edit::extract,
|
|
|
|
|
edit::elevation,
|
|
|
|
|
edit::clean,
|
|
|
|
|
edit::route,
|
|
|
|
|
edit::insert_anchor,
|
|
|
|
|
edit::change_loop_start,
|
|
|
|
|
edit::split,
|
|
|
|
|
// queries
|
|
|
|
|
queries::file_order,
|
|
|
|
|
queries::file_structure,
|
|
|
|
|
queries::file_statistics,
|
|
|
|
|
queries::selection,
|
|
|
|
|
queries::clipboard,
|
|
|
|
|
queries::can_undo,
|
|
|
|
|
queries::can_redo,
|
|
|
|
|
queries::can_paste,
|
|
|
|
|
queries::waypoint,
|
|
|
|
|
queries::trackpoint,
|
|
|
|
|
queries::export_file,
|
|
|
|
|
queries::exportable_data,
|
|
|
|
|
queries::reduction_distances,
|
|
|
|
|
queries::segment_coordinates,
|
|
|
|
|
queries::waypoint_coordinates,
|
|
|
|
|
// statistics
|
|
|
|
|
statistics::total_distance,
|
|
|
|
|
statistics::slope,
|
|
|
|
|
statistics::slope_segment_slope,
|
|
|
|
|
statistics::slope_segment_distance,
|
|
|
|
|
statistics::lng,
|
|
|
|
|
statistics::lat,
|
|
|
|
|
statistics::ele,
|
|
|
|
|
statistics::speed,
|
|
|
|
|
statistics::hr,
|
|
|
|
|
statistics::cad,
|
|
|
|
|
statistics::atemp,
|
|
|
|
|
statistics::power,
|
|
|
|
|
statistics::timestamps,
|
|
|
|
|
statistics::selection_statistics,
|
|
|
|
|
statistics::slice_statistics,
|
|
|
|
|
statistics::surface_starts,
|
|
|
|
|
statistics::surface_values,
|
|
|
|
|
statistics::highway_starts,
|
|
|
|
|
statistics::highway_values,
|
|
|
|
|
statistics::sac_scale_starts,
|
|
|
|
|
statistics::sac_scale_values,
|
|
|
|
|
statistics::mtb_scale_starts,
|
|
|
|
|
statistics::mtb_scale_values,
|
|
|
|
|
statistics::surfaces,
|
|
|
|
|
statistics::highways,
|
|
|
|
|
statistics::sac_scales,
|
|
|
|
|
statistics::mtb_scales,
|
|
|
|
|
statistics::anchor_indices,
|
|
|
|
|
statistics::anchor_zooms,
|
|
|
|
|
statistics::segment_starts,
|
|
|
|
|
statistics::segment_ids,
|
|
|
|
|
statistics::routing_revision,
|
|
|
|
|
// storage
|
|
|
|
|
storage::open_storage,
|
|
|
|
|
storage::flush_storage,
|
|
|
|
|
storage::sync_storage,
|
|
|
|
|
storage::set_setting,
|
|
|
|
|
storage::delete_setting,
|
|
|
|
|
// the file system
|
|
|
|
|
disk::workspace_state,
|
|
|
|
|
disk::open_files,
|
2026-10-16 18:14:59 +02:00
|
|
|
disk::open_paths,
|
2026-10-14 19:15:02 +02:00
|
|
|
disk::open_folder,
|
|
|
|
|
disk::close_folder,
|
2026-10-16 18:14:59 +02:00
|
|
|
disk::close_files,
|
2026-10-14 19:15:02 +02:00
|
|
|
disk::save_files,
|
2026-10-16 18:14:59 +02:00
|
|
|
disk::save_file_as,
|
|
|
|
|
disk::reload_files,
|
|
|
|
|
disk::export_files,
|
|
|
|
|
disk::list_folder,
|
2026-10-14 19:15:02 +02:00
|
|
|
])
|
2026-10-14 08:51:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests;
|