use crate::geom::{Point, ScreenPoint, ScreenRect};
use crate::transform::Transform;
use crate::view::Viewport;
use gpui::Rgba;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LineStyle {
pub color: Rgba,
pub width: f32,
}
impl Default for LineStyle {
fn default() -> Self {
Self {
color: Rgba {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
},
width: 1.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MarkerShape {
Circle,
Square,
Cross,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MarkerStyle {
pub color: Rgba,
pub size: f32,
pub shape: MarkerShape,
}
impl Default for MarkerStyle {
fn default() -> Self {
Self {
color: Rgba {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
},
size: 4.0,
shape: MarkerShape::Circle,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct RectStyle {
pub fill: Rgba,
pub stroke: Rgba,
pub stroke_width: f32,
}
impl Default for RectStyle {
fn default() -> Self {
Self {
fill: Rgba {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
},
stroke: Rgba {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
},
stroke_width: 1.0,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct TextStyle {
pub color: Rgba,
pub size: f32,
}
impl Default for TextStyle {
fn default() -> Self {
Self {
color: Rgba {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
},
size: 12.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct LineSegment {
pub start: ScreenPoint,
pub end: ScreenPoint,
}
impl LineSegment {
pub(crate) fn new(start: ScreenPoint, end: ScreenPoint) -> Self {
Self { start, end }
}
}
#[derive(Debug, Clone)]
pub(crate) enum RenderCommand {
ClipRect(ScreenRect),
ClipEnd,
LineSegments {
segments: Vec<LineSegment>,
style: LineStyle,
},
Points {
points: Vec<ScreenPoint>,
style: MarkerStyle,
},
Rect {
rect: ScreenRect,
style: RectStyle,
},
Text {
position: ScreenPoint,
text: String,
style: TextStyle,
},
}
#[derive(Debug, Default, Clone)]
pub(crate) struct RenderList {
commands: Vec<RenderCommand>,
}
impl RenderList {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn push(&mut self, command: RenderCommand) {
self.commands.push(command);
}
pub(crate) fn commands(&self) -> &[RenderCommand] {
&self.commands
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct RenderCacheKey {
pub viewport: Viewport,
pub size: (u32, u32),
pub generation: u64,
}
pub(crate) fn build_line_segments(
points: &[Point],
transform: &Transform,
clip: ScreenRect,
out: &mut Vec<LineSegment>,
) {
out.clear();
if points.len() < 2 {
return;
}
for window in points.windows(2) {
let Some(start) = transform.data_to_screen(window[0]) else {
continue;
};
let Some(end) = transform.data_to_screen(window[1]) else {
continue;
};
if let Some((clipped_start, clipped_end)) = clip_segment(start, end, clip) {
out.push(LineSegment::new(clipped_start, clipped_end));
}
}
}
pub(crate) fn build_scatter_points(
points: &[Point],
transform: &Transform,
clip: ScreenRect,
out: &mut Vec<ScreenPoint>,
) {
out.clear();
for point in points {
let Some(screen) = transform.data_to_screen(*point) else {
continue;
};
if screen.x >= clip.min.x
&& screen.x <= clip.max.x
&& screen.y >= clip.min.y
&& screen.y <= clip.max.y
{
out.push(screen);
}
}
}
fn clip_segment(
mut start: ScreenPoint,
mut end: ScreenPoint,
rect: ScreenRect,
) -> Option<(ScreenPoint, ScreenPoint)> {
const LEFT: u8 = 1;
const RIGHT: u8 = 2;
const TOP: u8 = 4;
const BOTTOM: u8 = 8;
let mut out_start = region_code(start, rect, LEFT, RIGHT, TOP, BOTTOM);
let mut out_end = region_code(end, rect, LEFT, RIGHT, TOP, BOTTOM);
loop {
if (out_start | out_end) == 0 {
return Some((start, end));
}
if (out_start & out_end) != 0 {
return None;
}
let out_code = if out_start != 0 { out_start } else { out_end };
let (mut x, mut y) = (0.0_f32, 0.0_f32);
if (out_code & TOP) != 0 {
x = start.x + (end.x - start.x) * (rect.min.y - start.y) / (end.y - start.y);
y = rect.min.y;
} else if (out_code & BOTTOM) != 0 {
x = start.x + (end.x - start.x) * (rect.max.y - start.y) / (end.y - start.y);
y = rect.max.y;
} else if (out_code & RIGHT) != 0 {
y = start.y + (end.y - start.y) * (rect.max.x - start.x) / (end.x - start.x);
x = rect.max.x;
} else if (out_code & LEFT) != 0 {
y = start.y + (end.y - start.y) * (rect.min.x - start.x) / (end.x - start.x);
x = rect.min.x;
}
let new_point = ScreenPoint::new(x, y);
if out_code == out_start {
start = new_point;
out_start = region_code(start, rect, LEFT, RIGHT, TOP, BOTTOM);
} else {
end = new_point;
out_end = region_code(end, rect, LEFT, RIGHT, TOP, BOTTOM);
}
}
}
fn region_code(
point: ScreenPoint,
rect: ScreenRect,
left: u8,
right: u8,
top: u8,
bottom: u8,
) -> u8 {
let mut code = 0;
if point.x < rect.min.x {
code |= left;
} else if point.x > rect.max.x {
code |= right;
}
if point.y < rect.min.y {
code |= top;
} else if point.y > rect.max.y {
code |= bottom;
}
code
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geom::Point;
use crate::view::Range;
use crate::view::Viewport;
#[test]
fn clip_segment_inside() {
let rect = ScreenRect::new(ScreenPoint::new(0.0, 0.0), ScreenPoint::new(10.0, 10.0));
let start = ScreenPoint::new(2.0, 2.0);
let end = ScreenPoint::new(8.0, 8.0);
let clipped = clip_segment(start, end, rect).expect("segment should clip");
assert_eq!(clipped.0, start);
assert_eq!(clipped.1, end);
}
#[test]
fn build_segments_with_transform() {
let viewport = Viewport::new(Range::new(0.0, 1.0), Range::new(0.0, 1.0));
let rect = ScreenRect::new(ScreenPoint::new(0.0, 0.0), ScreenPoint::new(10.0, 10.0));
let transform = Transform::new(viewport, rect).expect("valid transform");
let points = [Point::new(0.0, 0.0), Point::new(1.0, 1.0)];
let mut out = Vec::new();
build_line_segments(&points, &transform, rect, &mut out);
assert_eq!(out.len(), 1);
}
}