2019-01-05 15:23:40 +00:00
|
|
|
#![allow(unused_variables)]
|
|
|
|
|
2019-01-04 13:14:32 +00:00
|
|
|
/// Outputs render info in a format suitable for e.g. OpenGL.
|
|
|
|
use crate::{
|
|
|
|
font::Font,
|
2019-01-05 15:23:40 +00:00
|
|
|
math::{remap, vec2, Vec2, TAU},
|
2019-01-04 13:14:32 +00:00
|
|
|
types::{Color, PaintCmd},
|
|
|
|
};
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
|
|
pub struct Vertex {
|
2019-01-05 15:23:40 +00:00
|
|
|
/// Pixel coordinates
|
|
|
|
pub pos: Vec2,
|
2019-01-04 13:14:32 +00:00
|
|
|
/// Texel indices into the texture
|
2019-01-05 15:23:40 +00:00
|
|
|
pub uv: (u16, u16),
|
2019-01-04 13:14:32 +00:00
|
|
|
/// sRGBA
|
|
|
|
pub color: Color,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
|
|
pub struct Frame {
|
|
|
|
pub clear_color: Option<Color>,
|
2019-01-05 14:28:07 +00:00
|
|
|
/// Draw as triangles (i.e. the length is a multiple of three)
|
2019-01-04 13:14:32 +00:00
|
|
|
pub indices: Vec<u32>,
|
|
|
|
pub vertices: Vec<Vertex>,
|
|
|
|
}
|
|
|
|
|
2019-01-05 15:23:40 +00:00
|
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
|
|
pub enum PathType {
|
|
|
|
Open,
|
|
|
|
Closed,
|
|
|
|
}
|
|
|
|
use self::PathType::*;
|
|
|
|
|
2019-01-05 14:28:07 +00:00
|
|
|
impl Frame {
|
|
|
|
/// Uniformly colored rectangle
|
|
|
|
pub fn add_rect(&mut self, top_left: Vertex, bottom_right: Vertex) {
|
|
|
|
let idx = self.vertices.len() as u32;
|
|
|
|
self.indices.push(idx + 0);
|
|
|
|
self.indices.push(idx + 1);
|
|
|
|
self.indices.push(idx + 2);
|
|
|
|
self.indices.push(idx + 2);
|
|
|
|
self.indices.push(idx + 1);
|
|
|
|
self.indices.push(idx + 3);
|
|
|
|
|
|
|
|
let top_right = Vertex {
|
2019-01-05 15:23:40 +00:00
|
|
|
pos: vec2(bottom_right.pos.x, top_left.pos.y),
|
|
|
|
uv: (bottom_right.uv.0, top_left.uv.1),
|
2019-01-05 14:28:07 +00:00
|
|
|
color: top_left.color,
|
|
|
|
};
|
|
|
|
let botom_left = Vertex {
|
2019-01-05 15:23:40 +00:00
|
|
|
pos: vec2(top_left.pos.x, bottom_right.pos.y),
|
|
|
|
uv: (top_left.uv.0, bottom_right.uv.1),
|
2019-01-05 14:28:07 +00:00
|
|
|
color: top_left.color,
|
|
|
|
};
|
|
|
|
self.vertices.push(top_left);
|
|
|
|
self.vertices.push(top_right);
|
|
|
|
self.vertices.push(botom_left);
|
|
|
|
self.vertices.push(bottom_right);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn fill_closed_path(&mut self, points: &[Vec2], normals: &[Vec2], color: Color) {
|
|
|
|
// TODO: use normals for anti-aliasing
|
|
|
|
assert_eq!(points.len(), normals.len());
|
|
|
|
let n = points.len() as u32;
|
|
|
|
let idx = self.vertices.len() as u32;
|
2019-01-05 15:23:40 +00:00
|
|
|
self.vertices.extend(points.iter().map(|&pos| Vertex {
|
|
|
|
pos,
|
|
|
|
uv: (0, 0),
|
|
|
|
color,
|
|
|
|
}));
|
2019-01-05 14:28:07 +00:00
|
|
|
for i in 2..n {
|
|
|
|
self.indices.push(idx);
|
|
|
|
self.indices.push(idx + i - 1);
|
|
|
|
self.indices.push(idx + i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-05 15:23:40 +00:00
|
|
|
pub fn paint_path(
|
2019-01-05 14:28:07 +00:00
|
|
|
&mut self,
|
2019-01-05 15:23:40 +00:00
|
|
|
path_type: PathType,
|
2019-01-05 14:28:07 +00:00
|
|
|
points: &[Vec2],
|
|
|
|
normals: &[Vec2],
|
|
|
|
color: Color,
|
2019-01-05 15:23:40 +00:00
|
|
|
width: f32,
|
2019-01-05 14:28:07 +00:00
|
|
|
) {
|
|
|
|
// TODO: anti-aliasing
|
|
|
|
assert_eq!(points.len(), normals.len());
|
|
|
|
let n = points.len() as u32;
|
|
|
|
let hw = width / 2.0;
|
|
|
|
|
|
|
|
let idx = self.vertices.len() as u32;
|
2019-01-05 15:23:40 +00:00
|
|
|
let last_index = if path_type == Closed { n } else { n - 1 };
|
|
|
|
for i in 0..last_index {
|
2019-01-05 14:28:07 +00:00
|
|
|
self.indices.push(idx + (2 * i + 0) % (2 * n));
|
|
|
|
self.indices.push(idx + (2 * i + 1) % (2 * n));
|
|
|
|
self.indices.push(idx + (2 * i + 2) % (2 * n));
|
|
|
|
self.indices.push(idx + (2 * i + 2) % (2 * n));
|
|
|
|
self.indices.push(idx + (2 * i + 1) % (2 * n));
|
|
|
|
self.indices.push(idx + (2 * i + 3) % (2 * n));
|
|
|
|
}
|
|
|
|
|
2019-01-05 15:23:40 +00:00
|
|
|
for (&p, &n) in points.iter().zip(normals) {
|
2019-01-05 14:28:07 +00:00
|
|
|
self.vertices.push(Vertex {
|
2019-01-05 15:23:40 +00:00
|
|
|
pos: p + hw * n,
|
|
|
|
uv: (0, 0),
|
2019-01-05 14:28:07 +00:00
|
|
|
color,
|
|
|
|
});
|
|
|
|
self.vertices.push(Vertex {
|
2019-01-05 15:23:40 +00:00
|
|
|
pos: p - hw * n,
|
|
|
|
uv: (0, 0),
|
2019-01-05 14:28:07 +00:00
|
|
|
color,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 13:14:32 +00:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Painter {
|
|
|
|
font: Font,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Painter {
|
|
|
|
pub fn new() -> Painter {
|
|
|
|
Painter {
|
|
|
|
font: Font::new(13),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 8-bit row-major font atlas texture, (width, height, pixels).
|
2019-01-05 14:28:07 +00:00
|
|
|
pub fn texture(&self) -> (u16, u16, &[u8]) {
|
2019-01-04 13:14:32 +00:00
|
|
|
self.font.texture()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn paint(&self, commands: &[PaintCmd]) -> Frame {
|
2019-01-05 15:23:40 +00:00
|
|
|
let mut path_points = Vec::new();
|
|
|
|
let mut path_normals = Vec::new();
|
2019-01-05 14:28:07 +00:00
|
|
|
|
2019-01-04 13:14:32 +00:00
|
|
|
let mut frame = Frame::default();
|
|
|
|
for cmd in commands {
|
|
|
|
match cmd {
|
2019-01-05 14:28:07 +00:00
|
|
|
PaintCmd::Circle {
|
|
|
|
center,
|
|
|
|
fill_color,
|
|
|
|
outline,
|
|
|
|
radius,
|
|
|
|
} => {
|
2019-01-05 15:23:40 +00:00
|
|
|
path_points.clear();
|
|
|
|
path_normals.clear();
|
2019-01-05 14:28:07 +00:00
|
|
|
|
2019-01-05 15:23:40 +00:00
|
|
|
let n = 32; // TODO: parameter
|
|
|
|
for i in 0..n {
|
|
|
|
let angle = remap(i as f32, 0.0, n as f32, 0.0, TAU);
|
|
|
|
let normal = vec2(angle.cos(), angle.sin());
|
|
|
|
path_normals.push(normal);
|
|
|
|
path_points.push(*center + *radius * normal);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(color) = fill_color {
|
|
|
|
frame.fill_closed_path(&path_points, &path_normals, *color);
|
2019-01-05 14:28:07 +00:00
|
|
|
}
|
2019-01-05 15:23:40 +00:00
|
|
|
if let Some(outline) = outline {
|
|
|
|
frame.paint_path(
|
|
|
|
Closed,
|
|
|
|
&path_points,
|
|
|
|
&path_normals,
|
|
|
|
outline.color,
|
|
|
|
outline.width,
|
|
|
|
);
|
2019-01-05 14:28:07 +00:00
|
|
|
}
|
|
|
|
}
|
2019-01-04 13:14:32 +00:00
|
|
|
PaintCmd::Clear { fill_color } => {
|
|
|
|
frame.clear_color = Some(*fill_color);
|
|
|
|
}
|
2019-01-05 15:23:40 +00:00
|
|
|
PaintCmd::Line {
|
|
|
|
points,
|
|
|
|
color,
|
|
|
|
width,
|
|
|
|
} => {
|
|
|
|
let n = points.len();
|
|
|
|
if n >= 2 {
|
|
|
|
path_points = points.clone();
|
|
|
|
path_normals.clear();
|
|
|
|
|
|
|
|
path_normals.push((path_points[1] - path_points[0]).normalized().rot90());
|
|
|
|
for i in 1..n - 1 {
|
|
|
|
let n0 = (path_points[i] - path_points[i - 1]).normalized().rot90();
|
|
|
|
let n1 = (path_points[i + 1] - path_points[i]).normalized().rot90();
|
|
|
|
let v = (n0 + n1) / 2.0;
|
|
|
|
let normal = v / v.length_sq();
|
|
|
|
path_normals.push(normal); // TODO: handle VERY sharp turns better
|
|
|
|
}
|
|
|
|
path_normals.push(
|
|
|
|
(path_points[n - 1] - path_points[n - 2])
|
|
|
|
.normalized()
|
|
|
|
.rot90(),
|
|
|
|
);
|
|
|
|
|
|
|
|
frame.paint_path(Open, &path_points, &path_normals, *color, *width);
|
|
|
|
}
|
|
|
|
}
|
2019-01-04 13:14:32 +00:00
|
|
|
PaintCmd::Rect {
|
2019-01-05 14:28:07 +00:00
|
|
|
fill_color,
|
|
|
|
outline,
|
2019-01-04 13:14:32 +00:00
|
|
|
pos,
|
|
|
|
size,
|
|
|
|
..
|
|
|
|
} => {
|
2019-01-05 15:23:40 +00:00
|
|
|
path_points.clear();
|
|
|
|
path_normals.clear();
|
|
|
|
|
|
|
|
let min = *pos;
|
|
|
|
let max = *pos + *size;
|
|
|
|
|
2019-01-05 14:28:07 +00:00
|
|
|
// TODO: rounded corners
|
2019-01-05 15:23:40 +00:00
|
|
|
path_points.push(vec2(min.x, min.y));
|
|
|
|
path_normals.push(vec2(-1.0, -1.0));
|
|
|
|
path_points.push(vec2(max.x, min.y));
|
|
|
|
path_normals.push(vec2(1.0, -1.0));
|
|
|
|
path_points.push(vec2(max.x, max.y));
|
|
|
|
path_normals.push(vec2(1.0, 1.0));
|
|
|
|
path_points.push(vec2(min.x, max.y));
|
|
|
|
path_normals.push(vec2(-1.0, 1.0));
|
|
|
|
|
2019-01-05 14:28:07 +00:00
|
|
|
if let Some(color) = fill_color {
|
2019-01-05 15:23:40 +00:00
|
|
|
frame.fill_closed_path(&path_points, &path_normals, *color);
|
2019-01-05 14:28:07 +00:00
|
|
|
}
|
|
|
|
if let Some(outline) = outline {
|
2019-01-05 15:23:40 +00:00
|
|
|
frame.paint_path(
|
|
|
|
Closed,
|
|
|
|
&path_points,
|
|
|
|
&path_normals,
|
|
|
|
outline.color,
|
|
|
|
outline.width,
|
|
|
|
);
|
2019-01-05 14:28:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
PaintCmd::Text {
|
|
|
|
color,
|
|
|
|
pos,
|
|
|
|
text,
|
|
|
|
x_offsets,
|
|
|
|
} => {
|
|
|
|
for (c, x_offset) in text.chars().zip(x_offsets.iter()) {
|
|
|
|
if let Some(glyph) = self.font.glyph_info(c) {
|
|
|
|
let top_left = Vertex {
|
2019-01-05 15:23:40 +00:00
|
|
|
pos: *pos
|
|
|
|
+ vec2(
|
|
|
|
x_offset + (glyph.offset_x as f32),
|
|
|
|
glyph.offset_y as f32,
|
|
|
|
),
|
|
|
|
uv: (glyph.min_x, glyph.min_y),
|
2019-01-05 14:28:07 +00:00
|
|
|
color: *color,
|
|
|
|
};
|
|
|
|
let bottom_right = Vertex {
|
2019-01-05 15:23:40 +00:00
|
|
|
pos: top_left.pos
|
|
|
|
+ vec2(
|
|
|
|
(1 + glyph.max_x - glyph.min_x) as f32,
|
|
|
|
(1 + glyph.max_y - glyph.min_y) as f32,
|
|
|
|
),
|
|
|
|
uv: (glyph.max_x + 1, glyph.max_y + 1),
|
2019-01-05 14:28:07 +00:00
|
|
|
color: *color,
|
|
|
|
};
|
|
|
|
frame.add_rect(top_left, bottom_right);
|
|
|
|
}
|
|
|
|
}
|
2019-01-04 13:14:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
frame
|
|
|
|
}
|
|
|
|
}
|