use geom::{Angle, Bounds, GPSBounds, Polygon, Pt2D};
use crate::{
svg, Color, DeferDraw, Drawable, EventCtx, Fill, GfxCtx, JustDraw, Prerender, ScreenDims,
Widget,
};
pub mod geom_batch_stack;
#[derive(Clone)]
pub struct GeomBatch {
pub(crate) list: Vec<(Fill, Polygon, f64)>,
pub autocrop_dims: bool,
}
impl std::fmt::Debug for GeomBatch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GeomBatch")
.field("bounds", &self.get_bounds())
.field("items", &self.list.len())
.field("autocrop_dims", &self.autocrop_dims)
.finish()
}
}
impl GeomBatch {
pub fn new() -> GeomBatch {
GeomBatch {
list: Vec::new(),
autocrop_dims: true,
}
}
pub fn push<F: Into<Fill>>(&mut self, fill: F, p: Polygon) {
self.push_with_z(fill, p, 0.0);
}
pub fn push_with_z<F: Into<Fill>>(&mut self, fill: F, p: Polygon, z_offset: f64) {
debug_assert!(z_offset > -1.0);
debug_assert!(z_offset <= 0.0);
self.list.push((fill.into(), p, z_offset));
}
pub fn unshift<F: Into<Fill>>(&mut self, fill: F, p: Polygon) {
self.list.insert(0, (fill.into(), p, 0.0));
}
pub fn extend<F: Into<Fill>>(&mut self, fill: F, polys: Vec<Polygon>) {
let fill = fill.into();
for p in polys {
self.list.push((fill.clone(), p, 0.0));
}
}
pub fn append(&mut self, other: GeomBatch) {
self.list.extend(other.list);
}
pub fn consume(self) -> Vec<(Fill, Polygon, f64)> {
self.list
}
pub fn draw(self, g: &mut GfxCtx) {
let obj = g.prerender.upload_temporary(self);
g.redraw(&obj);
}
pub fn upload(self, ctx: &EventCtx) -> Drawable {
ctx.prerender.upload(self)
}
pub fn batch(self) -> Widget {
DeferDraw::new_widget(self)
}
pub fn into_widget(self, ctx: &EventCtx) -> Widget {
JustDraw::wrap(ctx, self)
}
pub fn get_bounds(&self) -> Bounds {
let mut bounds = Bounds::new();
for (_, poly, _) in &self.list {
bounds.union(poly.get_bounds());
}
if !self.autocrop_dims {
bounds.update(Pt2D::new(0.0, 0.0));
}
bounds
}
pub fn autocrop(mut self) -> GeomBatch {
let bounds = self.get_bounds();
if bounds.min_x == 0.0 && bounds.min_y == 0.0 {
return self;
}
for (_, poly, _) in &mut self.list {
*poly = poly.translate(-bounds.min_x, -bounds.min_y);
}
self
}
pub fn unioned_polygon(&self) -> Polygon {
let mut result = self.list[0].1.clone();
for (_, p, _) in &self.list[1..] {
result = result.union(p.clone());
}
result
}
pub fn is_empty(&self) -> bool {
self.list.is_empty()
}
pub fn get_dims(&self) -> ScreenDims {
if self.is_empty() {
return ScreenDims::new(0.0, 0.0);
}
let bounds = self.get_bounds();
ScreenDims::new(bounds.width(), bounds.height())
}
pub fn load_svg<P: AsRef<Prerender>, I: AsRef<str>>(prerender: &P, filename: I) -> GeomBatch {
svg::load_svg(prerender.as_ref(), filename.as_ref()).0
}
pub fn load_svg_bytes<P: AsRef<Prerender>>(
prerender: &P,
labeled_bytes: (&str, &[u8]),
) -> GeomBatch {
svg::load_svg_bytes(prerender.as_ref(), labeled_bytes.0, labeled_bytes.1)
.expect("invalid svg bytes")
.0
}
pub fn load_svg_bytes_uncached(raw: &[u8]) -> GeomBatch {
svg::load_svg_from_bytes_uncached(raw).unwrap().0
}
pub fn color(mut self, transformation: RewriteColor) -> GeomBatch {
for (fancy, _, _) in &mut self.list {
if let Fill::Color(ref mut c) = fancy {
*c = transformation.apply(*c);
}
}
self
}
pub fn centered_on(self, center: Pt2D) -> GeomBatch {
let dims = self.get_dims();
let dx = center.x() - dims.width / 2.0;
let dy = center.y() - dims.height / 2.0;
self.translate(dx, dy)
}
pub fn translate(mut self, dx: f64, dy: f64) -> GeomBatch {
for (_, poly, _) in &mut self.list {
*poly = poly.translate(dx, dy);
}
self
}
pub fn rotate(mut self, angle: Angle) -> GeomBatch {
for (_, poly, _) in &mut self.list {
*poly = poly.rotate(angle);
}
self
}
pub fn rotate_around_batch_center(mut self, angle: Angle) -> GeomBatch {
let center = self.get_bounds().center();
for (_, poly, _) in &mut self.list {
*poly = poly.rotate_around(angle, center);
}
self
}
pub fn scale(self, factor: f64) -> GeomBatch {
self.scale_xy(factor, factor)
}
pub fn scale_xy(mut self, x_factor: f64, y_factor: f64) -> GeomBatch {
#[allow(clippy::float_cmp)]
if x_factor == 1.0 && y_factor == 1.0 {
return self;
}
for (_, poly, _) in &mut self.list {
*poly = poly.strip_rings().scale_xy(x_factor, y_factor);
}
self
}
pub fn set_z_offset(mut self, offset: f64) -> GeomBatch {
if offset <= -1.0 || offset > 0.0 {
panic!("set_z_offset({}) must be in (-1, 0]", offset);
}
for (_, _, z) in &mut self.list {
*z = offset;
}
self
}
pub fn into_geojson(self, gps_bounds: Option<&GPSBounds>) -> Vec<geojson::Feature> {
let mut features = Vec::new();
for (fill, polygon, _) in self.list {
if let Fill::Color(color) = fill {
let mut properties = serde_json::Map::new();
properties.insert("color".to_string(), color.as_hex().into());
features.push(geojson::Feature {
bbox: None,
geometry: Some(polygon.to_geojson(gps_bounds)),
id: None,
properties: Some(properties),
foreign_members: None,
});
}
}
features
}
}
impl<F: Into<Fill>> From<Vec<(F, Polygon)>> for GeomBatch {
fn from(list: Vec<(F, Polygon)>) -> GeomBatch {
GeomBatch {
list: list.into_iter().map(|(c, p)| (c.into(), p, 0.0)).collect(),
autocrop_dims: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum RewriteColor {
NoOp,
Change(Color, Color),
ChangeAll(Color),
ChangeAlpha(f32),
MakeGrayscale,
}
impl std::convert::From<Color> for RewriteColor {
fn from(color: Color) -> RewriteColor {
RewriteColor::ChangeAll(color)
}
}
impl RewriteColor {
fn apply(&self, c: Color) -> Color {
match self {
RewriteColor::NoOp => c,
RewriteColor::Change(from, to) => {
if c == *from {
*to
} else {
c
}
}
RewriteColor::ChangeAll(to) => {
if c == Color::CLEAR {
c
} else {
*to
}
}
RewriteColor::ChangeAlpha(alpha) => c.alpha(*alpha),
RewriteColor::MakeGrayscale => {
let avg = (c.r + c.g + c.b) / 3.0;
Color::grey(avg).alpha(c.a)
}
}
}
}