// Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use std::sync::mpsc::Receiver; use std::{path::PathBuf, sync::Mutex}; use config::Config; use crossbeam::channel::Sender; use notify::PollWatcher; use overlay::{Event, Overlay}; use plan::{Plan, PlanMetadata}; use poe_data::world_area::WorldAreasMap; use poe_reader::filter_func; use poe_reader::{blocking_area_filtered_rx, poe_client_log_receiver}; use simple_logger::SimpleLogger; use storage::Storage; use tauri::CustomMenuItem; use tauri::Manager; use tauri::SystemTray; use tauri::SystemTrayEvent; use tauri::SystemTrayMenu; use tauri::SystemTrayMenuItem; use tauri::Window; use lazy_static::lazy_static; mod config; mod overlay; mod plan; mod poe_reader; mod storage; lazy_static! { static ref WORLD_AREAS_MAP: WorldAreasMap = poe_data::world_area::load_world_areas_map( include_str!("../../data/processed_world_areas.json") ); } #[tauri::command] fn set_interactable(interactable: bool, state: tauri::State>) { if interactable { state.send(overlay::State::Interactable {}.into()).ok(); } else { state.send(overlay::Event::Return).ok(); } } #[tauri::command] fn load_world_areas() -> WorldAreasMap { log::info!("Loading world areas"); WORLD_AREAS_MAP.clone() } #[tauri::command] fn load_config(state: tauri::State>) -> Option { Some(state.lock().ok()?.config.clone()) } #[tauri::command] fn update_config(config: Config, state: tauri::State>) { log::info!("Saved config: {:?}", config); if let Ok(mut storage) = state.lock() { storage.config = config.clone(); storage.save_config(); } } #[tauri::command] fn enumerate_stored_plans() -> Vec { Storage::enumerate_plans() } #[tauri::command] fn load_plan_at_path( path: PathBuf, save_local: Option, state: tauri::State>, ) -> Option { if !path.exists() { return None; } let mut storage = match state.lock() { Ok(storage) => storage, Err(_) => return None, }; let save_local = save_local.unwrap_or(true); storage.last_plan = Some(path.clone()); Storage::load_plan_at_path(path, save_local) } #[tauri::command] fn save_plan_at_path(path: PathBuf, plan: Plan) -> bool { if let Some(path_string) = path.with_extension("json").to_str() { return Storage::save_plan_at_path(path_string, plan).is_ok(); } false } #[tauri::command] fn save_plan_at_store(name: String, plan: Plan) -> Option { Storage::save_plan_at_store_path(&name, plan).ok() } #[tauri::command] fn base_plan() -> Plan { const BASE_PLAN_STRING: &str = include_str!("../../data/base_plan.json"); serde_json::from_str(BASE_PLAN_STRING).expect("could not load base_plan") } fn main() { SimpleLogger::new() .with_module_level("underlayer", log::LevelFilter::Info) .with_level(log::LevelFilter::Trace) .init() .expect("Could not init logger"); let settings = CustomMenuItem::new("settings".to_string(), "Settings"); let editor = CustomMenuItem::new("editor".to_string(), "Plan Editor"); let force_show = CustomMenuItem::new("force_show".to_string(), "Force show"); let exit = CustomMenuItem::new("exit".to_string(), "Exit"); let tray_menu = SystemTrayMenu::new() .add_item(settings) .add_item(editor) .add_item(force_show) .add_native_item(SystemTrayMenuItem::Separator) .add_item(exit); let system_tray = SystemTray::new().with_menu(tray_menu); tauri::Builder::default() .setup(|app| { let tx = Overlay::initialize( app.get_window("Overlay") .expect("Could not get main overlay window"), "Path of Exile", ); app.manage(tx); app.manage(Mutex::new(Storage::default())); if let Ok(storage) = app.state::>().lock() { listen_for_zone_changes( storage.config.poe_client_log_path.clone(), app.get_window("Overlay") .expect("Could not get main overlay window"), ); } // app.get_window("Overlay") // .expect("Could not get main overlay window") // .open_devtools(); Ok(()) }) .invoke_handler(tauri::generate_handler![ set_interactable, load_world_areas, load_config, update_config, enumerate_stored_plans, load_plan_at_path, save_plan_at_path, save_plan_at_store, base_plan, ]) .system_tray(system_tray) .on_system_tray_event(|app, event| match event { SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() { "exit" => { std::process::exit(0); } "editor" | "settings" => { if let Some(window) = app.get_window("Normal") { window.show().ok(); window.emit_to("Normal", "loadTab", id).ok(); } } "force_show" => { app.state::>() .send(overlay::State::Interactable {}.into()) .ok(); } _ => {} }, _ => {} }) .on_window_event(|event| match event.event() { tauri::WindowEvent::CloseRequested { api, .. } => { event.window().hide().ok(); api.prevent_close(); } _ => {} }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } fn listen_for_zone_changes(poe_client_log_path: Option, window: Window) { std::thread::spawn(move || { // need _watcher if let Some((enter_area_receiver, _watcher)) = receiver_from_path(&poe_client_log_path) { let world_areas: WorldAreasMap = load_world_areas(); for area in blocking_area_filtered_rx(&enter_area_receiver) { if let Some(area) = filter_func(area) { if let Some(entered) = world_areas.get(&area) { window.emit_to("Overlay", "entered", &entered.named_id).ok(); } } } } }); } fn receiver_from_path(path: &Option) -> Option<(Receiver, PollWatcher)> { if let Some(poe_path) = path { if poe_path.exists() { return Some(poe_client_log_receiver(poe_path.clone())); } } None }