cartography 0.10.0

Cartography is a map rendering library for Geographic features expressed using [georust](https://georust.org/) libraries.
Documentation
//! Integration with egui.

use std::{ops::Deref, sync::Arc};

use ccutils::{log::log_error, sync::ArcRwLock};
use egui::{ColorImage, TextureHandle};

use crate::{Result, map, renderer, styling};

/// Map Renderer
pub struct Renderer
{
  vello_renderer: crate::vello::ThreadedRenderer,
  cpu_image_sub: crossbeam::channel::Receiver<crate::vello::CpuImage>,
  texture: ArcRwLock<Option<TextureHandle>>,
}

impl Renderer
{
  /// Create a new Renderer for the given map, which can be used by a widget to show in the Ui.
  pub fn new<TFeature: map::Feature + 'static>(
    map: Arc<map::Map<TFeature>>,
    style: styling::Style<TFeature>,
  ) -> Result<Self>
  {
    let (device, queue) = crate::vello::ThreadedRenderer::new_device()?;
    let mut vello_renderer = crate::vello::ThreadedRenderer::new_thread(device, queue, map, style)?;
    let cpu_image_sub = vello_renderer.subscribe_to_cpu_image()?;

    Ok(Self {
      vello_renderer,
      cpu_image_sub,
      texture: None.into(),
    })
  }

  /// Redraw the map
  pub fn update(&mut self) -> Result<()>
  {
    self.vello_renderer.update()
  }
  fn resize(&mut self, s: egui::Vec2) -> Result<()>
  {
    self.vello_renderer.resize(
      s.x.ceil() as u32,
      s.y.ceil() as u32,
      Option::<fn(&crate::vello::Texture)>::None,
    )
  }
  /// Update the view controller
  pub fn update_view_controller(&mut self, u: impl Fn(&mut renderer::ViewController))
  {
    self.vello_renderer.update_view_controller(u)
  }
  /// Execute on view controller
  pub fn use_view_controller<T>(&self, u: impl FnOnce(&renderer::ViewController) -> T) -> T
  {
    self.vello_renderer.use_view_controller(u)
  }
}

/// Widget for displaying a map.
pub struct RendererWidget<'a>
{
  renderer: &'a mut Renderer,
}

impl<'a> RendererWidget<'a>
{
  fn new(renderer: &'a mut Renderer) -> Self
  {
    Self { renderer }
  }
}

impl<'a> egui::Widget for RendererWidget<'a>
{
  fn ui(self, ui: &mut egui::Ui) -> egui::Response
  {
    let (id, rect) = ui.allocate_space(ui.available_size());
    let response = ui.interact(rect, id, egui::Sense::click_and_drag());

    log_error!(self.renderer.resize(rect.size()), "While updating egui");

    if let Ok(cpu_image) = self.renderer.cpu_image_sub.try_recv()
    {
      let mut texture = self.renderer.texture.write().unwrap();

      let color_image = ColorImage::from_rgba_unmultiplied(
        [cpu_image.width as usize, cpu_image.height as usize],
        &cpu_image.data,
      );
      *texture = Some(ui.ctx().load_texture(
        "vello_image",
        color_image,
        egui::TextureOptions::LINEAR,
      ));
    }

    let texture = self.renderer.texture.read().unwrap();

    if let Some(texture) = texture.as_ref()
    {
      ui.painter().image(
        texture.id(),
        rect,
        egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)),
        egui::Color32::WHITE,
      );
    }
    response
  }
}

/// Wrap egui Response and extend it with controls specific to the map
pub struct Response<'a>
{
  raw_response: egui::Response,
  map: &'a mut Renderer,
  wheel_delta: Option<egui::Vec2>,
}

impl<'a> Deref for Response<'a>
{
  type Target = egui::Response;
  fn deref(&self) -> &Self::Target
  {
    &self.raw_response
  }
}

impl<'a> Response<'a>
{
  /// Handle zooming and panning
  pub fn handle_interractions(&mut self) -> Result<()>
  {
    let mut should_update = false;

    if let Some(wheel) = self.wheel_delta
    {
      if wheel.y > 0.0
      {
        self.map.update_view_controller(|vc| vc.zoom(0.75));
      }
      else
      {
        self.map.update_view_controller(|vc| vc.zoom(1.0 / 0.75));
      }
      should_update = true;
    }
    if self.raw_response.is_pointer_button_down_on()
    {
      let dm = self.drag_delta();
      self.map.update_view_controller(|vc| {
        let m = vc.motion_view_to_coord(&(dm.x as f64, dm.y as f64).into());
        vc.pan(-m.x, -m.y);
      });
      should_update = true;
    }

    if should_update
    {
      self.map.update()?;
    }
    Ok(())
  }
  /// Return the mouse pointer coordinate in the map coordinates
  pub fn latest_pos_map(&self) -> Option<geo::Coord>
  {
    self.hover_pos().map(|cp| {
      self.map.use_view_controller(|vc| {
        vc.view_to_coord(
          &(
            (cp.x - self.rect.left()) as f64,
            (cp.y - self.rect.top()) as f64,
          )
            .into(),
        )
      })
    })
  }
}

/// Extend egui::Ui to add widget for
pub trait Ui
{
  /// Create a new map viewer
  fn map_viewer<'a>(&mut self, map: &'a mut Renderer) -> Response<'a>;
}

impl Ui for egui::Ui
{
  fn map_viewer<'a>(&mut self, map: &'a mut Renderer) -> Response<'a>
  {
    use egui::Widget;
    let raw_response = RendererWidget::new(map).ui(self);
    let wheel_delta = if raw_response.hovered()
    {
      self.input(|i| {
        i.events.iter().find_map(|e| match e
        {
          egui::Event::MouseWheel {
            delta, modifiers, ..
          } if modifiers.command_only() => Some(*delta),
          _ => None,
        })
      })
    }
    else
    {
      None
    };
    Response {
      raw_response,
      wheel_delta,
      map,
    }
  }
}