9388e6f096
- 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.
1149 lines
39 KiB
Rust
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
|
|
}
|