forked from 1b-agent/opencadstudio-upstream
743 lines
31 KiB
Rust
743 lines
31 KiB
Rust
//! Multileader style manager.
|
|
|
|
use crate::app::Message;
|
|
use crate::t;
|
|
use iced::widget::{canvas, checkbox, column, container, row, text, text_input};
|
|
use iced::{mouse, Color, Element, Length, Point, Rectangle, Size, Theme};
|
|
use std::borrow::Cow;
|
|
use std::fmt;
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
struct EnumChoice {
|
|
code: String,
|
|
label: Cow<'static, str>,
|
|
}
|
|
|
|
impl fmt::Display for EnumChoice {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
formatter.write_str(self.label.as_ref())
|
|
}
|
|
}
|
|
|
|
fn choices(values: &[(&str, &str)]) -> Vec<EnumChoice> {
|
|
values
|
|
.iter()
|
|
.map(|(code, label)| EnumChoice {
|
|
code: (*code).to_string(),
|
|
label: crate::i18n::translate(label),
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub struct MLeaderStyleView<'a> {
|
|
pub styles: Vec<String>,
|
|
pub selected: &'a str,
|
|
pub style: Option<&'a acadrust::objects::MultiLeaderStyle>,
|
|
pub current: String,
|
|
pub tab: u8,
|
|
pub compare_name: String,
|
|
pub compare_opts: Vec<String>,
|
|
pub comparison_sections: Vec<String>,
|
|
pub in_use: bool,
|
|
pub landing_distance: &'a str,
|
|
pub landing_gap: &'a str,
|
|
pub arrowhead_size: &'a str,
|
|
pub text_height: &'a str,
|
|
pub scale_factor: &'a str,
|
|
pub break_gap: &'a str,
|
|
pub first_seg_angle: &'a str,
|
|
pub second_seg_angle: &'a str,
|
|
pub max_points: &'a str,
|
|
pub default_text: &'a str,
|
|
pub line_color: &'a str,
|
|
pub text_color: &'a str,
|
|
pub description: &'a str,
|
|
pub align_space: &'a str,
|
|
pub block_color: &'a str,
|
|
pub block_rotation: &'a str,
|
|
pub block_scale_x: &'a str,
|
|
pub block_scale_y: &'a str,
|
|
pub block_scale_z: &'a str,
|
|
pub block_opts: Vec<String>,
|
|
pub arrow_opts: Vec<String>,
|
|
pub lt_opts: Vec<String>,
|
|
pub textstyle_opts: Vec<String>,
|
|
pub line_type_name: String,
|
|
pub arrowhead_name: String,
|
|
pub text_style_name: String,
|
|
pub block_content_name: String,
|
|
pub rename_active: Option<&'a str>,
|
|
pub rename_buf: &'a str,
|
|
pub color_open: Option<&'static str>,
|
|
}
|
|
|
|
fn muted_style(theme: &Theme) -> iced::widget::text::Style {
|
|
iced::widget::text::Style {
|
|
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
|
|
}
|
|
}
|
|
|
|
fn input_row<'a>(
|
|
label: Cow<'static, str>,
|
|
placeholder: &'static str,
|
|
value: &'a str,
|
|
field: &'static str,
|
|
) -> Element<'a, Message> {
|
|
row![
|
|
text(label).size(11).style(muted_style).width(165),
|
|
text_input(placeholder, value)
|
|
.on_input(move |value| Message::MLeaderStyleEdit { field, value })
|
|
.size(11)
|
|
.width(190),
|
|
]
|
|
.spacing(8)
|
|
.align_y(iced::Center)
|
|
.into()
|
|
}
|
|
|
|
fn readonly_row<'a>(label: Cow<'static, str>, value: Cow<'static, str>) -> Element<'a, Message> {
|
|
row![
|
|
text(label).size(11).style(muted_style).width(165),
|
|
crate::ui::read_only::field(value.as_ref(), 11.0, Length::Fixed(190.0)),
|
|
]
|
|
.spacing(8)
|
|
.align_y(iced::Center)
|
|
.into()
|
|
}
|
|
|
|
fn color_row<'a>(
|
|
label: Cow<'static, str>,
|
|
value: &'a str,
|
|
field: &'static str,
|
|
open: bool,
|
|
) -> Element<'a, Message> {
|
|
let current = crate::ui::color_select::aci_string_to_color(value);
|
|
let selector = crate::ui::color_select::color_selector(
|
|
current,
|
|
open,
|
|
crate::ui::color_select::ColorExtras { by_layer: true, by_block: true },
|
|
move |color| Message::MLeaderStyleEdit {
|
|
field,
|
|
value: crate::ui::color_select::color_to_aci_string(color),
|
|
},
|
|
Message::MLeaderColorMore(field),
|
|
Message::OpenColorWindow(crate::app::ColorPickTarget::MLeader(field), current),
|
|
);
|
|
row![text(label).size(11).style(muted_style).width(165), selector]
|
|
.spacing(8)
|
|
.align_y(iced::Center)
|
|
.into()
|
|
}
|
|
|
|
fn enum_row<'a>(
|
|
label: Cow<'static, str>,
|
|
values: &[(&str, &str)],
|
|
selected: String,
|
|
field: &'static str,
|
|
) -> Element<'a, Message> {
|
|
let selected_label = values
|
|
.iter()
|
|
.find(|(code, _)| *code == selected)
|
|
.map(|(_, label)| crate::i18n::translate(label))
|
|
.unwrap_or_else(|| crate::i18n::translate(&selected));
|
|
row![
|
|
text(label).size(11).style(muted_style).width(165),
|
|
iced::widget::pick_list(
|
|
Some(EnumChoice { code: selected, label: selected_label }),
|
|
choices(values),
|
|
|value| value.to_string(),
|
|
)
|
|
.on_select(move |choice| Message::MLeaderStyleSetEnum { field, value: choice.code })
|
|
.text_size(11)
|
|
.width(220),
|
|
]
|
|
.spacing(8)
|
|
.align_y(iced::Center)
|
|
.into()
|
|
}
|
|
|
|
fn handle_row<'a>(
|
|
label: Cow<'static, str>,
|
|
options: Vec<String>,
|
|
selected: String,
|
|
field: &'static str,
|
|
) -> Element<'a, Message> {
|
|
let display_label = |value: &str| match value {
|
|
"None" | "ByBlock" | "ByLayer" | "Default" | "Closed filled" => {
|
|
crate::i18n::translate(value)
|
|
}
|
|
_ => Cow::Owned(value.to_string()),
|
|
};
|
|
let selected = EnumChoice {
|
|
code: selected.clone(),
|
|
label: display_label(&selected),
|
|
};
|
|
let options: Vec<EnumChoice> = options
|
|
.into_iter()
|
|
.map(|code| EnumChoice {
|
|
label: display_label(&code),
|
|
code,
|
|
})
|
|
.collect();
|
|
row![
|
|
text(label).size(11).style(muted_style).width(165),
|
|
iced::widget::pick_list(Some(selected), options, |value| value.to_string())
|
|
.on_select(move |value| Message::MLeaderStyleSetHandle { field, value: value.code })
|
|
.text_size(11)
|
|
.width(220),
|
|
]
|
|
.spacing(8)
|
|
.align_y(iced::Center)
|
|
.into()
|
|
}
|
|
|
|
fn toggle<'a>(label: Cow<'static, str>, value: bool, field: &'static str) -> Element<'a, Message> {
|
|
checkbox(value)
|
|
.label(label)
|
|
.on_toggle(move |_| Message::MLeaderStyleToggle(field))
|
|
.size(14)
|
|
.text_size(11)
|
|
.into()
|
|
}
|
|
|
|
const ATTACHMENTS: [(&str, &str); 11] = [
|
|
("TopOfTopLine", "Top of top line"),
|
|
("MiddleOfTopLine", "Middle of top line"),
|
|
("MiddleOfText", "Middle of text"),
|
|
("MiddleOfBottomLine", "Middle of bottom line"),
|
|
("BottomOfBottomLine", "Bottom of bottom line"),
|
|
("BottomLine", "Bottom line"),
|
|
("BottomOfTopLineUnderlineBottomLine", "Underline bottom line"),
|
|
("BottomOfTopLineUnderlineTopLine", "Underline top line"),
|
|
("BottomOfTopLineUnderlineAll", "Underline all text"),
|
|
("CenterOfText", "Center of text"),
|
|
("CenterOfTextOverline", "Center of text with overline"),
|
|
];
|
|
|
|
const LEADER_DRAW_ORDERS: [(&str, &str); 2] = [
|
|
("LeaderHeadFirst", "Arrowhead first"),
|
|
("LeaderTailFirst", "Content first"),
|
|
];
|
|
const MULTILEADER_DRAW_ORDERS: [(&str, &str); 2] = [
|
|
("ContentFirst", "Content first"),
|
|
("LeaderFirst", "Leader first"),
|
|
];
|
|
const BLOCK_CONNECTIONS: [(&str, &str); 2] = [
|
|
("BlockExtents", "Block extents"),
|
|
("BasePoint", "Base point"),
|
|
];
|
|
|
|
fn choice_label(values: &[(&str, &str)], selected: String) -> String {
|
|
values
|
|
.iter()
|
|
.find(|(code, _)| *code == selected)
|
|
.map(|(_, label)| crate::i18n::translate(label).into_owned())
|
|
.unwrap_or(selected)
|
|
}
|
|
|
|
fn handle_label(value: &str) -> String {
|
|
match value {
|
|
"None" | "ByBlock" | "ByLayer" | "Default" | "Closed filled" => {
|
|
crate::i18n::translate(value).into_owned()
|
|
}
|
|
_ => value.to_string(),
|
|
}
|
|
}
|
|
|
|
fn aci_color(value: &str, fallback: Color) -> Color {
|
|
crate::ui::color_select::aci_string_to_color(value)
|
|
.rgb()
|
|
.map(|(r, g, b)| Color::from_rgb8(r, g, b))
|
|
.unwrap_or(fallback)
|
|
}
|
|
|
|
struct LeaderPreviewCanvas {
|
|
path_type: String,
|
|
content_type: String,
|
|
line_color: String,
|
|
line_weight: i16,
|
|
line_type: String,
|
|
arrow_name: String,
|
|
arrow_name_code: String,
|
|
arrow_size: String,
|
|
break_gap: String,
|
|
landing: bool,
|
|
dogleg: bool,
|
|
landing_distance: String,
|
|
landing_gap: String,
|
|
max_points: String,
|
|
first_angle: String,
|
|
second_angle: String,
|
|
scale: String,
|
|
annotative: bool,
|
|
align_space: String,
|
|
default_text: String,
|
|
text_style: String,
|
|
text_height: String,
|
|
text_color: String,
|
|
text_angle: String,
|
|
text_alignment: String,
|
|
attachment_direction: String,
|
|
left_attachment: String,
|
|
left_attachment_code: String,
|
|
right_attachment: String,
|
|
top_attachment: String,
|
|
bottom_attachment: String,
|
|
text_frame: bool,
|
|
text_always_left: bool,
|
|
block_name: String,
|
|
block_color: String,
|
|
block_connection: String,
|
|
block_connection_code: String,
|
|
block_rotation: String,
|
|
block_scale: [String; 3],
|
|
enable_block_scale: bool,
|
|
enable_block_rotation: bool,
|
|
leader_draw_order: String,
|
|
multileader_draw_order: String,
|
|
description: String,
|
|
}
|
|
|
|
impl canvas::Program<Message> for LeaderPreviewCanvas {
|
|
type State = ();
|
|
|
|
fn draw(
|
|
&self,
|
|
_state: &(),
|
|
renderer: &iced::Renderer,
|
|
theme: &Theme,
|
|
bounds: Rectangle,
|
|
_cursor: mouse::Cursor,
|
|
) -> Vec<canvas::Geometry> {
|
|
let mut frame = canvas::Frame::new(renderer, bounds.size());
|
|
let ink = theme.palette().background.base.text;
|
|
let line_color = aci_color(&self.line_color, ink);
|
|
let scale = if self.annotative {
|
|
1.0
|
|
} else {
|
|
self.scale.trim().parse::<f32>().unwrap_or(1.0).abs().clamp(0.35, 2.2)
|
|
};
|
|
let head = Point::new(34.0, bounds.height - 37.0);
|
|
let angle1 = self.first_angle.trim().parse::<f32>().unwrap_or(28.0).to_radians();
|
|
let angle2 = self.second_angle.trim().parse::<f32>().unwrap_or(0.0).to_radians();
|
|
let points = self.max_points.trim().parse::<usize>().unwrap_or(2).clamp(2, 5);
|
|
let elbow = Point::new(
|
|
head.x + 105.0 * scale * angle1.cos().abs().max(0.25),
|
|
head.y - 70.0 * scale * angle1.sin().abs().max(0.2),
|
|
);
|
|
let landing_length = self
|
|
.landing_distance
|
|
.trim()
|
|
.parse::<f32>()
|
|
.unwrap_or(8.0)
|
|
.abs()
|
|
.mul_add(6.0, 18.0)
|
|
.clamp(24.0, 145.0)
|
|
* scale;
|
|
let tail = Point::new(elbow.x + landing_length * angle2.cos().abs(), elbow.y);
|
|
let width = (self.line_weight.abs() as f32 / 35.0).clamp(0.8, 4.0);
|
|
let dashed = !self.line_type.eq_ignore_ascii_case("None")
|
|
&& !self.line_type.eq_ignore_ascii_case("ByBlock")
|
|
&& !self.line_type.to_ascii_lowercase().contains("continuous");
|
|
let stroke = canvas::Stroke {
|
|
style: canvas::Style::Solid(line_color),
|
|
width,
|
|
line_dash: if dashed {
|
|
canvas::LineDash { segments: &[7.0, 4.0], offset: 0 }
|
|
} else {
|
|
canvas::LineDash::default()
|
|
},
|
|
..Default::default()
|
|
};
|
|
if self.path_type != "Invisible" {
|
|
if self.path_type == "Spline" {
|
|
let path = canvas::Path::new(|builder| {
|
|
builder.move_to(head);
|
|
for step in 1..=18 {
|
|
let t = step as f32 / 18.0;
|
|
let x = head.x + (elbow.x - head.x) * t;
|
|
let y = head.y + (elbow.y - head.y) * t - (t * std::f32::consts::PI).sin() * 15.0;
|
|
builder.line_to(Point::new(x, y));
|
|
}
|
|
});
|
|
frame.stroke(&path, stroke.clone());
|
|
} else {
|
|
let path = canvas::Path::new(|builder| {
|
|
builder.move_to(head);
|
|
for index in 1..points {
|
|
let t = index as f32 / (points - 1) as f32;
|
|
let bend = if index + 1 == points { 0.0 } else { (index as f32 * 7.0) % 14.0 - 7.0 };
|
|
builder.line_to(Point::new(
|
|
head.x + (elbow.x - head.x) * t,
|
|
head.y + (elbow.y - head.y) * t + bend,
|
|
));
|
|
}
|
|
});
|
|
frame.stroke(&path, stroke.clone());
|
|
}
|
|
if self.landing && self.dogleg {
|
|
let gap = self.break_gap.trim().parse::<f32>().unwrap_or(0.0).abs().clamp(0.0, 8.0);
|
|
frame.stroke(&canvas::Path::line(elbow, Point::new((elbow.x + tail.x) * 0.5 - gap, elbow.y)), stroke.clone());
|
|
frame.stroke(&canvas::Path::line(Point::new((elbow.x + tail.x) * 0.5 + gap, elbow.y), tail), stroke.clone());
|
|
}
|
|
}
|
|
let arrow = self.arrow_size.trim().parse::<f32>().unwrap_or(0.18).abs().mul_add(12.0, 4.0).clamp(5.0, 18.0) * scale;
|
|
let arrow_code = self.arrow_name_code.to_ascii_uppercase();
|
|
if arrow_code.contains("DOT") {
|
|
frame.fill(&canvas::Path::circle(head, arrow * 0.42), line_color);
|
|
} else if arrow_code.contains("TICK") || arrow_code.contains("OBLIQUE") {
|
|
frame.stroke(
|
|
&canvas::Path::line(
|
|
Point::new(head.x - arrow * 0.35, head.y + arrow * 0.65),
|
|
Point::new(head.x + arrow * 0.35, head.y - arrow * 0.65),
|
|
),
|
|
canvas::Stroke::default().with_color(line_color).with_width(width),
|
|
);
|
|
} else if arrow_code.contains("BOX") {
|
|
frame.fill(
|
|
&canvas::Path::rectangle(
|
|
Point::new(head.x - arrow * 0.35, head.y - arrow * 0.35),
|
|
Size::new(arrow * 0.7, arrow * 0.7),
|
|
),
|
|
line_color,
|
|
);
|
|
} else {
|
|
let arrow_path = canvas::Path::new(|builder| {
|
|
builder.move_to(head);
|
|
builder.line_to(Point::new(head.x + arrow, head.y - arrow * 0.45));
|
|
builder.line_to(Point::new(head.x + arrow, head.y + arrow * 0.45));
|
|
builder.close();
|
|
});
|
|
if arrow_code.contains("OPEN") {
|
|
let open_path = canvas::Path::new(|builder| {
|
|
builder.move_to(Point::new(head.x + arrow, head.y - arrow * 0.45));
|
|
builder.line_to(head);
|
|
builder.line_to(Point::new(head.x + arrow, head.y + arrow * 0.45));
|
|
});
|
|
frame.stroke(
|
|
&open_path,
|
|
canvas::Stroke::default().with_color(line_color).with_width(width),
|
|
);
|
|
} else {
|
|
frame.fill(&arrow_path, line_color);
|
|
}
|
|
}
|
|
let landing_gap = self.landing_gap.trim().parse::<f32>().unwrap_or(0.09).abs().mul_add(8.0, 5.0).clamp(5.0, 24.0);
|
|
let content_origin = Point::new(tail.x + landing_gap, tail.y);
|
|
if self.content_type == "Block" {
|
|
let scales = self.block_scale.iter().map(|value| value.trim().parse::<f32>().unwrap_or(1.0).abs()).collect::<Vec<_>>();
|
|
let (scale_x, scale_y) = if self.enable_block_scale {
|
|
(scales[0].clamp(0.3, 2.5), scales[1].clamp(0.3, 2.5))
|
|
} else {
|
|
(1.0, 1.0)
|
|
};
|
|
let size = Size::new(42.0 * scale_x, 30.0 * scale_y);
|
|
let rotation = if self.enable_block_rotation {
|
|
self.block_rotation.trim().parse::<f32>().unwrap_or(0.0).to_radians()
|
|
} else {
|
|
0.0
|
|
};
|
|
let center_x = if self.block_connection_code == "BasePoint" {
|
|
content_origin.x
|
|
} else {
|
|
content_origin.x + size.width * 0.5
|
|
};
|
|
frame.with_save(|frame| {
|
|
frame.translate(iced::Vector::new(
|
|
center_x,
|
|
content_origin.y,
|
|
));
|
|
frame.rotate(iced::Radians(rotation));
|
|
let top_left = Point::new(-size.width * 0.5, -size.height * 0.5);
|
|
let rect = canvas::Path::rectangle(top_left, size);
|
|
let color = aci_color(&self.block_color, ink);
|
|
frame.stroke(
|
|
&rect,
|
|
canvas::Stroke::default().with_color(color).with_width(1.5),
|
|
);
|
|
frame.stroke(
|
|
&canvas::Path::line(
|
|
top_left,
|
|
Point::new(size.width * 0.5, size.height * 0.5),
|
|
),
|
|
canvas::Stroke::default().with_color(color),
|
|
);
|
|
});
|
|
} else if self.content_type != "None" {
|
|
let content = if self.content_type == "Tolerance" {
|
|
"⌀0.01 | A".to_string()
|
|
} else if self.default_text.trim().is_empty() {
|
|
t!("Leader note").into_owned()
|
|
} else {
|
|
self.default_text.clone()
|
|
};
|
|
let text_size = self.text_height.trim().parse::<f32>().unwrap_or(0.18).abs().mul_add(22.0, 8.0).clamp(9.0, 19.0) * scale;
|
|
let box_width = (content.chars().count() as f32 * text_size * 0.55 + 14.0).clamp(60.0, bounds.width - content_origin.x - 8.0);
|
|
let box_height = text_size + 12.0 + self.align_space.trim().parse::<f32>().unwrap_or(0.0).abs().min(8.0);
|
|
let attachment_offset = if self.attachment_direction == "Horizontal" {
|
|
match self.left_attachment_code.as_str() {
|
|
"TopOfTopLine" | "BottomOfTopLineUnderlineTopLine" => box_height * 0.5,
|
|
"MiddleOfTopLine" => box_height * 0.25,
|
|
"MiddleOfBottomLine" => -box_height * 0.25,
|
|
"BottomOfBottomLine" | "BottomLine"
|
|
| "BottomOfTopLineUnderlineBottomLine" => -box_height * 0.5,
|
|
_ => 0.0,
|
|
}
|
|
} else {
|
|
0.0
|
|
};
|
|
let content_y = content_origin.y + attachment_offset;
|
|
if self.text_frame {
|
|
frame.stroke(
|
|
&canvas::Path::rectangle(Point::new(content_origin.x, content_y - box_height * 0.5), Size::new(box_width, box_height)),
|
|
canvas::Stroke::default().with_color(aci_color(&self.text_color, ink)).with_width(1.0),
|
|
);
|
|
}
|
|
let align = if self.text_always_left || self.text_alignment == "Left" {
|
|
iced::advanced::text::Alignment::Left
|
|
} else if self.text_alignment == "Right" {
|
|
iced::advanced::text::Alignment::Right
|
|
} else {
|
|
iced::advanced::text::Alignment::Center
|
|
};
|
|
let x = match align {
|
|
iced::advanced::text::Alignment::Left => content_origin.x + 5.0,
|
|
iced::advanced::text::Alignment::Right => content_origin.x + box_width - 5.0,
|
|
_ => content_origin.x + box_width * 0.5,
|
|
};
|
|
let rotation = if self.attachment_direction == "Vertical" {
|
|
std::f32::consts::FRAC_PI_2
|
|
} else if self.text_angle == "ParallelToLastLeaderLine" {
|
|
-angle2
|
|
} else {
|
|
0.0
|
|
};
|
|
frame.with_save(|frame| {
|
|
frame.translate(iced::Vector::new(x, content_y));
|
|
frame.rotate(iced::Radians(rotation));
|
|
frame.fill_text(canvas::Text {
|
|
content,
|
|
position: Point::ORIGIN,
|
|
color: aci_color(&self.text_color, ink),
|
|
size: iced::Pixels(text_size),
|
|
align_x: align,
|
|
align_y: iced::alignment::Vertical::Center,
|
|
..Default::default()
|
|
});
|
|
});
|
|
}
|
|
frame.fill_text(canvas::Text {
|
|
content: format!(
|
|
"{} · {} · {} · {} · {} / {} · {}",
|
|
handle_label(&self.line_type),
|
|
self.arrow_name,
|
|
self.text_style,
|
|
self.block_name,
|
|
self.leader_draw_order,
|
|
self.multileader_draw_order,
|
|
self.description,
|
|
),
|
|
position: Point::new(10.0, bounds.height - 9.0),
|
|
color: ink.scale_alpha(0.62),
|
|
size: iced::Pixels(9.0),
|
|
align_y: iced::alignment::Vertical::Center,
|
|
..Default::default()
|
|
});
|
|
frame.fill_text(canvas::Text {
|
|
content: format!(
|
|
"{} / {} / {} / {} · {} · {}{} · z {} · {}",
|
|
self.left_attachment,
|
|
self.right_attachment,
|
|
self.top_attachment,
|
|
self.bottom_attachment,
|
|
self.block_connection,
|
|
if self.enable_block_rotation { &self.block_rotation } else { "0" },
|
|
"°",
|
|
self.block_scale[2],
|
|
if self.annotative { t!("Annotative") } else { t!("Drawing scale") },
|
|
),
|
|
position: Point::new(10.0, 9.0),
|
|
color: ink.scale_alpha(0.55),
|
|
size: iced::Pixels(9.0),
|
|
align_y: iced::alignment::Vertical::Center,
|
|
..Default::default()
|
|
});
|
|
vec![frame.into_geometry()]
|
|
}
|
|
}
|
|
|
|
pub fn view_window<'a>(
|
|
v: MLeaderStyleView<'a>,
|
|
sizing: crate::ui::modal::ModalSizing,
|
|
) -> Element<'a, Message> {
|
|
let style = v.style;
|
|
let content: Element<'a, Message> = match (v.tab, style) {
|
|
(_, None) => text(t!("Select a style to view details.")).size(11).into(),
|
|
(0, Some(style)) => column![
|
|
input_row(t!("Description"), "", v.description, "description"),
|
|
enum_row(t!("Path type"), &[("Invisible", "None"), ("StraightLineSegments", "Straight"), ("Spline", "Spline")], format!("{:?}", style.path_type), "path_type"),
|
|
color_row(t!("Line color"), v.line_color, "line_color", v.color_open == Some("line_color")),
|
|
row![
|
|
text(t!("Line weight")).size(11).style(muted_style).width(165),
|
|
iced::widget::pick_list(
|
|
Some(crate::ui::properties::LwItem(style.line_weight)),
|
|
crate::ui::properties::lw_options(),
|
|
|value| value.to_string(),
|
|
)
|
|
.on_select(|item| Message::MLeaderStyleLineWeightChanged(item.0))
|
|
.text_size(11).width(220),
|
|
].spacing(8).align_y(iced::Center),
|
|
handle_row(t!("Line type"), v.lt_opts.clone(), v.line_type_name.clone(), "line_type_handle"),
|
|
handle_row(t!("Arrowhead"), v.arrow_opts.clone(), v.arrowhead_name.clone(), "arrowhead_handle"),
|
|
input_row(t!("Arrowhead size"), "0.18", v.arrowhead_size, "arrowhead_size"),
|
|
input_row(t!("Break gap size"), "0.125", v.break_gap, "break_gap"),
|
|
].spacing(8).into(),
|
|
(1, Some(style)) => column![
|
|
toggle(t!("Enable landing"), style.enable_landing, "enable_landing"),
|
|
toggle(t!("Enable dogleg"), style.enable_dogleg, "enable_dogleg"),
|
|
input_row(t!("Landing distance"), "8.0", v.landing_distance, "landing_distance"),
|
|
input_row(t!("Landing gap"), "0.09", v.landing_gap, "landing_gap"),
|
|
input_row(t!("Maximum leader points"), "2", v.max_points, "max_points"),
|
|
input_row(t!("First segment angle"), "0", v.first_seg_angle, "first_seg_angle"),
|
|
input_row(t!("Second segment angle"), "0", v.second_seg_angle, "second_seg_angle"),
|
|
if style.is_annotative { readonly_row(t!("Scale factor"), t!("By annotation scale")) } else { input_row(t!("Scale factor"), "1.0", v.scale_factor, "scale_factor") },
|
|
input_row(t!("Alignment spacing"), "4.0", v.align_space, "align_space"),
|
|
enum_row(t!("Leader draw order"), &LEADER_DRAW_ORDERS, format!("{:?}", style.leader_draw_order), "leader_draw_order"),
|
|
enum_row(t!("Multileader draw order"), &MULTILEADER_DRAW_ORDERS, format!("{:?}", style.multileader_draw_order), "multileader_draw_order"),
|
|
toggle(t!("Annotative"), style.is_annotative, "annotative"),
|
|
].spacing(8).into(),
|
|
(2, Some(style)) => column![
|
|
enum_row(t!("Content type"), &[("None", "None"), ("Block", "Block"), ("MText", "Text"), ("Tolerance", "Tolerance")], format!("{:?}", style.content_type), "content_type"),
|
|
input_row(t!("Default text"), "", v.default_text, "default_text"),
|
|
handle_row(t!("Text style"), v.textstyle_opts.clone(), v.text_style_name.clone(), "text_style_handle"),
|
|
input_row(t!("Text height"), "0.18", v.text_height, "text_height"),
|
|
color_row(t!("Text color"), v.text_color, "text_color", v.color_open == Some("text_color")),
|
|
enum_row(t!("Text angle"), &[("ParallelToLastLeaderLine", "Parallel to last segment"), ("Horizontal", "Horizontal"), ("Optimized", "Optimized")], format!("{:?}", style.text_angle_type), "text_angle_type"),
|
|
enum_row(t!("Text alignment"), &[("Left", "Left"), ("Center", "Center"), ("Right", "Right")], format!("{:?}", style.text_alignment), "text_alignment"),
|
|
enum_row(t!("Attachment direction"), &[("Horizontal", "Horizontal"), ("Vertical", "Vertical")], format!("{:?}", style.text_attachment_direction), "text_attachment_direction"),
|
|
enum_row(t!("Left attachment"), &ATTACHMENTS, format!("{:?}", style.text_left_attachment), "text_left_attachment"),
|
|
enum_row(t!("Right attachment"), &ATTACHMENTS, format!("{:?}", style.text_right_attachment), "text_right_attachment"),
|
|
enum_row(t!("Top attachment"), &ATTACHMENTS, format!("{:?}", style.text_top_attachment), "text_top_attachment"),
|
|
enum_row(t!("Bottom attachment"), &ATTACHMENTS, format!("{:?}", style.text_bottom_attachment), "text_bottom_attachment"),
|
|
row![toggle(t!("Text frame"), style.text_frame, "text_frame"), toggle(t!("Always left align"), style.text_always_left, "text_always_left")].spacing(18),
|
|
].spacing(8).into(),
|
|
(_, Some(style)) => column![
|
|
handle_row(t!("Block"), v.block_opts.clone(), v.block_content_name.clone(), "block_content_handle"),
|
|
color_row(t!("Block color"), v.block_color, "block_color", v.color_open == Some("block_color")),
|
|
enum_row(t!("Block connection"), &BLOCK_CONNECTIONS, format!("{:?}", style.block_content_connection), "block_content_connection"),
|
|
input_row(t!("Block rotation"), "0", v.block_rotation, "block_rotation"),
|
|
input_row(t!("Block scale X"), "1.0", v.block_scale_x, "block_scale_x"),
|
|
input_row(t!("Block scale Y"), "1.0", v.block_scale_y, "block_scale_y"),
|
|
input_row(t!("Block scale Z"), "1.0", v.block_scale_z, "block_scale_z"),
|
|
toggle(t!("Enable block scale"), style.enable_block_scale, "enable_block_scale"),
|
|
toggle(t!("Enable block rotation"), style.enable_block_rotation, "enable_block_rotation"),
|
|
].spacing(8).into(),
|
|
};
|
|
let preview: Element<'a, Message> = if let Some(style) = style {
|
|
canvas(LeaderPreviewCanvas {
|
|
path_type: format!("{:?}", style.path_type),
|
|
content_type: format!("{:?}", style.content_type),
|
|
line_color: v.line_color.to_string(),
|
|
line_weight: style.line_weight.value(),
|
|
line_type: v.line_type_name.clone(),
|
|
arrow_name: handle_label(&v.arrowhead_name),
|
|
arrow_name_code: v.arrowhead_name.clone(),
|
|
arrow_size: v.arrowhead_size.to_string(),
|
|
break_gap: v.break_gap.to_string(),
|
|
landing: style.enable_landing,
|
|
dogleg: style.enable_dogleg,
|
|
landing_distance: v.landing_distance.to_string(),
|
|
landing_gap: v.landing_gap.to_string(),
|
|
max_points: v.max_points.to_string(),
|
|
first_angle: v.first_seg_angle.to_string(),
|
|
second_angle: v.second_seg_angle.to_string(),
|
|
scale: v.scale_factor.to_string(),
|
|
annotative: style.is_annotative,
|
|
align_space: v.align_space.to_string(),
|
|
default_text: v.default_text.to_string(),
|
|
text_style: handle_label(&v.text_style_name),
|
|
text_height: v.text_height.to_string(),
|
|
text_color: v.text_color.to_string(),
|
|
text_angle: format!("{:?}", style.text_angle_type),
|
|
text_alignment: format!("{:?}", style.text_alignment),
|
|
attachment_direction: format!("{:?}", style.text_attachment_direction),
|
|
left_attachment: choice_label(&ATTACHMENTS, format!("{:?}", style.text_left_attachment)),
|
|
left_attachment_code: format!("{:?}", style.text_left_attachment),
|
|
right_attachment: choice_label(&ATTACHMENTS, format!("{:?}", style.text_right_attachment)),
|
|
top_attachment: choice_label(&ATTACHMENTS, format!("{:?}", style.text_top_attachment)),
|
|
bottom_attachment: choice_label(&ATTACHMENTS, format!("{:?}", style.text_bottom_attachment)),
|
|
text_frame: style.text_frame,
|
|
text_always_left: style.text_always_left,
|
|
block_name: handle_label(&v.block_content_name),
|
|
block_color: v.block_color.to_string(),
|
|
block_connection: choice_label(&BLOCK_CONNECTIONS, format!("{:?}", style.block_content_connection)),
|
|
block_connection_code: format!("{:?}", style.block_content_connection),
|
|
block_rotation: v.block_rotation.to_string(),
|
|
block_scale: [v.block_scale_x.to_string(), v.block_scale_y.to_string(), v.block_scale_z.to_string()],
|
|
enable_block_scale: style.enable_block_scale,
|
|
enable_block_rotation: style.enable_block_rotation,
|
|
leader_draw_order: choice_label(&LEADER_DRAW_ORDERS, format!("{:?}", style.leader_draw_order)),
|
|
multileader_draw_order: choice_label(&MULTILEADER_DRAW_ORDERS, format!("{:?}", style.multileader_draw_order)),
|
|
description: v.description.to_string(),
|
|
})
|
|
.width(Length::Fill)
|
|
.height(150)
|
|
.into()
|
|
} else {
|
|
container(text(t!("No preview"))).height(150).into()
|
|
};
|
|
let status = if v.selected.eq_ignore_ascii_case(&v.current) {
|
|
t!("Current style")
|
|
} else if v.in_use {
|
|
t!("Used in drawing")
|
|
} else {
|
|
t!("Available style")
|
|
};
|
|
let summary = if v.comparison_sections.is_empty() {
|
|
t!("No differences").into_owned()
|
|
} else {
|
|
format!("{}: {}", t!("Different"), v.comparison_sections.join(", "))
|
|
};
|
|
let editor = crate::ui::style::style_manager::editor_shell(
|
|
crate::ui::style::style_manager::EditorShell {
|
|
sizing,
|
|
selected: v.selected.to_string(),
|
|
status,
|
|
preview,
|
|
comparison: Some(crate::ui::style::style_manager::EditorComparison {
|
|
selected: v.compare_name,
|
|
options: v.compare_opts,
|
|
summary,
|
|
on_select: Message::MLeaderStyleDialogCompare,
|
|
}),
|
|
tabs: [t!("Leader Format"), t!("Leader Structure"), t!("Content"), t!("Block Content")]
|
|
.into_iter()
|
|
.enumerate()
|
|
.map(|(tab, label)| crate::ui::style::style_manager::EditorTab {
|
|
label,
|
|
active: v.tab == tab as u8,
|
|
on_press: Message::MLeaderStyleDialogTab(tab as u8),
|
|
})
|
|
.collect(),
|
|
content,
|
|
},
|
|
);
|
|
crate::ui::style::style_manager::view(crate::ui::style::style_manager::Scaffold {
|
|
sizing,
|
|
kind: crate::app::StyleKind::MLeader,
|
|
styles: &v.styles,
|
|
selected: v.selected,
|
|
current: Some(v.current.as_str()),
|
|
rename_active: v.rename_active,
|
|
rename_buf: v.rename_buf,
|
|
on_new: Message::MLeaderStyleDialogNew,
|
|
on_copy: Message::MLeaderStyleDialogCopy,
|
|
on_delete: Message::MLeaderStyleDialogDelete,
|
|
on_select: Message::MLeaderStyleDialogSelect,
|
|
on_set_current: Message::MLeaderStyleDialogSetCurrent,
|
|
on_apply: Message::MLeaderStyleApply,
|
|
read_only: false,
|
|
editor,
|
|
})
|
|
}
|