318 lines
11 KiB
Rust
318 lines
11 KiB
Rust
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<f64>,
|
|
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<ExteriorWidget>,
|
|
pub(super) interior: RefCell<InteriorWidget>,
|
|
pub(super) canvas: RefCell<Option<femtovg::Canvas<femtovg::renderer::OpenGl>>>,
|
|
pub config: RefCell<RenderConfig>,
|
|
pub status: RefCell<RenderStatus>,
|
|
pub mapper: RefCell<Mapper>,
|
|
pub(super) interior_layers: RefCell<Vec<Layer>>,
|
|
}
|
|
|
|
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));
|
|
// }
|
|
}
|
|
}
|