radar-g/src/render/imp.rs
2024-01-10 22:12:41 +08:00

287 lines
9.3 KiB
Rust

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<f64>,
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<ExteriorWidget>,
pub(super) interior: RefCell<InteriorWidget>,
pub config: RefCell<RenderConfig>,
pub status: RefCell<RenderStatus>,
pub mapper: RefCell<Mapper>,
pub(super) canvas: RefCell<Option<femtovg::Canvas<femtovg::renderer::OpenGl>>>,
}
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: &gtk::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)
}
}