use crate::color::Color;
use crate::error::{Error, Result};
use crate::font::GlyphAtlas;
use crate::input::{InputEvent, InteractionMode};
use crate::renderer::Renderer;
use crate::vertex::{DrawList, Vertex};
use crate::window::{OverlayTarget, OverlayWindow};
use std::f32::consts::PI;
const DEFAULT_FONT_SIZE: f32 = 16.0;
const CIRCLE_SEGMENTS: usize = 32;
pub struct Overlay {
window: OverlayWindow,
renderer: Renderer,
draw_list: DrawList,
font_atlas: GlyphAtlas,
in_frame: bool,
}
impl Overlay {
pub fn new(target: OverlayTarget) -> Result<Self> {
let window = OverlayWindow::create(&target)?;
let (w, h) = window.size();
let mut renderer = Renderer::new(window.hwnd, w, h)?;
let font_atlas = GlyphAtlas::new(DEFAULT_FONT_SIZE);
renderer.upload_font_atlas(&font_atlas)?;
Ok(Self {
window,
renderer,
draw_list: DrawList::new(),
font_atlas,
in_frame: false,
})
}
pub fn is_visible(&self) -> bool {
self.window.is_target_visible()
}
pub fn begin_frame(&mut self) -> Result<()> {
if !self.window.pump_messages() {
return Err(Error::OverlayClosed);
}
if !self.window.sync_position() {
return Err(Error::TargetWindowLost);
}
let (w, h) = self.window.size();
self.renderer.resize(w, h)?;
self.draw_list.clear();
self.renderer.begin_frame();
self.in_frame = true;
Ok(())
}
pub fn end_frame(&mut self) -> Result<()> {
if !self.in_frame {
return Err(Error::NoActiveFrame);
}
self.renderer.submit(
&self.draw_list.vertices,
&self.draw_list.indices,
&self.draw_list.commands,
)?;
self.renderer.end_frame()?;
self.in_frame = false;
Ok(())
}
pub fn interaction_mode(&self) -> InteractionMode {
self.window.interaction_mode()
}
pub fn set_interaction_mode(&mut self, mode: InteractionMode) -> Result<()> {
self.window.set_interaction_mode(mode)
}
pub fn drain_input_events(&mut self) -> Vec<InputEvent> {
self.window.drain_events()
}
pub fn size(&self) -> (u32, u32) {
self.window.size()
}
pub fn rect(&mut self, x: f32, y: f32, w: f32, h: f32, color: Color) {
let t = 1.0;
self.line(x, y, x + w, y, t, color);
self.line(x + w, y, x + w, y + h, t, color);
self.line(x + w, y + h, x, y + h, t, color);
self.line(x, y + h, x, y, t, color);
}
pub fn rect_filled(&mut self, x: f32, y: f32, w: f32, h: f32, color: Color) {
let c = color.to_f32_array();
self.draw_list.add_solid_quad(
Vertex::new(x, y, c),
Vertex::new(x + w, y, c),
Vertex::new(x + w, y + h, c),
Vertex::new(x, y + h, c),
);
}
pub fn line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, thickness: f32, color: Color) {
let dx = x2 - x1;
let dy = y2 - y1;
let len = (dx * dx + dy * dy).sqrt();
if len < f32::EPSILON {
return;
}
let nx = -dy / len * thickness * 0.5;
let ny = dx / len * thickness * 0.5;
let c = color.to_f32_array();
self.draw_list.add_solid_quad(
Vertex::new(x1 + nx, y1 + ny, c),
Vertex::new(x1 - nx, y1 - ny, c),
Vertex::new(x2 - nx, y2 - ny, c),
Vertex::new(x2 + nx, y2 + ny, c),
);
}
pub fn circle(&mut self, cx: f32, cy: f32, radius: f32, color: Color) {
let step = 2.0 * PI / CIRCLE_SEGMENTS as f32;
for i in 0..CIRCLE_SEGMENTS {
let a1 = step * i as f32;
let a2 = step * (i + 1) as f32;
self.line(
cx + a1.cos() * radius,
cy + a1.sin() * radius,
cx + a2.cos() * radius,
cy + a2.sin() * radius,
1.0,
color,
);
}
}
pub fn circle_filled(&mut self, cx: f32, cy: f32, radius: f32, color: Color) {
let c = color.to_f32_array();
let step = 2.0 * PI / CIRCLE_SEGMENTS as f32;
let center = Vertex::new(cx, cy, c);
let mut verts = vec![center];
for i in 0..=CIRCLE_SEGMENTS {
let angle = step * i as f32;
verts.push(Vertex::new(
cx + angle.cos() * radius,
cy + angle.sin() * radius,
c,
));
}
let mut indices = Vec::with_capacity(CIRCLE_SEGMENTS * 3);
for i in 1..=CIRCLE_SEGMENTS {
indices.push(0);
indices.push(i as u32);
indices.push((i % CIRCLE_SEGMENTS + 1) as u32);
}
self.draw_list.add_solid_triangles(&verts, &indices);
}
pub fn text(&mut self, x: f32, y: f32, text: &str, size: f32, color: Color) {
let scale = size / DEFAULT_FONT_SIZE;
let c = color.to_f32_array();
let atlas_w = self.font_atlas.width as f32;
let atlas_h = self.font_atlas.height as f32;
let mut cursor_x = x;
let mut cursor_y = y;
for ch in text.chars() {
if ch == '\n' {
cursor_x = x;
cursor_y += size;
continue;
}
let glyph = match self.font_atlas.glyph(ch) {
Some(g) => g,
None => continue,
};
if glyph.width == 0 || glyph.height == 0 {
cursor_x += glyph.advance * scale;
continue;
}
let gx = cursor_x + glyph.offset_x * scale;
let gy = cursor_y + (size - glyph.offset_y * scale - glyph.height as f32 * scale);
let gw = glyph.width as f32 * scale;
let gh = glyph.height as f32 * scale;
let u0 = glyph.x as f32 / atlas_w;
let v0 = glyph.y as f32 / atlas_h;
let u1 = (glyph.x + glyph.width) as f32 / atlas_w;
let v1 = (glyph.y + glyph.height) as f32 / atlas_h;
self.draw_list.add_textured_quad(
Vertex::with_uv(gx, gy, c, u0, v0),
Vertex::with_uv(gx + gw, gy, c, u1, v0),
Vertex::with_uv(gx + gw, gy + gh, c, u1, v1),
Vertex::with_uv(gx, gy + gh, c, u0, v1),
);
cursor_x += glyph.advance * scale;
}
}
pub fn text_bounds(&self, text: &str, size: f32) -> (f32, f32) {
let scale = size / DEFAULT_FONT_SIZE;
let (w, h) = self.font_atlas.measure(text);
(w * scale, h * scale)
}
}