radar-g/src/widgets/render/imp.rs
2024-03-12 18:38:54 +08:00

384 lines
13 KiB
Rust

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<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_scale_rate: f64,
init_translation: (f64, f64),
}
#[derive(Properties)]
#[properties(wrapper_type = super::Render)]
pub struct Render {
#[property(get, set)]
render_status: Cell<i64>,
#[property(get, set)]
range_changing: Cell<f64>,
#[property(get, set)]
scale: Cell<f64>,
pub(super) exterior: RefCell<ExteriorWidget>,
pub(super) interior: RefCell<InteriorWidget>,
pub(super) canvas: RefCell<Option<femtovg::Canvas<femtovg::renderer::OpenGl>>>,
pub(super) glow_context: RefCell<Option<glow::Context>>,
pub(super) tiles: RefCell<Rc<RefCell<HashMap<Tile, Option<Target>>>>>,
pub config: RefCell<RenderConfig>,
pub status: RefCell<RenderStatus>,
pub mapper: RefCell<Mapper>,
pub(super) interior_layers: RefCell<Rc<RefCell<Vec<Layer>>>>,
}
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: &gtk::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);
}
}