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:
@@ -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], ®ion_pixels);
|
||||
let region_image = egui::ColorImage::from_rgba_unmultiplied(
|
||||
[region_w, region_h],
|
||||
®ion_pixels,
|
||||
);
|
||||
tex.set_partial([x0 as usize, y0 as usize], region_image, options);
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user