use super::{
ArrowStyle, ChartPoint, DrawingToolType, FibonacciConfig, FontWeight, LineStyle,
TimeframeVisibility,
};
#[derive(Debug, Clone)]
pub struct Drawing {
pub id: usize,
pub tool_type: DrawingToolType,
pub points: Vec<egui::Pos2>,
pub chart_points: Vec<ChartPoint>,
pub color: [u8; 4],
pub stroke_width: f32,
pub completed: bool,
pub text: Option<String>,
pub line_style: LineStyle,
pub fill_color: Option<[u8; 4]>,
pub locked: bool,
pub visible: bool,
pub font_size: f32,
pub font_weight: FontWeight,
pub extend_left: bool,
pub extend_right: bool,
pub arrow_start_style: ArrowStyle,
pub arrow_end_style: ArrowStyle,
pub timeframe_visibility: TimeframeVisibility,
pub quantity: Option<f32>,
pub is_closed: bool,
pub curr_price: Option<f64>,
pub forecast_start_price: Option<f64>,
pub forecast_target_price: Option<f64>,
pub forecast_start_time: Option<i64>,
pub forecast_target_time: Option<i64>,
pub fib_config: Option<FibonacciConfig>,
pub z_order: i32,
}
impl Drawing {
pub fn new(id: usize, tool_type: DrawingToolType) -> Self {
Self {
id,
tool_type,
points: Vec::new(),
chart_points: Vec::new(),
color: [242, 54, 69, 255], stroke_width: 2.0,
completed: false,
text: None,
line_style: LineStyle::Solid,
fill_color: None,
locked: false,
visible: true,
font_size: 12.0,
font_weight: FontWeight::Normal,
extend_left: false,
extend_right: false,
arrow_start_style: ArrowStyle::None,
arrow_end_style: ArrowStyle::Arrow,
timeframe_visibility: TimeframeVisibility::all(),
quantity: Some(100.0),
is_closed: false,
curr_price: None,
forecast_start_price: None,
forecast_target_price: None,
forecast_start_time: None,
forecast_target_time: None,
fib_config: None,
z_order: 0,
}
}
pub fn with_color(id: usize, tool_type: DrawingToolType, color: [u8; 4]) -> Self {
let mut drawing = Self::new(id, tool_type);
drawing.color = color;
drawing
}
pub fn with_text(id: usize, text: String) -> Self {
let mut drawing = Self::new(id, DrawingToolType::TextLabel);
drawing.text = Some(text);
drawing.color = [255, 255, 255, 255]; drawing.arrow_end_style = ArrowStyle::None;
drawing
}
pub fn update_screen_coords<F, G>(&mut self, bar_to_x: F, price_to_y: G)
where
F: Fn(f32) -> f32,
G: Fn(f64) -> f32,
{
self.points.clear();
for cp in &self.chart_points {
let x = bar_to_x(cp.bar_idx);
let y = price_to_y(cp.price);
self.points.push(egui::Pos2::new(x, y));
}
}
pub fn add_point_with_chart_coords<F, G>(
&mut self,
screen_point: egui::Pos2,
x_to_bar: F,
y_to_price: G,
) where
F: Fn(f32) -> f32,
G: Fn(f32) -> f64,
{
let bar_idx = x_to_bar(screen_point.x);
let price = y_to_price(screen_point.y);
self.chart_points.push(ChartPoint::new(bar_idx, price));
self.points.push(screen_point);
self.check_completion();
}
pub fn update_last_point_with_chart_coords<F, G>(
&mut self,
screen_point: egui::Pos2,
x_to_bar: F,
y_to_price: G,
) where
F: Fn(f32) -> f32,
G: Fn(f32) -> f64,
{
if let (Some(last_point), Some(last_chart_point)) =
(self.points.last_mut(), self.chart_points.last_mut())
{
*last_point = screen_point;
*last_chart_point =
ChartPoint::new(x_to_bar(screen_point.x), y_to_price(screen_point.y));
}
}
pub fn add_point(&mut self, point: egui::Pos2) {
self.points.push(point);
self.check_completion();
}
pub fn shift_bar_indices(&mut self, delta: f32) {
for cp in &mut self.chart_points {
cp.shift_bar_idx(delta);
}
}
fn check_completion(&mut self) {
if let Some(required) = self.tool_type.required_points()
&& self.points.len() >= required
{
self.completed = true;
}
}
#[inline]
pub fn color32(&self) -> egui::Color32 {
egui::Color32::from_rgba_unmultiplied(
self.color[0],
self.color[1],
self.color[2],
self.color[3],
)
}
#[inline]
pub fn fill_color32(&self) -> Option<egui::Color32> {
self.fill_color
.map(|c| egui::Color32::from_rgba_unmultiplied(c[0], c[1], c[2], c[3]))
}
#[inline]
pub fn stroke(&self) -> egui::Stroke {
egui::Stroke::new(self.stroke_width, self.color32())
}
#[inline]
pub fn is_visible_on_timeframe(&self, timeframe: &str) -> bool {
self.visible && self.timeframe_visibility.is_visible_on(timeframe)
}
#[inline]
pub fn z_order(&self) -> i32 {
self.z_order
}
#[inline]
pub fn set_z_order(&mut self, order: i32) {
self.z_order = order;
}
pub fn bounding_rect(&self) -> Option<egui::Rect> {
if self.points.is_empty() {
return None;
}
let mut min_x = f32::MAX;
let mut min_y = f32::MAX;
let mut max_x = f32::MIN;
let mut max_y = f32::MIN;
for p in &self.points {
min_x = min_x.min(p.x);
min_y = min_y.min(p.y);
max_x = max_x.max(p.x);
max_y = max_y.max(p.y);
}
Some(egui::Rect::from_min_max(
egui::pos2(min_x, min_y),
egui::pos2(max_x, max_y),
))
}
pub fn quadratic_bezier(p0: egui::Pos2, p1: egui::Pos2, p2: egui::Pos2, t: f32) -> egui::Pos2 {
let u = 1.0 - t;
egui::Pos2::new(
u * u * p0.x + 2.0 * u * t * p1.x + t * t * p2.x,
u * u * p0.y + 2.0 * u * t * p1.y + t * t * p2.y,
)
}
pub fn format_timestamp(ts: i64) -> String {
use chrono::{DateTime, Utc};
match DateTime::<Utc>::from_timestamp(ts, 0) {
Some(dt) => dt.format("%Y-%m-%d %H:%M").to_string(),
None => "N/A".to_string(),
}
}
}
impl Default for Drawing {
fn default() -> Self {
Self::new(0, DrawingToolType::TrendLine)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_drawing() {
let drawing = Drawing::new(1, DrawingToolType::TrendLine);
assert_eq!(drawing.id, 1);
assert_eq!(drawing.tool_type, DrawingToolType::TrendLine);
assert!(drawing.points.is_empty());
assert!(!drawing.completed);
}
#[test]
fn test_with_color() {
let drawing = Drawing::with_color(1, DrawingToolType::TrendLine, [0, 255, 0, 255]);
assert_eq!(drawing.color, [0, 255, 0, 255]);
}
#[test]
fn test_shift_bar_indices() {
let mut drawing = Drawing::new(1, DrawingToolType::TrendLine);
drawing.chart_points.push(ChartPoint::new(100.0, 150.0));
drawing.chart_points.push(ChartPoint::new(110.0, 160.0));
drawing.shift_bar_indices(50.0);
assert_eq!(drawing.chart_points[0].bar_idx, 150.0);
assert_eq!(drawing.chart_points[1].bar_idx, 160.0);
}
#[test]
fn test_completion() {
let mut drawing = Drawing::new(1, DrawingToolType::TrendLine);
assert!(!drawing.completed);
drawing.add_point(egui::pos2(0.0, 0.0));
assert!(!drawing.completed);
drawing.add_point(egui::pos2(100.0, 100.0));
assert!(drawing.completed); }
#[test]
fn test_bounding_rect() {
let mut drawing = Drawing::new(1, DrawingToolType::TrendLine);
assert!(drawing.bounding_rect().is_none());
drawing.points.push(egui::pos2(10.0, 20.0));
drawing.points.push(egui::pos2(30.0, 50.0));
let rect = drawing.bounding_rect().unwrap();
assert_eq!(rect.min, egui::pos2(10.0, 20.0));
assert_eq!(rect.max, egui::pos2(30.0, 50.0));
}
}