use crate::types::{Color, Handle};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MLineStyleFlags {
pub fill_on: bool,
pub display_joints: bool,
pub start_square_cap: bool,
pub start_inner_arcs_cap: bool,
pub start_round_cap: bool,
pub end_square_cap: bool,
pub end_inner_arcs_cap: bool,
pub end_round_cap: bool,
}
impl MLineStyleFlags {
pub fn from_bits(bits: i32) -> Self {
Self {
fill_on: (bits & 1) != 0,
display_joints: (bits & 2) != 0,
start_square_cap: (bits & 16) != 0,
start_inner_arcs_cap: (bits & 32) != 0,
start_round_cap: (bits & 64) != 0,
end_square_cap: (bits & 256) != 0,
end_inner_arcs_cap: (bits & 512) != 0,
end_round_cap: (bits & 1024) != 0,
}
}
pub fn to_bits(&self) -> i32 {
let mut bits = 0;
if self.fill_on { bits |= 1; }
if self.display_joints { bits |= 2; }
if self.start_square_cap { bits |= 16; }
if self.start_inner_arcs_cap { bits |= 32; }
if self.start_round_cap { bits |= 64; }
if self.end_square_cap { bits |= 256; }
if self.end_inner_arcs_cap { bits |= 512; }
if self.end_round_cap { bits |= 1024; }
bits
}
pub fn simple() -> Self {
Self {
start_square_cap: true,
end_square_cap: true,
..Default::default()
}
}
pub fn round_caps() -> Self {
Self {
start_round_cap: true,
end_round_cap: true,
..Default::default()
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MLineStyleElement {
pub offset: f64,
pub color: Color,
pub linetype: String,
}
impl MLineStyleElement {
pub fn new(offset: f64) -> Self {
Self {
offset,
color: Color::ByLayer,
linetype: "BYLAYER".to_string(),
}
}
pub fn with_color(offset: f64, color: Color) -> Self {
Self {
offset,
color,
linetype: "BYLAYER".to_string(),
}
}
pub fn full(offset: f64, color: Color, linetype: impl Into<String>) -> Self {
Self {
offset,
color,
linetype: linetype.into(),
}
}
pub fn set_color(&mut self, color: Color) {
self.color = color;
}
pub fn set_linetype(&mut self, linetype: impl Into<String>) {
self.linetype = linetype.into();
}
pub fn duplicate(&self) -> Self {
self.clone()
}
}
impl Default for MLineStyleElement {
fn default() -> Self {
Self::new(0.0)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MLineStyle {
pub handle: Handle,
pub owner: Handle,
pub name: String,
pub description: String,
pub elements: Vec<MLineStyleElement>,
pub start_angle: f64,
pub end_angle: f64,
pub fill_color: Color,
pub flags: MLineStyleFlags,
}
impl MLineStyle {
pub const OBJECT_TYPE: &'static str = "MLINESTYLE";
pub const DEFAULT_NAME: &'static str = "Standard";
pub fn new(name: impl Into<String>) -> Self {
Self {
handle: Handle::NULL,
owner: Handle::NULL,
name: name.into(),
description: String::new(),
elements: Vec::new(),
start_angle: std::f64::consts::FRAC_PI_2, end_angle: std::f64::consts::FRAC_PI_2, fill_color: Color::ByLayer,
flags: MLineStyleFlags::default(),
}
}
pub fn standard() -> Self {
let mut style = Self::new(Self::DEFAULT_NAME);
style.add_element(MLineStyleElement::new(0.5));
style.add_element(MLineStyleElement::new(-0.5));
style
}
pub fn add_element(&mut self, element: MLineStyleElement) {
self.elements.push(element);
}
pub fn add_element_at_offset(&mut self, offset: f64) {
self.elements.push(MLineStyleElement::new(offset));
}
pub fn insert_element(&mut self, index: usize, element: MLineStyleElement) {
if index <= self.elements.len() {
self.elements.insert(index, element);
}
}
pub fn remove_element(&mut self, index: usize) -> Option<MLineStyleElement> {
if index < self.elements.len() {
Some(self.elements.remove(index))
} else {
None
}
}
pub fn element_count(&self) -> usize {
self.elements.len()
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
pub fn get_element(&self, index: usize) -> Option<&MLineStyleElement> {
self.elements.get(index)
}
pub fn get_element_mut(&mut self, index: usize) -> Option<&mut MLineStyleElement> {
self.elements.get_mut(index)
}
pub fn width(&self) -> f64 {
if self.elements.is_empty() {
return 0.0;
}
let mut min_offset = f64::MAX;
let mut max_offset = f64::MIN;
for element in &self.elements {
if element.offset < min_offset {
min_offset = element.offset;
}
if element.offset > max_offset {
max_offset = element.offset;
}
}
max_offset - min_offset
}
pub fn sort_elements(&mut self) {
self.elements.sort_by(|a, b| a.offset.partial_cmp(&b.offset).unwrap());
}
pub fn set_start_angle_degrees(&mut self, degrees: f64) {
self.start_angle = degrees.to_radians();
}
pub fn set_end_angle_degrees(&mut self, degrees: f64) {
self.end_angle = degrees.to_radians();
}
pub fn start_angle_degrees(&self) -> f64 {
self.start_angle.to_degrees()
}
pub fn end_angle_degrees(&self) -> f64 {
self.end_angle.to_degrees()
}
pub fn enable_fill(&mut self, color: Color) {
self.flags.fill_on = true;
self.fill_color = color;
}
pub fn disable_fill(&mut self) {
self.flags.fill_on = false;
}
pub fn set_round_caps(&mut self) {
self.flags.start_round_cap = true;
self.flags.end_round_cap = true;
self.flags.start_square_cap = false;
self.flags.end_square_cap = false;
}
pub fn set_square_caps(&mut self) {
self.flags.start_square_cap = true;
self.flags.end_square_cap = true;
self.flags.start_round_cap = false;
self.flags.end_round_cap = false;
}
pub fn iter(&self) -> impl Iterator<Item = &MLineStyleElement> {
self.elements.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut MLineStyleElement> {
self.elements.iter_mut()
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
pub fn with_element(mut self, element: MLineStyleElement) -> Self {
self.add_element(element);
self
}
pub fn with_fill(mut self, color: Color) -> Self {
self.enable_fill(color);
self
}
pub fn with_flags(mut self, flags: MLineStyleFlags) -> Self {
self.flags = flags;
self
}
}
impl Default for MLineStyle {
fn default() -> Self {
Self::standard()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::FRAC_PI_2;
#[test]
fn test_mlinestyle_creation() {
let style = MLineStyle::new("Custom");
assert_eq!(style.name, "Custom");
assert!(style.is_empty());
assert!((style.start_angle - FRAC_PI_2).abs() < 1e-10);
}
#[test]
fn test_mlinestyle_standard() {
let style = MLineStyle::standard();
assert_eq!(style.name, "Standard");
assert_eq!(style.element_count(), 2);
let offsets: Vec<f64> = style.elements.iter().map(|e| e.offset).collect();
assert!(offsets.contains(&0.5));
assert!(offsets.contains(&-0.5));
}
#[test]
fn test_mlinestyle_add_element() {
let mut style = MLineStyle::new("Test");
style.add_element(MLineStyleElement::new(0.0));
style.add_element(MLineStyleElement::new(1.0));
assert_eq!(style.element_count(), 2);
}
#[test]
fn test_mlinestyle_remove_element() {
let mut style = MLineStyle::standard();
assert_eq!(style.element_count(), 2);
let removed = style.remove_element(0);
assert!(removed.is_some());
assert_eq!(style.element_count(), 1);
}
#[test]
fn test_mlinestyle_width() {
let mut style = MLineStyle::new("Test");
style.add_element(MLineStyleElement::new(-1.0));
style.add_element(MLineStyleElement::new(0.0));
style.add_element(MLineStyleElement::new(1.5));
assert!((style.width() - 2.5).abs() < 1e-10);
}
#[test]
fn test_mlinestyle_sort() {
let mut style = MLineStyle::new("Test");
style.add_element(MLineStyleElement::new(1.0));
style.add_element(MLineStyleElement::new(-1.0));
style.add_element(MLineStyleElement::new(0.0));
style.sort_elements();
assert_eq!(style.elements[0].offset, -1.0);
assert_eq!(style.elements[1].offset, 0.0);
assert_eq!(style.elements[2].offset, 1.0);
}
#[test]
fn test_mlinestyle_element() {
let mut element = MLineStyleElement::new(0.5);
assert_eq!(element.offset, 0.5);
assert_eq!(element.color, Color::ByLayer);
element.set_color(Color::from_index(1));
assert_eq!(element.color, Color::from_index(1));
element.set_linetype("DASHED");
assert_eq!(element.linetype, "DASHED");
}
#[test]
fn test_mlinestyle_element_full() {
let element = MLineStyleElement::full(0.25, Color::from_index(2), "CENTER");
assert_eq!(element.offset, 0.25);
assert_eq!(element.color, Color::from_index(2));
assert_eq!(element.linetype, "CENTER");
}
#[test]
fn test_mlinestyle_flags() {
let flags = MLineStyleFlags::from_bits(1 | 16 | 256);
assert!(flags.fill_on);
assert!(flags.start_square_cap);
assert!(flags.end_square_cap);
assert!(!flags.display_joints);
assert_eq!(flags.to_bits(), 1 | 16 | 256);
}
#[test]
fn test_mlinestyle_caps() {
let mut style = MLineStyle::new("Test");
style.set_round_caps();
assert!(style.flags.start_round_cap);
assert!(style.flags.end_round_cap);
assert!(!style.flags.start_square_cap);
style.set_square_caps();
assert!(style.flags.start_square_cap);
assert!(style.flags.end_square_cap);
assert!(!style.flags.start_round_cap);
}
#[test]
fn test_mlinestyle_fill() {
let mut style = MLineStyle::new("Test");
style.enable_fill(Color::from_index(5));
assert!(style.flags.fill_on);
assert_eq!(style.fill_color, Color::from_index(5));
style.disable_fill();
assert!(!style.flags.fill_on);
}
#[test]
fn test_mlinestyle_builder() {
let style = MLineStyle::new("Test")
.with_description("A test style")
.with_element(MLineStyleElement::new(-0.5))
.with_element(MLineStyleElement::new(0.5))
.with_fill(Color::from_index(3));
assert_eq!(style.description, "A test style");
assert_eq!(style.element_count(), 2);
assert!(style.flags.fill_on);
}
#[test]
fn test_mlinestyle_angles() {
let mut style = MLineStyle::new("Test");
style.set_start_angle_degrees(45.0);
style.set_end_angle_degrees(60.0);
assert!((style.start_angle_degrees() - 45.0).abs() < 1e-10);
assert!((style.end_angle_degrees() - 60.0).abs() < 1e-10);
}
}