use super::context::{ChartMapping, LinearPriceMap, RenderContext};
use crate::model::{Bar, Marker, MarkerPos, MarkerShape};
use crate::tokens::DESIGN_TOKENS;
use egui::{Color32, FontId, Painter, Pos2, Rect, Shape, Stroke, Vec2, epaint::StrokeKind};
pub fn render_markers(
context: &RenderContext,
markers: &[Marker],
visible_data: &[Bar],
price_scale: &LinearPriceMap,
coords: &ChartMapping,
) {
for marker in markers {
if let Some(bar_idx) = visible_data.iter().position(|bar| bar.time == marker.time) {
let global_idx = coords.start_idx + bar_idx;
let bar = &visible_data[bar_idx];
let x = coords.idx_to_x(global_idx);
let y = match marker.position {
MarkerPos::AboveBar => {
let high_y = price_scale.price_to_y(bar.high, context.rect);
high_y - 20.0 * marker.size }
MarkerPos::BelowBar => {
let low_y = price_scale.price_to_y(bar.low, context.rect);
low_y + 20.0 * marker.size }
MarkerPos::InBar => price_scale.price_to_y(bar.close, context.rect),
MarkerPos::Top => context.rect.top() + 20.0 * marker.size,
MarkerPos::Bottom => context.rect.bottom() - 20.0 * marker.size,
MarkerPos::Left | MarkerPos::Right | MarkerPos::Absolute => {
price_scale.price_to_y(bar.close, context.rect)
}
MarkerPos::AbsoluteUp => {
let high_y = price_scale.price_to_y(bar.high, context.rect);
high_y - 10.0 * marker.size
}
MarkerPos::AbsoluteDown => {
let low_y = price_scale.price_to_y(bar.low, context.rect);
low_y + 10.0 * marker.size
}
};
let pos = Pos2::new(x, y);
render_marker_shape(
context.painter,
pos,
marker.shape,
marker.color,
marker.size,
);
if let Some(text) = &marker.text {
let text_pos = Pos2::new(pos.x, pos.y + 12.0 * marker.size);
context.painter.text(
text_pos,
egui::Align2::CENTER_TOP,
text,
FontId::proportional(10.0 * marker.size),
marker.color,
);
}
}
}
}
fn render_marker_shape(
painter: &Painter,
pos: Pos2,
shape: MarkerShape,
color: Color32,
size: f32,
) {
let base_size = 8.0 * size;
match shape {
MarkerShape::Circle => {
painter.circle_filled(pos, base_size, color);
painter.circle_stroke(
pos,
base_size,
Stroke::new(
DESIGN_TOKENS.stroke.hairline,
DESIGN_TOKENS.semantic.chart.crosshair_label_text,
),
);
}
MarkerShape::Square => {
painter.rect_filled(
Rect::from_center_size(pos, Vec2::splat(base_size * 2.0)),
0.0,
color,
);
painter.rect_stroke(
Rect::from_center_size(pos, Vec2::splat(base_size * 2.0)),
0.0,
Stroke::new(
DESIGN_TOKENS.stroke.hairline,
DESIGN_TOKENS.semantic.chart.crosshair_label_text,
),
StrokeKind::Outside,
);
}
MarkerShape::ArrowUp => {
let points = vec![
pos + Vec2::new(0.0, -base_size * 1.2), pos + Vec2::new(-base_size * 0.8, base_size * 0.6), pos + Vec2::new(base_size * 0.8, base_size * 0.6), ];
painter.add(Shape::convex_polygon(
points,
color,
Stroke::new(
DESIGN_TOKENS.stroke.hairline,
DESIGN_TOKENS.semantic.chart.crosshair_label_text,
),
));
}
MarkerShape::ArrowDown => {
let points = vec![
pos + Vec2::new(0.0, base_size * 1.2), pos + Vec2::new(-base_size * 0.8, -base_size * 0.6), pos + Vec2::new(base_size * 0.8, -base_size * 0.6), ];
painter.add(Shape::convex_polygon(
points,
color,
Stroke::new(
DESIGN_TOKENS.stroke.hairline,
DESIGN_TOKENS.semantic.chart.crosshair_label_text,
),
));
}
MarkerShape::TriangleUp => {
let points = vec![
pos + Vec2::new(0.0, -base_size * 1.2),
pos + Vec2::new(-base_size * 0.8, base_size * 0.6),
pos + Vec2::new(base_size * 0.8, base_size * 0.6),
];
painter.add(Shape::convex_polygon(
points,
Color32::TRANSPARENT,
Stroke::new(DESIGN_TOKENS.stroke.thick, color),
));
}
MarkerShape::TriangleDown => {
let points = vec![
pos + Vec2::new(0.0, base_size * 1.2),
pos + Vec2::new(-base_size * 0.8, -base_size * 0.6),
pos + Vec2::new(base_size * 0.8, -base_size * 0.6),
];
painter.add(Shape::convex_polygon(
points,
Color32::TRANSPARENT,
Stroke::new(DESIGN_TOKENS.stroke.thick, color),
));
}
MarkerShape::Diamond => {
let points = vec![
pos + Vec2::new(0.0, -base_size),
pos + Vec2::new(-base_size, 0.0),
pos + Vec2::new(0.0, base_size),
pos + Vec2::new(base_size, 0.0),
];
painter.add(Shape::convex_polygon(
points,
color,
Stroke::new(
DESIGN_TOKENS.stroke.hairline,
DESIGN_TOKENS.semantic.chart.crosshair_label_text,
),
));
}
MarkerShape::Star => {
let outer = base_size;
let inner = base_size * 0.4;
let points = vec![
pos + Vec2::new(0.0, -outer), pos + Vec2::new(inner * 0.3, -inner * 0.3),
pos + Vec2::new(outer, 0.0), pos + Vec2::new(inner * 0.3, inner * 0.3),
pos + Vec2::new(0.0, outer), pos + Vec2::new(-inner * 0.3, inner * 0.3),
pos + Vec2::new(-outer, 0.0), pos + Vec2::new(-inner * 0.3, -inner * 0.3),
];
painter.add(Shape::convex_polygon(
points,
color,
Stroke::new(
DESIGN_TOKENS.stroke.hairline,
DESIGN_TOKENS.semantic.chart.crosshair_label_text,
),
));
}
MarkerShape::Cross => {
let size = base_size * 1.2;
painter.line_segment(
[pos + Vec2::new(-size, -size), pos + Vec2::new(size, size)],
Stroke::new(DESIGN_TOKENS.stroke.thick, color),
);
painter.line_segment(
[pos + Vec2::new(-size, size), pos + Vec2::new(size, -size)],
Stroke::new(DESIGN_TOKENS.stroke.thick, color),
);
}
MarkerShape::Flag => {
painter.line_segment(
[pos, pos + Vec2::new(0.0, base_size * 2.0)],
Stroke::new(DESIGN_TOKENS.stroke.thick, color),
);
let points = vec![
pos,
pos + Vec2::new(base_size * 1.2, -base_size * 0.6),
pos + Vec2::new(0.0, -base_size * 1.2),
];
painter.add(Shape::convex_polygon(
points,
color,
Stroke::new(
DESIGN_TOKENS.stroke.hairline,
DESIGN_TOKENS.semantic.chart.crosshair_label_text,
),
));
}
}
}