hrrr 0.10.0

A fast native HRRR forecast-field viewer
use crate::{
    app::WeatherApp,
    map::MapGpu,
    tray::{Signal as TraySignal, Tray},
    vector_map::VectorMapGpu,
};
use anyhow::Result;
use eternalist_apps::{CloseDisposition, NativeApp, WindowSpec};
use std::sync::{
    Arc,
    atomic::{AtomicBool, Ordering},
};

const TITLE: &str = "HRRR ยท native forecast fields";

pub fn run(ctx: egui::Context) -> Result<()> {
    let app = ForecastViewer {
        weather: WeatherApp::open(&ctx)?,
        tray: None,
        tray_armed: false,
        quit: Arc::new(AtomicBool::new(false)),
    };
    eternalist_apps::run(ctx, app)
}

struct ForecastViewer {
    weather: WeatherApp,
    tray: Option<Tray>,
    tray_armed: bool,
    quit: Arc<AtomicBool>,
}

impl ForecastViewer {
    fn arm_tray(&mut self, ctx: &egui::Context) {
        if self.tray_armed {
            return;
        }
        self.tray_armed = true;
        let ctx = ctx.clone();
        let quit = Arc::clone(&self.quit);
        match Tray::raise(move |signal| match signal {
            TraySignal::Reveal => {
                ctx.send_viewport_cmd(egui::ViewportCommand::Visible(true));
                ctx.send_viewport_cmd(egui::ViewportCommand::Minimized(false));
                ctx.send_viewport_cmd(egui::ViewportCommand::Focus);
                ctx.request_repaint();
            }
            TraySignal::Quit => {
                quit.store(true, Ordering::Release);
                ctx.request_repaint();
            }
        }) {
            Ok(tray) => self.tray = Some(tray),
            Err(error) => eprintln!("could not raise HRRR tray icon: {error:#}"),
        }
    }
}

impl NativeApp for ForecastViewer {
    const WINDOW: WindowSpec = WindowSpec::new(TITLE, [1_440.0, 920.0]);

    fn draw(&mut self, ui: &mut egui::Ui) {
        self.arm_tray(ui.ctx());
        self.weather.pulse(ui);
    }

    fn close_requested(&mut self) -> CloseDisposition {
        self.weather.retire();
        if self.weather.close_minimizes() && self.tray.as_ref().is_some_and(Tray::available) {
            CloseDisposition::Hide
        } else {
            CloseDisposition::Exit
        }
    }

    fn exit_requested(&self) -> bool {
        self.quit.load(Ordering::Acquire)
    }

    fn after_present(&mut self) -> bool {
        false
    }

    fn water(
        &mut self,
        ctx: &egui::Context,
        pixels_per_point: f32,
        tooltip_rects: &[egui::Rect],
    ) -> dwemer_poolrooms::water::Frame {
        self.weather
            .water_frame(ctx, pixels_per_point, tooltip_rects)
    }

    fn register_gpu(
        renderer: &mut egui_wgpu::Renderer,
        device: &egui_wgpu::wgpu::Device,
        format: egui_wgpu::wgpu::TextureFormat,
    ) {
        let _prior = renderer
            .callback_resources
            .insert(MapGpu::new(device, format));
        let _prior = renderer
            .callback_resources
            .insert(VectorMapGpu::new(device, format));
    }

    #[cfg(feature = "egui-test")]
    type Observation = crate::witness::State;

    #[cfg(feature = "egui-test")]
    fn observe(&self, _text_edit_focused: bool) -> Self::Observation {
        self.weather.witness_state()
    }
}