Files
hcie-rust-v3.05/hcie-egui-app/crates/hcie-gui-egui/src/app/tools/layer_styles_panel.rs
T
phantom 9388e6f096 GOOD Refactor GUI components and add PlainSlider widget
- Added a new `PlainSlider` widget for keyboard-editable numeric sliders.
- Updated various panels to utilize the new `PlainSlider` for better user interaction.
- Removed unused code and dead code warnings across multiple files.
- Improved organization of modules by adding a `widgets` module.
- Cleaned up imports in several files to streamline the codebase.
- Added Qodana configuration file for code analysis and quality checks.
2026-07-19 00:55:40 +03:00

1149 lines
39 KiB
Rust

#![allow(dead_code)]
//! Interactive Layer Styles panel UI.
//!
//! **Purpose:**
//! Allows enabling/disabling and modifying parameters for all 10 layer style effects.
//!
//! **Logic & Workflow:**
//! 1. Displays all 10 styles in a scrollable panel.
//! 2. Renders collapsing headers with checkbox toggles for each style.
//! 3. Displays sliders, dropdowns, and color pickers for modifying details.
//! 4. Propagates changes to the engine which marks composite rendering flags dirty.
use crate::app::{AppDocument, ThemeColors, ToolState};
use eframe::egui;
use egui::{Color32, RichText};
use hcie_engine_api::LayerStyle;
/// **Purpose:**
/// Renders the entire Layer Styles docking panel UI containing collapsible settings for each effect.
///
/// **Logic & Workflow:**
/// 1. Determines active layer; returns if none.
/// 2. Fetches existing layer styles.
/// 3. For each of the 10 standard layer style variants, finds existing instances or constructs a default disabled style.
/// 4. Iteratively displays style sliders, color pickers, and checkboxes in a vertical ScrollArea.
/// 5. On any parameter change, updates the active layer's styles in the engine and flags the canvas as composite_dirty.
///
/// **Arguments:**
/// - `doc`: Mutable reference to the current active document.
/// - `state`: Mutable reference to the ToolState.
/// - `ui`: Mutable egui UI layout context.
pub fn show_layer_styles_panel(doc: &mut AppDocument, state: &mut ToolState, ui: &mut egui::Ui) {
let colors = ThemeColors::get(state.settings.theme);
let layer_id = doc.engine.active_layer_id();
if layer_id == 0 {
ui.label(RichText::new("No active layer").color(colors.text_secondary));
return;
}
let styles = doc.engine.get_layer_styles(layer_id);
let get_or_default_style = |discriminant_name: &str| -> LayerStyle {
for s in &styles {
let matches = match (discriminant_name, s) {
("DropShadow", LayerStyle::DropShadow { .. }) => true,
("InnerShadow", LayerStyle::InnerShadow { .. }) => true,
("OuterGlow", LayerStyle::OuterGlow { .. }) => true,
("InnerGlow", LayerStyle::InnerGlow { .. }) => true,
("BevelEmboss", LayerStyle::BevelEmboss { .. }) => true,
("Satin", LayerStyle::Satin { .. }) => true,
("ColorOverlay", LayerStyle::ColorOverlay { .. }) => true,
("GradientOverlay", LayerStyle::GradientOverlay { .. }) => true,
("PatternOverlay", LayerStyle::PatternOverlay { .. }) => true,
("Stroke", LayerStyle::Stroke { .. }) => true,
_ => false,
};
if matches {
return s.clone();
}
}
match discriminant_name {
"DropShadow" => LayerStyle::DropShadow {
enabled: false,
opacity: 0.75,
angle: 120.0,
distance: 5.0,
spread: 0.0,
size: 5.0,
color: [0, 0, 0, 255],
blend_mode: "Multiply".to_string(),
},
"InnerShadow" => LayerStyle::InnerShadow {
enabled: false,
opacity: 0.75,
angle: 120.0,
distance: 5.0,
spread: 0.0,
size: 5.0,
color: [0, 0, 0, 255],
blend_mode: "Multiply".to_string(),
},
"OuterGlow" => LayerStyle::OuterGlow {
enabled: false,
opacity: 0.75,
spread: 0.0,
size: 5.0,
color: [255, 255, 190, 255],
blend_mode: "Screen".to_string(),
},
"InnerGlow" => LayerStyle::InnerGlow {
enabled: false,
opacity: 0.75,
spread: 0.0,
size: 5.0,
color: [255, 255, 190, 255],
blend_mode: "Screen".to_string(),
},
"BevelEmboss" => LayerStyle::BevelEmboss {
enabled: false,
depth: 1.0,
size: 5.0,
angle: 120.0,
altitude: 30.0,
contour: "Linear".to_string(),
highlight_opacity: 0.75,
shadow_opacity: 0.75,
direction: "Up".to_string(),
style: "InnerBevel".to_string(),
technique: "Smooth".to_string(),
soften: 0.0,
highlight_blend_mode: "Screen".to_string(),
highlight_color: [255, 255, 255, 255],
shadow_blend_mode: "Multiply".to_string(),
shadow_color: [0, 0, 0, 255],
},
"Satin" => LayerStyle::Satin {
enabled: false,
opacity: 0.5,
angle: 120.0,
distance: 11.0,
size: 14.0,
color: [0, 0, 0, 255],
invert: true,
},
"ColorOverlay" => LayerStyle::ColorOverlay {
enabled: false,
opacity: 1.0,
color: [255, 0, 0, 255],
blend_mode: "Normal".to_string(),
},
"GradientOverlay" => LayerStyle::GradientOverlay {
enabled: false,
opacity: 1.0,
blend_mode: "Normal".to_string(),
angle: 90.0,
scale: 1.0,
gradient_type: 0,
},
"PatternOverlay" => LayerStyle::PatternOverlay {
enabled: false,
opacity: 1.0,
blend_mode: "Normal".to_string(),
scale: 1.0,
pattern_name: "".to_string(),
},
"Stroke" => LayerStyle::Stroke {
enabled: false,
size: 3.0,
position: "Outside".to_string(),
opacity: 1.0,
color: [255, 0, 0, 255],
blend_mode: "Normal".to_string(),
},
_ => panic!("Unknown style name"),
}
};
let style_names = [
"DropShadow",
"InnerShadow",
"OuterGlow",
"InnerGlow",
"BevelEmboss",
"Satin",
"ColorOverlay",
"GradientOverlay",
"PatternOverlay",
"Stroke",
];
ui.spacing_mut().item_spacing.y = 2.0;
ui.spacing_mut().slider_width = 90.0;
for name in style_names {
let mut style = get_or_default_style(name);
if show_style_effect_mut(&mut style, ui, &colors) {
doc.engine.update_layer_style(layer_id, style);
doc.engine.set_modified(true);
}
ui.add_space(3.0);
}
}
/// **Purpose:**
/// Displays a dropdown combo box to select the blend mode.
///
/// **Logic & Workflow:**
/// Lists all standard Photoshop blend modes and updates the `current` string on selection.
///
/// **Arguments:**
/// - `ui`: Mutable egui UI layout context.
/// - `current`: Mutable reference to the blend mode string.
///
/// **Returns:**
/// - `bool`: True if the selection changed.
fn blend_mode_combobox(ui: &mut egui::Ui, current: &mut String) -> bool {
let mut changed = false;
let blend_modes = [
"Normal",
"Dissolve",
"Darken",
"Multiply",
"Color Burn",
"Linear Burn",
"Darker Color",
"Lighten",
"Screen",
"Color Dodge",
"Linear Dodge",
"Lighter Color",
"Overlay",
"Soft Light",
"Hard Light",
"Vivid Light",
"Linear Light",
"Pin Light",
"Hard Mix",
"Difference",
"Exclusion",
"Subtract",
"Divide",
"Hue",
"Saturation",
"Color",
"Luminosity",
];
egui::ComboBox::from_label("Blend Mode")
.selected_text(current.as_str())
.show_ui(ui, |ui| {
for mode in blend_modes {
if ui
.selectable_value(current, mode.to_string(), mode)
.changed()
{
changed = true;
}
}
});
changed
}
/// **Purpose:**
/// Displays a dropdown combo box to select the bevel style.
///
/// **Logic & Workflow:**
/// Lists InnerBevel, OuterBevel, Emboss, etc. and updates `current` on selection.
///
/// **Arguments:**
/// - `ui`: Mutable egui UI layout context.
/// - `current`: Mutable reference to the style string.
///
/// **Returns:**
/// - `bool`: True if selection changed.
fn bevel_style_combobox(ui: &mut egui::Ui, current: &mut String) -> bool {
let mut changed = false;
let styles = [
"InnerBevel",
"OuterBevel",
"Emboss",
"PillowEmboss",
"StrokeEmboss",
];
egui::ComboBox::from_label("Style")
.selected_text(current.as_str())
.show_ui(ui, |ui| {
for s in styles {
if ui.selectable_value(current, s.to_string(), s).changed() {
changed = true;
}
}
});
changed
}
/// **Purpose:**
/// Displays a dropdown combo box to select the bevel direction.
///
/// **Logic & Workflow:**
/// Lists Up and Down direction options.
///
/// **Arguments:**
/// - `ui`: Mutable egui UI layout context.
/// - `current`: Mutable reference to the direction string.
///
/// **Returns:**
/// - `bool`: True if selection changed.
/// **Purpose:**
/// Displays a dropdown combo box to select the gradient type.
///
/// **Logic & Workflow:**
/// Lists Linear, Radial, Angle, Reflected, Diamond options.
///
/// **Arguments:**
/// - `ui`: Mutable egui UI layout context.
/// - `current`: Mutable reference to the gradient type u32.
///
/// **Returns:**
/// - `bool`: True if selection changed.
fn gradient_type_combobox(ui: &mut egui::Ui, current: &mut u32) -> bool {
let mut changed = false;
let types = [
(0, "Linear"),
(1, "Radial"),
(2, "Angle"),
(3, "Reflected"),
(4, "Diamond"),
];
let current_label = types
.iter()
.find(|(v, _)| v == current)
.map(|(_, l)| *l)
.unwrap_or("Linear");
egui::ComboBox::from_label("Type")
.selected_text(current_label)
.show_ui(ui, |ui| {
for (val, label) in types {
if ui.selectable_value(current, val, label).changed() {
changed = true;
}
}
});
changed
}
fn direction_combobox(ui: &mut egui::Ui, current: &mut String) -> bool {
let mut changed = false;
let directions = ["Up", "Down"];
egui::ComboBox::from_label("Direction")
.selected_text(current.as_str())
.show_ui(ui, |ui| {
for d in directions {
if ui.selectable_value(current, d.to_string(), d).changed() {
changed = true;
}
}
});
changed
}
fn technique_combobox(ui: &mut egui::Ui, current: &mut String) -> bool {
let mut changed = false;
let techniques = ["Smooth", "ChiselHard", "ChiselSoft"];
egui::ComboBox::from_label("Technique")
.selected_text(current.as_str())
.show_ui(ui, |ui| {
for t in techniques {
if ui.selectable_value(current, t.to_string(), t).changed() {
changed = true;
}
}
});
changed
}
/// **Purpose:**
/// Displays a dropdown combo box to select the bevel contour profile.
///
/// **Logic & Workflow:**
/// Lists common contour presets (Linear, Cone, Gaussian, etc.).
///
/// **Arguments:**
/// - `ui`: Mutable egui UI layout context.
/// - `current`: Mutable reference to the contour string.
///
/// **Returns:**
/// - `bool`: True if selection changed.
fn contour_combobox(ui: &mut egui::Ui, current: &mut String) -> bool {
let mut changed = false;
let contours = [
"Linear",
"Cone",
"Cone-Inverted",
"Gaussian",
"Half Round",
"Round",
"Ring",
"Ring-Double",
"Sawtooth",
"Square",
"Valley",
"Shallow-Slope",
"Wave",
"Cove",
"Washboard",
];
egui::ComboBox::from_label("Contour")
.selected_text(current.as_str())
.show_ui(ui, |ui| {
for c in contours {
if ui.selectable_value(current, c.to_string(), c).changed() {
changed = true;
}
}
});
changed
}
/// **Purpose:**
/// Displays a dropdown combo box to select the stroke position.
///
/// **Logic & Workflow:**
/// Lists Outside, Inside, Center options.
///
/// **Arguments:**
/// - `ui`: Mutable egui UI layout context.
/// - `current`: Mutable reference to the position string.
///
/// **Returns:**
/// - `bool`: True if selection changed.
fn stroke_position_combobox(ui: &mut egui::Ui, current: &mut String) -> bool {
let mut changed = false;
let positions = ["Outside", "Inside", "Center"];
egui::ComboBox::from_label("Position")
.selected_text(current.as_str())
.show_ui(ui, |ui| {
for p in positions {
if ui.selectable_value(current, p.to_string(), p).changed() {
changed = true;
}
}
});
changed
}
/// **Purpose:**
/// Displays a native egui color picker to edit color channels.
///
/// **Logic & Workflow:**
/// Normalizes colors to 0..1 f32, presents `color_edit_button_rgba_unmultiplied`,
/// and converts back to u8 on change.
///
/// **Arguments:**
/// - `ui`: Mutable egui UI layout context.
/// - `color`: Mutable reference to [u8; 4] array.
///
/// **Returns:**
/// - `bool`: True if color changed.
fn edit_color(ui: &mut egui::Ui, id: impl std::hash::Hash, color: &mut [u8; 4]) -> bool {
ui.push_id(id, |ui| {
let mut color_f32 = [
color[0] as f32 / 255.0,
color[1] as f32 / 255.0,
color[2] as f32 / 255.0,
color[3] as f32 / 255.0,
];
if ui
.color_edit_button_rgba_unmultiplied(&mut color_f32)
.changed()
{
color[0] = (color_f32[0] * 255.0).round().clamp(0.0, 255.0) as u8;
color[1] = (color_f32[1] * 255.0).round().clamp(0.0, 255.0) as u8;
color[2] = (color_f32[2] * 255.0).round().clamp(0.0, 255.0) as u8;
color[3] = (color_f32[3] * 255.0).round().clamp(0.0, 255.0) as u8;
true
} else {
false
}
})
.inner
}
fn param_label(ui: &mut egui::Ui, label: &str, colors: &ThemeColors) {
ui.label(RichText::new(label).size(10.0).color(colors.text_secondary));
}
/// Compact labeled slider row: label on the left, a short slider filling the
/// remaining width. Reduces vertical footprint by stacking label+value inline.
fn compact_slider(
ui: &mut egui::Ui,
label: &str,
value: &mut f32,
range: std::ops::RangeInclusive<f32>,
colors: &ThemeColors,
) -> bool {
ui.horizontal(|ui| {
param_label(ui, label, colors);
ui.add(
egui::Slider::new(value, range)
.show_value(true)
.clamping(egui::SliderClamping::Always),
)
.changed()
})
.inner
}
/// **Purpose:**
/// Renders an interactive collapsible panel for a specific LayerStyle, allowing mutations.
///
/// **Logic & Workflow:**
/// Draws a collapsing header with a checkbox for enabling/disabling the style.
/// If expanded, draws sliders, color pickers, and dropdowns for each variant parameter.
///
/// **Arguments:**
/// - `style`: Mutable reference to the LayerStyle enum variant to draw.
/// - `ui`: Mutable egui UI layout context.
/// - `colors`: Reference to the active theme colors.
///
/// **Returns:**
/// - `bool`: True if any parameter of the style was updated.
fn show_style_effect_mut(style: &mut LayerStyle, ui: &mut egui::Ui, colors: &ThemeColors) -> bool {
let mut changed = false;
match style {
LayerStyle::DropShadow {
enabled,
opacity,
angle,
distance,
spread,
size,
color,
blend_mode,
} => {
let title = "Drop Shadow";
let header_color = Color32::from_rgb(220, 100, 100);
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id(title),
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
})
.body(|ui| {
ui.spacing_mut().item_spacing.y = 1.0;
ui.horizontal(|ui| {
if blend_mode_combobox(ui, blend_mode) {
changed = true;
}
param_label(ui, "Color:", colors);
if edit_color(ui, "color", color) {
changed = true;
}
});
if compact_slider(ui, "Opacity:", opacity, 0.0..=1.0, colors) {
changed = true;
}
if compact_slider(ui, "Angle:", angle, 0.0..=360.0, colors) {
changed = true;
}
if compact_slider(ui, "Distance:", distance, 0.0..=100.0, colors) {
changed = true;
}
if compact_slider(ui, "Spread:", spread, 0.0..=100.0, colors) {
changed = true;
}
if compact_slider(ui, "Size:", size, 0.0..=100.0, colors) {
changed = true;
}
});
}
LayerStyle::InnerShadow {
enabled,
opacity,
angle,
distance,
spread,
size,
color,
blend_mode,
} => {
let title = "Inner Shadow";
let header_color = Color32::from_rgb(180, 120, 120);
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id(title),
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
})
.body(|ui| {
ui.spacing_mut().item_spacing.y = 1.0;
ui.horizontal(|ui| {
if blend_mode_combobox(ui, blend_mode) {
changed = true;
}
param_label(ui, "Color:", colors);
if edit_color(ui, "color", color) {
changed = true;
}
});
if compact_slider(ui, "Opacity:", opacity, 0.0..=1.0, colors) {
changed = true;
}
if compact_slider(ui, "Angle:", angle, 0.0..=360.0, colors) {
changed = true;
}
if compact_slider(ui, "Distance:", distance, 0.0..=100.0, colors) {
changed = true;
}
if compact_slider(ui, "Spread:", spread, 0.0..=100.0, colors) {
changed = true;
}
if compact_slider(ui, "Size:", size, 0.0..=100.0, colors) {
changed = true;
}
});
}
LayerStyle::OuterGlow {
enabled,
opacity,
spread,
size,
color,
blend_mode,
} => {
let title = "Outer Glow";
let header_color = Color32::from_rgb(220, 220, 100);
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id(title),
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
})
.body(|ui| {
ui.spacing_mut().item_spacing.y = 1.0;
ui.horizontal(|ui| {
if blend_mode_combobox(ui, blend_mode) {
changed = true;
}
param_label(ui, "Color:", colors);
if edit_color(ui, "color", color) {
changed = true;
}
});
if compact_slider(ui, "Opacity:", opacity, 0.0..=1.0, colors) {
changed = true;
}
if compact_slider(ui, "Spread:", spread, 0.0..=1.0, colors) {
changed = true;
}
if compact_slider(ui, "Size:", size, 0.0..=100.0, colors) {
changed = true;
}
});
}
LayerStyle::InnerGlow {
enabled,
opacity,
spread,
size,
color,
blend_mode,
} => {
let title = "Inner Glow";
let header_color = Color32::from_rgb(180, 180, 100);
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id(title),
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
})
.body(|ui| {
ui.spacing_mut().item_spacing.y = 1.0;
ui.horizontal(|ui| {
if blend_mode_combobox(ui, blend_mode) {
changed = true;
}
param_label(ui, "Color:", colors);
if edit_color(ui, "color", color) {
changed = true;
}
});
if compact_slider(ui, "Opacity:", opacity, 0.0..=1.0, colors) {
changed = true;
}
if compact_slider(ui, "Spread:", spread, 0.0..=100.0, colors) {
changed = true;
}
if compact_slider(ui, "Size:", size, 0.0..=100.0, colors) {
changed = true;
}
});
}
LayerStyle::BevelEmboss {
enabled,
depth,
size,
angle,
altitude,
highlight_opacity,
shadow_opacity,
direction,
style,
technique,
soften,
highlight_blend_mode,
highlight_color,
shadow_blend_mode,
shadow_color,
contour,
} => {
let title = "Bevel & Emboss";
let header_color = Color32::from_rgb(100, 180, 220);
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id(title),
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
})
.body(|ui| {
ui.spacing_mut().item_spacing.y = 1.0;
egui::Grid::new("bevel_emboss_grid")
.num_columns(2)
.spacing([6.0, 2.0])
.show(ui, |ui| {
ui.label(
RichText::new("Style")
.size(10.0)
.color(colors.text_secondary),
);
ui.horizontal(|ui| {
if bevel_style_combobox(ui, style) {
changed = true;
}
});
ui.end_row();
ui.label(
RichText::new("Direction")
.size(10.0)
.color(colors.text_secondary),
);
ui.horizontal(|ui| {
if direction_combobox(ui, direction) {
changed = true;
}
});
ui.end_row();
ui.label(
RichText::new("Technique")
.size(10.0)
.color(colors.text_secondary),
);
ui.horizontal(|ui| {
if technique_combobox(ui, technique) {
changed = true;
}
});
ui.end_row();
ui.label(
RichText::new("Contour")
.size(10.0)
.color(colors.text_secondary),
);
ui.horizontal(|ui| {
if contour_combobox(ui, contour) {
changed = true;
}
});
ui.end_row();
ui.label(
RichText::new("Depth")
.size(10.0)
.color(colors.text_secondary),
);
if ui
.add(egui::Slider::new(depth, 0.0..=10.0).show_value(true))
.changed()
{
changed = true;
}
ui.end_row();
ui.label(
RichText::new("Size")
.size(10.0)
.color(colors.text_secondary),
);
if ui
.add(egui::Slider::new(size, 0.0..=100.0).show_value(true))
.changed()
{
changed = true;
}
ui.end_row();
ui.label(
RichText::new("Soften")
.size(10.0)
.color(colors.text_secondary),
);
if ui
.add(egui::Slider::new(soften, 0.0..=100.0).show_value(true))
.changed()
{
changed = true;
}
ui.end_row();
ui.label(
RichText::new("Angle")
.size(10.0)
.color(colors.text_secondary),
);
if ui
.add(egui::Slider::new(angle, 0.0..=360.0).show_value(true))
.changed()
{
changed = true;
}
ui.end_row();
ui.label(
RichText::new("Altitude")
.size(10.0)
.color(colors.text_secondary),
);
if ui
.add(egui::Slider::new(altitude, 0.0..=90.0).show_value(true))
.changed()
{
changed = true;
}
ui.end_row();
});
ui.add_space(2.0);
ui.label(
RichText::new("Highlight")
.strong()
.color(colors.text_secondary),
);
egui::Grid::new("bevel_emboss_highlight_grid")
.num_columns(2)
.spacing([6.0, 2.0])
.show(ui, |ui| {
ui.label(
RichText::new("Blend")
.size(10.0)
.color(colors.text_secondary),
);
ui.horizontal(|ui| {
if blend_mode_combobox(ui, highlight_blend_mode) {
changed = true;
}
});
ui.end_row();
ui.label(
RichText::new("Color")
.size(10.0)
.color(colors.text_secondary),
);
ui.horizontal(|ui| {
if edit_color(ui, "highlight_color", highlight_color) {
changed = true;
}
});
ui.end_row();
ui.label(
RichText::new("Opacity")
.size(10.0)
.color(colors.text_secondary),
);
if ui
.add(egui::Slider::new(highlight_opacity, 0.0..=1.0).show_value(true))
.changed()
{
changed = true;
}
ui.end_row();
});
ui.add_space(2.0);
ui.label(
RichText::new("Shadow")
.strong()
.color(colors.text_secondary),
);
egui::Grid::new("bevel_emboss_shadow_grid")
.num_columns(2)
.spacing([6.0, 2.0])
.show(ui, |ui| {
ui.label(
RichText::new("Blend")
.size(10.0)
.color(colors.text_secondary),
);
ui.horizontal(|ui| {
if blend_mode_combobox(ui, shadow_blend_mode) {
changed = true;
}
});
ui.end_row();
ui.label(
RichText::new("Color")
.size(10.0)
.color(colors.text_secondary),
);
ui.horizontal(|ui| {
if edit_color(ui, "shadow_color", shadow_color) {
changed = true;
}
});
ui.end_row();
ui.label(
RichText::new("Opacity")
.size(10.0)
.color(colors.text_secondary),
);
if ui
.add(egui::Slider::new(shadow_opacity, 0.0..=1.0).show_value(true))
.changed()
{
changed = true;
}
ui.end_row();
});
});
}
LayerStyle::Satin {
enabled,
opacity,
angle,
distance,
size,
color,
invert,
} => {
let title = "Satin";
let header_color = Color32::from_rgb(180, 140, 220);
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id(title),
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
})
.body(|ui| {
ui.spacing_mut().item_spacing.y = 1.0;
ui.horizontal(|ui| {
param_label(ui, "Color:", colors);
if edit_color(ui, "color", color) {
changed = true;
}
if ui.checkbox(invert, "Invert").changed() {
changed = true;
}
});
if compact_slider(ui, "Opacity:", opacity, 0.0..=1.0, colors) {
changed = true;
}
if compact_slider(ui, "Angle:", angle, 0.0..=360.0, colors) {
changed = true;
}
if compact_slider(ui, "Distance:", distance, 0.0..=100.0, colors) {
changed = true;
}
if compact_slider(ui, "Size:", size, 0.0..=100.0, colors) {
changed = true;
}
});
}
LayerStyle::ColorOverlay {
enabled,
opacity,
color,
blend_mode,
} => {
let title = "Color Overlay";
let header_color = Color32::GRAY;
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id(title),
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
})
.body(|ui| {
ui.spacing_mut().item_spacing.y = 1.0;
ui.horizontal(|ui| {
if blend_mode_combobox(ui, blend_mode) {
changed = true;
}
param_label(ui, "Color:", colors);
if edit_color(ui, "color", color) {
changed = true;
}
});
if compact_slider(ui, "Opacity:", opacity, 0.0..=1.0, colors) {
changed = true;
}
});
}
LayerStyle::GradientOverlay {
enabled,
opacity,
blend_mode,
angle,
scale,
gradient_type,
} => {
let title = "Gradient Overlay";
let header_color = Color32::GRAY;
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id(title),
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
})
.body(|ui| {
ui.spacing_mut().item_spacing.y = 1.0;
ui.horizontal(|ui| {
if blend_mode_combobox(ui, blend_mode) {
changed = true;
}
param_label(ui, "Type:", colors);
if gradient_type_combobox(ui, gradient_type) {
changed = true;
}
});
if compact_slider(ui, "Opacity:", opacity, 0.0..=1.0, colors) {
changed = true;
}
if compact_slider(ui, "Angle:", angle, 0.0..=360.0, colors) {
changed = true;
}
if compact_slider(ui, "Scale:", scale, 0.0..=10.0, colors) {
changed = true;
}
});
}
LayerStyle::PatternOverlay {
enabled,
opacity,
blend_mode,
scale,
pattern_name,
} => {
let title = "Pattern Overlay";
let header_color = Color32::GRAY;
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id(title),
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
})
.body(|ui| {
ui.spacing_mut().item_spacing.y = 1.0;
ui.horizontal(|ui| {
if blend_mode_combobox(ui, blend_mode) {
changed = true;
}
param_label(ui, "Pattern:", colors);
ui.add(egui::TextEdit::singleline(pattern_name).desired_width(60.0));
});
if compact_slider(ui, "Opacity:", opacity, 0.0..=1.0, colors) {
changed = true;
}
if compact_slider(ui, "Scale:", scale, 0.0..=10.0, colors) {
changed = true;
}
});
}
LayerStyle::Stroke {
enabled,
size,
position,
opacity,
color,
blend_mode,
} => {
let title = "Stroke";
let header_color = Color32::GRAY;
egui::collapsing_header::CollapsingState::load_with_default_open(
ui.ctx(),
ui.make_persistent_id(title),
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
})
.body(|ui| {
ui.spacing_mut().item_spacing.y = 1.0;
ui.horizontal(|ui| {
if blend_mode_combobox(ui, blend_mode) {
changed = true;
}
if stroke_position_combobox(ui, position) {
changed = true;
}
param_label(ui, "Color:", colors);
if edit_color(ui, "color", color) {
changed = true;
}
});
if compact_slider(ui, "Size:", size, 0.0..=100.0, colors) {
changed = true;
}
if compact_slider(ui, "Opacity:", opacity, 0.0..=1.0, colors) {
changed = true;
}
});
}
}
changed
}