use geom::{Angle, Bounds, GPSBounds, Polygon, Pt2D};
use crate::widgets::button::BtnBuilder;
use crate::{
svg, Btn, Color, DeferDraw, Drawable, EventCtx, Fill, GfxCtx, Prerender, ScreenDims, Widget,
};
#[derive(Clone)]
pub struct GeomBatch {
pub(crate) list: Vec<(Fill, Polygon, f64)>,
pub autocrop_dims: bool,
}
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(self)
}
pub fn to_btn(self, ctx: &EventCtx) -> BtnBuilder {
self.to_btn_custom(RewriteColor::ChangeAll(ctx.style().hovering_color))
}
pub fn to_btn_custom(self, rewrite: RewriteColor) -> BtnBuilder {
let hovered = self.clone().color(rewrite);
let hitbox = self.get_bounds().get_rectangle();
Btn::custom(self, hovered, hitbox, None)
}
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 from_svg_contents(raw: Vec<u8>) -> GeomBatch {
let mut batch = GeomBatch::new();
let svg_tree = usvg::Tree::from_data(&raw, &usvg::Options::default()).unwrap();
svg::add_svg_inner(&mut batch, svg_tree, svg::HIGH_QUALITY).unwrap();
batch
}
pub fn load_svg<P: AsRef<Prerender>>(prerender: &P, filename: &str) -> GeomBatch {
svg::load_svg(prerender.as_ref(), filename).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(mut self, factor: f64) -> GeomBatch {
if factor == 1.0 {
return self;
}
for (_, poly, _) in &mut self.list {
*poly = poly.strip_rings().scale(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 to_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.to_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,
}
}
}
pub enum RewriteColor {
NoOp,
Change(Color, Color),
ChangeAll(Color),
ChangeAlpha(f32),
MakeGrayscale,
}
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) => *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)
}
}
}
}