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.
This commit is contained in:
2026-07-19 00:55:40 +03:00
parent bbabd2acdb
commit 9388e6f096
46 changed files with 1680 additions and 872 deletions
@@ -630,12 +630,22 @@ impl<'a> TabViewer for HcieTabViewer<'a> {
// Pin / float toggle for docked utility panels only.
// LayerStyles, LayerDetails and Plugins are dialog-style panels: they
// cannot float and do not show a pin toggle.
let supports_float =
!tab.is_document() && !matches!(tab, HciePane::Tools | HciePane::LayerStyles | HciePane::LayerDetails | HciePane::Plugins);
let supports_float = !tab.is_document()
&& !matches!(
tab,
HciePane::Tools
| HciePane::LayerStyles
| HciePane::LayerDetails
| HciePane::Plugins
);
if supports_float {
// Pin icon is always visible on the right side of utility tabs so
// users can float/re-dock a panel without first hovering the tab.
let pin_offset = if is_closeable && response.hovered() { 24.0 } else { 8.0 };
let pin_offset = if is_closeable && response.hovered() {
24.0
} else {
8.0
};
let pin_center =
egui::pos2(response.rect.right() - pin_offset, response.rect.center().y);
let pin_rect = egui::Rect::from_center_size(pin_center, egui::vec2(14.0, 14.0));
@@ -750,7 +760,10 @@ impl<'a> TabViewer for HcieTabViewer<'a> {
!tab.is_document()
&& !matches!(
tab,
HciePane::Tools | HciePane::LayerStyles | HciePane::LayerDetails | HciePane::Plugins
HciePane::Tools
| HciePane::LayerStyles
| HciePane::LayerDetails
| HciePane::Plugins
)
}
@@ -837,7 +850,13 @@ impl<'a> TabViewer for HcieTabViewer<'a> {
// Float / close options intentionally disabled for LayerStyles, LayerDetails and Plugins
// because these panels render as popups / dialogs, not docked utility cards.
let is_floating_allowed = !is_doc
&& !matches!(tab, HciePane::Tools | HciePane::LayerStyles | HciePane::LayerDetails | HciePane::Plugins);
&& !matches!(
tab,
HciePane::Tools
| HciePane::LayerStyles
| HciePane::LayerDetails
| HciePane::Plugins
);
if is_floating_allowed {
let mut float = false;
menu_item(ui, "Yeni Pencere", true, &mut float);
@@ -863,7 +882,11 @@ impl<'a> TabViewer for HcieTabViewer<'a> {
let close_enabled = self.is_closeable(tab);
// Dialog-style panels (LayerStyles/LayerDetails/Plugins) are closed by the
// dialog itself, not by the dock tab.
let close_in_context = close_enabled && !matches!(tab, HciePane::LayerStyles | HciePane::LayerDetails | HciePane::Plugins);
let close_in_context = close_enabled
&& !matches!(
tab,
HciePane::LayerStyles | HciePane::LayerDetails | HciePane::Plugins
);
menu_item(ui, close_label, close_in_context, &mut close);
if close && close_in_context {
if let HciePane::Document(idx) = tab {
@@ -11,9 +11,16 @@ pub use crate::app::document_state::{AppDocument, AppMode};
// Settings is named directly only by integration tests, but re-exported for API continuity.
#[allow(unused_imports)]
pub use crate::app::settings::{DockProfile, RecentFileEntry, Settings};
pub use crate::app::theme::{ThemeColors, ThemePreset, apply_theme, default_font_bytes, setup_custom_fonts};
pub use crate::app::tool_state::{SelectionOp, SelectionTransform, TextEditState, ToolState, TransformHandle};
pub use crate::app::utils::{build_save_dialog, composite_flood_fill, crop_colorimage, draw_premium_pin, format_color, format_save_error_hint, save_colorimage, suggest_alternative_path};
pub use crate::app::theme::{
apply_theme, default_font_bytes, setup_custom_fonts, ThemeColors, ThemePreset,
};
pub use crate::app::tool_state::{
SelectionOp, SelectionTransform, TextEditState, ToolState, TransformHandle,
};
pub use crate::app::utils::{
build_save_dialog, composite_flood_fill, crop_colorimage, draw_premium_pin, format_color,
format_save_error_hint, save_colorimage, suggest_alternative_path,
};
use crate::event_bus::{AppEvent, EventBus};
use crate::plugins;
use eframe::egui;
@@ -78,8 +85,6 @@ pub const TOOL_SLOTS: &[&[Tool]] = &[
],
];
// ── HcieApp ──────────────────────────────────────────────────────────────────
pub struct HcieApp {
@@ -267,11 +272,8 @@ fn default_dock_state() -> egui_dock::DockState<dock::HciePane> {
// Default split fractions are chosen for a 1280px-wide window and are
// immediately overridden by the layout solver using stored column widths.
// They only determine the tree structure, not final pixel sizes.
let [remaining, left_col2] = tree.split_left(
egui_dock::NodeIndex::root(),
0.17,
vec![HciePane::Brushes],
);
let [remaining, left_col2] =
tree.split_left(egui_dock::NodeIndex::root(), 0.17, vec![HciePane::Brushes]);
let [_left_brushes, _left_filters] = tree.split_below(left_col2, 0.5, vec![HciePane::Filters]);
let [center, right_col2] = tree.split_right(remaining, 0.83, vec![HciePane::Layers]);
@@ -677,7 +679,10 @@ impl HcieApp {
log::info!("Added {} ABR brush preset(s) from {:?}", added, path);
rfd::MessageDialog::new()
.set_title("Brush Import")
.set_description(&format!("Added {} brush preset(s) to the Imported category.", added))
.set_description(&format!(
"Added {} brush preset(s) to the Imported category.",
added
))
.set_level(rfd::MessageLevel::Info)
.show();
}
@@ -789,7 +794,7 @@ impl HcieApp {
/// * `doc_pane`: The `dock::HciePane` document pane enum to push.
pub fn push_document_to_center(&mut self, doc_pane: dock::HciePane) {
let mut target_node = None;
// 1. Prefer a leaf that already has document tabs
for (path, leaf) in self.dock_state.iter_leaves() {
if leaf.tabs.iter().any(|t| t.is_document()) {
@@ -797,7 +802,7 @@ impl HcieApp {
break;
}
}
if let Some(node_path) = target_node {
self.dock_state.set_focused_node_and_surface(node_path);
self.dock_state.push_to_focused_leaf(doc_pane.clone());
@@ -806,24 +811,22 @@ impl HcieApp {
// enforce_constraints will relocate it to the center if needed.
self.dock_state.push_to_focused_leaf(doc_pane.clone());
}
// Set the newly pushed tab as active to make it immediately visible
if let Some(tab_path) = self.dock_state.find_tab(&doc_pane) {
let _ = self.dock_state.set_active_tab(tab_path);
}
// Ensure dock layout settles and canvas texture uploads before the
// user sees the new document. Without this, the canvas appears blank
// on the first frame after opening a file.
self.dock_settle_repaints = self.dock_settle_repaints.max(5);
}
/// Push a newly opened/created document tab into the center document leaf.
fn push_document_to_center_placeholder() {}
/// Panic-safe tab removal: validates indices before calling egui_dock::remove_tab.
fn push_document_to_center_placeholder() {}
/// Panic-safe tab removal: validates indices before calling egui_dock::remove_tab.
/// Panic-safe tab removal: validates indices before calling egui_dock::remove_tab.
/// Returns `None` and logs a warning if indices are stale.
@@ -1021,9 +1024,7 @@ impl HcieApp {
}
self.active_doc = new_idx;
} else {
log::trace!(
"Removing document tab from egui_dock multi-document state"
);
log::trace!("Removing document tab from egui_dock multi-document state");
let tabs_to_remove = self.dock_find_tabs(
|t| matches!(t, dock::HciePane::Document(d) if *d == idx),
);
@@ -1833,10 +1834,7 @@ impl HcieApp {
.into_iter()
.collect::<Vec<_>>()
} else {
crate::brush_import::import_abr_with_prefix(
&data,
&file_stem,
)
crate::brush_import::import_abr_with_prefix(&data, &file_stem)
};
if imported.is_empty() {
rfd::MessageDialog::new()
@@ -1850,12 +1848,21 @@ impl HcieApp {
} else {
let added = imported.len();
for preset in imported {
if !self.state.brush_presets.iter().any(|p| p.id == preset.id) {
if !self
.state
.brush_presets
.iter()
.any(|p| p.id == preset.id)
{
self.state.brush_presets.push(preset);
}
}
self.event_bus.push(AppEvent::SaveSettings);
log::info!("Added {} imported brush preset(s) from {:?}", added, path);
log::info!(
"Added {} imported brush preset(s) from {:?}",
added,
path
);
rfd::MessageDialog::new()
.set_title("Brush Import")
.set_description(&format!(
@@ -5118,7 +5125,9 @@ impl eframe::App for HcieApp {
.state
.brush_presets
.iter()
.filter(|p| p.category == "Custom" || p.category == "Imported" || p.category == "Imported (ABR)")
.filter(|p| {
p.category == "Custom" || p.category == "Imported" || p.category == "Imported (ABR)"
})
.cloned()
.collect();
if let Ok(json) = serde_json::to_string(&persisted_presets) {
@@ -5139,4 +5148,3 @@ impl eframe::App for HcieApp {
std::time::Duration::from_secs(120)
}
}
@@ -218,26 +218,21 @@ pub fn apply_theme(ctx: &egui::Context, theme: ThemePreset) {
};
// ── Font sizes matching Qt 12px default ──
style.text_styles.insert(
egui::TextStyle::Body,
egui::FontId::proportional(12.0),
);
style.text_styles.insert(
egui::TextStyle::Button,
egui::FontId::proportional(12.0),
);
style.text_styles.insert(
egui::TextStyle::Small,
egui::FontId::proportional(11.0),
);
style.text_styles.insert(
egui::TextStyle::Heading,
egui::FontId::proportional(13.5),
);
style.text_styles.insert(
egui::TextStyle::Monospace,
egui::FontId::proportional(11.5),
);
style
.text_styles
.insert(egui::TextStyle::Body, egui::FontId::proportional(12.0));
style
.text_styles
.insert(egui::TextStyle::Button, egui::FontId::proportional(12.0));
style
.text_styles
.insert(egui::TextStyle::Small, egui::FontId::proportional(11.0));
style
.text_styles
.insert(egui::TextStyle::Heading, egui::FontId::proportional(13.5));
style
.text_styles
.insert(egui::TextStyle::Monospace, egui::FontId::proportional(11.5));
// ── Harmonized layout parameters (8px grid) ──
style.spacing.item_spacing = egui::vec2(4.0, 4.0);
File diff suppressed because it is too large Load Diff
@@ -102,6 +102,7 @@ pub fn show_layer_styles_panel(doc: &mut AppDocument, state: &mut ToolState, ui:
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(),
@@ -352,6 +353,49 @@ fn technique_combobox(ui: &mut egui::Ui, current: &mut String) -> bool {
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.
///
@@ -476,7 +520,7 @@ fn show_style_effect_mut(style: &mut LayerStyle, ui: &mut egui::Ui, colors: &The
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, "").changed() {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
@@ -527,7 +571,7 @@ fn show_style_effect_mut(style: &mut LayerStyle, ui: &mut egui::Ui, colors: &The
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, "").changed() {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
@@ -576,7 +620,7 @@ fn show_style_effect_mut(style: &mut LayerStyle, ui: &mut egui::Ui, colors: &The
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, "").changed() {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
@@ -619,7 +663,7 @@ fn show_style_effect_mut(style: &mut LayerStyle, ui: &mut egui::Ui, colors: &The
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, "").changed() {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
@@ -662,6 +706,7 @@ fn show_style_effect_mut(style: &mut LayerStyle, ui: &mut egui::Ui, colors: &The
highlight_color,
shadow_blend_mode,
shadow_color,
contour,
} => {
let title = "Bevel & Emboss";
let header_color = Color32::from_rgb(100, 180, 220);
@@ -671,7 +716,7 @@ fn show_style_effect_mut(style: &mut LayerStyle, ui: &mut egui::Ui, colors: &The
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, "").changed() {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
@@ -718,6 +763,18 @@ fn show_style_effect_mut(style: &mut LayerStyle, ui: &mut egui::Ui, colors: &The
});
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)
@@ -898,7 +955,7 @@ fn show_style_effect_mut(style: &mut LayerStyle, ui: &mut egui::Ui, colors: &The
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, "").changed() {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
@@ -942,7 +999,7 @@ fn show_style_effect_mut(style: &mut LayerStyle, ui: &mut egui::Ui, colors: &The
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, "").changed() {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
@@ -979,7 +1036,7 @@ fn show_style_effect_mut(style: &mut LayerStyle, ui: &mut egui::Ui, colors: &The
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, "").changed() {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
@@ -1021,7 +1078,7 @@ fn show_style_effect_mut(style: &mut LayerStyle, ui: &mut egui::Ui, colors: &The
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, "").changed() {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
@@ -1059,7 +1116,7 @@ fn show_style_effect_mut(style: &mut LayerStyle, ui: &mut egui::Ui, colors: &The
false,
)
.show_header(ui, |ui| {
if ui.checkbox(enabled, "").changed() {
if ui.checkbox(enabled, String::new()).changed() {
changed = true;
}
ui.label(RichText::new(title).strong().color(header_color));
@@ -149,7 +149,11 @@ pub fn suggest_alternative_path(original: &std::path::Path) -> std::path::PathBu
/// * `default_format` - Lowercase extension to pre-select (`kra`, `hcie`, `png`, ...).
/// * `file_stem` - Base name for the suggested file.
/// * `is_export` - When `true`, appends `_export` to the file stem.
pub fn build_save_dialog(default_format: &str, file_stem: &str, is_export: bool) -> rfd::FileDialog {
pub fn build_save_dialog(
default_format: &str,
file_stem: &str,
is_export: bool,
) -> rfd::FileDialog {
let suffix = if is_export { "_export" } else { "" };
let file_name = format!("{}{}.{}", file_stem, suffix, default_format);
@@ -252,7 +252,8 @@ fn harvest_brs2_sampled_brushes(data: &[u8], base_pos: usize) -> Vec<BrushPreset
} else {
(w, h)
};
if let Some(decoded) = try_decode_and_scale(slice, w as usize, h as usize, final_w, final_h) {
if let Some(decoded) = try_decode_and_scale(slice, w as usize, h as usize, final_w, final_h)
{
presets.push(BrushPreset {
id: format!("abr_brs2_{}_{}x{}", base_pos + i, final_w, final_h),
name: format!("Brush {}x{}", final_w, final_h),
@@ -441,7 +442,8 @@ fn extract_desc_block_names(data: &[u8]) -> Vec<String> {
if p + 4 > block.len() {
break;
}
let nchars = u32::from_be_bytes([block[p], block[p + 1], block[p + 2], block[p + 3]]) as usize;
let nchars =
u32::from_be_bytes([block[p], block[p + 1], block[p + 2], block[p + 3]]) as usize;
let text_start = p + 4;
if nchars == 0 || text_start + nchars * 2 > block.len() {
idx += 1;
@@ -555,7 +557,12 @@ pub fn import_kpp(data: &[u8], name_hint: &str) -> Option<BrushPreset> {
}
}
Some(BrushPreset {
id: format!("kpp_imported_{}_{}x{}", name_hint.replace(' ', "_"), final_w, final_h),
id: format!(
"kpp_imported_{}_{}x{}",
name_hint.replace(' ', "_"),
final_w,
final_h
),
name: format!("{} (KPP)", name_hint),
category: "Imported".to_string(),
tip: BrushTip {
@@ -623,7 +630,10 @@ mod tests {
}
};
let presets = import_abr(&data);
let bitmap: Vec<_> = presets.iter().filter(|p| p.style == BrushStyle::Bitmap).collect();
let bitmap: Vec<_> = presets
.iter()
.filter(|p| p.style == BrushStyle::Bitmap)
.collect();
assert!(!bitmap.is_empty(), "Should have bitmap presets");
let has_names = bitmap.iter().any(|p| !p.name.starts_with("Brush "));
assert!(has_names, "Bitmap presets should have descriptive names");
@@ -1098,7 +1098,10 @@ impl<'a> egui::Widget for CanvasWidget<'a> {
let t_stroke_end = std::time::Instant::now();
let layer_id = self.doc.engine.active_layer_id();
self.doc.engine.end_stroke(layer_id);
log::warn!("[PERF] self.doc.engine.end_stroke took {}ms", t_stroke_end.elapsed().as_millis());
log::warn!(
"[PERF] self.doc.engine.end_stroke took {}ms",
t_stroke_end.elapsed().as_millis()
);
self.doc.last_stroke_end_time = Some(std::time::Instant::now());
let desc = match self.state.active_tool {
@@ -1474,40 +1477,108 @@ impl<'a> egui::Widget for CanvasWidget<'a> {
}
Tool::Brush | _ => {
tip.style = match self.state.tool_configs.brush.style {
hcie_engine_api::tools::BrushStyle::Default => hcie_engine_api::BrushStyle::Round,
hcie_engine_api::tools::BrushStyle::Round => hcie_engine_api::BrushStyle::Round,
hcie_engine_api::tools::BrushStyle::Square => hcie_engine_api::BrushStyle::Square,
hcie_engine_api::tools::BrushStyle::HardRound => hcie_engine_api::BrushStyle::HardRound,
hcie_engine_api::tools::BrushStyle::SoftRound => hcie_engine_api::BrushStyle::SoftRound,
hcie_engine_api::tools::BrushStyle::Noise => hcie_engine_api::BrushStyle::Noise,
hcie_engine_api::tools::BrushStyle::Texture => hcie_engine_api::BrushStyle::Texture,
hcie_engine_api::tools::BrushStyle::Spray => hcie_engine_api::BrushStyle::Spray,
hcie_engine_api::tools::BrushStyle::Pen => hcie_engine_api::BrushStyle::Pen,
hcie_engine_api::tools::BrushStyle::Oil => hcie_engine_api::BrushStyle::Oil,
hcie_engine_api::tools::BrushStyle::Charcoal => hcie_engine_api::BrushStyle::Charcoal,
hcie_engine_api::tools::BrushStyle::Leaf => hcie_engine_api::BrushStyle::Leaf,
hcie_engine_api::tools::BrushStyle::Rock => hcie_engine_api::BrushStyle::Rock,
hcie_engine_api::tools::BrushStyle::Meadow => hcie_engine_api::BrushStyle::Meadow,
hcie_engine_api::tools::BrushStyle::Wood => hcie_engine_api::BrushStyle::Wood,
hcie_engine_api::tools::BrushStyle::Watercolor => hcie_engine_api::BrushStyle::Watercolor,
hcie_engine_api::tools::BrushStyle::Calligraphy => hcie_engine_api::BrushStyle::Calligraphy,
hcie_engine_api::tools::BrushStyle::Marker => hcie_engine_api::BrushStyle::Marker,
hcie_engine_api::tools::BrushStyle::Sketch => hcie_engine_api::BrushStyle::Sketch,
hcie_engine_api::tools::BrushStyle::Hatch => hcie_engine_api::BrushStyle::Hatch,
hcie_engine_api::tools::BrushStyle::Star => hcie_engine_api::BrushStyle::Star,
hcie_engine_api::tools::BrushStyle::Glow => hcie_engine_api::BrushStyle::Glow,
hcie_engine_api::tools::BrushStyle::Airbrush => hcie_engine_api::BrushStyle::Airbrush,
hcie_engine_api::tools::BrushStyle::Pencil => hcie_engine_api::BrushStyle::Pencil,
hcie_engine_api::tools::BrushStyle::Crayon => hcie_engine_api::BrushStyle::Crayon,
hcie_engine_api::tools::BrushStyle::WetPaint => hcie_engine_api::BrushStyle::WetPaint,
hcie_engine_api::tools::BrushStyle::InkPen => hcie_engine_api::BrushStyle::InkPen,
hcie_engine_api::tools::BrushStyle::Clouds => hcie_engine_api::BrushStyle::Clouds,
hcie_engine_api::tools::BrushStyle::Dirt => hcie_engine_api::BrushStyle::Dirt,
hcie_engine_api::tools::BrushStyle::Tree => hcie_engine_api::BrushStyle::Tree,
hcie_engine_api::tools::BrushStyle::Bristle => hcie_engine_api::BrushStyle::Bristle,
hcie_engine_api::tools::BrushStyle::Mixer => hcie_engine_api::BrushStyle::Mixer,
hcie_engine_api::tools::BrushStyle::Blender => hcie_engine_api::BrushStyle::Blender,
hcie_engine_api::tools::BrushStyle::Bitmap => hcie_engine_api::BrushStyle::Bitmap,
hcie_engine_api::tools::BrushStyle::Default => {
hcie_engine_api::BrushStyle::Round
}
hcie_engine_api::tools::BrushStyle::Round => {
hcie_engine_api::BrushStyle::Round
}
hcie_engine_api::tools::BrushStyle::Square => {
hcie_engine_api::BrushStyle::Square
}
hcie_engine_api::tools::BrushStyle::HardRound => {
hcie_engine_api::BrushStyle::HardRound
}
hcie_engine_api::tools::BrushStyle::SoftRound => {
hcie_engine_api::BrushStyle::SoftRound
}
hcie_engine_api::tools::BrushStyle::Noise => {
hcie_engine_api::BrushStyle::Noise
}
hcie_engine_api::tools::BrushStyle::Texture => {
hcie_engine_api::BrushStyle::Texture
}
hcie_engine_api::tools::BrushStyle::Spray => {
hcie_engine_api::BrushStyle::Spray
}
hcie_engine_api::tools::BrushStyle::Pen => {
hcie_engine_api::BrushStyle::Pen
}
hcie_engine_api::tools::BrushStyle::Oil => {
hcie_engine_api::BrushStyle::Oil
}
hcie_engine_api::tools::BrushStyle::Charcoal => {
hcie_engine_api::BrushStyle::Charcoal
}
hcie_engine_api::tools::BrushStyle::Leaf => {
hcie_engine_api::BrushStyle::Leaf
}
hcie_engine_api::tools::BrushStyle::Rock => {
hcie_engine_api::BrushStyle::Rock
}
hcie_engine_api::tools::BrushStyle::Meadow => {
hcie_engine_api::BrushStyle::Meadow
}
hcie_engine_api::tools::BrushStyle::Wood => {
hcie_engine_api::BrushStyle::Wood
}
hcie_engine_api::tools::BrushStyle::Watercolor => {
hcie_engine_api::BrushStyle::Watercolor
}
hcie_engine_api::tools::BrushStyle::Calligraphy => {
hcie_engine_api::BrushStyle::Calligraphy
}
hcie_engine_api::tools::BrushStyle::Marker => {
hcie_engine_api::BrushStyle::Marker
}
hcie_engine_api::tools::BrushStyle::Sketch => {
hcie_engine_api::BrushStyle::Sketch
}
hcie_engine_api::tools::BrushStyle::Hatch => {
hcie_engine_api::BrushStyle::Hatch
}
hcie_engine_api::tools::BrushStyle::Star => {
hcie_engine_api::BrushStyle::Star
}
hcie_engine_api::tools::BrushStyle::Glow => {
hcie_engine_api::BrushStyle::Glow
}
hcie_engine_api::tools::BrushStyle::Airbrush => {
hcie_engine_api::BrushStyle::Airbrush
}
hcie_engine_api::tools::BrushStyle::Pencil => {
hcie_engine_api::BrushStyle::Pencil
}
hcie_engine_api::tools::BrushStyle::Crayon => {
hcie_engine_api::BrushStyle::Crayon
}
hcie_engine_api::tools::BrushStyle::WetPaint => {
hcie_engine_api::BrushStyle::WetPaint
}
hcie_engine_api::tools::BrushStyle::InkPen => {
hcie_engine_api::BrushStyle::InkPen
}
hcie_engine_api::tools::BrushStyle::Clouds => {
hcie_engine_api::BrushStyle::Clouds
}
hcie_engine_api::tools::BrushStyle::Dirt => {
hcie_engine_api::BrushStyle::Dirt
}
hcie_engine_api::tools::BrushStyle::Tree => {
hcie_engine_api::BrushStyle::Tree
}
hcie_engine_api::tools::BrushStyle::Bristle => {
hcie_engine_api::BrushStyle::Bristle
}
hcie_engine_api::tools::BrushStyle::Mixer => {
hcie_engine_api::BrushStyle::Mixer
}
hcie_engine_api::tools::BrushStyle::Blender => {
hcie_engine_api::BrushStyle::Blender
}
hcie_engine_api::tools::BrushStyle::Bitmap => {
hcie_engine_api::BrushStyle::Bitmap
}
};
tip.size = self.state.tool_configs.brush.size;
tip.opacity = self.state.tool_configs.brush.opacity;
@@ -1600,25 +1671,20 @@ impl<'a> egui::Widget for CanvasWidget<'a> {
});
}
if let Some(tex) = ui
.data(|d| d.get_temp::<egui::TextureHandle>(ui.id().with("cursor_stamp_tex")))
if let Some(tex) =
ui.data(|d| d.get_temp::<egui::TextureHandle>(ui.id().with("cursor_stamp_tex")))
{
// Display the stamp preview at the correct diameter.
// The texture was rendered at tex_size × tex_size pixels;
// display it at diameter_px × diameter_px screen pixels so
// the preview matches the actual brush footprint.
let display_dim = egui::vec2(diameter_px, diameter_px);
let rect = egui::Rect::from_center_size(
egui::pos2(screen_x, screen_y),
display_dim,
);
let rect =
egui::Rect::from_center_size(egui::pos2(screen_x, screen_y), display_dim);
painter.add(egui::Shape::image(
tex.id(),
rect,
egui::Rect::from_min_max(
egui::pos2(0.0, 0.0),
egui::pos2(1.0, 1.0),
),
egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)),
egui::Color32::WHITE,
));
@@ -2551,5 +2617,3 @@ fn find_text_layer_at(engine: &hcie_engine_api::Engine, x: f32, y: f32) -> Optio
}
None
}
@@ -203,11 +203,14 @@ pub fn render_composition(
let src_start = ((y * rw + x0) as usize) * 4;
let dst_start = ((y - y0) as usize * region_w) * 4;
let row_bytes = region_w * 4;
region_pixels[dst_start..dst_start + row_bytes]
.copy_from_slice(&doc.composite_buffer[src_start..src_start + row_bytes]);
region_pixels[dst_start..dst_start + row_bytes].copy_from_slice(
&doc.composite_buffer[src_start..src_start + row_bytes],
);
}
let region_image =
egui::ColorImage::from_rgba_unmultiplied([region_w, region_h], &region_pixels);
let region_image = egui::ColorImage::from_rgba_unmultiplied(
[region_w, region_h],
&region_pixels,
);
tex.set_partial([x0 as usize, y0 as usize], region_image, options);
}
} else {