refactor: replace standard sliders with PlainSlider for better UI consistency

This commit is contained in:
2026-07-19 01:53:51 +03:00
parent 9388e6f096
commit e90cb2d31d
8 changed files with 228 additions and 170 deletions
@@ -2,8 +2,9 @@
use crate::app::Message; use crate::app::Message;
use crate::theme::ThemeColors; use crate::theme::ThemeColors;
use iced::widget::{button, column, horizontal_rule, row, slider, text}; use crate::widgets::plain_slider::plain_slider;
use iced::{Element, Length}; use iced::widget::{column, horizontal_rule, row, text};
use iced::Element;
/// Build the Brightness/Contrast adjustment dialog. /// Build the Brightness/Contrast adjustment dialog.
pub fn brightness_contrast_view( pub fn brightness_contrast_view(
@@ -11,30 +12,35 @@ pub fn brightness_contrast_view(
contrast: f32, contrast: f32,
colors: ThemeColors, colors: ThemeColors,
) -> Element<'static, Message> { ) -> Element<'static, Message> {
let brightness_slider = slider(-100.0..=100.0, brightness, |v| Message::AdjBrightness(v)) let brightness_slider = plain_slider(
.step(1.0) "Brightness",
.width(Length::Fill); brightness,
-100.0..=100.0,
1.0,
"",
0,
|v| Message::AdjBrightness(v),
);
let contrast_slider = slider(-100.0..=100.0, contrast, |v| Message::AdjContrast(v)) let contrast_slider = plain_slider(
.step(1.0) "Contrast",
.width(Length::Fill); contrast,
-100.0..=100.0,
1.0,
"",
0,
|v| Message::AdjContrast(v),
);
let apply_btn = button(text("Apply").size(11)) let apply_btn = super::primary_btn("Apply", Message::AdjApply, colors);
.on_press(Message::AdjApply) let cancel_btn = super::secondary_btn("Cancel", Message::DialogClose, colors);
.padding([6, 12]);
let cancel_btn = button(text("Cancel").size(11))
.on_press(Message::DialogClose)
.padding([6, 12]);
let dialog = column![ let dialog = column![
text("Brightness / Contrast") text("Brightness / Contrast")
.size(14) .size(14)
.font(iced::Font::MONOSPACE), .font(iced::Font::MONOSPACE),
horizontal_rule(1), horizontal_rule(1),
text(format!("Brightness: {:.0}", brightness)).size(11),
brightness_slider, brightness_slider,
text(format!("Contrast: {:.0}", contrast)).size(11),
contrast_slider, contrast_slider,
horizontal_rule(1), horizontal_rule(1),
row![apply_btn, cancel_btn].spacing(12), row![apply_btn, cancel_btn].spacing(12),
@@ -53,36 +59,28 @@ pub fn hsl_view(
lightness: f32, lightness: f32,
colors: ThemeColors, colors: ThemeColors,
) -> Element<'static, Message> { ) -> Element<'static, Message> {
let hue_slider = slider(-180.0..=180.0, hue, |v| Message::AdjHue(v)) let hue_slider = plain_slider("Hue", hue, -180.0..=180.0, 1.0, "°", 0, |v| {
.step(1.0) Message::AdjHue(v)
.width(Length::Fill); });
let sat_slider = slider(-100.0..=100.0, saturation, |v| Message::AdjSaturation(v)) let sat_slider = plain_slider("Saturation", saturation, -100.0..=100.0, 1.0, "", 0, |v| {
.step(1.0) Message::AdjSaturation(v)
.width(Length::Fill); });
let light_slider = slider(-100.0..=100.0, lightness, |v| Message::AdjLightness(v)) let light_slider = plain_slider("Lightness", lightness, -100.0..=100.0, 1.0, "", 0, |v| {
.step(1.0) Message::AdjLightness(v)
.width(Length::Fill); });
let apply_btn = button(text("Apply").size(11)) let apply_btn = super::primary_btn("Apply", Message::AdjApply, colors);
.on_press(Message::AdjApply) let cancel_btn = super::secondary_btn("Cancel", Message::DialogClose, colors);
.padding([6, 12]);
let cancel_btn = button(text("Cancel").size(11))
.on_press(Message::DialogClose)
.padding([6, 12]);
let dialog = column![ let dialog = column![
text("Hue / Saturation / Lightness") text("Hue / Saturation / Lightness")
.size(14) .size(14)
.font(iced::Font::MONOSPACE), .font(iced::Font::MONOSPACE),
horizontal_rule(1), horizontal_rule(1),
text(format!("Hue: {:.0}°", hue)).size(11),
hue_slider, hue_slider,
text(format!("Saturation: {:.0}", saturation)).size(11),
sat_slider, sat_slider,
text(format!("Lightness: {:.0}", lightness)).size(11),
light_slider, light_slider,
horizontal_rule(1), horizontal_rule(1),
row![apply_btn, cancel_btn].spacing(12), row![apply_btn, cancel_btn].spacing(12),
@@ -2,24 +2,17 @@
use crate::app::Message; use crate::app::Message;
use crate::theme::ThemeColors; use crate::theme::ThemeColors;
use iced::widget::{button, column, horizontal_rule, row, slider, text}; use crate::widgets::plain_slider::plain_slider;
use iced::{Element, Length}; use iced::widget::{column, horizontal_rule, row, text};
use iced::Element;
/// Build a confirmation dialog with save/discard/cancel. /// Build a confirmation dialog with save/discard/cancel.
pub fn close_confirm_view(doc_name: &str, colors: ThemeColors) -> Element<'static, Message> { pub fn close_confirm_view(doc_name: &str, colors: ThemeColors) -> Element<'static, Message> {
let msg = format!("\"{}\" has unsaved changes.", doc_name); let msg = format!("\"{}\" has unsaved changes.", doc_name);
let save_btn = button(text("Save").size(12)) let save_btn = super::primary_btn("Save", Message::DialogCloseSave, colors);
.on_press(Message::DialogCloseSave) let discard_btn = super::secondary_btn("Discard", Message::DialogCloseDiscard, colors);
.padding([8, 16]); let cancel_btn = super::secondary_btn("Cancel", Message::DialogClose, colors);
let discard_btn = button(text("Discard").size(12))
.on_press(Message::DialogCloseDiscard)
.padding([8, 16]);
let cancel_btn = button(text("Cancel").size(12))
.on_press(Message::DialogClose)
.padding([8, 16]);
let dialog = column![ let dialog = column![
text("Unsaved Changes").size(16).font(iced::Font::MONOSPACE), text("Unsaved Changes").size(16).font(iced::Font::MONOSPACE),
@@ -44,22 +37,16 @@ pub fn selection_op_view(
colors: ThemeColors, colors: ThemeColors,
) -> Element<'static, Message> { ) -> Element<'static, Message> {
let op_owned = op_name.to_string(); let op_owned = op_name.to_string();
let value_slider = slider(min..=max, value, |v| Message::SelectionOpValue(v)) let value_slider = plain_slider("Value", value, min..=max, 1.0, "", 0, |v| {
.step(1.0) Message::SelectionOpValue(v)
.width(Length::Fill); });
let apply_btn = button(text("Apply").size(11)) let apply_btn = super::primary_btn("Apply", Message::SelectionOpApply, colors);
.on_press(Message::SelectionOpApply) let cancel_btn = super::secondary_btn("Cancel", Message::DialogClose, colors);
.padding([6, 12]);
let cancel_btn = button(text("Cancel").size(11))
.on_press(Message::DialogClose)
.padding([6, 12]);
let dialog = column![ let dialog = column![
text(op_owned).size(14).font(iced::Font::MONOSPACE), text(op_owned).size(14).font(iced::Font::MONOSPACE),
horizontal_rule(1), horizontal_rule(1),
text(format!("Value: {:.0}", value)).size(11),
value_slider, value_slider,
horizontal_rule(1), horizontal_rule(1),
row![apply_btn, cancel_btn].spacing(12), row![apply_btn, cancel_btn].spacing(12),
@@ -2,8 +2,8 @@
use crate::app::Message; use crate::app::Message;
use crate::theme::ThemeColors; use crate::theme::ThemeColors;
use iced::widget::container; use iced::widget::{button, container, text};
use iced::{Element, Length}; use iced::{Border, Element, Length};
pub mod about; pub mod about;
pub mod adjustments; pub mod adjustments;
@@ -55,3 +55,85 @@ pub fn modal<'a>(
.height(Length::Fill) .height(Length::Fill)
.into() .into()
} }
/// Blend a color with white by a factor (0.0 = no blend, 1.0 = full white).
fn lighten(c: iced::Color, factor: f32) -> iced::Color {
iced::Color::new(
c.r + (1.0 - c.r) * factor,
c.g + (1.0 - c.g) * factor,
c.b + (1.0 - c.b) * factor,
c.a,
)
}
/// Blend a color with black by a factor (0.0 = no blend, 1.0 = full black).
fn darken(c: iced::Color, factor: f32) -> iced::Color {
iced::Color::new(
c.r * (1.0 - factor),
c.g * (1.0 - factor),
c.b * (1.0 - factor),
c.a,
)
}
/// Modern theme-compatible primary button (accent fill).
pub fn primary_btn(
label: &str,
msg: Message,
colors: ThemeColors,
) -> Element<'static, Message> {
let l = label.to_string();
let c = colors;
button(text(l).size(12).style(move |_theme: &iced::Theme| iced::widget::text::Style {
color: Some(iced::Color::WHITE),
}))
.on_press(msg)
.padding([8, 16])
.style(move |_theme, status| {
let bg = match status {
iced::widget::button::Status::Hovered => lighten(c.accent, 0.15),
iced::widget::button::Status::Pressed => darken(c.accent, 0.15),
_ => c.accent,
};
iced::widget::button::Style {
background: Some(iced::Background::Color(bg)),
text_color: iced::Color::WHITE,
border: Border::default().rounded(6),
..Default::default()
}
})
.into()
}
/// Modern theme-compatible secondary button (bordered ghost).
pub fn secondary_btn(
label: &str,
msg: Message,
colors: ThemeColors,
) -> Element<'static, Message> {
let l = label.to_string();
let c = colors;
button(text(l).size(12).style(move |_theme: &iced::Theme| iced::widget::text::Style {
color: Some(c.text_primary),
}))
.on_press(msg)
.padding([8, 16])
.style(move |_theme, status| {
let bg = match status {
iced::widget::button::Status::Hovered => {
let mut h = c.bg_hover;
h.a *= 0.5;
h
}
iced::widget::button::Status::Pressed => c.bg_hover,
_ => iced::Color::TRANSPARENT,
};
iced::widget::button::Style {
background: Some(iced::Background::Color(bg)),
text_color: c.text_primary,
border: Border::default().rounded(6).color(c.border_high).width(1),
..Default::default()
}
})
.into()
}
@@ -2,8 +2,9 @@
use crate::app::Message; use crate::app::Message;
use crate::theme::ThemeColors; use crate::theme::ThemeColors;
use crate::widgets::plain_slider::plain_slider;
use iced::widget::{ use iced::widget::{
button, checkbox, column, horizontal_rule, row, scrollable, slider, text, text_input, button, checkbox, column, horizontal_rule, row, scrollable, text, text_input,
}; };
use iced::{Element, Length}; use iced::{Element, Length};
@@ -70,16 +71,25 @@ pub fn view(
.on_input(|s| Message::DialogNewImageName(s)) .on_input(|s| Message::DialogNewImageName(s))
.width(Length::Fill); .width(Length::Fill);
let width_text = text(format!("Width: {}", width)).size(11); let width_slider = plain_slider(
let height_text = text(format!("Height: {}", height)).size(11); "Width",
width as f32,
1.0..=4096.0,
1.0,
"px",
0,
|v| Message::DialogNewImageWidth(v as u32),
);
let width_slider = slider(1_u32..=4096, width, |v| Message::DialogNewImageWidth(v)) let height_slider = plain_slider(
.step(1_u32) "Height",
.width(Length::Fill); height as f32,
1.0..=4096.0,
let height_slider = slider(1_u32..=4096, height, |v| Message::DialogNewImageHeight(v)) 1.0,
.step(1_u32) "px",
.width(Length::Fill); 0,
|v| Message::DialogNewImageHeight(v as u32),
);
let transparent_check = checkbox("Transparent background", transparent) let transparent_check = checkbox("Transparent background", transparent)
.on_toggle(|v| Message::DialogNewImageTransparent(v)); .on_toggle(|v| Message::DialogNewImageTransparent(v));
@@ -96,22 +106,15 @@ pub fn view(
presets_col = presets_col.push(preset_btn); presets_col = presets_col.push(preset_btn);
} }
let create_btn = button(text("Create").size(12)) let create_btn = super::primary_btn("Create", Message::DialogNewImageCreate, colors);
.on_press(Message::DialogNewImageCreate) let cancel_btn = super::secondary_btn("Cancel", Message::DialogClose, colors);
.padding([8, 16]);
let cancel_btn = button(text("Cancel").size(12))
.on_press(Message::DialogClose)
.padding([8, 16]);
let dialog = column![ let dialog = column![
text("New Image").size(16).font(iced::Font::MONOSPACE), text("New Image").size(16).font(iced::Font::MONOSPACE),
horizontal_rule(1), horizontal_rule(1),
text("Name:").size(11), text("Name:").size(11),
name_input, name_input,
width_text,
width_slider, width_slider,
height_text,
height_slider, height_slider,
transparent_check, transparent_check,
horizontal_rule(1), horizontal_rule(1),
@@ -10,8 +10,9 @@
use crate::app::Message; use crate::app::Message;
use crate::theme::ThemeColors; use crate::theme::ThemeColors;
use crate::widgets::plain_slider::plain_slider;
use hcie_engine_api::FilterType; use hcie_engine_api::FilterType;
use iced::widget::{column, container, row, slider, text}; use iced::widget::{column, container, row, text};
use iced::{Element, Length}; use iced::{Element, Length};
use serde_json::json; use serde_json::json;
@@ -35,11 +36,11 @@ fn param_str<'a>(params: &'a serde_json::Value, key: &str, default: &'a str) ->
params.get(key).and_then(|v| v.as_str()).unwrap_or(default) params.get(key).and_then(|v| v.as_str()).unwrap_or(default)
} }
/// Build a labeled float slider row. /// Build a labeled float slider row with keyboard editing.
/// ///
/// Returns a row with label, slider, and numeric display. The slider /// Returns a column with label and keyboard-editable slider. The slider
/// emits `Message::FilterParamChanged(filter_id, json!({key: new_value}))` /// emits `Message::FilterParamChanged(filter_id, json!({key: new_value}))`
/// when the user drags it. /// when the user drags or types a value.
fn float_slider( fn float_slider(
label: &str, label: &str,
key: &str, key: &str,
@@ -50,30 +51,28 @@ fn float_slider(
step: f64, step: f64,
_colors: ThemeColors, _colors: ThemeColors,
) -> Element<'static, Message> { ) -> Element<'static, Message> {
let val = value as f32;
let min_f = min as f32;
let max_f = max as f32;
let step_f = step as f32;
let key_owned = key.to_string(); let key_owned = key.to_string();
let filter_owned = filter_id.to_string(); let filter_owned = filter_id.to_string();
let label_owned = label.to_string(); let step_f = step as f32;
let lbl = text(label_owned).size(11).width(Length::Fixed(80.0)); plain_slider(
let display = text(format!("{:.1}", value)) label,
.size(11) value as f32,
.width(Length::Fixed(40.0)); (min as f32)..=(max as f32),
step_f,
let s = slider(min_f..=max_f, val, move |v| { "",
let rounded = (v as f64 / step).round() * step; 1,
Message::FilterParamChanged(filter_owned.clone(), json!({ key_owned.clone(): rounded })) move |v| {
}) let rounded = (v as f64 / step).round() * step;
.step(step_f) Message::FilterParamChanged(
.width(Length::Fill); filter_owned.clone(),
json!({ key_owned.clone(): rounded }),
row![lbl, s, display].spacing(4).into() )
},
)
} }
/// Build a labeled integer slider row. /// Build a labeled integer slider row with keyboard editing.
fn int_slider( fn int_slider(
label: &str, label: &str,
key: &str, key: &str,
@@ -83,28 +82,23 @@ fn int_slider(
max: u64, max: u64,
_colors: ThemeColors, _colors: ThemeColors,
) -> Element<'static, Message> { ) -> Element<'static, Message> {
let val = value as f32;
let min_f = min as f32;
let max_f = max as f32;
let key_owned = key.to_string(); let key_owned = key.to_string();
let filter_owned = filter_id.to_string(); let filter_owned = filter_id.to_string();
let label_owned = label.to_string();
let lbl = text(label_owned).size(11).width(Length::Fixed(80.0)); plain_slider(
let display = text(format!("{}", value)) label,
.size(11) value as f32,
.width(Length::Fixed(40.0)); (min as f32)..=(max as f32),
1.0,
let s = slider(min_f..=max_f, val, move |v| { "",
Message::FilterParamChanged( 0,
filter_owned.clone(), move |v| {
json!({ key_owned.clone(): v.round() as u64 }), Message::FilterParamChanged(
) filter_owned.clone(),
}) json!({ key_owned.clone(): v.round() as u64 }),
.step(1.0) )
.width(Length::Fill); },
)
row![lbl, s, display].spacing(4).into()
} }
/// Build a section header for a group of parameters. /// Build a section header for a group of parameters.
@@ -12,9 +12,10 @@ use crate::panels::styles;
use crate::panels::thumbnails; use crate::panels::thumbnails;
use crate::panels::typography::BODY; use crate::panels::typography::BODY;
use crate::theme::ThemeColors; use crate::theme::ThemeColors;
use crate::widgets::plain_slider::plain_slider;
use hcie_engine_api::{BlendMode, LayerInfo, LayerType}; use hcie_engine_api::{BlendMode, LayerInfo, LayerType};
use iced::widget::{ use iced::widget::{
button, column, container, horizontal_rule, pick_list, row, scrollable, slider, text, Space, button, column, container, horizontal_rule, pick_list, row, scrollable, text, Space,
}; };
use iced::{Element, Length}; use iced::{Element, Length};
@@ -129,10 +130,10 @@ fn layer_thumbnail<'a>(
colors: ThemeColors, colors: ThemeColors,
) -> Element<'a, Message> { ) -> Element<'a, Message> {
if entry.is_group { if entry.is_group {
return container(text("\u{1F4C1}").size(13)) return container(text("\u{1F4C1}").size(16))
.width(28) .width(50)
.height(20) .height(38)
.center_y(Length::Fixed(20.0)) .center_y(Length::Fixed(38.0))
.into(); .into();
} }
@@ -140,15 +141,15 @@ fn layer_thumbnail<'a>(
let thumb = engine let thumb = engine
.get_layer_pixels(info.id) .get_layer_pixels(info.id)
.filter(|px| !px.is_empty() && info.width > 0 && info.height > 0) .filter(|px| !px.is_empty() && info.width > 0 && info.height > 0)
.map(|px| thumbnails::generate_thumbnail(&px, info.width, info.height, 28)); .map(|px| thumbnails::generate_thumbnail(&px, info.width, info.height, 48));
if let Some(pixels) = thumb { if let Some(pixels) = thumb {
let handle = iced::widget::image::Handle::from_rgba(28, 20, pixels); let handle = iced::widget::image::Handle::from_rgba(48, 36, pixels);
let img = iced::widget::Image::new(handle).width(28).height(20); let img = iced::widget::Image::new(handle).width(48).height(36);
return container(img) return container(img)
.width(30) .width(50)
.height(22) .height(38)
.center_y(Length::Fixed(22.0)) .center_y(Length::Fixed(38.0))
.padding(1) .padding(1)
.style(move |_theme| iced::widget::container::Style { .style(move |_theme| iced::widget::container::Style {
border: iced::Border::default().color(colors.border_high).width(1), border: iced::Border::default().color(colors.border_high).width(1),
@@ -165,15 +166,15 @@ fn layer_thumbnail<'a>(
_ => ("\u{25A3}", colors.text_secondary), _ => ("\u{25A3}", colors.text_secondary),
}; };
container(text(icon_char).size(10).style(move |_theme: &iced::Theme| { container(text(icon_char).size(12).style(move |_theme: &iced::Theme| {
iced::widget::text::Style { iced::widget::text::Style {
color: Some(icon_color), color: Some(icon_color),
} }
})) }))
.width(30) .width(50)
.height(22) .height(38)
.center_x(Length::Fixed(30.0)) .center_x(Length::Fixed(50.0))
.center_y(Length::Fixed(22.0)) .center_y(Length::Fixed(38.0))
.style(move |_theme| iced::widget::container::Style { .style(move |_theme| iced::widget::container::Style {
background: Some(iced::Background::Color(iced::Color::from_rgba( background: Some(iced::Background::Color(iced::Color::from_rgba(
1.0, 1.0, 1.0, 0.05, 1.0, 1.0, 1.0, 0.05,
@@ -222,18 +223,17 @@ pub fn view<'a>(
.width(Length::Fill) .width(Length::Fill)
.text_size(BODY); .text_size(BODY);
let opacity_label = text(format!("{}%", (active_opacity * 100.0) as u32)) let opacity_slider = plain_slider(
.size(BODY) "Opacity",
.width(Length::Fixed(32.0)); active_opacity,
let opacity_slider = slider(0.0..=1.0, active_opacity, move |v| { 0.0..=1.0,
Message::LayerSetOpacity(active_layer_id, v) 0.01,
}) "%",
.step(0.01) 0,
.width(Length::Fill); move |v| Message::LayerSetOpacity(active_layer_id, v),
);
let opacity_row = row![text("Op").size(BODY), opacity_slider, opacity_label] let opacity_row = opacity_slider;
.spacing(3)
.align_y(iced::Alignment::Center);
let lock_icon = if active_locked { let lock_icon = if active_locked {
"\u{1F512}" "\u{1F512}"
@@ -293,14 +293,14 @@ pub fn view<'a>(
Space::with_width(Length::Fixed(12.0)).into() Space::with_width(Length::Fixed(12.0)).into()
}; };
// Visibility toggle // Visibility toggle (eye icon)
let vis_char = if info.visible { "\u{25CF}" } else { "\u{25CB}" }; let vis_char = if info.visible { "\u{1F441}" } else { "\u{1F441}" };
let vis_color = if info.visible { let vis_color = if info.visible {
colors.text_primary colors.text_primary
} else { } else {
colors.text_secondary iced::Color { a: colors.text_secondary.a * 0.4, ..colors.text_secondary }
}; };
let vis_btn = button(text(vis_char).size(10).style(move |_theme: &iced::Theme| { let vis_btn = button(text(vis_char).size(11).style(move |_theme: &iced::Theme| {
iced::widget::text::Style { iced::widget::text::Style {
color: Some(vis_color), color: Some(vis_color),
} }
@@ -15,9 +15,10 @@ use crate::app::{Message, ToolState};
use crate::panels::styles; use crate::panels::styles;
use crate::settings::AppSettings; use crate::settings::AppSettings;
use crate::theme::ThemeColors; use crate::theme::ThemeColors;
use crate::widgets::plain_slider::plain_slider;
use hcie_engine_api::Tool; use hcie_engine_api::Tool;
use iced::widget::{ use iced::widget::{
button, column, container, horizontal_rule, row, scrollable, slider, text, text_input, button, column, container, horizontal_rule, row, scrollable, text, text_input,
}; };
use iced::{Element, Length}; use iced::{Element, Length};
@@ -462,19 +463,7 @@ fn prop_slider<F>(
where where
F: Fn(f32) -> Message + 'static, F: Fn(f32) -> Message + 'static,
{ {
let l = label.to_string(); plain_slider(label, value, min..=max, 0.1, "", 1, msg_fn)
let label_text = text(l).size(10).width(Length::Fixed(70.0));
let val_text = text(format!("{:.1}", value))
.size(10)
.width(Length::Fixed(40.0));
let sl = slider(min..=max, value, msg_fn)
.step(0.1)
.width(Length::Fill);
row![label_text, sl, val_text]
.spacing(4)
.align_y(iced::Alignment::Center)
.into()
} }
fn color_row(label: &str, color: [u8; 4], _c: ThemeColors) -> Element<'static, Message> { fn color_row(label: &str, color: [u8; 4], _c: ThemeColors) -> Element<'static, Message> {
+7 -2
View File
@@ -294,14 +294,19 @@ fn render_shape(layer: &mut Layer, shape: &VectorShape) {
let (rot_x3, rot_y3) = rotate_point(rx3, ry3, cx, cy, *angle); let (rot_x3, rot_y3) = rotate_point(rx3, ry3, cx, cy, *angle);
let (rot_x4, rot_y4) = rotate_point(rx4, ry4, cx, cy, *angle); let (rot_x4, rot_y4) = rotate_point(rx4, ry4, cx, cy, *angle);
draw_line(layer, rot_x1, rot_y1, rot_x2, rot_y2, px_color, *stroke, None); let head_mid_x = (rot_x3 + rot_x4) / 2.0;
let head_mid_y = (rot_y3 + rot_y4) / 2.0;
draw_line(layer, rot_x1, rot_y1, head_mid_x, head_mid_y, px_color, *stroke, None);
draw_line(layer, rot_x2, rot_y2, rot_x3, rot_y3, px_color, *stroke, None); draw_line(layer, rot_x2, rot_y2, rot_x3, rot_y3, px_color, *stroke, None);
draw_line(layer, rot_x2, rot_y2, rot_x4, rot_y4, px_color, *stroke, None); draw_line(layer, rot_x2, rot_y2, rot_x4, rot_y4, px_color, *stroke, None);
draw_line(layer, rot_x3, rot_y3, rot_x4, rot_y4, px_color, *stroke, None);
if *fill { if *fill {
let fill_alpha = (fill_color[3] as f32 * opacity).round() as u8; let fill_alpha = (fill_color[3] as f32 * opacity).round() as u8;
let px_fill = [fill_color[0], fill_color[1], fill_color[2], fill_alpha]; let px_fill = [fill_color[0], fill_color[1], fill_color[2], fill_alpha];
draw_line(layer, rot_x2, rot_y2, rot_x3, rot_y3, px_fill, arrow_head_len, None); let arrowhead_pts = vec![(rot_x2, rot_y2), (rot_x3, rot_y3), (rot_x4, rot_y4)];
fill_polygon(layer, &arrowhead_pts, px_fill);
} }
} }
Star { x1, y1, x2, y2, stroke, color, fill_color, fill, points, inner_radius, opacity, angle, .. } => { Star { x1, y1, x2, y2, stroke, color, fill_color, fill, points, inner_radius, opacity, angle, .. } => {