From e90cb2d31d89cd1a6542cd2fb883e5332b3ff940 Mon Sep 17 00:00:00 2001 From: Halit Can Date: Sun, 19 Jul 2026 01:53:51 +0300 Subject: [PATCH] refactor: replace standard sliders with PlainSlider for better UI consistency --- .../hcie-iced-gui/src/dialogs/adjustments.rs | 70 ++++++++------- .../hcie-iced-gui/src/dialogs/confirm.rs | 35 +++----- .../crates/hcie-iced-gui/src/dialogs/mod.rs | 86 ++++++++++++++++++- .../hcie-iced-gui/src/dialogs/new_image.rs | 41 +++++---- .../hcie-iced-gui/src/panels/filter_params.rs | 78 ++++++++--------- .../crates/hcie-iced-gui/src/panels/layers.rs | 62 ++++++------- .../hcie-iced-gui/src/panels/tool_settings.rs | 17 +--- hcie-vector/src/lib.rs | 9 +- 8 files changed, 228 insertions(+), 170 deletions(-) diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/adjustments.rs b/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/adjustments.rs index 01d4d0f..cd829ee 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/adjustments.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/adjustments.rs @@ -2,8 +2,9 @@ use crate::app::Message; use crate::theme::ThemeColors; -use iced::widget::{button, column, horizontal_rule, row, slider, text}; -use iced::{Element, Length}; +use crate::widgets::plain_slider::plain_slider; +use iced::widget::{column, horizontal_rule, row, text}; +use iced::Element; /// Build the Brightness/Contrast adjustment dialog. pub fn brightness_contrast_view( @@ -11,30 +12,35 @@ pub fn brightness_contrast_view( contrast: f32, colors: ThemeColors, ) -> Element<'static, Message> { - let brightness_slider = slider(-100.0..=100.0, brightness, |v| Message::AdjBrightness(v)) - .step(1.0) - .width(Length::Fill); + let brightness_slider = plain_slider( + "Brightness", + 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)) - .step(1.0) - .width(Length::Fill); + let contrast_slider = plain_slider( + "Contrast", + contrast, + -100.0..=100.0, + 1.0, + "", + 0, + |v| Message::AdjContrast(v), + ); - let apply_btn = button(text("Apply").size(11)) - .on_press(Message::AdjApply) - .padding([6, 12]); - - let cancel_btn = button(text("Cancel").size(11)) - .on_press(Message::DialogClose) - .padding([6, 12]); + let apply_btn = super::primary_btn("Apply", Message::AdjApply, colors); + let cancel_btn = super::secondary_btn("Cancel", Message::DialogClose, colors); let dialog = column![ text("Brightness / Contrast") .size(14) .font(iced::Font::MONOSPACE), horizontal_rule(1), - text(format!("Brightness: {:.0}", brightness)).size(11), brightness_slider, - text(format!("Contrast: {:.0}", contrast)).size(11), contrast_slider, horizontal_rule(1), row![apply_btn, cancel_btn].spacing(12), @@ -53,36 +59,28 @@ pub fn hsl_view( lightness: f32, colors: ThemeColors, ) -> Element<'static, Message> { - let hue_slider = slider(-180.0..=180.0, hue, |v| Message::AdjHue(v)) - .step(1.0) - .width(Length::Fill); + let hue_slider = plain_slider("Hue", hue, -180.0..=180.0, 1.0, "°", 0, |v| { + Message::AdjHue(v) + }); - let sat_slider = slider(-100.0..=100.0, saturation, |v| Message::AdjSaturation(v)) - .step(1.0) - .width(Length::Fill); + let sat_slider = plain_slider("Saturation", saturation, -100.0..=100.0, 1.0, "", 0, |v| { + Message::AdjSaturation(v) + }); - let light_slider = slider(-100.0..=100.0, lightness, |v| Message::AdjLightness(v)) - .step(1.0) - .width(Length::Fill); + let light_slider = plain_slider("Lightness", lightness, -100.0..=100.0, 1.0, "", 0, |v| { + Message::AdjLightness(v) + }); - let apply_btn = button(text("Apply").size(11)) - .on_press(Message::AdjApply) - .padding([6, 12]); - - let cancel_btn = button(text("Cancel").size(11)) - .on_press(Message::DialogClose) - .padding([6, 12]); + let apply_btn = super::primary_btn("Apply", Message::AdjApply, colors); + let cancel_btn = super::secondary_btn("Cancel", Message::DialogClose, colors); let dialog = column![ text("Hue / Saturation / Lightness") .size(14) .font(iced::Font::MONOSPACE), horizontal_rule(1), - text(format!("Hue: {:.0}°", hue)).size(11), hue_slider, - text(format!("Saturation: {:.0}", saturation)).size(11), sat_slider, - text(format!("Lightness: {:.0}", lightness)).size(11), light_slider, horizontal_rule(1), row![apply_btn, cancel_btn].spacing(12), diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/confirm.rs b/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/confirm.rs index 53b4cbb..8f70f06 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/confirm.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/confirm.rs @@ -2,24 +2,17 @@ use crate::app::Message; use crate::theme::ThemeColors; -use iced::widget::{button, column, horizontal_rule, row, slider, text}; -use iced::{Element, Length}; +use crate::widgets::plain_slider::plain_slider; +use iced::widget::{column, horizontal_rule, row, text}; +use iced::Element; /// Build a confirmation dialog with save/discard/cancel. pub fn close_confirm_view(doc_name: &str, colors: ThemeColors) -> Element<'static, Message> { let msg = format!("\"{}\" has unsaved changes.", doc_name); - let save_btn = button(text("Save").size(12)) - .on_press(Message::DialogCloseSave) - .padding([8, 16]); - - 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 save_btn = super::primary_btn("Save", Message::DialogCloseSave, colors); + let discard_btn = super::secondary_btn("Discard", Message::DialogCloseDiscard, colors); + let cancel_btn = super::secondary_btn("Cancel", Message::DialogClose, colors); let dialog = column![ text("Unsaved Changes").size(16).font(iced::Font::MONOSPACE), @@ -44,22 +37,16 @@ pub fn selection_op_view( colors: ThemeColors, ) -> Element<'static, Message> { let op_owned = op_name.to_string(); - let value_slider = slider(min..=max, value, |v| Message::SelectionOpValue(v)) - .step(1.0) - .width(Length::Fill); + let value_slider = plain_slider("Value", value, min..=max, 1.0, "", 0, |v| { + Message::SelectionOpValue(v) + }); - let apply_btn = button(text("Apply").size(11)) - .on_press(Message::SelectionOpApply) - .padding([6, 12]); - - let cancel_btn = button(text("Cancel").size(11)) - .on_press(Message::DialogClose) - .padding([6, 12]); + let apply_btn = super::primary_btn("Apply", Message::SelectionOpApply, colors); + let cancel_btn = super::secondary_btn("Cancel", Message::DialogClose, colors); let dialog = column![ text(op_owned).size(14).font(iced::Font::MONOSPACE), horizontal_rule(1), - text(format!("Value: {:.0}", value)).size(11), value_slider, horizontal_rule(1), row![apply_btn, cancel_btn].spacing(12), diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/mod.rs b/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/mod.rs index 58d50f4..7735361 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/mod.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/mod.rs @@ -2,8 +2,8 @@ use crate::app::Message; use crate::theme::ThemeColors; -use iced::widget::container; -use iced::{Element, Length}; +use iced::widget::{button, container, text}; +use iced::{Border, Element, Length}; pub mod about; pub mod adjustments; @@ -55,3 +55,85 @@ pub fn modal<'a>( .height(Length::Fill) .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() +} diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/new_image.rs b/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/new_image.rs index f42d12c..8597c9f 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/new_image.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/dialogs/new_image.rs @@ -2,8 +2,9 @@ use crate::app::Message; use crate::theme::ThemeColors; +use crate::widgets::plain_slider::plain_slider; 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}; @@ -70,16 +71,25 @@ pub fn view( .on_input(|s| Message::DialogNewImageName(s)) .width(Length::Fill); - let width_text = text(format!("Width: {}", width)).size(11); - let height_text = text(format!("Height: {}", height)).size(11); + let width_slider = plain_slider( + "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)) - .step(1_u32) - .width(Length::Fill); - - let height_slider = slider(1_u32..=4096, height, |v| Message::DialogNewImageHeight(v)) - .step(1_u32) - .width(Length::Fill); + let height_slider = plain_slider( + "Height", + height as f32, + 1.0..=4096.0, + 1.0, + "px", + 0, + |v| Message::DialogNewImageHeight(v as u32), + ); let transparent_check = checkbox("Transparent background", transparent) .on_toggle(|v| Message::DialogNewImageTransparent(v)); @@ -96,22 +106,15 @@ pub fn view( presets_col = presets_col.push(preset_btn); } - let create_btn = button(text("Create").size(12)) - .on_press(Message::DialogNewImageCreate) - .padding([8, 16]); - - let cancel_btn = button(text("Cancel").size(12)) - .on_press(Message::DialogClose) - .padding([8, 16]); + let create_btn = super::primary_btn("Create", Message::DialogNewImageCreate, colors); + let cancel_btn = super::secondary_btn("Cancel", Message::DialogClose, colors); let dialog = column![ text("New Image").size(16).font(iced::Font::MONOSPACE), horizontal_rule(1), text("Name:").size(11), name_input, - width_text, width_slider, - height_text, height_slider, transparent_check, horizontal_rule(1), diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/panels/filter_params.rs b/hcie-iced-app/crates/hcie-iced-gui/src/panels/filter_params.rs index aa95088..2f815c0 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/panels/filter_params.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/panels/filter_params.rs @@ -10,8 +10,9 @@ use crate::app::Message; use crate::theme::ThemeColors; +use crate::widgets::plain_slider::plain_slider; 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 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) } -/// 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}))` -/// when the user drags it. +/// when the user drags or types a value. fn float_slider( label: &str, key: &str, @@ -50,30 +51,28 @@ fn float_slider( step: f64, _colors: ThemeColors, ) -> 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 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)); - let display = text(format!("{:.1}", value)) - .size(11) - .width(Length::Fixed(40.0)); - - let s = slider(min_f..=max_f, val, move |v| { - let rounded = (v as f64 / step).round() * step; - Message::FilterParamChanged(filter_owned.clone(), json!({ key_owned.clone(): rounded })) - }) - .step(step_f) - .width(Length::Fill); - - row![lbl, s, display].spacing(4).into() + plain_slider( + label, + value as f32, + (min as f32)..=(max as f32), + step_f, + "", + 1, + move |v| { + let rounded = (v as f64 / step).round() * step; + Message::FilterParamChanged( + filter_owned.clone(), + json!({ key_owned.clone(): rounded }), + ) + }, + ) } -/// Build a labeled integer slider row. +/// Build a labeled integer slider row with keyboard editing. fn int_slider( label: &str, key: &str, @@ -83,28 +82,23 @@ fn int_slider( max: u64, _colors: ThemeColors, ) -> 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 filter_owned = filter_id.to_string(); - let label_owned = label.to_string(); - let lbl = text(label_owned).size(11).width(Length::Fixed(80.0)); - let display = text(format!("{}", value)) - .size(11) - .width(Length::Fixed(40.0)); - - let s = slider(min_f..=max_f, val, move |v| { - 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() + plain_slider( + label, + value as f32, + (min as f32)..=(max as f32), + 1.0, + "", + 0, + move |v| { + Message::FilterParamChanged( + filter_owned.clone(), + json!({ key_owned.clone(): v.round() as u64 }), + ) + }, + ) } /// Build a section header for a group of parameters. diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/panels/layers.rs b/hcie-iced-app/crates/hcie-iced-gui/src/panels/layers.rs index 3a042b1..7ab7c6b 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/panels/layers.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/panels/layers.rs @@ -12,9 +12,10 @@ use crate::panels::styles; use crate::panels::thumbnails; use crate::panels::typography::BODY; use crate::theme::ThemeColors; +use crate::widgets::plain_slider::plain_slider; use hcie_engine_api::{BlendMode, LayerInfo, LayerType}; 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}; @@ -129,10 +130,10 @@ fn layer_thumbnail<'a>( colors: ThemeColors, ) -> Element<'a, Message> { if entry.is_group { - return container(text("\u{1F4C1}").size(13)) - .width(28) - .height(20) - .center_y(Length::Fixed(20.0)) + return container(text("\u{1F4C1}").size(16)) + .width(50) + .height(38) + .center_y(Length::Fixed(38.0)) .into(); } @@ -140,15 +141,15 @@ fn layer_thumbnail<'a>( let thumb = engine .get_layer_pixels(info.id) .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 { - let handle = iced::widget::image::Handle::from_rgba(28, 20, pixels); - let img = iced::widget::Image::new(handle).width(28).height(20); + let handle = iced::widget::image::Handle::from_rgba(48, 36, pixels); + let img = iced::widget::Image::new(handle).width(48).height(36); return container(img) - .width(30) - .height(22) - .center_y(Length::Fixed(22.0)) + .width(50) + .height(38) + .center_y(Length::Fixed(38.0)) .padding(1) .style(move |_theme| iced::widget::container::Style { border: iced::Border::default().color(colors.border_high).width(1), @@ -165,15 +166,15 @@ fn layer_thumbnail<'a>( _ => ("\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 { color: Some(icon_color), } })) - .width(30) - .height(22) - .center_x(Length::Fixed(30.0)) - .center_y(Length::Fixed(22.0)) + .width(50) + .height(38) + .center_x(Length::Fixed(50.0)) + .center_y(Length::Fixed(38.0)) .style(move |_theme| iced::widget::container::Style { background: Some(iced::Background::Color(iced::Color::from_rgba( 1.0, 1.0, 1.0, 0.05, @@ -222,18 +223,17 @@ pub fn view<'a>( .width(Length::Fill) .text_size(BODY); - let opacity_label = text(format!("{}%", (active_opacity * 100.0) as u32)) - .size(BODY) - .width(Length::Fixed(32.0)); - let opacity_slider = slider(0.0..=1.0, active_opacity, move |v| { - Message::LayerSetOpacity(active_layer_id, v) - }) - .step(0.01) - .width(Length::Fill); + let opacity_slider = plain_slider( + "Opacity", + active_opacity, + 0.0..=1.0, + 0.01, + "%", + 0, + move |v| Message::LayerSetOpacity(active_layer_id, v), + ); - let opacity_row = row![text("Op").size(BODY), opacity_slider, opacity_label] - .spacing(3) - .align_y(iced::Alignment::Center); + let opacity_row = opacity_slider; let lock_icon = if active_locked { "\u{1F512}" @@ -293,14 +293,14 @@ pub fn view<'a>( Space::with_width(Length::Fixed(12.0)).into() }; - // Visibility toggle - let vis_char = if info.visible { "\u{25CF}" } else { "\u{25CB}" }; + // Visibility toggle (eye icon) + let vis_char = if info.visible { "\u{1F441}" } else { "\u{1F441}" }; let vis_color = if info.visible { colors.text_primary } 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 { color: Some(vis_color), } diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/panels/tool_settings.rs b/hcie-iced-app/crates/hcie-iced-gui/src/panels/tool_settings.rs index e64fa65..c7bd7da 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/panels/tool_settings.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/panels/tool_settings.rs @@ -15,9 +15,10 @@ use crate::app::{Message, ToolState}; use crate::panels::styles; use crate::settings::AppSettings; use crate::theme::ThemeColors; +use crate::widgets::plain_slider::plain_slider; use hcie_engine_api::Tool; 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}; @@ -462,19 +463,7 @@ fn prop_slider( where F: Fn(f32) -> Message + 'static, { - let l = label.to_string(); - 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() + plain_slider(label, value, min..=max, 0.1, "", 1, msg_fn) } fn color_row(label: &str, color: [u8; 4], _c: ThemeColors) -> Element<'static, Message> { diff --git a/hcie-vector/src/lib.rs b/hcie-vector/src/lib.rs index e1581d6..b7bb406 100644 --- a/hcie-vector/src/lib.rs +++ b/hcie-vector/src/lib.rs @@ -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_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_x4, rot_y4, px_color, *stroke, None); + draw_line(layer, rot_x3, rot_y3, rot_x4, rot_y4, px_color, *stroke, None); if *fill { 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]; - 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, .. } => {