2019-05-03 22:49:27 +03:00
|
|
|
use crate::input::ContextMenu;
|
2019-02-01 03:43:09 +03:00
|
|
|
use crate::{
|
2019-05-03 22:49:27 +03:00
|
|
|
text, Canvas, Color, Drawable, HorizontalAlignment, Key, Prerender, ScreenPt, Text,
|
2019-02-01 10:32:38 +03:00
|
|
|
VerticalAlignment,
|
2019-02-01 03:43:09 +03:00
|
|
|
};
|
|
|
|
use geom::{Bounds, Circle, Distance, Line, Polygon, Pt2D};
|
2019-09-11 00:08:05 +03:00
|
|
|
use glium::uniforms::UniformValue;
|
|
|
|
use glium::Surface;
|
2019-05-13 02:40:52 +03:00
|
|
|
const NO_HATCHING: f32 = 0.0;
|
|
|
|
const HATCHING: f32 = 1.0;
|
2019-08-15 02:19:54 +03:00
|
|
|
const SCREENSPACE: f32 = 2.0;
|
2019-01-25 03:09:29 +03:00
|
|
|
|
2019-09-11 01:21:58 +03:00
|
|
|
struct Uniforms<'a> {
|
2019-09-11 00:08:05 +03:00
|
|
|
// (cam_x, cam_y, cam_zoom)
|
|
|
|
transform: [f32; 3],
|
|
|
|
// (window_width, window_height, weird enum)
|
|
|
|
// - weird enum = 0.0 (map-space, no hatching)
|
|
|
|
// = 1.0 (map-space, hatching)
|
|
|
|
// = 2.0 (screen-space, no hatching)
|
|
|
|
// Things are awkwardly grouped because passing uniforms is either broken or horribly
|
|
|
|
// documented.
|
|
|
|
window: [f32; 3],
|
2019-09-11 01:21:58 +03:00
|
|
|
canvas: &'a Canvas,
|
2019-09-11 00:08:05 +03:00
|
|
|
}
|
|
|
|
|
2019-09-11 01:21:58 +03:00
|
|
|
impl<'a> Uniforms<'a> {
|
|
|
|
fn new(canvas: &'a Canvas) -> Uniforms<'a> {
|
2019-09-11 00:08:05 +03:00
|
|
|
Uniforms {
|
|
|
|
transform: [
|
|
|
|
canvas.cam_x as f32,
|
|
|
|
canvas.cam_y as f32,
|
|
|
|
canvas.cam_zoom as f32,
|
|
|
|
],
|
|
|
|
window: [
|
|
|
|
canvas.window_width as f32,
|
|
|
|
canvas.window_height as f32,
|
|
|
|
NO_HATCHING,
|
|
|
|
],
|
2019-09-11 01:21:58 +03:00
|
|
|
canvas,
|
2019-09-11 00:08:05 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-11 01:21:58 +03:00
|
|
|
impl<'b> glium::uniforms::Uniforms for Uniforms<'b> {
|
2019-09-11 00:08:05 +03:00
|
|
|
fn visit_values<'a, F: FnMut(&str, UniformValue<'a>)>(&'a self, mut output: F) {
|
|
|
|
output("transform", UniformValue::Vec3(self.transform));
|
|
|
|
output("window", UniformValue::Vec3(self.window));
|
2019-09-11 01:44:07 +03:00
|
|
|
for (idx, (_, tex)) in self.canvas.textures.iter().enumerate() {
|
|
|
|
output(&format!("tex{}", idx), UniformValue::Texture2d(tex, None));
|
|
|
|
}
|
2019-09-11 00:08:05 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-25 03:09:29 +03:00
|
|
|
pub struct GfxCtx<'a> {
|
2019-05-05 00:40:03 +03:00
|
|
|
pub(crate) target: &'a mut glium::Frame,
|
2019-01-25 03:09:29 +03:00
|
|
|
program: &'a glium::Program,
|
2019-09-11 01:21:58 +03:00
|
|
|
uniforms: Uniforms<'a>,
|
2019-01-25 03:09:29 +03:00
|
|
|
params: glium::DrawParameters<'a>,
|
2019-01-25 03:19:02 +03:00
|
|
|
|
2019-04-24 01:33:52 +03:00
|
|
|
screencap_mode: bool,
|
|
|
|
pub(crate) naming_hint: Option<String>,
|
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
// TODO Don't be pub. Delegate everything.
|
|
|
|
pub canvas: &'a Canvas,
|
2019-02-06 01:43:46 +03:00
|
|
|
pub prerender: &'a Prerender<'a>,
|
2019-05-03 22:49:27 +03:00
|
|
|
context_menu: &'a ContextMenu,
|
2019-02-01 03:43:09 +03:00
|
|
|
|
2019-01-25 03:19:02 +03:00
|
|
|
pub num_draw_calls: usize,
|
2019-05-13 02:40:52 +03:00
|
|
|
hatching: f32,
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> GfxCtx<'a> {
|
2019-02-06 01:43:46 +03:00
|
|
|
pub(crate) fn new(
|
2019-02-01 03:43:09 +03:00
|
|
|
canvas: &'a Canvas,
|
2019-02-06 01:43:46 +03:00
|
|
|
prerender: &'a Prerender<'a>,
|
2019-01-25 03:09:29 +03:00
|
|
|
target: &'a mut glium::Frame,
|
|
|
|
program: &'a glium::Program,
|
2019-05-03 22:49:27 +03:00
|
|
|
context_menu: &'a ContextMenu,
|
2019-04-24 01:33:52 +03:00
|
|
|
screencap_mode: bool,
|
2019-01-25 03:09:29 +03:00
|
|
|
) -> GfxCtx<'a> {
|
|
|
|
let params = glium::DrawParameters {
|
|
|
|
blend: glium::Blend::alpha_blending(),
|
2019-08-15 02:19:54 +03:00
|
|
|
depth: glium::Depth {
|
|
|
|
test: glium::DepthTest::IfLessOrEqual,
|
|
|
|
write: true,
|
|
|
|
..Default::default()
|
|
|
|
},
|
2019-01-25 03:09:29 +03:00
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
2019-09-11 00:08:05 +03:00
|
|
|
let uniforms = Uniforms::new(&canvas);
|
2019-01-25 03:09:29 +03:00
|
|
|
|
|
|
|
GfxCtx {
|
2019-02-01 03:43:09 +03:00
|
|
|
canvas,
|
2019-02-06 01:43:46 +03:00
|
|
|
prerender,
|
2019-01-25 03:09:29 +03:00
|
|
|
target,
|
|
|
|
program,
|
|
|
|
uniforms,
|
|
|
|
params,
|
2019-01-25 03:19:02 +03:00
|
|
|
num_draw_calls: 0,
|
2019-04-24 01:33:52 +03:00
|
|
|
screencap_mode,
|
|
|
|
naming_hint: None,
|
2019-05-03 22:49:27 +03:00
|
|
|
context_menu,
|
2019-05-13 02:40:52 +03:00
|
|
|
hatching: NO_HATCHING,
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Up to the caller to call unfork()!
|
|
|
|
// TODO Canvas doesn't understand this change, so things like text drawing that use
|
|
|
|
// map_to_screen will just be confusing.
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn fork(&mut self, top_left_map: Pt2D, top_left_screen: ScreenPt, zoom: f64) {
|
2019-01-25 03:09:29 +03:00
|
|
|
// map_to_screen of top_left_map should be top_left_screen
|
|
|
|
let cam_x = (top_left_map.x() * zoom) - top_left_screen.x;
|
|
|
|
let cam_y = (top_left_map.y() * zoom) - top_left_screen.y;
|
|
|
|
|
2019-09-11 00:08:05 +03:00
|
|
|
self.uniforms.transform = [cam_x as f32, cam_y as f32, zoom as f32];
|
|
|
|
self.uniforms.window = [
|
|
|
|
self.canvas.window_width as f32,
|
|
|
|
self.canvas.window_height as f32,
|
|
|
|
SCREENSPACE,
|
|
|
|
];
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn fork_screenspace(&mut self) {
|
2019-09-11 00:08:05 +03:00
|
|
|
self.uniforms.transform = [0.0, 0.0, 1.0];
|
|
|
|
self.uniforms.window = [
|
|
|
|
self.canvas.window_width as f32,
|
|
|
|
self.canvas.window_height as f32,
|
|
|
|
SCREENSPACE,
|
|
|
|
];
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn unfork(&mut self) {
|
2019-09-11 00:08:05 +03:00
|
|
|
self.uniforms = Uniforms::new(&self.canvas);
|
|
|
|
self.uniforms.window[2] = self.hatching;
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn clear(&mut self, color: Color) {
|
2019-09-11 02:41:35 +03:00
|
|
|
match color {
|
|
|
|
Color::RGBA(r, g, b, a) => {
|
|
|
|
// Without this, SRGB gets enabled and post-processes the color from the fragment
|
|
|
|
// shader.
|
|
|
|
self.target.clear_color_srgb_and_depth((r, g, b, a), 1.0);
|
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
2019-01-31 02:10:05 +03:00
|
|
|
pub fn draw_line(&mut self, color: Color, thickness: Distance, line: &Line) {
|
2019-01-25 19:01:08 +03:00
|
|
|
self.draw_polygon(color, &line.make_polygons(thickness));
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
2019-01-31 02:10:05 +03:00
|
|
|
pub fn draw_rounded_line(&mut self, color: Color, thickness: Distance, line: &Line) {
|
2019-04-12 03:04:59 +03:00
|
|
|
self.draw_polygons(
|
|
|
|
color,
|
|
|
|
&vec![
|
|
|
|
line.make_polygons(thickness),
|
|
|
|
Circle::new(line.pt1(), thickness / 2.0).to_polygon(),
|
|
|
|
Circle::new(line.pt2(), thickness / 2.0).to_polygon(),
|
|
|
|
],
|
|
|
|
);
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
2019-01-31 02:10:05 +03:00
|
|
|
pub fn draw_arrow(&mut self, color: Color, thickness: Distance, line: &Line) {
|
2019-05-29 03:58:47 +03:00
|
|
|
self.draw_polygon(color, &line.to_polyline().make_arrow(thickness).unwrap());
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
|
|
|
|
2019-01-25 03:25:06 +03:00
|
|
|
pub fn draw_circle(&mut self, color: Color, circle: &Circle) {
|
2019-02-02 23:54:16 +03:00
|
|
|
self.draw_polygon(color, &circle.to_polygon());
|
2019-01-25 03:25:06 +03:00
|
|
|
}
|
|
|
|
|
2019-01-25 03:09:29 +03:00
|
|
|
pub fn draw_polygon(&mut self, color: Color, poly: &Polygon) {
|
2019-02-12 01:01:17 +03:00
|
|
|
let obj = self.prerender.upload_temporary(vec![(color, poly)]);
|
2019-01-25 21:59:39 +03:00
|
|
|
self.redraw(&obj);
|
2019-01-25 03:25:06 +03:00
|
|
|
}
|
|
|
|
|
2019-04-12 03:04:59 +03:00
|
|
|
pub fn draw_polygons(&mut self, color: Color, polygons: &Vec<Polygon>) {
|
2019-05-17 22:49:50 +03:00
|
|
|
let obj = self
|
|
|
|
.prerender
|
|
|
|
.upload_temporary(polygons.iter().map(|p| (color, p)).collect());
|
2019-01-25 21:24:29 +03:00
|
|
|
self.redraw(&obj);
|
2019-01-25 20:09:55 +03:00
|
|
|
}
|
|
|
|
|
2019-01-25 21:24:29 +03:00
|
|
|
pub fn redraw(&mut self, obj: &Drawable) {
|
2019-01-25 20:27:53 +03:00
|
|
|
self.target
|
|
|
|
.draw(
|
|
|
|
&obj.vertex_buffer,
|
|
|
|
&obj.index_buffer,
|
|
|
|
&self.program,
|
|
|
|
&self.uniforms,
|
|
|
|
&self.params,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
self.num_draw_calls += 1;
|
2019-05-17 03:10:22 +03:00
|
|
|
|
|
|
|
// println!("{:?}", backtrace::Backtrace::new());
|
2019-01-25 20:27:53 +03:00
|
|
|
}
|
|
|
|
|
2019-05-13 02:40:52 +03:00
|
|
|
pub fn enable_hatching(&mut self) {
|
|
|
|
assert_eq!(self.hatching, NO_HATCHING);
|
|
|
|
self.hatching = HATCHING;
|
|
|
|
self.unfork();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn disable_hatching(&mut self) {
|
|
|
|
assert_eq!(self.hatching, HATCHING);
|
|
|
|
self.hatching = NO_HATCHING;
|
|
|
|
self.unfork();
|
|
|
|
}
|
|
|
|
|
2019-02-01 10:32:38 +03:00
|
|
|
// Canvas stuff.
|
|
|
|
|
|
|
|
// The text box covers up what's beneath and eats the cursor (for get_cursor_in_map_space).
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn draw_blocking_text(
|
|
|
|
&mut self,
|
2019-04-22 23:19:36 +03:00
|
|
|
txt: &Text,
|
2019-02-01 03:43:09 +03:00
|
|
|
(horiz, vert): (HorizontalAlignment, VerticalAlignment),
|
|
|
|
) {
|
2019-02-01 10:32:38 +03:00
|
|
|
if txt.is_empty() {
|
|
|
|
return;
|
|
|
|
}
|
2019-05-02 20:21:11 +03:00
|
|
|
let (mut width, height) = self.text_dims(&txt);
|
2019-02-01 10:32:38 +03:00
|
|
|
let x1 = match horiz {
|
|
|
|
HorizontalAlignment::Left => 0.0,
|
|
|
|
HorizontalAlignment::Center => (self.canvas.window_width - width) / 2.0,
|
|
|
|
HorizontalAlignment::Right => self.canvas.window_width - width,
|
2019-05-02 20:21:11 +03:00
|
|
|
HorizontalAlignment::FillScreen => {
|
|
|
|
width = self.canvas.window_width;
|
|
|
|
0.0
|
|
|
|
}
|
2019-02-01 10:32:38 +03:00
|
|
|
};
|
|
|
|
let y1 = match vert {
|
|
|
|
VerticalAlignment::Top => 0.0,
|
|
|
|
VerticalAlignment::Center => (self.canvas.window_height - height) / 2.0,
|
|
|
|
VerticalAlignment::Bottom => self.canvas.window_height - height,
|
|
|
|
};
|
2019-06-21 00:26:49 +03:00
|
|
|
self.canvas.mark_covered_area(text::draw_text_bubble(
|
|
|
|
self,
|
|
|
|
ScreenPt::new(x1, y1),
|
|
|
|
txt,
|
|
|
|
(width, height),
|
|
|
|
));
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
2019-02-01 10:32:38 +03:00
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn get_screen_bounds(&self) -> Bounds {
|
|
|
|
self.canvas.get_screen_bounds()
|
|
|
|
}
|
2019-02-01 10:32:38 +03:00
|
|
|
|
|
|
|
// TODO Rename these draw_nonblocking_text_*
|
2019-04-22 23:19:36 +03:00
|
|
|
pub fn draw_text_at(&mut self, txt: &Text, map_pt: Pt2D) {
|
2019-02-01 10:32:38 +03:00
|
|
|
let (width, height) = self.text_dims(&txt);
|
|
|
|
let pt = self.canvas.map_to_screen(map_pt);
|
|
|
|
text::draw_text_bubble(
|
|
|
|
self,
|
|
|
|
ScreenPt::new(pt.x - (width / 2.0), pt.y - (height / 2.0)),
|
|
|
|
txt,
|
|
|
|
(width, height),
|
2019-04-23 06:23:50 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn draw_text_at_mapspace(&mut self, txt: &Text, map_pt: Pt2D) {
|
2019-08-13 21:24:04 +03:00
|
|
|
let (width, height) = self.text_dims(&txt);
|
2019-04-23 06:23:50 +03:00
|
|
|
text::draw_text_bubble_mapspace(
|
|
|
|
self,
|
2019-08-13 21:24:04 +03:00
|
|
|
Pt2D::new(
|
|
|
|
map_pt.x() - (width / (2.0 * text::SCALE_DOWN)),
|
|
|
|
map_pt.y() - (height / (2.0 * text::SCALE_DOWN)),
|
|
|
|
),
|
2019-04-23 06:23:50 +03:00
|
|
|
txt,
|
|
|
|
(width, height),
|
2019-08-13 21:24:04 +03:00
|
|
|
);
|
2019-02-01 03:43:09 +03:00
|
|
|
}
|
2019-02-01 10:32:38 +03:00
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn text_dims(&self, txt: &Text) -> (f64, f64) {
|
|
|
|
self.canvas.text_dims(txt)
|
|
|
|
}
|
2019-02-01 10:32:38 +03:00
|
|
|
|
2019-04-22 23:19:36 +03:00
|
|
|
pub fn draw_text_at_screenspace_topleft(&mut self, txt: &Text, pt: ScreenPt) {
|
2019-02-01 10:32:38 +03:00
|
|
|
let dims = self.text_dims(&txt);
|
2019-06-21 00:26:49 +03:00
|
|
|
self.canvas
|
|
|
|
.mark_covered_area(text::draw_text_bubble(self, pt, txt, dims));
|
2019-02-01 03:43:09 +03:00
|
|
|
}
|
2019-02-01 10:32:38 +03:00
|
|
|
|
2019-04-22 23:19:36 +03:00
|
|
|
pub fn draw_mouse_tooltip(&mut self, txt: &Text) {
|
2019-02-01 10:32:38 +03:00
|
|
|
let (width, height) = self.text_dims(&txt);
|
|
|
|
let x1 = self.canvas.cursor_x - (width / 2.0);
|
|
|
|
let y1 = self.canvas.cursor_y - (height / 2.0);
|
|
|
|
// No need to cover the tooltip; this tooltip follows the mouse anyway.
|
|
|
|
text::draw_text_bubble(self, ScreenPt::new(x1, y1), txt, (width, height));
|
2019-02-01 03:43:09 +03:00
|
|
|
}
|
2019-02-01 10:32:38 +03:00
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn screen_to_map(&self, pt: ScreenPt) -> Pt2D {
|
|
|
|
self.canvas.screen_to_map(pt)
|
|
|
|
}
|
2019-02-01 10:32:38 +03:00
|
|
|
|
2019-02-01 03:43:09 +03:00
|
|
|
pub fn get_cursor_in_map_space(&self) -> Option<Pt2D> {
|
|
|
|
self.canvas.get_cursor_in_map_space()
|
2019-01-25 21:59:39 +03:00
|
|
|
}
|
2019-02-06 01:43:46 +03:00
|
|
|
|
|
|
|
pub fn get_num_uploads(&self) -> usize {
|
|
|
|
self.prerender.num_uploads.get()
|
|
|
|
}
|
2019-04-24 01:33:52 +03:00
|
|
|
|
|
|
|
pub fn is_screencap(&self) -> bool {
|
|
|
|
self.screencap_mode
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_screencap_naming_hint(&mut self, hint: String) {
|
|
|
|
assert!(self.screencap_mode);
|
|
|
|
assert!(self.naming_hint.is_none());
|
|
|
|
self.naming_hint = Some(hint);
|
|
|
|
}
|
2019-05-03 22:49:27 +03:00
|
|
|
|
|
|
|
pub fn get_active_context_menu_keys(&self) -> Vec<Key> {
|
|
|
|
match self.context_menu {
|
|
|
|
ContextMenu::Inactive(ref keys) => keys.iter().cloned().collect(),
|
|
|
|
ContextMenu::Displaying(ref menu) => {
|
2019-05-05 03:22:27 +03:00
|
|
|
menu.active_choices().into_iter().cloned().collect()
|
2019-05-03 22:49:27 +03:00
|
|
|
}
|
|
|
|
_ => Vec::new(),
|
|
|
|
}
|
|
|
|
}
|
2019-08-15 00:32:02 +03:00
|
|
|
|
|
|
|
pub fn upload(&mut self, batch: GeomBatch) -> Drawable {
|
|
|
|
self.prerender.upload(batch)
|
|
|
|
}
|
2019-01-25 03:09:29 +03:00
|
|
|
}
|
2019-05-17 03:10:22 +03:00
|
|
|
|
|
|
|
pub struct GeomBatch {
|
2019-05-17 22:49:50 +03:00
|
|
|
pub(crate) list: Vec<(Color, Polygon)>,
|
2019-05-17 03:10:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl GeomBatch {
|
|
|
|
pub fn new() -> GeomBatch {
|
|
|
|
GeomBatch { list: Vec::new() }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn push(&mut self, color: Color, p: Polygon) {
|
|
|
|
self.list.push((color, p));
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn extend(&mut self, color: Color, polys: Vec<Polygon>) {
|
|
|
|
for p in polys {
|
|
|
|
self.list.push((color, p));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-22 22:30:58 +03:00
|
|
|
pub fn append(&mut self, other: &GeomBatch) {
|
|
|
|
self.list.extend(other.list.clone());
|
|
|
|
}
|
|
|
|
|
2019-05-17 03:10:22 +03:00
|
|
|
pub fn draw(self, g: &mut GfxCtx) {
|
|
|
|
let refs = self.list.iter().map(|(color, p)| (*color, p)).collect();
|
2019-05-17 22:49:50 +03:00
|
|
|
let obj = g.prerender.upload_temporary(refs);
|
|
|
|
g.redraw(&obj);
|
2019-05-17 03:10:22 +03:00
|
|
|
}
|
|
|
|
}
|