use crate::tokens::DESIGN_TOKENS;
use egui::{Color32, Painter, Pos2, Rect, Shape, Stroke};
#[derive(Clone, Copy)]
pub struct PriceCoords {
pub min_price: f64,
pub price_range: f64,
pub rect: Rect,
}
impl PriceCoords {
pub fn new(min_price: f64, max_price: f64, rect: Rect) -> Self {
Self {
min_price,
price_range: (max_price - min_price).max(1e-12),
rect,
}
}
#[inline]
pub fn price_to_y(&self, price: f64) -> f32 {
self.rect.max.y - ((price - self.min_price) / self.price_range) as f32 * self.rect.height()
}
}
pub struct OhlcYCoords {
pub y_high: f32,
pub y_low: f32,
pub body_top: f32,
pub body_bottom: f32,
}
impl OhlcYCoords {
pub fn from_bar(bar: &crate::model::Bar, coords: &PriceCoords) -> Self {
let y_open = coords.price_to_y(bar.open);
let y_high = coords.price_to_y(bar.high);
let y_low = coords.price_to_y(bar.low);
let y_close = coords.price_to_y(bar.close);
Self {
y_high,
y_low,
body_top: y_open.min(y_close),
body_bottom: y_open.max(y_close),
}
}
}
pub fn draw_wicks(painter: &Painter, x: f32, y: &OhlcYCoords, wick_width: f32, color: Color32) {
painter.add(Shape::line_segment(
[Pos2::new(x, y.y_high), Pos2::new(x, y.body_top)],
Stroke::new(wick_width, color),
));
painter.add(Shape::line_segment(
[Pos2::new(x, y.body_bottom), Pos2::new(x, y.y_low)],
Stroke::new(wick_width, color),
));
}
pub fn draw_body_filled(
painter: &Painter,
x: f32,
bar_width: f32,
y: &OhlcYCoords,
color: Color32,
) {
let body_rect = Rect::from_min_max(
Pos2::new(x - bar_width / 2.0, y.body_top),
Pos2::new(
x + bar_width / 2.0,
y.body_bottom
.max(y.body_top + DESIGN_TOKENS.sizing.candle.min_body_height),
),
);
painter.add(Shape::rect_filled(body_rect, 0.0, color));
}
pub fn draw_body_hollow(
painter: &Painter,
x: f32,
bar_width: f32,
y: &OhlcYCoords,
color: Color32,
) {
let body_rect = Rect::from_min_max(
Pos2::new(x - bar_width / 2.0, y.body_top),
Pos2::new(x + bar_width / 2.0, y.body_bottom),
);
painter.add(Shape::rect_stroke(
body_rect,
0.0,
Stroke::new(DESIGN_TOKENS.stroke.medium, color),
egui::epaint::StrokeKind::Inside,
));
}
pub fn draw_brick(painter: &Painter, rect: Rect, color: Color32, corner_radius: f32) {
painter.add(Shape::rect_filled(rect, corner_radius, color));
let border_color = color.gamma_multiply(0.7);
painter.add(Shape::rect_stroke(
rect,
corner_radius,
Stroke::new(DESIGN_TOKENS.stroke.hairline, border_color),
egui::epaint::StrokeKind::Inside,
));
}
pub fn draw_x_symbol(painter: &Painter, center: Pos2, half_size: f32, color: Color32) {
painter.add(Shape::line_segment(
[
Pos2::new(center.x - half_size, center.y - half_size),
Pos2::new(center.x + half_size, center.y + half_size),
],
Stroke::new(DESIGN_TOKENS.stroke.thick, color),
));
painter.add(Shape::line_segment(
[
Pos2::new(center.x - half_size, center.y + half_size),
Pos2::new(center.x + half_size, center.y - half_size),
],
Stroke::new(DESIGN_TOKENS.stroke.thick, color),
));
}
pub fn draw_o_symbol(painter: &Painter, center: Pos2, radius: f32, color: Color32) {
painter.add(Shape::circle_stroke(
center,
radius,
Stroke::new(DESIGN_TOKENS.stroke.thick, color),
));
}
#[inline]
pub fn bar_color(bar: &crate::model::Bar, bullish: Color32, bearish: Color32) -> Color32 {
if bar.close > bar.open {
bullish
} else {
bearish
}
}