use crate::error::Result;
use crate::style::ChartStyle;
use crate::theme::{ChartTheme, Color};
use crate::viewport::{Rect, Viewport};
use std::sync::Arc;
use super::vertex::{Vertex, VertexBuffer};
#[cfg(feature = "text-rendering")]
use crate::text::{TextAnchor, TextBaseline, TextItem};
pub struct RenderContext {
pub device: Arc<wgpu::Device>,
pub queue: Arc<wgpu::Queue>,
vertex_buffers: Vec<VertexBuffer>,
current_vertices: Vec<Vertex>,
max_vertices_per_buffer: u32,
pending_draws: Vec<usize>,
next_buffer_index: usize,
viewport: Viewport,
theme: ChartTheme,
style: ChartStyle,
#[cfg(feature = "text-rendering")]
staged_text: std::collections::BTreeMap<i32, Vec<TextItem>>,
#[cfg(feature = "text-rendering")]
current_stage_priority: i32,
}
impl RenderContext {
pub fn new(
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
viewport: Viewport,
theme: ChartTheme,
style: ChartStyle,
) -> Self {
Self {
device,
queue,
vertex_buffers: Vec::new(),
current_vertices: Vec::new(),
max_vertices_per_buffer: 100_000, pending_draws: Vec::new(),
next_buffer_index: 0,
viewport,
theme,
style,
#[cfg(feature = "text-rendering")]
staged_text: std::collections::BTreeMap::new(),
#[cfg(feature = "text-rendering")]
current_stage_priority: 0,
}
}
#[cfg(feature = "text-rendering")]
pub fn set_stage_priority(&mut self, priority: i32) {
self.current_stage_priority = priority;
}
pub fn viewport(&self) -> &Viewport {
&self.viewport
}
pub fn theme(&self) -> &ChartTheme {
&self.theme
}
pub fn style(&self) -> &ChartStyle {
&self.style
}
pub fn update(&mut self, viewport: Viewport, theme: ChartTheme, style: ChartStyle) {
self.viewport = viewport;
self.theme = theme;
self.style = style;
}
pub fn add_vertices(&mut self, vertices: &[Vertex]) {
self.current_vertices.extend_from_slice(vertices);
}
pub fn add_vertex(&mut self, vertex: Vertex) {
self.current_vertices.push(vertex);
}
pub fn draw_line(&mut self, start: [f32; 2], end: [f32; 2], color: Color, thickness: f32) {
let half_thickness = thickness * 0.5;
let dx = end[0] - start[0];
let dy = end[1] - start[1];
let len = (dx * dx + dy * dy).sqrt();
if len > 0.0 {
let nx = -dy / len * half_thickness;
let ny = dx / len * half_thickness;
let vertices = [
Vertex::new([start[0] + nx, start[1] + ny], color),
Vertex::new([start[0] - nx, start[1] - ny], color),
Vertex::new([end[0] + nx, end[1] + ny], color),
Vertex::new([start[0] - nx, start[1] - ny], color),
Vertex::new([end[0] - nx, end[1] - ny], color),
Vertex::new([end[0] + nx, end[1] + ny], color),
];
self.add_vertices(&vertices);
}
}
pub fn draw_dashed_line(
&mut self,
start: [f32; 2],
end: [f32; 2],
color: Color,
thickness: f32,
dash: f32,
gap: f32,
) {
let dx = end[0] - start[0];
let dy = end[1] - start[1];
let len = (dx * dx + dy * dy).sqrt();
if len == 0.0 {
return;
}
let dir_x = dx / len;
let dir_y = dy / len;
let mut distance = 0.0;
let mut current_start = start;
while distance < len {
let seg_len = (dash).min(len - distance);
let current_end = [
current_start[0] + dir_x * seg_len,
current_start[1] + dir_y * seg_len,
];
self.draw_line(current_start, current_end, color, thickness);
distance += dash + gap;
current_start = [start[0] + dir_x * distance, start[1] + dir_y * distance];
}
}
pub fn draw_rect(&mut self, rect: Rect, color: Color) {
let x1 = rect.x;
let y1 = rect.y;
let x2 = rect.x + rect.width;
let y2 = rect.y + rect.height;
let vertices = [
Vertex::new([x1, y1], color),
Vertex::new([x2, y1], color),
Vertex::new([x1, y2], color),
Vertex::new([x2, y1], color),
Vertex::new([x2, y2], color),
Vertex::new([x1, y2], color),
];
self.add_vertices(&vertices);
}
pub fn draw_triangle(&mut self, p1: [f32; 2], p2: [f32; 2], p3: [f32; 2], color: Color) {
let vertices = [
Vertex::new(p1, color),
Vertex::new(p2, color),
Vertex::new(p3, color),
];
self.add_vertices(&vertices);
}
pub fn draw_circle(&mut self, center: [f32; 2], radius: f32, color: Color) {
const SEGMENTS: u32 = 16;
let angle_step = std::f32::consts::TAU / SEGMENTS as f32;
for i in 0..SEGMENTS {
let angle1 = i as f32 * angle_step;
let angle2 = (i + 1) as f32 * angle_step;
let p1 = [
center[0] + radius * angle1.cos(),
center[1] + radius * angle1.sin(),
];
let p2 = [
center[0] + radius * angle2.cos(),
center[1] + radius * angle2.sin(),
];
self.draw_triangle(center, p1, p2, color);
}
}
pub fn draw_rounded_rect(&mut self, rect: Rect, radius: f32, color: Color) {
let r = radius.min(rect.width / 2.0).min(rect.height / 2.0);
self.draw_rect(
Rect::new(rect.x, rect.y + r, rect.width, rect.height - 2.0 * r),
color,
);
self.draw_rect(
Rect::new(rect.x + r, rect.y, rect.width - 2.0 * r, r),
color,
);
self.draw_rect(
Rect::new(
rect.x + r,
rect.y + rect.height - r,
rect.width - 2.0 * r,
r,
),
color,
);
const SEGMENTS: u32 = 8;
let angle_step = std::f32::consts::FRAC_PI_2 / SEGMENTS as f32;
let tl_center = [rect.x + r, rect.y + r];
for i in 0..SEGMENTS {
let a1 = std::f32::consts::PI + i as f32 * angle_step;
let a2 = std::f32::consts::PI + (i + 1) as f32 * angle_step;
self.draw_triangle(
tl_center,
[tl_center[0] + r * a1.cos(), tl_center[1] + r * a1.sin()],
[tl_center[0] + r * a2.cos(), tl_center[1] + r * a2.sin()],
color,
);
}
let tr_center = [rect.x + rect.width - r, rect.y + r];
for i in 0..SEGMENTS {
let a1 = -std::f32::consts::FRAC_PI_2 + i as f32 * angle_step;
let a2 = -std::f32::consts::FRAC_PI_2 + (i + 1) as f32 * angle_step;
self.draw_triangle(
tr_center,
[tr_center[0] + r * a1.cos(), tr_center[1] + r * a1.sin()],
[tr_center[0] + r * a2.cos(), tr_center[1] + r * a2.sin()],
color,
);
}
let br_center = [rect.x + rect.width - r, rect.y + rect.height - r];
for i in 0..SEGMENTS {
let a1 = i as f32 * angle_step;
let a2 = (i + 1) as f32 * angle_step;
self.draw_triangle(
br_center,
[br_center[0] + r * a1.cos(), br_center[1] + r * a1.sin()],
[br_center[0] + r * a2.cos(), br_center[1] + r * a2.sin()],
color,
);
}
let bl_center = [rect.x + r, rect.y + rect.height - r];
for i in 0..SEGMENTS {
let a1 = std::f32::consts::FRAC_PI_2 + i as f32 * angle_step;
let a2 = std::f32::consts::FRAC_PI_2 + (i + 1) as f32 * angle_step;
self.draw_triangle(
bl_center,
[bl_center[0] + r * a1.cos(), bl_center[1] + r * a1.sin()],
[bl_center[0] + r * a2.cos(), bl_center[1] + r * a2.sin()],
color,
);
}
}
pub fn draw_rect_with_border(
&mut self,
rect: Rect,
fill_color: Color,
border_color: Color,
border_width: f32,
) {
self.draw_rect(rect, fill_color);
let half_border = border_width * 0.5;
self.draw_rect(
Rect::new(
rect.x - half_border,
rect.y - half_border,
rect.width + border_width,
border_width,
),
border_color,
);
self.draw_rect(
Rect::new(
rect.x - half_border,
rect.y + rect.height - half_border,
rect.width + border_width,
border_width,
),
border_color,
);
self.draw_rect(
Rect::new(
rect.x - half_border,
rect.y - half_border,
border_width,
rect.height + border_width,
),
border_color,
);
self.draw_rect(
Rect::new(
rect.x + rect.width - half_border,
rect.y - half_border,
border_width,
rect.height + border_width,
),
border_color,
);
}
pub fn flush(&mut self) -> Result<()> {
if self.current_vertices.is_empty() {
return Ok(());
}
let required_capacity = self.current_vertices.len() as u32;
let buffer_index = if self.next_buffer_index < self.vertex_buffers.len() {
if self.vertex_buffers[self.next_buffer_index].capacity >= required_capacity {
self.next_buffer_index
} else {
let capacity = required_capacity.max(self.max_vertices_per_buffer);
let buffer = VertexBuffer::new(&self.device, capacity);
self.vertex_buffers.push(buffer);
self.vertex_buffers.len() - 1
}
} else {
let capacity = required_capacity.max(self.max_vertices_per_buffer);
let buffer = VertexBuffer::new(&self.device, capacity);
self.vertex_buffers.push(buffer);
self.vertex_buffers.len() - 1
};
self.vertex_buffers[buffer_index].update(&self.queue, &self.current_vertices)?;
self.pending_draws.push(buffer_index);
self.next_buffer_index = buffer_index + 1;
self.current_vertices.clear();
Ok(())
}
pub fn commit(&mut self, render_pass: &mut wgpu::RenderPass) -> Result<()> {
if !self.current_vertices.is_empty() {
self.flush()?;
}
for &buffer_index in &self.pending_draws {
let buffer = &mut self.vertex_buffers[buffer_index];
if buffer.vertex_count > 0 {
render_pass.set_vertex_buffer(0, buffer.slice());
render_pass.draw(0..buffer.vertex_count, 0..1);
buffer.vertex_count = 0;
}
}
self.pending_draws.clear();
Ok(())
}
pub fn vertex_buffers(&self) -> &[VertexBuffer] {
&self.vertex_buffers
}
pub fn clear(&mut self) {
self.current_vertices.clear();
self.pending_draws.clear();
self.next_buffer_index = 0;
for buffer in &mut self.vertex_buffers {
buffer.vertex_count = 0;
}
#[cfg(feature = "text-rendering")]
{
self.staged_text.clear();
self.current_stage_priority = 0;
}
}
#[cfg(feature = "text-rendering")]
pub fn text_items(&self) -> Vec<TextItem> {
self.staged_text
.values()
.flat_map(|items| items.iter().cloned())
.collect()
}
#[cfg(feature = "text-rendering")]
pub fn text_items_for_stage(&self, stage_priority: i32) -> &[TextItem] {
self.staged_text
.get(&stage_priority)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
#[cfg(feature = "text-rendering")]
pub fn text_stage_priorities(&self) -> Vec<i32> {
self.staged_text.keys().copied().collect()
}
#[cfg(feature = "text-rendering")]
pub fn take_text_for_stage(&mut self, stage_priority: i32) -> Vec<TextItem> {
self.staged_text.remove(&stage_priority).unwrap_or_default()
}
#[cfg(feature = "text-rendering")]
pub fn draw_text(&mut self, text: &str, x: f32, y: f32, color: Color, font_size: Option<f32>) {
self.staged_text
.entry(self.current_stage_priority)
.or_default()
.push(TextItem {
text: text.to_string(),
x,
y,
font_size: font_size.unwrap_or(12.0),
color,
anchor: TextAnchor::Start,
baseline: TextBaseline::Top,
});
}
#[cfg(feature = "text-rendering")]
pub fn draw_text_anchored(
&mut self,
text: &str,
x: f32,
y: f32,
color: Color,
font_size: Option<f32>,
anchor: TextAnchor,
baseline: TextBaseline,
) {
self.staged_text
.entry(self.current_stage_priority)
.or_default()
.push(TextItem {
text: text.to_string(),
x,
y,
font_size: font_size.unwrap_or(12.0),
color,
anchor,
baseline,
});
}
}