use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HandlePos {
Start,
End,
Middle,
TopLeft,
TopRight,
BottomLeft,
BottomRight,
Point(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub enum LineStyle {
#[default]
Solid,
Dashed,
Dotted,
}
impl LineStyle {
#[inline]
pub fn dash_pattern(&self) -> Option<(f32, f32)> {
match self {
LineStyle::Solid => None,
LineStyle::Dashed => Some((8.0, 4.0)),
LineStyle::Dotted => Some((2.0, 3.0)),
}
}
pub fn all() -> &'static [LineStyle] {
&[LineStyle::Solid, LineStyle::Dashed, LineStyle::Dotted]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub enum ArrowStyle {
None,
#[default]
Arrow,
OpenArrow,
Circle,
Square,
Diamond,
}
impl ArrowStyle {
#[inline]
pub fn is_visible(&self) -> bool {
!matches!(self, ArrowStyle::None)
}
#[inline]
pub fn size_multiplier(&self) -> f32 {
match self {
ArrowStyle::None => 0.0,
ArrowStyle::Arrow | ArrowStyle::OpenArrow => 4.0,
ArrowStyle::Circle => 3.0,
ArrowStyle::Square => 3.0,
ArrowStyle::Diamond => 3.5,
}
}
pub fn all() -> &'static [ArrowStyle] {
&[
ArrowStyle::None,
ArrowStyle::Arrow,
ArrowStyle::OpenArrow,
ArrowStyle::Circle,
ArrowStyle::Square,
ArrowStyle::Diamond,
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub enum FontWeight {
#[default]
Normal,
Bold,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct TimeframeVisibility {
pub show_on_all: bool,
pub visible_timeframes: HashSet<String>,
}
impl TimeframeVisibility {
pub fn all() -> Self {
Self {
show_on_all: true,
visible_timeframes: HashSet::new(),
}
}
pub fn specific(timeframes: &[&str]) -> Self {
Self {
show_on_all: false,
visible_timeframes: timeframes.iter().map(|s| (*s).to_string()).collect(),
}
}
#[inline]
pub fn is_visible_on(&self, timeframe: &str) -> bool {
self.show_on_all || self.visible_timeframes.contains(timeframe)
}
pub fn add_timeframe(&mut self, timeframe: &str) {
self.show_on_all = false;
self.visible_timeframes.insert(timeframe.to_string());
}
pub fn remove_timeframe(&mut self, timeframe: &str) {
self.visible_timeframes.remove(timeframe);
if self.visible_timeframes.is_empty() {
self.show_on_all = true;
}
}
}
#[derive(Debug, Clone)]
pub struct DrawingOptions {
pub snap_to_price: bool,
pub snap_to_time: bool,
pub snap_distance: f32,
pub magnet_mode: bool,
pub magnet_distance: f32,
pub show_handles: bool,
pub handle_size: f32,
pub handle_color: [u8; 4],
pub handle_sel_color: [u8; 4],
pub default_color: [u8; 4],
}
impl Default for DrawingOptions {
fn default() -> Self {
Self {
snap_to_price: true,
snap_to_time: true,
snap_distance: 10.0,
magnet_mode: false,
magnet_distance: 15.0,
show_handles: true,
handle_size: 6.0,
handle_color: [255, 255, 255, 200],
handle_sel_color: [242, 54, 69, 255], default_color: [242, 54, 69, 255], }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FibonacciLevel {
pub value: f32,
pub label: String,
pub color: [u8; 4],
pub visible: bool,
pub show_price: bool,
}
impl FibonacciLevel {
pub fn new(value: f32, label: &str, color: [u8; 4]) -> Self {
Self {
value,
label: label.to_string(),
color,
visible: true,
show_price: true,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FibonacciConfig {
pub levels: Vec<FibonacciLevel>,
pub show_background: bool,
pub background_opacity: u8,
pub extend_left: bool,
pub extend_right: bool,
pub reverse: bool,
}
impl Default for FibonacciConfig {
fn default() -> Self {
Self {
levels: vec![
FibonacciLevel::new(0.0, "0%", [120, 123, 134, 255]),
FibonacciLevel::new(0.236, "23.6%", [242, 54, 69, 255]),
FibonacciLevel::new(0.382, "38.2%", [255, 152, 0, 255]),
FibonacciLevel::new(0.5, "50%", [76, 175, 80, 255]),
FibonacciLevel::new(0.618, "61.8%", [33, 150, 243, 255]),
FibonacciLevel::new(0.786, "78.6%", [156, 39, 176, 255]),
FibonacciLevel::new(1.0, "100%", [120, 123, 134, 255]),
],
show_background: false,
background_opacity: 30,
extend_left: false,
extend_right: false,
reverse: false,
}
}
}
impl FibonacciConfig {
pub fn extension_default() -> Self {
Self {
levels: vec![
FibonacciLevel::new(0.0, "0%", [120, 123, 134, 255]),
FibonacciLevel::new(0.618, "61.8%", [33, 150, 243, 255]),
FibonacciLevel::new(1.0, "100%", [120, 123, 134, 255]),
FibonacciLevel::new(1.272, "127.2%", [242, 54, 69, 255]),
FibonacciLevel::new(1.618, "161.8%", [255, 152, 0, 255]),
FibonacciLevel::new(2.0, "200%", [76, 175, 80, 255]),
FibonacciLevel::new(2.618, "261.8%", [156, 39, 176, 255]),
],
show_background: false,
background_opacity: 30,
extend_left: false,
extend_right: true,
reverse: false,
}
}
pub fn visible_levels(&self) -> impl Iterator<Item = &FibonacciLevel> {
self.levels.iter().filter(|l| l.visible)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_line_style_dash_pattern() {
assert!(LineStyle::Solid.dash_pattern().is_none());
assert!(LineStyle::Dashed.dash_pattern().is_some());
assert!(LineStyle::Dotted.dash_pattern().is_some());
}
#[test]
fn test_arrow_style_visibility() {
assert!(!ArrowStyle::None.is_visible());
assert!(ArrowStyle::Arrow.is_visible());
assert!(ArrowStyle::Circle.is_visible());
}
#[test]
fn test_timeframe_visibility() {
let mut vis = TimeframeVisibility::all();
assert!(vis.is_visible_on("1h"));
assert!(vis.is_visible_on("1D"));
vis.add_timeframe("1h");
assert!(vis.is_visible_on("1h"));
assert!(!vis.is_visible_on("1D"));
}
#[test]
fn test_fibonacci_config_default() {
let config = FibonacciConfig::default();
assert_eq!(config.levels.len(), 7);
assert!(!config.show_background);
}
}