use super::layer::foreground::exterior::ExteriorWidget; use super::layer::foreground::interior::InteriorWidget; use super::WindowCoord; use crate::coords::proj::Mercator; use crate::coords::Mapper; use femtovg::{Color, FontId, Paint, Path, Transform2D}; use gtk::glib; use gtk::subclass::prelude::*; use gtk::traits::{GLAreaExt, WidgetExt}; use ndarray::Array2; use std::cell::RefCell; use std::num::NonZeroU32; #[derive(Debug, Default, Clone)] 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(Debug, Default, Clone)] pub struct RenderStatus { pub window_size: (i32, i32), pub(super) scale_rate: Option, translation: Option<(f64, f64)>, init_translation: (f64, f64), pub pointer_location: WindowCoord, pub motion: RenderMotion, pub scale: f32, pub translate: Option<(f32, f32)>, } struct Fonts { sans: FontId, bold: FontId, light: FontId, } pub struct Render { pub(super) exterior: RefCell, pub(super) interior: RefCell, pub config: RefCell, pub status: RefCell, pub mapper: RefCell, pub(super) canvas: RefCell>>, } impl Default for Render { fn default() -> Self { Self { exterior: RefCell::new(ExteriorWidget::default()), interior: RefCell::new(InteriorWidget::default()), 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 = "MyRender"; type Type = super::Render; type ParentType = gtk::GLArea; } // Trait shared by all GObjects impl ObjectImpl for Render { fn constructed(&self) { self.parent_constructed(); let area = self.obj(); area.set_has_stencil_buffer(true); } } // Trait shared by all widgets impl WidgetImpl for Render {} impl GLAreaImpl for Render { fn resize(&self, width: i32, height: i32) { self.status.borrow_mut().window_size = (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 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(); let configs = self.config.borrow(); canvas.clear_rect( 0, 0, (w as i32 * dpi) as u32, (h as i32 * dpi) as u32, Color::rgba(0, 0, 0, 255), ); { 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 => {} } } self.interior .borrow() .draw(canvas, &self.obj(), self.status.borrow(), configs); 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("/Users/tsuki/projects/radar-g/src/assets") .unwrap(); self.canvas.replace(Some(canvas)); } pub(super) fn window_size(&self) -> Option<(i32, i32)> { Some(self.status.borrow().window_size) } pub(super) fn window_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 (_, h) = self.window_size().unwrap(); 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 padding = self.padding(); // let left_padding = padding[3]; // let bottom_padding = padding[2]; 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) } }