use super::exterior::ExteriorWidget; use super::interior::InteriorWidget; use super::{Layer, WindowCoord}; use crate::coords::proj::Mercator; use crate::coords::Mapper; use crate::pipeline::element::{Target, TargetType}; use femtovg::{Canvas, Color, FontId, Paint, Renderer}; use gtk::glib::{self, prelude::*, Properties}; use gtk::subclass::prelude::*; use gtk::traits::{GLAreaExt, WidgetExt}; use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::num::NonZeroU32; use std::rc::Rc; use std::sync::{Arc, Mutex}; use slippy_map_tiles::Tile; use tracing::info; #[derive(Debug, Default, Clone, Copy, PartialEq)] pub struct RenderConfig { pub padding: [f32; 4], } #[derive(Debug, Clone)] pub enum RenderMotion { Translate, Scale, None, } impl Default for RenderMotion { fn default() -> Self { Self::None } } #[derive(Default)] pub struct RenderStatus { pub(super) window_size: Option<(i32, i32)>, pub(super) scale_rate: Option, pub(super) pointer_location: WindowCoord, pub(super) motion: RenderMotion, pub(super) scale: f32, pub(super) translate: Option<(f32, f32)>, pub(super) view_range: Option<((f64, f64), (f64, f64))>, translation: Option<(f64, f64)>, init_scale_rate: f64, init_translation: (f64, f64), } #[derive(Properties)] #[properties(wrapper_type = super::Render)] pub struct Render { #[property(get, set)] render_status: Cell, #[property(get, set)] range_changing: Cell, #[property(get, set)] scale: Cell, pub(super) exterior: RefCell, pub(super) interior: RefCell, pub(super) canvas: RefCell>>, pub(super) glow_context: RefCell>, pub(super) tiles: RefCell>>>>, pub config: RefCell, pub status: RefCell, pub mapper: RefCell, pub(super) interior_layers: RefCell>>>, } impl Default for Render { fn default() -> Self { Self { scale: Cell::new(1.0), range_changing: Cell::new(0.0), render_status: Cell::new(0), exterior: RefCell::new(ExteriorWidget::default()), interior: RefCell::new(InteriorWidget::default()), glow_context: RefCell::new(None), interior_layers: RefCell::new(Rc::new(RefCell::new(Vec::new()))), config: RefCell::new(RenderConfig::default()), status: RefCell::new(RenderStatus::default()), tiles: RefCell::new(Rc::new(RefCell::new(HashMap::new()))), mapper: RefCell::new(Mercator::default().into()), canvas: RefCell::new(None), } } } #[glib::object_subclass] impl ObjectSubclass for Render { const NAME: &'static str = "Render"; type Type = super::Render; type ParentType = gtk::GLArea; } impl ObjectImpl for Render { fn constructed(&self) { self.parent_constructed(); let area = self.obj(); area.set_has_stencil_buffer(true); } fn properties() -> &'static [glib::ParamSpec] { Self::derived_properties() } fn set_property(&self, _id: usize, _value: &glib::Value, _pspec: &glib::ParamSpec) { Self::derived_set_property(&self, _id, _value, _pspec) } fn property(&self, _id: usize, _pspec: &glib::ParamSpec) -> glib::Value { Self::derived_property(&self, _id, _pspec) } } impl WidgetImpl for Render {} impl GLAreaImpl for Render { fn resize(&self, width: i32, height: i32) { self.status.borrow_mut().window_size = Some((width, height)); self.ensure_canvas(); let mut canvas = self.canvas.borrow_mut(); let canvas = canvas.as_mut().unwrap(); canvas.set_size( width as u32, height as u32, self.obj().scale_factor() as f32, ); let translation = self.status.borrow().translation; let scale_rate = self.status.borrow().scale_rate; let mapper = self.mapper.borrow(); if let None = translation { let mut status = self.status.borrow_mut(); status.translation = Some((mapper.get_bounds().0, mapper.get_bounds().2)); status.init_translation = (mapper.get_bounds().0, mapper.get_bounds().2); } if let None = scale_rate { let scale = (mapper.get_bounds().3 - mapper.get_bounds().2) / canvas.height() as f64; self.status.borrow_mut().scale_rate = Some(scale); } } fn render(&self, context: >k::gdk::GLContext) -> bool { self.ensure_canvas(); let configs = self.config.borrow(); let (w, h) = { let mut canvas = self.canvas.borrow_mut(); let canvas = canvas.as_mut().unwrap(); let dpi = self.obj().scale_factor(); let w = canvas.width(); let h = canvas.height(); canvas.clear_rect( 0, 0, (w as i32 * dpi) as u32, (h as i32 * dpi) as u32, Color::rgba(0, 0, 0, 255), ); (w, h) }; let mut binding = self.tiles.borrow(); let mut tiles = binding.borrow_mut(); for tile in tiles.values_mut(){ if let Some(tile) = tile.as_mut() { self.draw_target(tile); } } let render_range = self.status.borrow().view_range.clone(); if let Some(((lat1, lat2), (lon1, lon2))) = render_range { let mut status = self.status.borrow_mut(); let mapper = self.mapper.borrow(); let (tx, ty) = mapper.map((lon1, lat1)).unwrap(); status.translation.replace((tx, ty)); status.init_translation = (tx, ty); let (lon1, lat1) = mapper.map((lon1, lat1)).unwrap(); let (lon2, lat2) = mapper.map((lon2, lat2)).unwrap(); let scale = ((lat1 - lat2).abs() / h as f64).max((lon1 - lon2).abs() / w as f64); status.scale_rate.replace(scale); status.init_scale_rate = scale; status.view_range = None; } else { let mut status = self.status.borrow_mut(); match status.motion { RenderMotion::Translate => { if let Some((x, y)) = status.translate { let (ix, iy) = status.init_translation; status.translation = Some(( ix + x as f64 * status.scale_rate.unwrap(), iy + y as f64 * status.scale_rate.unwrap(), )); } else { status.init_translation = status.translation.unwrap(); } } RenderMotion::Scale => { let scale_rate = status.scale_rate.unwrap(); let scale_flag = status.scale as f64; let initial = status.init_scale_rate; let step = scale_rate * 0.1; let (tx, ty) = status.translation.unwrap(); let (px, py) = status.pointer_location; let scaled = scale_rate + scale_flag * step; status.scale_rate = Some(scaled); self.obj().set_scale(initial / scaled); let sx = scale_flag * step * px as f64; let sy = scale_flag * step * py as f64; status.translation = Some((tx - sx, ty - sy)); status.init_translation = status.translation.unwrap(); } RenderMotion::None => {} } } let c = self.interior_layers.borrow(); let c = &mut *c.borrow_mut(); self.interior .borrow() .draw(c, &self.obj(), self.status.borrow(), configs); { let mut canvas = self.canvas.borrow_mut(); let canvas = canvas.as_mut().unwrap(); self.exterior.borrow().draw(canvas, &self.obj()); canvas.flush(); } true } } impl Render { fn ensure_canvas(&self) { use femtovg::{renderer, Canvas}; use glow::HasContext; if self.canvas.borrow().is_some() { return; } let widget = self.obj(); widget.attach_buffers(); static LOAD_FN: fn(&str) -> *const std::ffi::c_void = |s| epoxy::get_proc_addr(s) as *const _; // SAFETY: Need to get the framebuffer id that gtk expects us to draw into, so femtovg // knows which framebuffer to bind. This is safe as long as we call attach_buffers // beforehand. Also unbind it here just in case, since this can be called outside render. let (mut renderer, fbo) = unsafe { let renderer = renderer::OpenGl::new_from_function(LOAD_FN).expect("Cannot create renderer"); let ctx = glow::Context::from_loader_function(LOAD_FN); let id = NonZeroU32::new(ctx.get_parameter_i32(glow::DRAW_FRAMEBUFFER_BINDING) as u32) .expect("No GTK provided framebuffer binding"); ctx.bind_framebuffer(glow::FRAMEBUFFER, None); self.glow_context.replace(Some(ctx)); (renderer, glow::NativeFramebuffer(id)) }; renderer.set_screen_target(Some(fbo)); let mut canvas = Canvas::new(renderer).expect("Cannot create canvas"); canvas .add_font_dir(std::path::Path::new("./src/assets")) .unwrap(); self.canvas.replace(Some(canvas)); } pub(super) fn window_size(&self) -> Option<(i32, i32)> { self.status.borrow().window_size } pub(super) fn view_range(&self) -> Option<((f64, f64), (f64, f64))> { let padding = self.padding(); let (w, h) = self.window_size().unwrap(); let (w, h) = ( w as f32 - padding[1] - padding[3], h as f32 - padding[0] - padding[2], ); let (w, h) = (w as f64, h as f64); let mapper = self.mapper.borrow(); let status = self.status.borrow(); status.translation.and_then(|(tx, ty)| { status.scale_rate.and_then(|scale| { let (x1, y1) = (tx + padding[3] as f64, ty + padding[2] as f64); let (x2, y2) = ( tx + w * scale + padding[3] as f64, ty + h * scale + padding[2] as f64, ); let (x1, y1) = mapper.inverse_map((x1, y1)).unwrap(); let (x2, y2) = mapper.inverse_map((x2, y2)).unwrap(); Some(((x1, x2), (y1, y2))) }) }) } pub(super) fn inverse_map(&self, loc: (f32, f32)) -> Option<(f64, f64)> { let (x, y) = loc; let status = self.status.borrow(); status.translation.and_then(|(tx, ty)| { status.scale_rate.and_then(|scale| { let (x, y) = (x as f64, y as f64); Some((tx + x * scale, (ty + y * scale))) }) }) } fn padding(&self) -> [f32; 4] { self.config.borrow().padding } pub(super) fn map(&self, loc: (f64, f64)) -> Option<(f32, f32)> { let (x, y) = loc; let (_, h) = self.window_size().unwrap(); let status = self.status.borrow(); status.translation.and_then(|(tx, ty)| { status.scale_rate.and_then(|scale| { Some(( (x - tx as f64) as f32 / scale as f32, h as f32 - (y - ty as f64) as f32 / scale as f32, )) }) }) } pub(super) fn set_translation(&self, translation: (f64, f64)) { let mut status = self.status.borrow_mut(); status.translation = Some(translation); } fn pointer_loc(&self) -> (f32, f32) { let (x, y) = self.status.borrow().pointer_location.clone(); let (_, h) = self.window_size().unwrap(); (x, h as f32 - y) } pub(super) fn set_view(&self, range: (f64, f64, f64, f64)) { let (lat1, lat2, lon1, lon2) = range; self.status .borrow_mut() .view_range .replace(((lat1, lat2), (lon1, lon2))); } pub(super) fn draw_target(&self, target: &mut Target) { let mut canvas = self.canvas.borrow_mut(); let canvas = canvas.as_mut().unwrap(); let obj = self.obj(); let (ox,oy) = target.origin(&obj); let (x, y) = target.size(&obj); let id = match target.target{ TargetType:: ImageId(id) => id, TargetType::Mem(ref mem) => { let converted = canvas.load_image_mem(mem, femtovg::ImageFlags::empty()).unwrap(); target.set_target(TargetType::ImageId(converted)); converted } }; let painter = Paint::image(id, ox, oy, x, y, 0.0, 1.0); let mut path = femtovg::Path::new(); path.rect(ox, oy, x, y); canvas.fill_path(&path, &painter); } }