use super::exterior::ExteriorWidget; use super::interior::InteriorWidget; use super::{Layer, WindowCoord}; use crate::coords::proj::Mercator; use crate::coords::Mapper; use femtovg::{Canvas, Color, FontId, Renderer}; use gtk::glib; use gtk::subclass::prelude::*; use gtk::traits::{GLAreaExt, WidgetExt}; use std::cell::RefCell; use std::num::NonZeroU32; #[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_translation: (f64, f64), } pub struct Render { pub(super) exterior: RefCell, pub(super) interior: RefCell, pub(super) canvas: RefCell>>, pub config: RefCell, pub status: RefCell, pub mapper: RefCell, pub(super) interior_layers: RefCell>, } impl Default for Render { fn default() -> Self { Self { exterior: RefCell::new(ExteriorWidget::default()), interior: RefCell::new(InteriorWidget::default()), interior_layers: RefCell::new(Vec::new()), config: RefCell::new(RenderConfig::default()), status: RefCell::new(RenderStatus::default()), 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); } } 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 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)); 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.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 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); 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()); 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); (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))); // if let Some((w, h)) = self.window_size() { // println!("w:{}, h:{}", w, h); // let scale = ((lat1 - lat2).abs() / h as f64).max((lon1 - lon2).abs() / w as f64); // self.status.borrow_mut().scale_rate.replace(scale); // self.set_translation((lat1, lon1)); // } } }