feat: add star sparkle brush preset and enhance star brush parameters
This commit is contained in:
@@ -233,6 +233,14 @@ pub fn dry_media_presets() -> Vec<BrushPreset> {
|
||||
false,
|
||||
true,
|
||||
),
|
||||
preset(
|
||||
"star_sparkle",
|
||||
"Star Sparkle",
|
||||
"Effects",
|
||||
tip(BrushStyle::Star, 14.0, 0.95, 1.0, 0.35),
|
||||
true,
|
||||
false,
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -245,14 +253,14 @@ mod tests {
|
||||
#[test]
|
||||
fn dry_media_catalog_is_complete_and_unique() {
|
||||
let presets = dry_media_presets();
|
||||
assert_eq!(presets.len(), 19);
|
||||
assert_eq!(presets.len(), 20);
|
||||
assert_eq!(
|
||||
presets
|
||||
.iter()
|
||||
.map(|preset| &preset.id)
|
||||
.collect::<HashSet<_>>()
|
||||
.len(),
|
||||
19
|
||||
20
|
||||
);
|
||||
assert!(presets
|
||||
.iter()
|
||||
|
||||
@@ -587,6 +587,12 @@ pub struct ToolState {
|
||||
pub brush_color_variant: bool,
|
||||
/// Magnitude of the per-dab color variation (0.0..1.0).
|
||||
pub brush_variant_amount: f32,
|
||||
/// Star brush base rotation angle in radians.
|
||||
pub brush_angle: f32,
|
||||
/// Star brush per-dab size variance (0.0..1.0).
|
||||
pub brush_size_variance: f32,
|
||||
/// Star brush per-dab angle variance (0.0..1.0).
|
||||
pub brush_angle_variance: f32,
|
||||
/// True when the brush color should cycle through the hue over stroke distance.
|
||||
pub brush_cyclic_color: bool,
|
||||
/// Speed of the hue cycle along the stroke (0.01..5.0).
|
||||
@@ -625,6 +631,9 @@ impl Default for ToolState {
|
||||
brush_spacing: 1.0,
|
||||
brush_color_variant: false,
|
||||
brush_variant_amount: 0.0,
|
||||
brush_angle: 0.0,
|
||||
brush_size_variance: 0.0,
|
||||
brush_angle_variance: 0.0,
|
||||
brush_cyclic_color: false,
|
||||
brush_cyclic_speed: 0.5,
|
||||
last_update_instant: std::time::Instant::now(),
|
||||
@@ -784,6 +793,9 @@ pub enum Message {
|
||||
BrushSpacingChanged(f32),
|
||||
BrushColorVariantToggled(bool),
|
||||
BrushVariantAmountChanged(f32),
|
||||
BrushAngleChanged(f32),
|
||||
BrushSizeVarianceChanged(f32),
|
||||
BrushAngleVarianceChanged(f32),
|
||||
BrushCyclicColorToggled(bool),
|
||||
BrushCyclicSpeedChanged(f32),
|
||||
BrushImportAbr,
|
||||
@@ -1323,6 +1335,9 @@ fn build_brush_tip(state: &ToolState) -> BrushTip {
|
||||
spacing: state.brush_spacing,
|
||||
color_variant: state.brush_color_variant,
|
||||
variant_amount: state.brush_variant_amount,
|
||||
angle: state.brush_angle,
|
||||
roundness: state.brush_size_variance,
|
||||
rotation_random: state.brush_angle_variance,
|
||||
..BrushTip::default()
|
||||
}
|
||||
}
|
||||
@@ -4976,6 +4991,21 @@ impl HcieIcedApp {
|
||||
self.settings.update_from_tool_state(&self.tool_state);
|
||||
let _ = self.settings.save();
|
||||
}
|
||||
Message::BrushAngleChanged(angle) => {
|
||||
self.tool_state.brush_angle = angle;
|
||||
self.settings.update_from_tool_state(&self.tool_state);
|
||||
let _ = self.settings.save();
|
||||
}
|
||||
Message::BrushSizeVarianceChanged(variance) => {
|
||||
self.tool_state.brush_size_variance = variance;
|
||||
self.settings.update_from_tool_state(&self.tool_state);
|
||||
let _ = self.settings.save();
|
||||
}
|
||||
Message::BrushAngleVarianceChanged(variance) => {
|
||||
self.tool_state.brush_angle_variance = variance;
|
||||
self.settings.update_from_tool_state(&self.tool_state);
|
||||
let _ = self.settings.save();
|
||||
}
|
||||
Message::BrushCyclicColorToggled(enabled) => {
|
||||
self.tool_state.brush_cyclic_color = enabled;
|
||||
self.documents[self.active_doc]
|
||||
|
||||
@@ -186,6 +186,9 @@ pub fn panel_body<'a>(
|
||||
app.tool_state.brush_variant_amount,
|
||||
app.tool_state.brush_cyclic_color,
|
||||
app.tool_state.brush_cyclic_speed,
|
||||
app.tool_state.brush_angle,
|
||||
app.tool_state.brush_size_variance,
|
||||
app.tool_state.brush_angle_variance,
|
||||
app.brush_category,
|
||||
&app.tool_state.imported_brushes,
|
||||
app.tool_state.active_brush_preset.as_ref(),
|
||||
|
||||
@@ -113,9 +113,9 @@ const BRUSH_STYLES: &[BrushStyleEntry] = &[
|
||||
category: BrushCategory::Drawing,
|
||||
},
|
||||
BrushStyleEntry {
|
||||
name: "Star",
|
||||
name: "Star Sparkle",
|
||||
style: BrushStyle::Star,
|
||||
category: BrushCategory::Drawing,
|
||||
category: BrushCategory::Effects,
|
||||
},
|
||||
BrushStyleEntry {
|
||||
name: "Oil",
|
||||
@@ -510,6 +510,9 @@ fn build_tip_from_state(
|
||||
opacity: f32,
|
||||
hardness: f32,
|
||||
cyclic_color: bool,
|
||||
angle: f32,
|
||||
size_variance: f32,
|
||||
angle_variance: f32,
|
||||
) -> hcie_engine_api::BrushTip {
|
||||
hcie_engine_api::BrushTip {
|
||||
style,
|
||||
@@ -523,6 +526,9 @@ fn build_tip_from_state(
|
||||
time_opacity_end: 1.0,
|
||||
time_angle_start: 0.0,
|
||||
time_angle_end: 0.0,
|
||||
angle,
|
||||
roundness: size_variance,
|
||||
rotation_random: angle_variance,
|
||||
..hcie_engine_api::BrushTip::default()
|
||||
}
|
||||
}
|
||||
@@ -575,6 +581,9 @@ fn get_live_stroke_preview(
|
||||
hardness: f32,
|
||||
cyclic_color: bool,
|
||||
fg_color: [u8; 4],
|
||||
angle: f32,
|
||||
size_variance: f32,
|
||||
angle_variance: f32,
|
||||
) -> iced::widget::image::Handle {
|
||||
// Clamp the rendered brush size so a 110 px soft brush does not fill the
|
||||
// entire 40 px preview well with a solid blob. The preview shows texture and
|
||||
@@ -588,7 +597,16 @@ fn get_live_stroke_preview(
|
||||
if let Some(handle) = cache.get(&key) {
|
||||
return handle.clone();
|
||||
}
|
||||
let tip = build_tip_from_state(style, preview_size, opacity, hardness, cyclic_color);
|
||||
let tip = build_tip_from_state(
|
||||
style,
|
||||
preview_size,
|
||||
opacity,
|
||||
hardness,
|
||||
cyclic_color,
|
||||
angle,
|
||||
size_variance,
|
||||
angle_variance,
|
||||
);
|
||||
let pixels =
|
||||
render_stroke_preview(LIVE_PREVIEW_IMG_SIZE, LIVE_PREVIEW_IMG_SIZE, &tip, fg_color);
|
||||
let handle = iced::widget::image::Handle::from_rgba(
|
||||
@@ -656,6 +674,9 @@ pub fn view(
|
||||
brush_variant_amount: f32,
|
||||
brush_cyclic_color: bool,
|
||||
brush_cyclic_speed: f32,
|
||||
brush_angle: f32,
|
||||
brush_size_variance: f32,
|
||||
brush_angle_variance: f32,
|
||||
active_category: BrushCategory,
|
||||
imported_presets: &[hcie_engine_api::BrushPreset],
|
||||
active_brush_preset: Option<&hcie_engine_api::BrushPreset>,
|
||||
@@ -1051,6 +1072,50 @@ pub fn view(
|
||||
Message::BrushCyclicSpeedChanged,
|
||||
);
|
||||
|
||||
let is_star = current_style == BrushStyle::Star;
|
||||
let star_angle_slider = if is_star {
|
||||
Some(plain_slider(
|
||||
"Star Angle",
|
||||
brush_angle,
|
||||
0.0..=std::f32::consts::TAU,
|
||||
0.01,
|
||||
"rad",
|
||||
2,
|
||||
colors,
|
||||
Message::BrushAngleChanged,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let star_size_var_slider = if is_star {
|
||||
Some(plain_slider(
|
||||
"Size Variance",
|
||||
brush_size_variance,
|
||||
0.0..=1.0,
|
||||
0.01,
|
||||
"%",
|
||||
0,
|
||||
colors,
|
||||
Message::BrushSizeVarianceChanged,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let star_angle_var_slider = if is_star {
|
||||
Some(plain_slider(
|
||||
"Angle Variance",
|
||||
brush_angle_variance,
|
||||
0.0..=1.0,
|
||||
0.01,
|
||||
"%",
|
||||
0,
|
||||
colors,
|
||||
Message::BrushAngleVarianceChanged,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Compact color-variant toggle: an empty 12 px checkbox plus an 8 px label so
|
||||
// the control matches the rest of the compact panel typography.
|
||||
let color_variant_check = row![
|
||||
@@ -1082,6 +1147,9 @@ pub fn view(
|
||||
current_hardness,
|
||||
brush_cyclic_color,
|
||||
fg_color,
|
||||
brush_angle,
|
||||
brush_size_variance,
|
||||
brush_angle_variance,
|
||||
);
|
||||
let live_preview = container(
|
||||
iced::widget::image(preview_handle)
|
||||
@@ -1095,12 +1163,16 @@ pub fn view(
|
||||
.style(move |_theme| styles::recessed_control(colors));
|
||||
|
||||
// Panel title is in the dock tab — no duplicate inside the panel.
|
||||
let panel = column![
|
||||
tabs,
|
||||
horizontal_rule(1),
|
||||
let mut panel_children: Vec<Element<'static, Message>> = vec![
|
||||
tabs.into(),
|
||||
horizontal_rule(1).into(),
|
||||
scrollable(column![grid_rows, wc_rows, media_rows, imported_rows].spacing(GRID_SPACING))
|
||||
.height(Length::Fill),
|
||||
horizontal_rule(1),
|
||||
.height(Length::Fill)
|
||||
.into(),
|
||||
horizontal_rule(1).into(),
|
||||
];
|
||||
|
||||
panel_children.push(
|
||||
row![button(text("Import ABR").size(LABEL_SIZE))
|
||||
.on_press(Message::BrushImportAbr)
|
||||
.padding([3, 7])
|
||||
@@ -1122,19 +1194,31 @@ pub fn view(
|
||||
}
|
||||
}
|
||||
)]
|
||||
.spacing(GRID_SPACING),
|
||||
text("Preview").size(LABEL_SIZE + 1),
|
||||
live_preview,
|
||||
size_slider,
|
||||
opacity_slider,
|
||||
hardness_slider,
|
||||
color_variant_check,
|
||||
variant_slider,
|
||||
cyclic_color_check,
|
||||
cyclic_speed_slider,
|
||||
]
|
||||
.spacing(ITEM_SPACING)
|
||||
.padding(3);
|
||||
.spacing(GRID_SPACING)
|
||||
.into(),
|
||||
);
|
||||
panel_children.push(text("Preview").size(LABEL_SIZE + 1).into());
|
||||
panel_children.push(live_preview.into());
|
||||
panel_children.push(size_slider.into());
|
||||
panel_children.push(opacity_slider.into());
|
||||
panel_children.push(hardness_slider.into());
|
||||
panel_children.push(color_variant_check.into());
|
||||
panel_children.push(variant_slider.into());
|
||||
panel_children.push(cyclic_color_check.into());
|
||||
panel_children.push(cyclic_speed_slider.into());
|
||||
if let Some(s) = star_angle_slider {
|
||||
panel_children.push(s.into());
|
||||
}
|
||||
if let Some(s) = star_size_var_slider {
|
||||
panel_children.push(s.into());
|
||||
}
|
||||
if let Some(s) = star_angle_var_slider {
|
||||
panel_children.push(s.into());
|
||||
}
|
||||
|
||||
let panel = iced::widget::Column::with_children(panel_children)
|
||||
.spacing(ITEM_SPACING)
|
||||
.padding(3);
|
||||
|
||||
container(panel)
|
||||
.width(Length::Fill)
|
||||
@@ -1181,7 +1265,7 @@ mod tests {
|
||||
presets.extend(hcie_paint_brushes::paint_presets());
|
||||
presets.extend(hcie_digital_brushes::digital_presets());
|
||||
presets.extend(hcie_watercolor_brushes::watercolor_presets());
|
||||
assert_eq!(presets.len(), 47);
|
||||
assert_eq!(presets.len(), 48);
|
||||
assert_eq!(
|
||||
presets
|
||||
.iter()
|
||||
|
||||
@@ -59,6 +59,15 @@ pub struct ToolSettings {
|
||||
/// Magnitude of the per-dab color variation (0.0..1.0).
|
||||
#[serde(default)]
|
||||
pub brush_variant_amount: f32,
|
||||
/// Star brush base rotation angle in radians.
|
||||
#[serde(default)]
|
||||
pub brush_angle: f32,
|
||||
/// Star brush per-dab size variance (0.0..1.0).
|
||||
#[serde(default)]
|
||||
pub brush_size_variance: f32,
|
||||
/// Star brush per-dab angle variance (0.0..1.0).
|
||||
#[serde(default)]
|
||||
pub brush_angle_variance: f32,
|
||||
/// True when the brush color should cycle through the hue over stroke distance.
|
||||
#[serde(default)]
|
||||
pub brush_cyclic_color: bool,
|
||||
@@ -198,6 +207,9 @@ impl Default for ToolSettings {
|
||||
brush_spacing: 1.0,
|
||||
brush_color_variant: false,
|
||||
brush_variant_amount: 0.0,
|
||||
brush_angle: 0.0,
|
||||
brush_size_variance: 0.0,
|
||||
brush_angle_variance: 0.0,
|
||||
brush_cyclic_color: false,
|
||||
brush_cyclic_speed: 0.5,
|
||||
eraser_size: 20.0,
|
||||
@@ -352,6 +364,9 @@ impl AppSettings {
|
||||
self.tool_settings.brush_hardness = tool_state.brush_hardness;
|
||||
self.tool_settings.brush_color_variant = tool_state.brush_color_variant;
|
||||
self.tool_settings.brush_variant_amount = tool_state.brush_variant_amount;
|
||||
self.tool_settings.brush_angle = tool_state.brush_angle;
|
||||
self.tool_settings.brush_size_variance = tool_state.brush_size_variance;
|
||||
self.tool_settings.brush_angle_variance = tool_state.brush_angle_variance;
|
||||
self.tool_settings.brush_cyclic_color = tool_state.brush_cyclic_color;
|
||||
self.tool_settings.brush_cyclic_speed = tool_state.brush_cyclic_speed;
|
||||
self.tool_settings.last_active_tool = format!("{:?}", tool_state.active_tool);
|
||||
@@ -364,6 +379,9 @@ impl AppSettings {
|
||||
tool_state.brush_hardness = self.tool_settings.brush_hardness;
|
||||
tool_state.brush_color_variant = self.tool_settings.brush_color_variant;
|
||||
tool_state.brush_variant_amount = self.tool_settings.brush_variant_amount;
|
||||
tool_state.brush_angle = self.tool_settings.brush_angle;
|
||||
tool_state.brush_size_variance = self.tool_settings.brush_size_variance;
|
||||
tool_state.brush_angle_variance = self.tool_settings.brush_angle_variance;
|
||||
tool_state.brush_cyclic_color = self.tool_settings.brush_cyclic_color;
|
||||
tool_state.brush_cyclic_speed = self.tool_settings.brush_cyclic_speed;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user