egui/emgui/src/painter.rs

265 lines
9 KiB
Rust
Raw Normal View History

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 14:28:07 +00:00
math::{remap, Vec2, TAU},
2019-01-04 13:14:32 +00:00
types::{Color, PaintCmd},
};
#[derive(Clone, Copy, Debug, Default)]
pub struct Vertex {
/// Pixel coordinated
pub x: f32,
/// Pixel coordinated
pub y: f32,
/// Texel indices into the texture
pub u: u16,
/// Texel indices into the texture
pub v: u16,
/// 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 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 {
x: bottom_right.x,
y: top_left.y,
u: bottom_right.u,
v: top_left.v,
color: top_left.color,
};
let botom_left = Vertex {
x: top_left.x,
y: bottom_right.y,
u: top_left.u,
v: bottom_right.v,
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) {
self.vertices.extend(points.iter().map(|p| Vertex {
x: p.x,
y: p.y,
u: 0,
v: 0,
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;
for i in 2..n {
self.indices.push(idx);
self.indices.push(idx + i - 1);
self.indices.push(idx + i);
}
}
pub fn draw_closed_path(
&mut self,
points: &[Vec2],
normals: &[Vec2],
width: f32,
color: Color,
) {
// 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;
for i in 0..n {
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));
}
for (p, n) in points.iter().zip(normals) {
self.vertices.push(Vertex {
x: p.x + hw * n.x,
y: p.y + hw * n.x,
u: 0,
v: 0,
color,
});
self.vertices.push(Vertex {
x: p.x - hw * n.x,
y: p.y - hw * n.x,
u: 0,
v: 0,
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 14:28:07 +00:00
// let mut path_points = Vec::new();
// let mut path_normals = Vec::new();
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,
} => {
let n = 64; // TODO: parameter
if let Some(color) = fill_color {
let idx = frame.vertices.len() as u32;
for i in 2..n {
frame.indices.push(idx);
frame.indices.push(idx + i - 1);
frame.indices.push(idx + i);
}
for i in 0..n {
let angle = remap(i as f32, 0.0, n as f32, 0.0, TAU);
frame.vertices.push(Vertex {
x: center.x + radius * angle.cos(),
y: center.y + radius * angle.sin(),
u: 0,
v: 0,
color: *color,
});
}
}
if let Some(_outline) = outline {
// TODO
}
}
2019-01-04 13:14:32 +00:00
PaintCmd::Clear { fill_color } => {
frame.clear_color = Some(*fill_color);
}
PaintCmd::Line { .. } => {} // TODO
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 14:28:07 +00:00
// TODO: rounded corners
// TODO: anti-aliasing
// TODO: FilledRect and RectOutline as separate commands?
if let Some(color) = fill_color {
let vert = |pos: Vec2| Vertex {
x: pos.x,
y: pos.y,
u: 0,
v: 0,
color: *color,
};
frame.add_rect(vert(*pos), vert(*pos + *size));
}
if let Some(outline) = outline {
let vert = |x, y| Vertex {
x,
y,
u: 0,
v: 0,
color: outline.color,
};
// Draw this counter-clockwise from top-left corner,
// outer to inner on each step.
let hw = outline.width / 2.0;
let idx = frame.vertices.len() as u32;
for i in 0..4 {
frame.indices.push(idx + (2 * i + 0) % 8);
frame.indices.push(idx + (2 * i + 1) % 8);
frame.indices.push(idx + (2 * i + 2) % 8);
frame.indices.push(idx + (2 * i + 2) % 8);
frame.indices.push(idx + (2 * i + 1) % 8);
frame.indices.push(idx + (2 * i + 3) % 8);
}
let min = *pos;
let max = *pos + *size;
frame.vertices.push(vert(min.x - hw, min.y - hw));
frame.vertices.push(vert(min.x + hw, min.y + hw));
frame.vertices.push(vert(max.x + hw, min.y - hw));
frame.vertices.push(vert(max.x - hw, min.y + hw));
frame.vertices.push(vert(max.x + hw, max.y + hw));
frame.vertices.push(vert(max.x - hw, max.y - hw));
frame.vertices.push(vert(min.x - hw, max.y + hw));
frame.vertices.push(vert(min.x + hw, max.y - hw));
}
}
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 {
x: pos.x + x_offset + (glyph.offset_x as f32),
y: pos.y + (glyph.offset_y as f32),
u: glyph.min_x,
v: glyph.min_y,
color: *color,
};
let bottom_right = Vertex {
x: top_left.x + (1 + glyph.max_x - glyph.min_x) as f32,
y: top_left.y + (1 + glyph.max_y - glyph.min_y) as f32,
u: glyph.max_x + 1,
v: glyph.max_y + 1,
color: *color,
};
frame.add_rect(top_left, bottom_right);
}
}
2019-01-04 13:14:32 +00:00
}
}
}
frame
}
}