init
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
[env]
|
||||
PKG_CONFIG_PATH = "/tmp/dav1d-dev/usr/lib/x86_64-linux-gnu/pkgconfig"
|
||||
LIBRARY_PATH = "/tmp/dav1d-dev/usr/lib/x86_64-linux-gnu"
|
||||
Executable
+1075
File diff suppressed because it is too large
Load Diff
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||
SCRIPT="${REPO_ROOT}/.githooks/generate_semantic_report.py"
|
||||
REPORT="${REPO_ROOT}/logs/semantic_impact_report.txt"
|
||||
|
||||
if [ -f "$SCRIPT" ]; then
|
||||
cd "$REPO_ROOT"
|
||||
echo ""
|
||||
echo "🔍 SEMANTIK ETKI RAPORU (commit sonrasi):"
|
||||
echo "─────────────────────────────────────────────"
|
||||
python3 "$SCRIPT" --mode last | tee "$REPORT"
|
||||
RESULT=${PIPESTATUS[0]}
|
||||
|
||||
if [ $RESULT -ne 0 ]; then
|
||||
echo ""
|
||||
echo "⚠️ Yukarida BLOKE EDIEN risk seviyesi tespit edildi. Commit tamamlanmistir;"
|
||||
echo " ama ilgili modüllerin doğru çalışıp çalışmadığını `cargo check --workspace` ile kontrol edin."
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
CHANGED_FILES=$(git diff --cached --name-only)
|
||||
if [ -z "$CHANGED_FILES" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "🔍 Semantik etki analizi başlatılıyor..."
|
||||
echo "Değişen dosyalar:"
|
||||
echo "$CHANGED_FILES"
|
||||
echo "--------------------------------------------------"
|
||||
|
||||
python3 .githooks/semantic_analyzer.py "$CHANGED_FILES"
|
||||
ANALYSIS_RESULT=$?
|
||||
|
||||
if [ $ANALYSIS_RESULT -ne 0 ]; then
|
||||
echo "❌ COMMIT ENGELLENDİ: Kritik bir modül risk altında veya testler başarısız."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
def load_manifest():
|
||||
with open("project_manifest.json", "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
def main():
|
||||
changed_files = sys.argv[1].split('\n')
|
||||
manifest = load_manifest()
|
||||
|
||||
critical_targets = set()
|
||||
high_risk_change = False
|
||||
|
||||
for changed_file in changed_files:
|
||||
if not changed_file.strip(): continue
|
||||
|
||||
files = manifest.get("files", []) if isinstance(manifest, dict) else manifest
|
||||
for entry in files:
|
||||
if entry.get("file_path", "") in changed_file:
|
||||
print(f"⚠️ [UYARI] Değişiklik yapılan modül: {entry['file_path']}")
|
||||
print(f" Açıklama: {entry['description']}")
|
||||
print(f" Patlama Yarıçapı (Blast Radius): {entry['blast_radius']}")
|
||||
|
||||
if entry["blast_radius"] == "HIGH":
|
||||
high_risk_change = True
|
||||
|
||||
for dep in entry.get("semantic_dependencies", []):
|
||||
critical_targets.add((dep["target_file"], dep["impact_description"]))
|
||||
|
||||
if critical_targets:
|
||||
os.makedirs("logs", exist_ok=True)
|
||||
print("\n🚨 BU DEĞİŞİKLİKTEN ETKİLENEBİLECEK ÜCRA KÖŞELER VE ÖZELLİKLER:")
|
||||
with open("logs/semantic_impact_report.txt", "w") as log:
|
||||
log.write("=== SEMANTIK ETKI VE RISK RAPORU ===\n\n")
|
||||
for target, reason in critical_targets:
|
||||
report_line = f"- ETKİLENEN MODÜL: {target}\n RİSK NEDENİ: {reason}\n"
|
||||
print(report_line)
|
||||
log.write(report_line + "\n")
|
||||
|
||||
if high_risk_change:
|
||||
print("🔄 Yüksek riskli alan değiştiği için Rust entegrasyon testleri zorunlu olarak çalıştırılıyor...")
|
||||
result = subprocess.run("cargo test --all-features", shell=True)
|
||||
if result.returncode != 0:
|
||||
print("❌ Entegrasyon testleri başarısız oldu! Özellik kaybı var.")
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SCRIPT="$REPO_ROOT/.githooks/generate_semantic_report.py"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Kullanım: $0 <command>
|
||||
|
||||
Commands:
|
||||
precommit Layer 1+2 hızlı kontrol (staged)
|
||||
audit Layer 1+2 son commit analizi
|
||||
full Layer 1+2+3 tam pre-commit (yavaş)
|
||||
full-audit Layer 1+2+3 tam post-commit
|
||||
quick Sadece Layer 1 bağımlılık grafiği
|
||||
json JSON çıktı (CI için)
|
||||
help Bu mesaj
|
||||
|
||||
Örnek:
|
||||
./\$(basename "$0") precommit
|
||||
./\$(basename "$0") full --save
|
||||
./\$(basename "$0") json
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
[ $# -eq 0 ] && usage
|
||||
|
||||
case "$1" in
|
||||
precommit)
|
||||
shift
|
||||
python3 "$SCRIPT" --mode staged "$@"
|
||||
;;
|
||||
audit)
|
||||
shift
|
||||
python3 "$SCRIPT" --mode last "$@"
|
||||
;;
|
||||
full)
|
||||
shift
|
||||
python3 "$SCRIPT" --mode staged --semver "$@"
|
||||
;;
|
||||
full-audit)
|
||||
shift
|
||||
python3 "$SCRIPT" --mode last --semver "$@"
|
||||
;;
|
||||
quick)
|
||||
shift
|
||||
python3 "$SCRIPT" --mode staged --no-api "$@"
|
||||
;;
|
||||
json)
|
||||
shift
|
||||
python3 "$SCRIPT" --mode last --json "$@"
|
||||
;;
|
||||
help|--help|-h)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echo "Bilinmeyen komut: $1"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
# Cargo / Rust build artifacts
|
||||
/target/
|
||||
# c++
|
||||
/build/
|
||||
|
||||
# IDEs and Editors
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
.directory
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
*.bk
|
||||
|
||||
# Local/User-specific configurations
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Consolidated rules from sub-projects
|
||||
# Cargo/Rust additional artifacts
|
||||
**/target/
|
||||
Cargo.toml.orig
|
||||
*.so
|
||||
*.dll
|
||||
*.dylib
|
||||
*.rlib
|
||||
*.pdb
|
||||
**/*.rs.bk
|
||||
**/Cargo.lock
|
||||
!/Cargo.lock
|
||||
**/build.id
|
||||
|
||||
# Node/Kilo artifacts
|
||||
node_modules/
|
||||
**/node_modules/
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
bun.lock
|
||||
yarn.lock
|
||||
agent-manager.json
|
||||
**/package.json
|
||||
|
||||
# OS-specific
|
||||
Thumbs.db
|
||||
|
||||
#custom
|
||||
_images
|
||||
_tmp
|
||||
.history
|
||||
# Custom dev environment
|
||||
/.rustup/
|
||||
/logs/last_semantic_audit.txt
|
||||
/logs/semantic_impact_*.txt
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"$schema": "https://app.kilo.ai/config.json"
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
# Plan: Custom Title Bar (Revised)
|
||||
|
||||
> Scope: title bar only. Panels/docking widths are already acceptable.
|
||||
> Target workspace: `hcie-egui-app/crates/hcie-gui-egui/`
|
||||
|
||||
## Goal
|
||||
Draw a single custom title bar at the very top of the main window.
|
||||
- Height: 25 px.
|
||||
- Contains: app icon, menus, search field, centered title, customizable button area, and window controls (min/max/close).
|
||||
- No extra row below/above it.
|
||||
- Distinct colour from the main window.
|
||||
- Used to drag the main window.
|
||||
- Title shows program name, full version, and active document name (truncatable).
|
||||
- Provides an extensible area for custom buttons; default button toggles the panel list.
|
||||
- Includes a quick search box for feature access.
|
||||
|
||||
## Decisions
|
||||
|
||||
| Decision | Chosen value |
|
||||
|----------|--------------|
|
||||
| Always visible? | **Yes** — remove the `use_grimdock` gate so the bar is always drawn. |
|
||||
| Height | 25 px. |
|
||||
| Colour source | Theme token: add `title_bar_bg` to the theme palette, defaulting to a tone clearly different from `bg_panel`. |
|
||||
| App icon | `hcie-egui-app/assets/icons/hcie-icon.png`, rendered at 16×16 px. |
|
||||
| Title text | `HCIE v{full_version} — {doc_name}`, 11 px, strong, truncatable. `full_version` comes from `env!("CARGO_PKG_VERSION")` of `hcie-gui-egui`. |
|
||||
| Title position | Centered in its own layout column. |
|
||||
| Custom button area | Separate right-of-center column; default contains only the panel-list toggle (☰/◧). Future buttons are added here. |
|
||||
| Window controls | Rightmost column, 32×25 px, right-to-left order: **close, maximize, minimize** (close at the far right). |
|
||||
| Drag behaviour | Drag on any empty bar area + double-click to maximize/restore. Remove the left/right grip-dot handles. |
|
||||
| Native decorations | Default `false`; user may still enable via Settings. |
|
||||
|
||||
## Layout (left → right)
|
||||
|
||||
```
|
||||
[icon] [File] [Edit] [Tools] ... [Help] [Search…] | [HCIE v4.0.0 — Untitled] | [☰] [future buttons] | [_ □ ✕]
|
||||
```
|
||||
|
||||
- Left column packs to its content.
|
||||
- Center column fills remaining width, centers the title, truncates overflow.
|
||||
- Right-of-center column packs to its content.
|
||||
- Rightmost column packs to its content.
|
||||
|
||||
## Files to modify
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `hcie-egui-app/crates/hcie-gui-egui/src/app/panels.rs` | Rewrite `show_menu_bar` as a 4-column layout. Add `build_title` helper. Remove grip-dot helpers. Load and render `hcie-icon.png`. Draw window controls at 32×25. Remove `use_grimdock` guards for icon/title/search/toggle. |
|
||||
| `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` | Add `title_bar_bg` to `ThemeColors` and resolve it in every theme preset. |
|
||||
| `hcie-egui-app/crates/hcie-gui-egui/src/app/settings.rs` | Default `show_native_decorations` to `false`. |
|
||||
|
||||
## Implementation notes
|
||||
|
||||
1. **Icon loading:** use `include_bytes!("../../../assets/icons/hcie-icon.png")` inside `panels.rs` and `image::load_from_memory` to create an `egui::ColorImage` / `egui::ImageSource`. Cache the loaded handle in `HcieApp` to avoid decoding every frame.
|
||||
2. **Version string:** use `env!("CARGO_PKG_VERSION")` when building the title.
|
||||
3. **Window controls:** update `draw_window_controls` signature to accept `bar_h` and use `btn_h = bar_h`, `btn_w = 32.0`.
|
||||
4. **Drag area:** allocate the entire remaining bar rectangle with `Sense::click_and_drag()` (or wrap the bar content with a single invisible drag responder). Double-click toggles maximized state.
|
||||
5. **Native decorations:** when `show_native_decorations == false`, the custom controls are shown. When `true`, the OS draws its own bar and the custom controls are hidden, but the custom bar still contains icon/menus/search/title/custom buttons. The setting default becomes `false` so the single custom bar is the out-of-box experience.
|
||||
6. **Extensible button area:** add a `Vec` or small helper in `HcieApp` to register future custom title-bar buttons; for this plan implement only the panel-list toggle.
|
||||
|
||||
## Validation
|
||||
|
||||
1. `cargo build -p hcie-gui-egui` succeeds.
|
||||
2. Launching the app shows one 25 px bar at the top with icon, menus, search, centered title, panel toggle, and window controls.
|
||||
3. Title truncates when the window is very narrow.
|
||||
4. Dragging the bar moves the window; double-click maximizes/restores.
|
||||
5. Panel-list toggle opens/closes the panel list.
|
||||
6. Native decorations default is off; enabling in Settings shows the OS title bar in addition to the custom bar (documented behaviour).
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Adding further custom buttons beyond the panel-list toggle (area is reserved and documented).
|
||||
- OS-level accent colour detection; kept as a theme token for determinism.
|
||||
- Changing the docking/panel minimum widths or layout (already acceptable).
|
||||
@@ -0,0 +1,105 @@
|
||||
# Document Expansion Plan for Pasted Panel Larger Than Canvas
|
||||
|
||||
## Problem
|
||||
When pasting a panel via Ctrl+V that is larger than the current canvas, the pasted content gets cropped instead of permanently expanding the document and all layers.
|
||||
|
||||
## Discoveries
|
||||
|
||||
### Paste Handler Location
|
||||
`hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs:1827-1898` — `AppEvent::ClipboardPaste`:
|
||||
- Gets clipboard image, centers it, creates new layer
|
||||
- Line 1871: `if dst_x >= 0 && dst_x < canvas_w as i32 && dst_y >= 0 && dst_y < canvas_h as i32` — silently clips pixels outside canvas
|
||||
|
||||
### Engine API — resize_canvas Already Exists
|
||||
`hcie-engine-api/src/lib.rs:504-507`:
|
||||
```rust
|
||||
pub fn resize_canvas(&mut self, w: u32, h: u32) {
|
||||
self.document.resize_canvas(w, h);
|
||||
self.pre_tile_all_layers();
|
||||
}
|
||||
```
|
||||
|
||||
### Document Resize Implementation
|
||||
`hcie-document/src/lib.rs:384-407` — Resizes all layer pixel buffers, copies existing content, zeroes new areas. Already handles all layers.
|
||||
|
||||
### Dialog System
|
||||
`dialogs.rs` — `show_new_image_dialog` pattern exists. `modern_dialogs.rs` — ready button/frame helpers.
|
||||
|
||||
**No locked crate changes needed** — `resize_canvas` is already exposed in `hcie-engine-api`.
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
### Task 1: Add `ExpandCanvas` event variant
|
||||
**File:** `hcie-egui-app/crates/hcie-gui-egui/src/event_bus.rs`
|
||||
|
||||
Add to `AppEvent` enum:
|
||||
```rust
|
||||
ExpandCanvas { w: u32, h: u32 },
|
||||
```
|
||||
|
||||
### Task 2: Add `show_expand_dialog` state field
|
||||
**File:** `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs`
|
||||
|
||||
Add field to `HcieApp`:
|
||||
```rust
|
||||
pub show_expand_dialog: Option<(u32, u32, u32, u32)>,
|
||||
// (paste_width, paste_height, canvas_width, canvas_height)
|
||||
```
|
||||
|
||||
Initialize in `HcieApp::new`:
|
||||
```rust
|
||||
show_expand_dialog: None,
|
||||
```
|
||||
|
||||
### Task 3: Modify paste handler to detect oversized paste
|
||||
**File:** `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` — `ClipboardPaste` handler (lines 1827-1898)
|
||||
|
||||
After clipboard data is resolved (after line 1856), before creating the layer:
|
||||
- If `clip.width > canvas_w || clip.height > canvas_h`:
|
||||
- Set `self.show_expand_dialog = Some((clip.width, clip.height, canvas_w, canvas_h))`
|
||||
- Store clipboard data in `self.internal_clipboard` for later use
|
||||
- **Skip** the paste (don't create layer, don't write pixels)
|
||||
- Else: proceed with existing paste flow unchanged
|
||||
|
||||
### Task 4: Add `ExpandCanvas` event handler
|
||||
**File:** `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` — event loop
|
||||
|
||||
```rust
|
||||
AppEvent::ExpandCanvas { w, h } => {
|
||||
if let Some(doc) = self.active_doc_mut() {
|
||||
doc.engine.resize_canvas(w, h);
|
||||
doc.engine.set_modified(true);
|
||||
self.event_bus.push(AppEvent::RenderRequested);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Task 5: Create expand confirmation dialog
|
||||
**File:** `hcie-egui-app/crates/hcie-gui-egui/src/app/dialogs.rs`
|
||||
|
||||
New function `show_expand_dialog(app: &mut HcieApp, ctx: &egui::Context)`:
|
||||
- Follow `show_new_image_dialog` pattern (dim background, centered area, modern_dialog_frame)
|
||||
- Show: "Pasted image (WxH) exceeds canvas (WxH). Expand document to fit?"
|
||||
- Two buttons:
|
||||
- **Expand** (accent_button): Push `AppEvent::ExpandCanvas { w: paste_w, h: paste_h }`, then push `AppEvent::ClipboardPaste` (to retry paste on expanded canvas), close dialog
|
||||
- **Cancel** (ghost_button): Close dialog, paste proceeds as-is (will be cropped)
|
||||
|
||||
### Task 6: Render expand dialog in update loop
|
||||
**File:** `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` — `update` method
|
||||
|
||||
Add alongside existing dialog calls:
|
||||
```rust
|
||||
if self.show_expand_dialog.is_some() {
|
||||
dialogs::show_expand_dialog(self, ctx);
|
||||
}
|
||||
```
|
||||
|
||||
## Validation Plan
|
||||
|
||||
1. **Paste smaller image**: 100x100 into 800x600 → no dialog, normal paste
|
||||
2. **Paste larger image + Cancel**: 1920x1080 into 800x600 → dialog appears, Cancel → image is cropped to canvas
|
||||
3. **Paste larger image + Expand**: 1920x1080 into 800x600 → dialog appears, Expand → canvas resizes to 1920x1080, all layers expand, pasted image fits perfectly
|
||||
|
||||
## Risks
|
||||
- **Performance**: `resize_canvas` reallocates all layer pixel buffers. For very large documents this may cause a frame drop. Acceptable for user-initiated paste.
|
||||
- **Retrigger loop**: After Expand, pushing `ClipboardPaste` again re-reads clipboard. Ensure `internal_clipboard` is still set so it doesn't re-prompt. The second paste will see canvas is now large enough and proceed normally.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Hide Tools-panel hover-blue expand bar
|
||||
|
||||
## Goal
|
||||
|
||||
Make the vertical dodge-blue bar that appears on the right edge of the fixed-width Tools panel disappear permanently so the panel no longer advertises a resize affordance that is functionally disabled.
|
||||
|
||||
## Root cause
|
||||
|
||||
In `hcie-gui-egui/src/app/mod.rs:2573-2617` the `egui_dock` separator style is configured with the active accent colour for `color_hovered` and `color_dragged`. At the boundary between the locked 36 px Tools column and the main content it renders a 4 px bar that turns blue on hover. The column is pinned (`layout_solver.lock_tools_column` + `Tools => TOOLBOX_WIDTH` in `dock.rs`), so resize is impossible; the coloured separator is purely cosmetic and unwanted.
|
||||
|
||||
## Changes
|
||||
|
||||
**File:** `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs`
|
||||
**Range:** `ui_main_assembly` separator-style block (~L2570-2617)
|
||||
|
||||
Replace the existing colour assignments:
|
||||
|
||||
```rust
|
||||
style.separator.color_idle = splitter_idle;
|
||||
style.separator.color_hovered = splitter_hover;
|
||||
style.separator.color_dragged = splitter_hover;
|
||||
```
|
||||
|
||||
with fully-transparent values and remove the drag affordance:
|
||||
|
||||
```rust
|
||||
style.separator.width = 4.0; // visual width -- zeroing is safe but keep 4px to avoid layout shift
|
||||
style.separator.extra_interact_width = 0.0; // no invisible drag target since resize is locked anyway
|
||||
style.separator.extra = 175.0;
|
||||
style.separator.color_idle = egui::Color32::TRANSPARENT;
|
||||
style.separator.color_hovered = egui::Color32::TRANSPARENT;
|
||||
style.separator.color_dragged = egui::Color32::TRANSPARENT;
|
||||
```
|
||||
|
||||
The now-unused locals `splitter_idle` and `splitter_hover` (~L2573-2574) that were only fed into the separator block can be removed in the same pass, or left in place since they have no further effect.
|
||||
|
||||
## Constraints / non-goals
|
||||
|
||||
- Do NOT change the 36 px Tools column width or the `lock_tools_column` logic in `layout_solver.rs`.
|
||||
- Do NOT touch any engine/Layer 1-4 code (not relevant here).
|
||||
- Do NOT introduce new dependencies or touch egui_dock source.
|
||||
|
||||
## Validation
|
||||
|
||||
1. `cargo check -p hcie-gui-egui` -- confirm no type errors.
|
||||
2. Run the application and visually verify that hovering the right edge of the Tools panel no longer produces a blue bar.
|
||||
3. Squeeze/expand the main window to confirm the 36 px Tools column stays fixed and no stray scrollbar or resize artefact appears (the `extra_interact_width = 0.0` change means the separator has no drag zone; column width is still enforced by `lock_tools_column`).
|
||||
@@ -0,0 +1,104 @@
|
||||
# Plan: Meadow Brush Geometry + Color Variant
|
||||
|
||||
## Context
|
||||
|
||||
HCIE-Rust v4 brush engine’inde `BrushStyle::Meadow` mevcut implementasyonu sadece 3 dikey nokta/dab çiziyor; hedefteki gibi kıvrımlı, farklı açılı, farklı uzunlukta çimen yaprağı demetlerine benzemiyor. Ayrıca kullanıcı, seçilen ana renkin yakınında benzer ama farklı renklerle çizim yapan bir **Color Variant** parametresi istiyor.
|
||||
|
||||
## Decisions
|
||||
|
||||
| # | Decision | Rationale |
|
||||
|---|----------|-----------|
|
||||
| 1 | `color_variant: bool` ve `variant_amount: f32` alanları `BrushTip` içinde **genel** olarak eklenecek. | İleride Leaf, Tree gibi doğal brush’lara kolayca açılabilir; UI başta sadece Meadow için gösterilecek. |
|
||||
| 2 | Renk varyasyonu **HSL tabanlı** olacak. | Ana renk ailesi korunur: yeşil → farklı yeşiller, mor → farklı morlar. RGB ofsetle kirlenme/gri kayma olmaz. |
|
||||
| 3 | `variant_amount ∈ [0, 1]`. `amount = 0` hiç varyasyon yok; `amount = 1` için maksimum **±60° hue**, **±40% lightness** sapma. Ara değerler lineer. | Kullanıcıya sezgisel, sınırsız renk ailesi içinde kontrol sunar. |
|
||||
| 4 | Meadow her stamp (dab) için 7–12 adet kıvrımlı çimen yaprağı çizecek. | Hedef görseldeki doğal, değişken çimen demetini yakalar. |
|
||||
| 5 | Her yaprağın başlangıç açısı yukarı (−90°) etrafında ±45° rastgele sapacak; uzunluk `0.3 .. 1.1 * effective_size`; kalınlık kökte ~0.15×size, uçta ~0.03×size; quadratic bezier ile 2–3 segment. | Farklı açı, uzunluk ve eğri her stamp’ı benzersiz kılar. |
|
||||
| 6 | Her yaprağın opasitesi ve rengi ayrı ayrı varyasyonlu olacak. | Tek tip “matbaa” hissi önlenir. |
|
||||
| 7 | GUI’de Color Variant kontrolleri sadece **Meadow brush aktifken** gösterilecek. | Risk azaltılır, diğer brush’lara sonradan genişletilebilir. |
|
||||
| 8 | `meadow_grass` preset’i `color_variant: true`, `variant_amount: 0.5` ile güncellenecek. | Kullanıcı varsayılan olarak hedef görsele yakın sonuç alır. |
|
||||
|
||||
## Affected Files
|
||||
|
||||
1. `hcie-protocol/src/lib.rs` — `BrushTip` struct: `color_variant`, `variant_amount` alanları + `Default`.
|
||||
2. `hcie-protocol/src/tools.rs` — opsiyonel; değişiklik yok (alanlar zaten `BrushTip` içinde).
|
||||
3. `hcie-brush-engine/src/lib.rs` — `BrushTip` mirror struct; `draw_meadow_brush` yeniden yazımı; HSL varyasyon helper; `Default` güncellemesi.
|
||||
4. `hcie-brush-engine/src/presets.rs` — `meadow_grass()` default’ları.
|
||||
5. `hcie-engine-api/src/dynamic_loader.rs` — `hcie_protocol::BrushTip` ↔ `hcie_brush_engine::BrushTip` dönüşümü.
|
||||
6. `hcie-engine-api/src/lib.rs` — yeni alanların default/senkronizasyonu (gerekirse).
|
||||
7. `hcie-egui-app/crates/hcie-gui-egui/src/app/panels.rs` — UI: Meadow aktifken “Color Variant” checkbox + “Variant Amount” slider.
|
||||
8. `hcie-egui-app/crates/hcie-gui-egui/src/canvas/mod.rs` — brush tip senkronizasyonuna yeni alanları ekle.
|
||||
9. `hcie-egui-app/crates/hcie-gui-egui/src/app/brushes_panel.rs` — preview’a varyasyon yansıması isteğe bağlı.
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
1. **Protocol layer:** `hcie-protocol/src/lib.rs` `BrushTip` içine ekle:
|
||||
```rust
|
||||
pub color_variant: bool,
|
||||
pub variant_amount: f32,
|
||||
```
|
||||
Default: `false`, `0.0`. Serde `default` desteği eski kayıtlı presetlerle uyumluluğu korur.
|
||||
|
||||
2. **Brush engine struct:** `hcie-brush-engine/src/lib.rs` `BrushTip` mirror struct’ına aynı alanları ekle ve `Default` güncelle.
|
||||
|
||||
3. **Meadow brush renderer:** `draw_meadow_brush` fonksiyonunu yeniden yaz:
|
||||
- `effective_size = size * pressure`
|
||||
- `blade_count = 7..12` rastgele
|
||||
- Her yaprak için:
|
||||
- base angle = −π/2 + rng.gen_range(−π/4 .. π/4)
|
||||
- length = effective_size * rng.gen_range(0.3 .. 1.1)
|
||||
- bend = rng.gen_range(−0.3 .. 0.3) * length
|
||||
- thickness root = effective_size * 0.15, tip = effective_size * 0.03
|
||||
- quadratic bezier: `base → control → tip`, 3–5 segmentle draw_dab zinciri
|
||||
- op = particle_opacity * rng.gen_range(0.6 .. 1.0)
|
||||
- renk: eğer `color_variant` true ise `variant_amount`’a göre HSL varyasyon; yoksa `color`
|
||||
|
||||
4. **HSL varyasyon helper:** RGB → HSL → hue/lightness rastgele sap → HSL → RGB. Basit bir implementasyon yeterli (doğrusal interpolasyonlu basit HSL). Helper: `vary_color(color: [u8;4], amount: f32, rng: &mut R) -> [u8;4]`.
|
||||
|
||||
5. **Dynamic loader dönüşümü:** `hcie-engine-api/src/dynamic_loader.rs` `draw_brush_stroke` içinde `color_variant` ve `variant_amount` alanlarını kopyala.
|
||||
|
||||
6. **Preset:** `hcie-brush-engine/src/presets.rs` `meadow_grass()`:
|
||||
- `tip.color_variant = true`
|
||||
- `tip.variant_amount = 0.5`
|
||||
|
||||
7. **GUI Properties panel:** `hcie-egui-app/crates/hcie-gui-egui/src/app/panels.rs` `Tool::Brush` bloğunda, `state.tool_configs.brush.style == Meadow` iken:
|
||||
- Checkbox: `Color Variant` → `state.tool_configs.brush.color_variant`
|
||||
- Slider: `Variant Amount` → `state.tool_configs.brush.variant_amount`, 0.0..=1.0 (sadece checkbox true ise)
|
||||
|
||||
8. **GUI brush config sync:** `ToolConfigs` → `BrushConfig` içine `color_variant` ve `variant_amount` ekle (zaten `BrushTip` içinde tutuluyordu; brush config’e de kullanıcı tarafından ayarlanabilir olması için ekle). Sonra `canvas/mod.rs`’te brush tip oluştururken bu alanları aktar.
|
||||
|
||||
9. **Serialization/backward compatibility:** `BrushConfig` ve `BrushTip` Serde `Default` annotation’ları kontrol et; eksik alanlar 0/default olarak deserialze edilsin.
|
||||
|
||||
10. **Build/test:**
|
||||
- `cargo test -p hcie-brush-engine --test stamp`
|
||||
- `cargo test -p hcie-engine-api --test visual_regression`
|
||||
- `cargo test -p hcie-engine-api --test performance_stroke_4k -- --nocapture`
|
||||
- `cargo build -p hcie-gui-egui`
|
||||
|
||||
11. **Visual validation:**
|
||||
- GUI açılışında Meadow brush seç.
|
||||
- Birkaç farklı stroke çiz; benzersiz demetler ve renk varyasyonu görülmeli.
|
||||
- F12 screenshot al (`target/screenshots/toolbox_*.png`).
|
||||
|
||||
## Risks & Mitigations
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| `BrushTip` serialization format değişikliği eski kaydedilmiş custom presetleri bozar. | Serde `default` attribute ile eksik alanlar `false`/`0.0` olarak doldurulur. |
|
||||
| GUI testlerinde veya canvas sync’te unutulan yeni alanlar compile hatası/varsayılan davranış yaratır. | Tüm `BrushTip` literal init yerlerini (brushes_panel, canvas, script, import) tarayıp yeni alanları ekle. |
|
||||
| Meadow her dab daha fazla işlem yapar, 4K performans testini geçemez. | Önce testi çalıştır; eğer regresyon varsa yaprak sayısını ve segment sayısını sınırla veya small-region optimizasyonu koru. |
|
||||
| HSL helper’ı eklenen yeni crate bağımlılığı gerektirir. | Sadece basit HSL ↔ RGB kodu yaz; dış bağımlılık ekleme. |
|
||||
|
||||
## Open Questions / Out of Scope
|
||||
|
||||
- Preview panelinde Meadow brush’ın color variant etkisini göstermek isteğe bağlıdır; ilk iterasyonda zorunlu değil.
|
||||
- Renk varyasyonu ileride Leaf, Tree, Rock gibi brush’lara genişletilebilir; bu plan sadece Meadow UI gösterimini açar.
|
||||
- Varyasyon algoritması hue/lightness dışında saturation varyasyonunu da içerebilir; şimdilik hue + lightness yeterli.
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
- [ ] `cargo test -p hcie-brush-engine --test stamp` geçer.
|
||||
- [ ] `cargo test -p hcie-engine-api --test visual_regression` geçer.
|
||||
- [ ] `cargo test -p hcie-engine-api --test performance_stroke_4k -- --nocapture` performans regresyonu göstermez.
|
||||
- [ ] GUI’de Meadow brush ile çizilen her stroke farklı yaprak demeti görünür.
|
||||
- [ ] Color Variant aktifken aynı ana renk etrafında farklı tonlar/açıklıklar görünür.
|
||||
- [ ] F12 screenshot’ta doğal çimen benzeri sonuç gözlemlenir.
|
||||
@@ -0,0 +1,102 @@
|
||||
# Plan: Brush Editor — Dynamics, Real Preview, Scrollable Panels
|
||||
|
||||
## Goal
|
||||
Extend the existing Krita-style brush editor in `hcie-egui-app/crates/hcie-gui-egui/src/app/brush_editor.rs` so it exposes real brush dynamics, shows an accurate engine-rendered preview, and keeps all dock panels scrollable by mouse wheel / pan without visible scrollbars.
|
||||
|
||||
## Current State
|
||||
- A basic brush editor modal exists with four categories: **Brush Tip**, **Dynamics**, **Color**, **Texture**.
|
||||
- `BrushConfig` (in `hcie-protocol/src/tools.rs`) exposes only `size`, `opacity`, `hardness`, `flow`, `spacing`, `style`, `blend_mode`, `cyclic_color`, `cyclic_speed`, `color_variant`, `variant_amount`, `density`.
|
||||
- Engine `BrushTip` already has `jitter_amount`, `scatter_amount`, `angle`, `roundness`, but the GUI never sets them (canvas code hard-codes `0.0` / `1.0`).
|
||||
- Preview is procedural (`brushes_panel::draw_brush_preview`) and does not reflect actual engine output.
|
||||
- Dock panels vary: some use `ScrollArea`, some do not; scrollbars are visible.
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. New brush dynamics fields
|
||||
Add to `hcie-protocol/src/tools.rs` `BrushConfig`:
|
||||
```rust
|
||||
pub jitter_amount: f32,
|
||||
pub scatter_amount: f32,
|
||||
pub angle: f32,
|
||||
pub roundness: f32,
|
||||
pub drawing_angle: bool,
|
||||
pub rotation_random: f32,
|
||||
pub time_enabled: bool,
|
||||
pub time_size_start: f32,
|
||||
pub time_size_end: f32,
|
||||
pub time_opacity_start: f32,
|
||||
pub time_opacity_end: f32,
|
||||
pub time_angle_start: f32,
|
||||
pub time_angle_end: f32,
|
||||
```
|
||||
Defaults: `0.0`, `0.0`, `0.0`, `1.0`, `false`, `0.0`, `false`, `1.0/1.0/1.0/1.0/0.0/0.0`.
|
||||
|
||||
### 2. GUI controls
|
||||
- **Brush Tip panel**: keep `Diameter`, `Hardness`, `Spacing`, `Style`, `Blend Mode`; add **Angle**, **Roundness**, **Drawing Angle** checkbox, **Rotation Random**.
|
||||
- **Dynamics panel**: keep `Opacity`, `Flow`, `Pressure Curve` placeholder; add **Jitter**, **Scatter**.
|
||||
- **Color panel**: keep `Color Variant`, `Cyclic Color`; add a **Time** subsection with checkbox and start/end sliders for `size`, `opacity`, `angle`.
|
||||
- Every slider pairs with a right-aligned `DragValue` so mouse users can click/type exact values.
|
||||
- Angles displayed and edited in **degrees**, stored in **radians**.
|
||||
|
||||
### 3. Engine wiring
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/canvas/mod.rs`: when building `BrushTip`, copy `jitter_amount`, `scatter_amount`, `angle` (converted to radians), `roundness` from `state.tool_configs.brush`.
|
||||
- `hcie-brush-engine/src/lib.rs`: update `generate_brush_stamp` and dab dispatchers so that:
|
||||
- `drawing_angle == true` overrides `angle` with the stroke segment direction.
|
||||
- `rotation_random` adds a per-dab random offset to the effective angle.
|
||||
- `roundness` scales the stamp along the effective angle (elliptical dab).
|
||||
- `hcie-engine-api/src/stroke_brush.rs`: apply `time_*` interpolation using accumulated stroke distance (`brush_accumulated_dist`) before passing size/opacity/angle to the dab functions. Reset at stroke start (`begin_stroke`).
|
||||
|
||||
### 4. Real preview
|
||||
- Add `Engine::render_brush_preview(&self, width, height, tip, color, stroke_points) -> Vec<u8>` in `hcie-engine-api/src/lib.rs`.
|
||||
- It creates a temporary 1-layer document, draws the supplied stroke points, composites, and returns RGBA pixels.
|
||||
- `brush_editor.rs` calls this on every significant parameter change (when `brush_editor_instant_preview` is true), caches the texture, and draws it in the preview area instead of the procedural function.
|
||||
- Guard with a small debounce to avoid 4K-preview cost.
|
||||
|
||||
### 5. Scrollable panels
|
||||
- Create a helper `scrollable_panel(ui, id, contents)` in a new file `hcie-egui-app/crates/hcie-gui-egui/src/app/scrollable_panel.rs`.
|
||||
- It wraps `egui::ScrollArea::vertical().always_show_scroll(false)` and implements drag-to-pan by tracking pointer delta over the content rect when the left button is held and no widget is being dragged.
|
||||
- Apply the helper to every dock panel body:
|
||||
- `brushes_panel::show_brushes_ui`
|
||||
- `panels.rs` tool-property bodies (Tools, Layers, History, Properties, etc.)
|
||||
- `brush_editor.rs` right-hand settings area
|
||||
- Keep existing `ScrollArea` calls but switch them to the helper so behavior is consistent.
|
||||
|
||||
## Affected Boundaries (Locked Crates)
|
||||
The following crates are normally locked by `hcie-guard`; the implementation agent must run `unlock.sh <crate>`, edit, then `lock.sh <crate>`:
|
||||
- `hcie-protocol/src/tools.rs` — `BrushConfig` fields
|
||||
- `hcie-engine-api/src/lib.rs` — new public preview API
|
||||
- `hcie-engine-api/src/stroke_brush.rs` — time interpolation
|
||||
- `hcie-brush-engine/src/lib.rs` — drawing angle / rotation / roundness behavior
|
||||
|
||||
GUI-only files (open to agent edits):
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/app/brush_editor.rs`
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/app/brushes_panel.rs`
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/app/panels.rs`
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs`
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/canvas/mod.rs`
|
||||
|
||||
## Task Order
|
||||
1. Extend `BrushConfig` in `hcie-protocol` and rebuild engine.
|
||||
2. Implement engine preview API.
|
||||
3. Implement `drawing_angle`, `rotation_random`, `roundness` in `hcie-brush-engine`.
|
||||
4. Implement time interpolation in `hcie-engine-api/src/stroke_brush.rs`.
|
||||
5. Wire new fields from GUI config to `BrushTip` in `canvas/mod.rs`.
|
||||
6. Extend `brush_editor.rs` UI (new sliders, real preview, scrollable right panel).
|
||||
7. Implement `scrollable_panel` helper and apply to all dock panels.
|
||||
8. Run `cargo test -p hcie-engine-api --test visual_regression` and `cargo test -p hcie-engine-api --test performance_stroke_4k`.
|
||||
9. Lock affected engine crates.
|
||||
10. Capture screenshots of brush editor and each scrollable panel.
|
||||
|
||||
## Validation
|
||||
- Visual regression tests (`visual_regression`) must still pass.
|
||||
- Performance regression benchmark (`performance_stroke_4k`) must not regress beyond 5%.
|
||||
- Brush editor preview updates within 200 ms of parameter change.
|
||||
- All dock panels scroll by mouse wheel and pan-drag without visible scrollbars.
|
||||
|
||||
## Risks
|
||||
- `hcie-brush-engine` angle/roundness changes can alter default brush appearance if defaults are wrong; test with `visual_regression`.
|
||||
- Real preview allocates a temporary engine/document per frame; debounce/caching is mandatory.
|
||||
- Locked crate modifications require `unlock.sh` / `lock.sh` and possible terminal password prompt.
|
||||
|
||||
## Open Questions
|
||||
- None remaining; user confirmed all decisions.
|
||||
@@ -0,0 +1,245 @@
|
||||
# Photoshop-Grade GUI Aesthetic Overhaul
|
||||
|
||||
## Problem
|
||||
|
||||
The current GUI uses emoji icons, inconsistent spacing, flat color tokens with poor contrast
|
||||
hierarchy, and egui's default widget styling in many places. The result looks like a
|
||||
prototype, not a professional image editor. The goal is Photoshop-level polish:
|
||||
refined warm-dark palette, consistent SVG iconography, subtle depth cues, proper typography,
|
||||
and disciplined spacing.
|
||||
|
||||
## Root Causes Identified
|
||||
|
||||
### 1. Emoji Icons Everywhere (HIGH IMPACT)
|
||||
|
||||
| Location | File:Line | Emoji Used | Problem |
|
||||
|---|---|---|---|
|
||||
| Toolbar buttons | `panels.rs:413-470` | `📄 📂 💾 ↩ ↪ 🔍+ 🔍- ☀ 🌙` | Looks amateurish on desktop |
|
||||
| Layer visibility | `panels.rs:879-882` | `👁` | Cross-platform rendering inconsistency |
|
||||
| Layer lock | `panels.rs:799` | `🔒` | Same |
|
||||
| Layer group folder | `panels.rs:914` | `📁` | Same |
|
||||
| Layer link/chain | `panels.rs:47` | `🔗 🔓` | Same |
|
||||
| Toolbox toggle | `toolbox.rs:211` | `<<` `>>` text | Not a real icon |
|
||||
| Layer move/delete | `panels.rs:933-946` | `× ▲ ▼ M↓` text | Plain text buttons |
|
||||
| Layer FX badge | `panels.rs:951-952` | `fx` / `fx*` text | Text badge, not icon |
|
||||
| Toolbox swap icon | `toolbox.rs:286` | `⇄` | Unicode character, not icon |
|
||||
| Panel list toggle | `panels.rs:212` | `◧ ☰` | Unicode characters |
|
||||
| Search box icon | `brushes_panel.rs:818` | `🔍` | Emoji |
|
||||
|
||||
### 2. Theme Color Palette — Flat Grays, No Warmth (HIGH IMPACT)
|
||||
|
||||
File: `egui-panel-adapter/src/theme.rs:79-91`
|
||||
|
||||
The Photoshop preset uses pure neutral grays `(30,30,30)`, `(45,45,45)`, `(60,60,60)`. Real
|
||||
Photoshop's dark theme uses slightly warm-tinted grays with more contrast depth between
|
||||
workspace, panels, and elevated surfaces. The current palette has:
|
||||
|
||||
- **bg_app to bg_panel delta is only 15** (30→45). Real Photoshop has ~20-25 step.
|
||||
- **No surface elevation system** — everything is the same flat gray. Missing:
|
||||
elevated panels, recessed tracks, inset controls.
|
||||
- **accent blue is generic** `(59,130,246)` — Photoshop uses a more muted steel-blue.
|
||||
|
||||
### 3. Toolbar Styling (MEDIUM IMPACT)
|
||||
|
||||
File: `panels.rs:337-390` (`draw_modern_toolbar_button`)
|
||||
|
||||
- Uses emoji+text labels (`"📄 New"`) instead of icon-only buttons.
|
||||
- 34px toolbar height is cramped.
|
||||
- No icon-only compact mode; every button has text.
|
||||
- Separators between groups are just `ui.separator()` — no visual grouping.
|
||||
|
||||
### 4. Layer Panel Visual Noise (MEDIUM IMPACT)
|
||||
|
||||
File: `panels.rs:728-960`
|
||||
|
||||
- Row height is too tight (`3px item_spacing.y`).
|
||||
- Emoji icons for visibility/lock create inconsistent rendering.
|
||||
- `small_button("×")`, `small_button("▲")` etc. use egui's default styling.
|
||||
- Active layer highlight uses `accent.gamma_multiply(0.2)` which is too subtle.
|
||||
- Thumbnail is only 22×16px — too small.
|
||||
- Blend mode ComboBox uses egui default styling.
|
||||
|
||||
### 5. egui Default Widget Styling Leaks (MEDIUM IMPACT)
|
||||
|
||||
File: `mod.rs:166-223` (`apply_theme`)
|
||||
|
||||
- `Visuals::dark()` is called first, then overridden. This leaves many
|
||||
sub-properties at dark() defaults that clash with the custom palette.
|
||||
- Scrollbar styling, popup shadows, and tooltip styling are inherited defaults.
|
||||
- ComboBox, DragValue, Checkbox all use egui's built-in rendering which
|
||||
looks generic.
|
||||
- `spacing.slider_width = 180.0` is too wide for panel toolbars.
|
||||
|
||||
### 6. Tab & Header Styling (MEDIUM IMPACT)
|
||||
|
||||
File: `mod.rs:3008-3040` (egui_dock style)
|
||||
|
||||
- Tab corner radius `{ nw: 3, ne: 3, sw: 0, se: 0 }` is correct but the
|
||||
active tab has no bottom accent line indicator.
|
||||
- Tab spacing `0.0` makes tabs feel cramped.
|
||||
- `fill_tab_bar = true` means inactive tabs fill the entire bar width,
|
||||
looking blocky.
|
||||
|
||||
### 7. Status Bar Styling (LOW-MEDIUM IMPACT)
|
||||
|
||||
File: `panels.rs:502-653`
|
||||
|
||||
- Minimized-panel icon buttons use accent-colored 28×28 squares.
|
||||
- Zoom slider uses egui default `Slider` — not the custom `PlainSlider`.
|
||||
- Mixed font sizes (10px, 11px, 12px) create visual noise.
|
||||
- Canvas size label and zoom percentage compete for attention.
|
||||
|
||||
### 8. Brushes Panel (LOW IMPACT)
|
||||
|
||||
File: `brushes_panel.rs:790-890`
|
||||
|
||||
- Category pills are fine but use `egui::Button` default frame.
|
||||
- "✏ Edit Brush" button uses emoji.
|
||||
- Search box uses emoji `🔍`.
|
||||
|
||||
### 9. Color Swatch Widget (LOW IMPACT)
|
||||
|
||||
File: `toolbox.rs:239-331`
|
||||
|
||||
- Overlapping foreground/background swatches are custom-painted with
|
||||
zero rounding and basic 1px borders — looks flat.
|
||||
- Swap icon is Unicode `⇄`, default icon is plain black/white squares.
|
||||
|
||||
### 10. Spacing Inconsistencies (LOW IMPACT)
|
||||
|
||||
- `item_spacing` is `(4, 4)` globally but panel content overrides it locally
|
||||
to `(1, 1)`, `(2, 2)`, `(3, 3)`, `(4, 4)` at different places.
|
||||
- `inner_margin` varies: `Margin::symmetric(4, 4)` toolbar, `Margin::symmetric(0, 0)` menubar,
|
||||
`Margin::symmetric(2, 3)` layer rows, `Margin::symmetric(8, 6)` brush search.
|
||||
- No consistent 4px or 8px grid.
|
||||
|
||||
## Fix Plan
|
||||
|
||||
### Task 1: Refine Photoshop Theme Colors
|
||||
**Files:** `egui-panel-adapter/src/theme.rs`
|
||||
|
||||
- Warm the grays slightly: add +2 red, +1 green to each dark step to get
|
||||
Photoshop's characteristic warm-dark look.
|
||||
- Increase bg_app→bg_panel contrast: `bg_app: (28,28,30)` → `bg_panel: (50,50,54)`.
|
||||
- Add a `bg_elevated` token for popups/tooltips: `(55,55,60)`.
|
||||
- Add a `bg_recessed` token for slider tracks/insets: `(18,18,20)`.
|
||||
- Mute the accent: `(59,130,246)` → `(56,120,220)` (slightly less saturated).
|
||||
- Tighten `border_high`: `(70,70,70)` → `(62,62,66)` for subtler borders.
|
||||
|
||||
### Task 2: Replace Emoji with SVG Icons in Toolbar
|
||||
**Files:** `panels.rs`, `assets/icons/`
|
||||
|
||||
- Create or reuse SVG icons for: New, Open, Save, Undo, Redo, ZoomIn, ZoomOut, Light/Dark.
|
||||
- Modify `draw_modern_toolbar_button` to accept an optional icon source.
|
||||
- Change button style to icon-only (24×24) with tooltip on hover.
|
||||
- Keep text label in tooltip only.
|
||||
|
||||
### Task 3: Replace Emoji with SVG Icons in Layer Panel
|
||||
**Files:** `panels.rs`, toolbox/assets
|
||||
|
||||
- Replace `👁` with an SVG eye icon.
|
||||
- Replace `🔒` with an SVG lock icon.
|
||||
- Replace `📁` with an SVG folder icon.
|
||||
- Replace `🔗 🔓` with SVG chain/unlink icons.
|
||||
- Replace `× ▲ ▼` with SVG close/up/down icons.
|
||||
- Replace `fx` text badge with an SVG FX icon (or styled text with proper font).
|
||||
|
||||
### Task 4: Improve egui Visuals Configuration
|
||||
**Files:** `mod.rs` (`apply_theme`)
|
||||
|
||||
- After `Visuals::dark()`, explicitly set:
|
||||
- `visuals.popup_shadow` — subtle shadow for depth.
|
||||
- `visuals.window_rounding` — `CornerRadius::same(6)`.
|
||||
- `visuals.menu_rounding` — `CornerRadius::same(6)`.
|
||||
- `visuals.scrollbar` — narrower (8px), recessed track.
|
||||
- `visuals.slider_trailing_fill` — true.
|
||||
- `visuals.handle_shape` — `HandleShape::Rect { aspect_ratio: 0.5 }`.
|
||||
- `visuals.interact_cursor` — `CursorIcon::PointingHand` for buttons.
|
||||
- Reduce `spacing.slider_width` from 180 to 140.
|
||||
- Reduce `spacing.button_padding` from `(8, 4)` to `(6, 3)` for compactness.
|
||||
|
||||
### Task 5: Improve Tab & Header Styling
|
||||
**Files:** `mod.rs` (egui_dock style section)
|
||||
|
||||
- Add 2px bottom accent line on active tab.
|
||||
- Increase tab spacing to `2.0`.
|
||||
- Set `fill_tab_bar = false` so tabs don't stretch to fill width.
|
||||
- Increase tab bar height from `28.0` to `30.0`.
|
||||
- Tab text size: 11.5px (consistent).
|
||||
|
||||
### Task 6: Improve Layer Panel Layout
|
||||
**Files:** `panels.rs` (`show_layers`)
|
||||
|
||||
- Increase layer row height: `item_spacing.y` from 3 to 4.
|
||||
- Increase thumbnail size from 22×16 to 32×24.
|
||||
- Use 6px rounded corners on layer row frames.
|
||||
- Stronger active layer accent: `gamma_multiply(0.2)` → `gamma_multiply(0.3)`.
|
||||
- Replace `small_button` calls with styled icon buttons.
|
||||
- Add 1px separator between layer groups.
|
||||
|
||||
### Task 7: Improve Status Bar
|
||||
**Files:** `panels.rs` (`show_status_bar`)
|
||||
|
||||
- Replace egui default `Slider` with `PlainSlider` for zoom.
|
||||
- Standardize font size to 11px.
|
||||
- Make minimized-panel icon buttons 24×24 (from 28×28).
|
||||
- Remove redundant "Active Tool:" prefix — just show the tool name.
|
||||
|
||||
### Task 8: Improve Toolbar Layout
|
||||
**Files:** `panels.rs` (`show_unified_toolbar`)
|
||||
|
||||
- Increase toolbar height from 34 to 36.
|
||||
- Replace emoji toolbar buttons with icon-only SVG buttons (24×24).
|
||||
- Add subtle group separators (2px vertical line, `border_low` color).
|
||||
- Right-align theme toggle and AI status.
|
||||
|
||||
### Task 9: Improve Color Swatch Widget
|
||||
**Files:** `toolbox.rs` (`render_overlapping_swatches`)
|
||||
|
||||
- Add 3px rounding to foreground/background swatch rects.
|
||||
- Use SVG swap icon instead of `⇄` text.
|
||||
- Use SVG default-color icon instead of plain black/white squares.
|
||||
- Add 1px shadow on foreground swatch for depth.
|
||||
|
||||
### Task 10: Standardize Spacing Grid
|
||||
**Files:** Multiple (panels.rs, toolbox.rs, brushes_panel.rs, mod.rs)
|
||||
|
||||
- Adopt an 8px base grid: all margins and padding are multiples of 4px.
|
||||
- Panel content `inner_margin`: 8px horizontal, 6px vertical.
|
||||
- Panel header: 4px padding.
|
||||
- Widget gaps: 4px.
|
||||
- Remove local overrides of `item_spacing` where possible.
|
||||
|
||||
### Task 11: Improve Brushes Panel
|
||||
**Files:** `brushes_panel.rs`
|
||||
|
||||
- Replace `🔍` emoji with SVG search icon.
|
||||
- Replace `✏ Edit Brush` with SVG edit icon + text.
|
||||
- Category pills: increase rounding to 14px, add 1px border on hover.
|
||||
|
||||
## Execution Order
|
||||
|
||||
1. **Task 1** (theme colors) — foundation, everything else depends on it
|
||||
2. **Task 4** (egui visuals) — global widget styling
|
||||
3. **Task 5** (tab styling) — immediate visual impact
|
||||
4. **Task 2 + 3** (SVG icons) — replace emoji everywhere
|
||||
5. **Task 8** (toolbar layout) — biggest visible change
|
||||
6. **Task 6** (layer panel) — second biggest visible area
|
||||
7. **Task 7** (status bar)
|
||||
8. **Task 9** (color swatches)
|
||||
9. **Task 10** (spacing grid)
|
||||
10. **Task 11** (brushes panel)
|
||||
|
||||
## Validation
|
||||
|
||||
After each task:
|
||||
1. `cargo build -p hcie-gui-egui` — must compile clean.
|
||||
2. Launch app, press F12 for full viewport screenshot.
|
||||
3. Compare against previous screenshot.
|
||||
4. Check all 5 themes (Photoshop, ProDark, Amoled, PhotoshopLight, ProLight).
|
||||
|
||||
Final validation:
|
||||
- `cargo run -p hcie-gui-egui -- --screenshot-panel "Tools"` — verify toolbox.
|
||||
- `cargo run -p hcie-gui-egui -- --screenshot-panel "Layers"` — verify layers.
|
||||
- `cargo run -p hcie-gui-egui -- --screenshot-panel "Color Palette"` — verify colors.
|
||||
- Visual inspection: no emoji visible in any panel, consistent icon style.
|
||||
@@ -0,0 +1,91 @@
|
||||
# Plan: Photopea-Style Inline Text Tool
|
||||
|
||||
## Goal
|
||||
Replace the current modal/separate text dialog with a direct-on-canvas inline text editor that is dedicated to the Text tool and behaves like Photopea/Photoshop.
|
||||
|
||||
## Design Decisions (resolved)
|
||||
|
||||
| Topic | Decision |
|
||||
|-------|----------|
|
||||
| Activation | Text tool active → click on canvas opens an editable text box at the clicked point. |
|
||||
| Box position | Clicked point is the **top-left** corner of the text box; alignment controls internal line alignment only. |
|
||||
| Box size | Box grows/shrinks dynamically with the typed text; height expands for multi-line content. |
|
||||
| Visual style | Thin-framed editable box with caret, rendered on top of the canvas. |
|
||||
| Live preview | Every keystroke updates the underlying `Text` layer through the engine API (`set_text_layer_text`). |
|
||||
| Accept | **Ctrl/Cmd+Enter** or the toolbar **Accept ✓** button finalizes the edit. |
|
||||
| Cancel | **Escape** or the toolbar **Cancel ✕** button discards the draft layer (`ToolTextCancelled`). |
|
||||
| Toolbar | Accept/Cancel buttons are shown **only in the top menu bar** and only while the Text tool is active and an edit is in progress. |
|
||||
| Tool switching guard | While a text edit is active, switching to another tool is **blocked** until Accept or Cancel is pressed. |
|
||||
| Re-edit existing layer | Double-click a text layer, or click it while the Text tool is active, enters edit mode for that layer. |
|
||||
| Non-text layers | Clicking a non-text layer while editing cancels the draft. |
|
||||
|
||||
## Affected code boundaries
|
||||
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/canvas/text_overlay.rs` — inline editor widget.
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/canvas/mod.rs` — hook overlay, double-click detection, Text-tool click on existing text layer.
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/app/panels.rs` — add Accept/Cancel to the top menu bar when Text tool + edit active.
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` — event handling, tool-change guard, Accept/Cancel events.
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/app/text_editor.rs` — keep properties panel live-preview controls.
|
||||
|
||||
## Implementation tasks
|
||||
|
||||
1. **Inline text box (`canvas/text_overlay.rs`)**
|
||||
- Use `egui::TextEdit::multiline` bound to `state.text_edit.draft_text`.
|
||||
- Make the box auto-sized: remove fixed width/height and let egui measure content.
|
||||
- Add a visible frame (thin border + subtle fill) so the user sees the edit area.
|
||||
- Place top-left at `(canvas_rect.min.x + edit.canvas_x * zoom, canvas_rect.min.y + edit.canvas_y * zoom)`.
|
||||
- On change, call `doc.engine.set_text_layer_text(layer_id, text)` and push `RenderRequested`.
|
||||
- Keyboard:
|
||||
- `Enter` → new line.
|
||||
- `Ctrl/Cmd+Enter` → push `ToolTextAccepted`.
|
||||
- `Escape` → already handled globally, pushes `ToolTextCancelled`.
|
||||
|
||||
2. **Top toolbar Accept/Cancel (`app/panels.rs` / `app/mod.rs`)**
|
||||
- In `show_menu_bar`, after the right window-controls cluster or in a dedicated center/right slot, render two small buttons **only when** `state.active_tool == Tool::Text && state.text_edit.active`.
|
||||
- Accept: green checkmark / “Accept” label; pushes `ToolTextAccepted`.
|
||||
- Cancel: red cross / “Cancel” label; pushes `ToolTextCancelled`.
|
||||
- Keep existing layout stable when buttons are hidden.
|
||||
|
||||
3. **Event handling updates (`app/mod.rs`)**
|
||||
- `ToolTextStart { x, y }`: already creates a draft Text layer and opens edit. Keep behavior.
|
||||
- `ToolTextAccepted`: finalize, clear edit state.
|
||||
- `ToolTextCancelled`: delete draft layer, clear edit state.
|
||||
- `TextLayerSelected(id)`: load existing text layer into edit state.
|
||||
- `ToolChanged(new_tool)` / `LayerSelected(id)`:
|
||||
- If `state.text_edit.active`, **do not apply the change**; optionally show a status message like “Finish text edit first”.
|
||||
- Only allow the change after `ToolTextAccepted` or `ToolTextCancelled`.
|
||||
|
||||
4. **Canvas interaction updates (`canvas/mod.rs`)**
|
||||
- Detect **double-click** on the canvas.
|
||||
- If double-clicked point is over a text layer (use engine `get_pixel` or hit-test via layer bounds), push `TextLayerSelected(layer_id)`.
|
||||
- If Text tool active and single-click lands on an existing text layer, also push `TextLayerSelected` instead of creating a new draft.
|
||||
- If Text tool active and single-click lands on empty canvas, push `ToolTextStart` (already implemented).
|
||||
|
||||
5. **Properties panel (`app/text_editor.rs`)**
|
||||
- Keep current live-preview controls (text content, font, size, color, angle, alignment, orientation).
|
||||
- Remove Accept/Cancel from here because they now live in the top toolbar.
|
||||
- Ensure changes while editing update the draft layer and request a render.
|
||||
|
||||
6. **State consistency**
|
||||
- When `TextLayerSelected` loads an existing layer, populate `TextEditState` from `LayerData::Text`.
|
||||
- When Accept is pressed, ensure the final text is written to the layer and the edit state is cleared.
|
||||
- When Cancel is pressed, delete the draft layer only if it was newly created; if editing an existing layer, Cancel should revert to the original text.
|
||||
- To support safe Cancel for existing layers, store an `original_text` snapshot in `TextEditState` when entering edit mode.
|
||||
|
||||
## Validation plan
|
||||
|
||||
- Build: `cargo build -p hcie-gui-egui`
|
||||
- Regression tests: `cargo test -p hcie-engine-api --test visual_regression --test performance_stroke_4k -- --nocapture`
|
||||
- Manual GUI checks:
|
||||
1. Select Text tool, click canvas → inline box appears and typing shows live text on canvas.
|
||||
2. Press Ctrl+Enter or toolbar Accept → edit closes, layer stays visible.
|
||||
3. Click canvas again → new draft. Press Escape or toolbar Cancel → draft disappears.
|
||||
4. While editing, press B/Brush shortcut or click another tool → nothing happens, status message appears.
|
||||
5. Create a text layer, accept it, then double-click it → edit re-opens with original text.
|
||||
6. Edit existing layer, change text, press Cancel → text reverts to original.
|
||||
7. Change font, size, color, angle, alignment, orientation in Properties panel → canvas updates live.
|
||||
|
||||
## Open / out-of-scope
|
||||
|
||||
- Warp, 3D and other non-destructive text effects remain metadata-only and will be rasterized in a follow-up phase.
|
||||
- PSD/XCF/Krita text-layer round-trip import is out of scope for this plan; the data structures are already prepared for it.
|
||||
@@ -0,0 +1,152 @@
|
||||
# Dock Panel Sorunları Düzeltme Planı
|
||||
|
||||
## Durum Özeti
|
||||
|
||||
Önceki oturumda kod değişiklikleri yapıldı (`cargo check` geçti) ama `cargo build` hiç çalıştırılmadı — eski binary hâlâ çalışıyor olabilir. Değişikliklerin görünür olması için önce build gerekli.
|
||||
|
||||
Ayrıca, bazı değişiklikler sorunları doğru adreslemiyor olabilir:
|
||||
|
||||
- `ScrollArea` sadece **floating** panellere eklendi — screenshot'taki #1/#2 sorunu floating panel içerik taşması, bu doğru
|
||||
- `persist_when_empty: false` ghost panel için doğru ama collapsed strip 32px yer kaplamaya devam ediyor
|
||||
- Docked panel border'ları grimdock `header.rs:95-100`'de zaten çiziliyor — renk uyumsuzluğu olabilir
|
||||
|
||||
## Tespit Edilen 5 Sorun (panel-problems.png)
|
||||
|
||||
### #1/#2: Floating panel içerik taşması
|
||||
**Root cause:** Color Palette ve Layers panellerinin içeriği (renk tekerleği, katman kontrolleri) panel sınırlarının dışına taşıyor. `render_pane_content()` çağrısı panel sınırlarını zorlamıyor.
|
||||
|
||||
**Mevcut durum:** `ScrollArea::vertical()` eklendi ama bu egui::Window'un kendi content area'sına eklendi — floating panelin `ui` referansı zaten clip'li olmalı.
|
||||
|
||||
**Şüpheli:** `render_pane_content` içindeki widget'lar sabit genişlik/hızlık kullanıyor olabilir (örn: renk tekerleği sabit 200px). ScrollArea eklentisi içerik taşmasını engellemez — `clip()` veya `max_rect` sınırlaması gerekir.
|
||||
|
||||
**Çözüm:** Floating panel show callback'inde `ui.set_clip_rect()` ile panel sınırlarını zorla, VEYA `ui.vertical(|ui| { ui.set_max_width(...); ... })` ile içerik genişliğini sınırla.
|
||||
|
||||
### #3: Docked panel arası border/resize grip yok
|
||||
**Root cause:** Alt taraftaki docked paneller (Properties, History area) ile canvas arasında görünür border veya resize grip yok.
|
||||
|
||||
**Mevcut durum:** grimdock `layout.rs:99-119` resize handle çiziyor (`ui.painter().rect_filled(handle_rect, 0.0, color)`). Handle rengi `style.handle.color` — default dark theme'de `Color32::from_rgb(45, 45, 55)` çok koyu, arka planla karışıyor.
|
||||
|
||||
**Çözüm:** Handle genişliğini artır (5→6px) ve rengi daha belirgin yap (panel border rengiyle aynı veya daha açık).
|
||||
|
||||
### #4: Docked panel border yok, arka plan ile karışıyor
|
||||
**Root cause:** Sağ taraftaki docked paneller (Properties, History, Layers area) ile canvas arasında net çizgi yok.
|
||||
|
||||
**Mevcut durum:** grimdock `header.rs:95-100` content_rect'e 1px stroke çiziyor: `Stroke::new(1.0, pane_border)`. `pane_border` default olarak `widgets.noninteractive.bg_stroke.color` — bu çok ince ve koyu olabilir.
|
||||
|
||||
**Çözüm:** `style.content.border_color`'yi daha belirgin renge ayarla (theme'deki `border_high` rengi).
|
||||
|
||||
### #5: Ghost panel — boşalan yerler hayalet gibi kalıyor
|
||||
**Root cause:** Kapatılan panellerin leaf node'u tree'de kalıyor. `persist_when_empty: false` yapıldı ama post-render collapse loop'u sadece `set_collapsed(true)` çağrısı yapıyor — strip hâlâ 32px yer kaplıyor.
|
||||
|
||||
**Mevcut durum:** `effective_collapsed_thickness()` layout.rs:42-58'de vanished leaf'lere 0 thickness veriyor. Bu doğru çalışmalı — ghost paneller kaybolmalı. Eğer hâlâ görünüyorsa, ya `persist_when_empty` change'i compile edilmedi ya da `is_node_vanished` farklı bir condition döndürüyor.
|
||||
|
||||
**Kontrol:** `persist_when_empty: false` sadece `utility_options`'a uygulandı. Tools (`persist_when_empty: true`) ve Document (`persist_when_empty: true`) etkilenmedi — doğru. Ama grimdock `ensure_pane_at_anchor` (tree.rs:1113) non-center anchor'lara otomatik `persist_when_empty: true` atıyor — bu override edebilir!
|
||||
|
||||
## Ek Sorunlar (kullanıcı raporu)
|
||||
|
||||
### #6: Floating panel pin ile kayboluyor
|
||||
**Root cause:** `render_floating_panels` içindeki re-dock mantığı `float_source_leaf` map'inden hedef leaf'i bulmaya çalışıyor. Bulamazsa `default_leaf_for_pane` fallback'i kullanıyor. Ama `leaf_alive` kontrolü eklenen kodda — leaf tab_count=0 ve persist_when_empty=false ise, fallback default leaf'e gidiyor. Bu default leaf'de de tab yoksa, tab ekleniyor ama ghost loop'unu tetikleyebilir.
|
||||
|
||||
**Asıl sorun:** `float_source_leaf` map'i `PaneId` key kullanıyor. `render_floating_panels`'te `PaneId::from_raw(pane_id_for_pane(&pane))` ile hardcoded ID'den PaneId oluşturuyor. Ama `float_source_leaf`'e insert ederken grimdock tree'den gelen actual PaneId kullanılıyor. Bunlar aynı mı?
|
||||
|
||||
**Kontrol:** `PANE_LAYERS = 4`, `pane_id_for_pane(Layers) = 4`. Tree'de Layers leaf'i `PaneId::from_raw(4)`. Eşleşmeli.
|
||||
|
||||
**Beyin fırtınası:** Asıl sorun şu olabilir: `render_floating_panels` fonksiyonu `mod.rs`'den `grim_dock_host.render(ui, self)` çağrısından SONRA çağrılıyor. Yani re-dock tree'yi bir sonraki frame'de etkiler. Bu doğru — bir frame gecikme normal. Ama `pinned_panels.insert()` aynı frame'de çalışıyor — menü hemen güncel görünmeli.
|
||||
|
||||
**Kesin sorun:** `re_dock` processing'de `app.state.pinned_panels.insert()` çağrısı var ama CLOSE handler'ı da aynı frame'de çalışabilir. Close handler'ı `pinned_panels.remove()` çağırıyor — re-dock'tan önce çalışırsa pin ekranı kaybolur!
|
||||
|
||||
### #7: X ile kapatınca geri getirememek
|
||||
**Root cause:** X ile kapatıldığında tab tree'den siliniyor + `pinned_panels.remove()`. View menu'den geri açmak istediğinde `panels_to_focus` handler'ı çalışmalı. Bu handler'da `else` branch'i (pane ne floating ne tree'de) `default_leaf_for_pane` fallback'i ile tab ekliyor. Bu doğru görünüyor.
|
||||
|
||||
**Ama:** `persist_when_empty: false` yüzünden default leaf collapsed ve vanished olabilir. `pane_options()` Some döner ama leaf layout'ta 0px. Tab eklenince ghost loop'u collapsed'ı true yapar → strip kaybolmaz → ama tab visible olmayabilir (collapsed strip'in içinde).
|
||||
|
||||
## Uygulama Planı
|
||||
|
||||
### Adım 0: Build + Doğrulama
|
||||
```bash
|
||||
cargo build -p hcie-gui-egui --release
|
||||
# veya
|
||||
cargo run -p hcie-gui-egui
|
||||
```
|
||||
Önce mevcut değişikliklerin çalışıp çalışmadığını kontrol et. Eski binary hâlâ çalışıyorsa, tüm düzeltmeler görünmez.
|
||||
|
||||
### Adım 1: Floating Panel İçerik Taşması (#1/#2)
|
||||
**Dosya:** `grim_dock.rs:789-877` (render_floating_panels show callback)
|
||||
|
||||
**Değişiklik:** Floating panel content area'sında clip rect sınırlaması ekle. `render_pane_content` çağrısından ÖNCE panel inner rect'i ile `ui.set_clip_rect()` yap. Alternatif olarak, her widget'ın `max_width`'ini panel genişliğine sınırla.
|
||||
|
||||
**Test:** Uygulamayı başlat → Color Palette'i float et → renk tekerleği panel sınırları içinde scroll edebilmeli.
|
||||
|
||||
### Adım 2: Docked Panel Border (#3/#4)
|
||||
**Dosya:** `grim_dock.rs:406-438` (style override section)
|
||||
|
||||
**Değişiklik:**
|
||||
```rust
|
||||
style.content.border_color = colors.border_high; // zaten var
|
||||
style.handle.color = colors.border_high; // ekle — resize grip belirginliği
|
||||
style.handle.hover_color = colors.accent; // ekle
|
||||
```
|
||||
|
||||
**Ayrıca:** grimdock `style.handle_width` default 5.0px — artırılabilir mi kontrol et.
|
||||
|
||||
**Test:** Properties ve History panelleri ile canvas arasında belirgin çizgi görünmeli.
|
||||
|
||||
### Adım 3: Ghost Panel (#5)
|
||||
**Dosya:** `grim_dock.rs:563-575` (post-render collapse loop) + `layout.rs:42-58`
|
||||
|
||||
**Kontrol:** `ensure_pane_at_anchor` (tree.rs:1113) non-center anchor'lara `persist_when_empty: true` atıyor. Bu `utility_options.persist_when_empty = false` override'ını geri alıyor olabilir!
|
||||
|
||||
**Değişiklik:** İki seçenek:
|
||||
a) `ensure_pane_at_anchor`'daki otomatik persist'i kaldır (ama bu grimdock crate'inde — locked olabilir)
|
||||
b) Post-render collapse loop'da ek kontrol: leaf tab_count=0 ise VE persist_when_empty=true olmasına rağmenfloating/close yoluyla boşaldıysa, yine de collapse et (ama bu tree modifikasyonu gerektirir)
|
||||
|
||||
**En basit çözüm:** `ensure_pane_at_anchor`'ın `persist_when_empty = true` satırını sil veya condition'ı değiştir. grimdock patches/ altında olduğu için değiştirilebilir.
|
||||
|
||||
### Adım 4: Pin Re-dock (#6)
|
||||
**Dosya:** `grim_dock.rs:913-962` (re-dock after pin click)
|
||||
|
||||
**Değişiklik:** Re-dock processing'inin `panels_to_close` processing'den SONRA çalıştığından emin ol (ki close re-dock'u iptal etmesin). Mevcut sıralama zaten doğru: render_floating_panels → re_dock, grim_dock.render → panels_to_close. Ama ikisi farklı fonksiyonlar — mod.rs'de sıralamayı kontrol et.
|
||||
|
||||
**Ek:** Re-dock'ta `pinned_panels.insert()` çağrısı var mı? Evet, var (satır 956-958). Bu doğru.
|
||||
|
||||
**Beyin fırtınası asıl sorun:** `render_floating_panels` `host: &mut GrimDockHost` alıyor. Ama `GrimDockHost::render()` zaten `&mut self` ile tree'yi modified etmiş. `render_floating_panels` ayrı bir çağrı — `host`'u tekrar mutable olarak alıyor. Bu Rust borrow checker'dan geçiyorsa sorun yok. Ama `app` (HcieApp) referansı her iki yerde de paylaşılıyor — `grim_floating_panels` hem `GrimDockHost::render()` hem `render_floating_panels()` tarafından değiştiriliyor.
|
||||
|
||||
**ASIL SORUN BULUNMUŞ OLABİLİR:** `grim_dock_host.render(ui, app)` çağrısı `app`'i `&mut` olarak alıyor. Sonra `render_floating_panels(ctx, app, host, dock_area)` tekrar `app`'i `&mut` olarak alıyor. Bu iki çağrı arasında `app.panels_to_float`, `app.panels_to_close`, `app.grim_floating_panels` gibi field'lar değiştiriliyor. İlk çağrıda `panels_to_float` drain ediliyor, ikinci çağrıda `grim_floating_panels`'e push ediliyor. Bu doğru bir sıralama.
|
||||
|
||||
**Ama re-dock:** `render_floating_panels` içinde `re_dock` set ediliyor ve loop SONRASINDA işleniyor. Bu sırada `host.tree` modifiye ediliyor. Tree bir sonraki frame'de render edilecek. Bu normal.
|
||||
|
||||
**Kesin test:** Log'ları kontrol et — "grimdock: docked tab ... back to leaf ..." log'u çıkıyor mu?
|
||||
|
||||
### Adım 5: Menu/Statusbar Sync (#7)
|
||||
**Dosya:** `grim_dock.rs` (close handler: 248-274, focus handler: 322-402), `menus.rs:534-553`
|
||||
|
||||
**Mevcut durum:** Close handler `pinned_panels.remove()`, focus handler `pinned_panels.insert()` çağrısı var. View menu de `pinned_panels.contains()` ile checked durumunu belirliyor.
|
||||
|
||||
**Senkronizasyon sorunu:** grimdock header'daki X butonu `output.closed_tabs` ile döndürüyor (grim_dock.rs:577-588). Burada `pinned_panels.remove()` çağrılıyor. Bu doğru.
|
||||
|
||||
**Ama:** grimdock'un KENDİ header'ındaki collapse butonu (`toggle_collapsed`) `pinned_panels`'i değiştirmiyor! Kullanıcı collapse ederse, panel dar strip'e dönüşüyor ama `pinned_panels` hâlâ panel'i "açık" olarak gösteriyor → menüde checkmark kalıyor.
|
||||
|
||||
**Çözüm:** grimdock collapse/expand handler'ında da `pinned_panels` güncelle. Veya daha iyisi: menüdeki `is_open` kontrolünü `pinned_panels` yerine grimdock tree'den sorgula (`find_leaf_containing` + `!is_collapsed`).
|
||||
|
||||
## Öncelik Sıralaması
|
||||
|
||||
| # | Sorun | Dosya | Zorluk |
|
||||
|---|-------|-------|--------|
|
||||
| 0 | Build çalıştır | - | Kolay |
|
||||
| 1 | Floating panel taşma | grim_dock.rs | Orta |
|
||||
| 2 | Docked panel border | grim_dock.rs | Kolay |
|
||||
| 3 | Ghost panel | grimdock/tree.rs | Orta |
|
||||
| 4 | Pin re-dock kaybolma | grim_dock.rs | Orta |
|
||||
| 5 | Menu sync | grim_dock.rs + menus.rs | Kolay |
|
||||
|
||||
## Test Senaryosu
|
||||
|
||||
1. Uygulamayı başlat
|
||||
2. View → Panels → "Color Palette" tıkla → panel collapse olmalı (32px strip, dikey yazı)
|
||||
3. Tekrar tıkla → panel expand olmalı
|
||||
4. Color Palette'i sağ tıkla → "Float Panel" → floating window açılmalı
|
||||
5. Floating window'da pin tıkla → panel eski yerine dönmeli + menüde checkmark görünmeli
|
||||
6. Floating window'da X tıkla → panel kaybolmalı + menüde checkmark kalkmalı
|
||||
7. Tekrar View → Panels → "Color Palette" → panel geri gelmeli
|
||||
8. Tüm panelleri kapat → ghost strip kalmamalı
|
||||
9. Resize grip'leri görünür olmalı (paneller arası çizgiler)
|
||||
@@ -0,0 +1,330 @@
|
||||
# HCIE-egui-app Refactoring Plan
|
||||
|
||||
**Goal:** Reduce spaghetti score from 7.5/10 to ~3/10 by splitting god files, removing dead code, standardizing architecture.
|
||||
|
||||
**Constraints:**
|
||||
- Each phase must compile (`cargo check`) and pass existing tests (`cargo test`)
|
||||
- Do NOT change any business logic
|
||||
- Do NOT touch locked engine crates (Layer 1–5)
|
||||
- All new files go in `hcie-egui-app/crates/hcie-gui-egui/src/`
|
||||
- Panel crates go in `hcie-egui-app/crates/`
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: Baseline & Preparation
|
||||
|
||||
**Files:** `hcie-egui-app/Cargo.toml`, test files.
|
||||
|
||||
1. Run full test suite, record pass/fail:
|
||||
```bash
|
||||
cargo test -p hcie-gui-egui 2>&1 | tee /tmp/baseline_test.log
|
||||
cargo clippy -p hcie-gui-egui 2>&1 | tee /tmp/baseline_clippy.log
|
||||
```
|
||||
2. Remove direct engine crate deps from `hcie-egui-app/Cargo.toml`:
|
||||
- Remove `hcie-protocol`, `hcie-io`, `hcie-color`, `hcie-blend` entries
|
||||
- If any crate in workspace actually uses them, add the dep only there
|
||||
3. Verify compile with `cargo check -p hcie-gui-egui`
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Remove Grimdock Backend
|
||||
|
||||
**Files to delete:**
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/app/grim_dock.rs`
|
||||
|
||||
**Files to modify:**
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs`:
|
||||
- Remove `pub mod grim_dock;`
|
||||
- Remove `use_grimdock: bool` field from `HcieApp`
|
||||
- Remove `grim_dock_host: Option<GrimDockHost>` field
|
||||
- Remove `grim_floating_panels` field
|
||||
- Remove all `if self.use_grimdock { ... } else { ... }` branches in `drain_events`, `ui_main_assembly`, `HcieApp::new`
|
||||
- Remove `default_dock_state()` calls guarded by grimdock toggle
|
||||
- Simplify `drain_events` DocClosed handler (remove grimdock branch)
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/app/dock_controller.rs`:
|
||||
- Remove any grimdock references
|
||||
- `hcie-egui-app/Cargo.toml`:
|
||||
- Remove `grimdock` dependency if present
|
||||
- Any test files referencing grimdock
|
||||
|
||||
**Verification:** `cargo check -p hcie-gui-egui && cargo test -p hcie-gui-egui`
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Extract State Structs from `app/mod.rs`
|
||||
|
||||
Create `app/state/` module. Move struct definitions out of `mod.rs`.
|
||||
|
||||
### 2a — `app/state/mod.rs`
|
||||
```rust
|
||||
pub mod app_state;
|
||||
pub mod document;
|
||||
pub mod tool_state;
|
||||
```
|
||||
|
||||
### 2b — `app/state/document.rs`
|
||||
Move `AppDocument` struct + impl (lines ~431–541) **exactly as-is**. Re-export from `state::document`.
|
||||
|
||||
### 2c — `app/state/tool_state.rs`
|
||||
Move `ToolState` struct + impl (lines ~546–950) **exactly as-is**. Includes `TextEditState`, `AiChatMessage`, `AiChatConfig`, `ComfyStatus`, `ComfyConfig`, `ComfyMode`, `VisionTask`, `VisionBackend`, `SelectionTransform`, `TransformHandle`, `Settings`.
|
||||
|
||||
### 2d — `app/state/app_state.rs`
|
||||
Move `HcieApp` struct + fields (lines ~960–1095), `SaveErrorState` (lines ~1097–1108), `AppMode`, constants (`TOOL_SLOTS`, `PANEL_SIDES`).
|
||||
|
||||
### Replace in `app/mod.rs`
|
||||
- Remove moved struct definitions
|
||||
- Add `mod state;` and `pub use state::*;`
|
||||
- Update all references to use new paths
|
||||
|
||||
**Verification:** `cargo check -p hcie-gui-egui && cargo test`
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Extract Event Handlers from `drain_events`
|
||||
|
||||
**Rationale:** The 1,475-line `drain_events` function handles 60+ event variants inline. Each domain gets its own handler file.
|
||||
|
||||
### Create `app/events/` module:
|
||||
|
||||
**Files:**
|
||||
| File | Handles AppEvent variants |
|
||||
|------|--------------------------|
|
||||
| `app/events/mod.rs` | Dispatch function + `EventError` type |
|
||||
| `app/events/layers.rs` | LayerAdd, LayerDelete, LayerSelect, LayerDuplicate, LayerMerge, LayerFlatten, LayerClear, LayerMoveUp/Down, SetLayerOpacity, SetLayerBlend, ToggleLayerVisibility |
|
||||
| `app/events/file.rs` | DocCreated, DocClosed, FileOpen, FileSave, FileSaveAs, FileExport, FileReload |
|
||||
| `app/events/selection.rs` | SelectionGrow, SelectionShrink, SelectionFeather, SelectionInvert, SelectionDeselect, SelectionFromLayer |
|
||||
| `app/events/vision.rs` | VisionSmartSelect, VisionSegmentAnything, VisionInpaint, VisionSuperResolution, VisionBackgroundRemove |
|
||||
| `app/events/ai.rs` | AiChatSend, AiChatStop, AiActionRequest, AiToolCall |
|
||||
| `app/events/edit.rs` | ClipboardCopy, ClipboardCut, ClipboardPaste, DeleteLayerContent, ClearSelection, TransformStart/Apply/Cancel |
|
||||
| `app/events/view.rs` | ZoomIn, ZoomOut, ZoomReset, PanReset, ToggleGrid, ToggleGuides, ThemeChanged |
|
||||
|
||||
### `app/events/mod.rs` pattern:
|
||||
```rust
|
||||
pub fn dispatch_event(app: &mut HcieApp, ctx: &egui::Context, event: AppEvent) {
|
||||
match event {
|
||||
AppEvent::LayerAdd { .. } => layers::handle_layer_add(app, ctx, ...),
|
||||
AppEvent::FileSave => file::handle_file_save(app, ctx),
|
||||
// ... one match arm per variant, delegating to handler fn
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Replace `drain_events` body:
|
||||
Collapse the 1,475-line function to:
|
||||
```rust
|
||||
pub fn drain_events(&mut self, ctx: &egui::Context) {
|
||||
let mut count = 0;
|
||||
while let Some(event) = self.event_bus.pop() {
|
||||
count += 1;
|
||||
if count > 100 { break; }
|
||||
let _plugin_actions = self.plugin_host.dispatch_app_event(&event, &mut self.state);
|
||||
events::dispatch_event(self, ctx, event);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Verification:** `cargo check -p hcie-gui-egui && cargo test -p hcie-gui-egui`
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Extract Lifecycle & Shortcuts from `app/mod.rs`
|
||||
|
||||
### `app/lifecycle.rs`
|
||||
- `HcieApp::new(cc, tablet_state, panel_screenshot_request)` (~315 lines)
|
||||
- `HcieApp::logic(ctx)` (~165 lines)
|
||||
- `enforce_constraints(...)` (~202 lines)
|
||||
|
||||
### `app/shortcuts.rs`
|
||||
- `HcieApp::handle_shortcuts(ctx)` (~195 lines)
|
||||
|
||||
### `app/theme.rs`
|
||||
- `apply_theme(ctx, theme)` function
|
||||
- `setup_custom_fonts(ctx)` function
|
||||
- `ThemeColors` struct and `ThemePreset` enum
|
||||
- `default_font_bytes()` function
|
||||
- `PANEL_SIDES` constant
|
||||
|
||||
### `app/dock_layout.rs`
|
||||
- `default_dock_state()` (~30 lines)
|
||||
- `pane_from_name()` (~15 lines)
|
||||
|
||||
After extraction, `app/mod.rs` should be ~400-600 lines (public module declarations, re-exports, `drain_events` function, `ui_main_assembly`).
|
||||
|
||||
**Verification:** `cargo check -p hcie-gui-egui && cargo test`
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Split `panels.rs` (1,658 lines → individual panel crates)
|
||||
|
||||
**Decision:** Each major panel becomes a separate crate in `hcie-egui-app/crates/`.
|
||||
|
||||
### New crates to create:
|
||||
|
||||
| Crate | Source code from `panels.rs` | Approx lines |
|
||||
|-------|------------------------------|-------------|
|
||||
| `egui-panel-menubar` | `show_menu_bar()` + helpers | ~120 |
|
||||
| `egui-panel-toolbar` | `show_unified_toolbar()` + helpers | ~250 |
|
||||
| `egui-panel-statusbar` | `show_status_bar()` + helpers | ~80 |
|
||||
| `egui-panel-layers` | `show_layers_ui()` + helpers | ~400 |
|
||||
| `egui-panel-history` | History panel section | ~80 |
|
||||
| `egui-panel-properties` | Properties panel section | ~350 |
|
||||
| `egui-panel-plugins` | `show_plugins_ui()` | ~80 |
|
||||
|
||||
### Each crate follows:
|
||||
```
|
||||
crates/egui-panel-<name>/
|
||||
├── Cargo.toml (depends on hcie-engine-api, egui)
|
||||
└── src/
|
||||
└── lib.rs (exports pub fn show(…))
|
||||
```
|
||||
|
||||
### `app/panels.rs` after extraction:
|
||||
Becomes a thin re-export module (~40 lines).
|
||||
|
||||
### Update:
|
||||
- `hcie-egui-app/Cargo.toml` — add new crate deps
|
||||
- All call sites `panels::show_menu_bar(app, ctx)` → `egui_panel_menubar::show(app, ctx)`
|
||||
|
||||
**Verification:** `cargo check -p hcie-gui-egui && cargo test`
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Split `canvas/mod.rs` (1,800 lines)
|
||||
|
||||
**Files to create in `canvas/`:**
|
||||
|
||||
| File | Content | Est. lines |
|
||||
|------|---------|-----------|
|
||||
| `canvas/navigation.rs` | Zoom computation, scroll-wheel zoom, middle-mouse pan | ~200 |
|
||||
| `canvas/drawing.rs` | Stroke handling, brush/pen input, pressure mapping | ~400 |
|
||||
| `canvas/selection.rs` | Lasso selection drawing, marching ants, selection rect | ~250 |
|
||||
| `canvas/text.rs` | Text overlay editing canvas | ~150 |
|
||||
| `canvas/interaction.rs` | Pointer event dispatch, tool routing, context menu | ~300 |
|
||||
|
||||
After extraction, `canvas/mod.rs` contains only `CanvasWidget` struct + `ui()` entry point that delegates to sub-modules.
|
||||
|
||||
**Verification:** `cargo check -p hcie-gui-egui && cargo test && cargo test -p hcie-gui-egui --test canvas_engine`
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Remove Dead Code
|
||||
|
||||
1. Remove `#![allow(dead_code)]` from these files one at a time and fix what the compiler flags:
|
||||
- `app/mod.rs`
|
||||
- `app/panels.rs`
|
||||
- `app/grim_dock.rs` (already deleted in Phase 1)
|
||||
- `app/dock.rs`
|
||||
- `app/dialogs.rs`
|
||||
- `app/gui_layout.rs`
|
||||
- `app/viewer.rs`
|
||||
- `event_bus.rs`
|
||||
- `plugin.rs`
|
||||
|
||||
2. Remove unused modules:
|
||||
- `app/custom_loaders.rs` (if superseded by `custom_loaders_kra_v2.rs`)
|
||||
- `app/panel_layout.rs` → `panel_layout.json` if layout-driven is ready
|
||||
- `app/scrollable_panel.rs` if nothing imports it
|
||||
|
||||
3. Remove dead code inside live files (commented-out blocks, TODO stubs marked dead)
|
||||
|
||||
**Verification:** `cargo check -p hcie-gui-egui && cargo clippy -p hcie-gui-egui`
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Fix Panic Points
|
||||
|
||||
Replace `unwrap()` / `expect()` calls with proper error handling:
|
||||
|
||||
1. **30 unwrap() calls** — audit each:
|
||||
- `Mutex::lock().unwrap()` → if not poison-poison, use `let Ok(guard) = ...` else `log::error! + continue`
|
||||
- `HashMap::get().unwrap()` → `.unwrap_or_else(|| { log::warn!; default })`
|
||||
- `Vec::last().unwrap()` → pattern match or `if let Some`
|
||||
|
||||
2. **24 expect() calls** — same treatment with informative error messages in `expect()` or convert to Result
|
||||
|
||||
3. Priority order: event handlers (user-triggered) > startup > tests
|
||||
|
||||
**Verification:** `cargo check -p hcie-gui-egui && cargo test`
|
||||
|
||||
---
|
||||
|
||||
## Phase 9: Standardize Panel Architecture
|
||||
|
||||
After Phase 5, all panels are separate crates. Now:
|
||||
|
||||
1. **Define a `Panel` trait** in `hcie-gui-egui`:
|
||||
```rust
|
||||
pub trait Panel {
|
||||
fn id(&self) -> &'static str;
|
||||
fn title(&self) -> &'static str;
|
||||
fn show(&mut self, app: &mut HcieApp, ui: &mut egui::Ui);
|
||||
}
|
||||
```
|
||||
|
||||
2. **Wrap each panel crate's `show` function** in a struct implementing `Panel`.
|
||||
|
||||
3. **Register panels** in a `PanelRegistry` owned by `HcieApp`, replacing the manual `match` dispatch in `dock.rs/HcieTabViewer`.
|
||||
|
||||
This unifies how panels are discovered, rendered, and toggled.
|
||||
|
||||
**Verification:** `cargo check -p hcie-gui-egui && cargo test -p hcie-gui-egui --test dock_behavior --test invariant_guards`
|
||||
|
||||
---
|
||||
|
||||
## Dependency Graph (safe order)
|
||||
|
||||
```
|
||||
Phase 0 (baseline)
|
||||
│
|
||||
▼
|
||||
Phase 1 (grimdock remove) ──► Phase 7 (dead code, partial)
|
||||
│
|
||||
▼
|
||||
Phase 2 (state extract)
|
||||
│
|
||||
▼
|
||||
Phase 3 (event handlers)
|
||||
│
|
||||
▼
|
||||
Phase 4 (lifecycle extract)
|
||||
│
|
||||
▼
|
||||
Phase 5 (panel crates) ──► Phase 9 (panel trait)
|
||||
│
|
||||
▼
|
||||
Phase 6 (canvas split)
|
||||
│
|
||||
▼
|
||||
Phase 7 (dead code, full)
|
||||
│
|
||||
▼
|
||||
Phase 8 (unwrap fix)
|
||||
```
|
||||
|
||||
Phases 1–4 and 7 are sequential (mod.rs shrinks step by step). Phases 5, 6 can parallelize. Phase 8 is last because line numbers shift. Phase 9 depends on Phase 5.
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|-----------|
|
||||
| `app/mod.rs` changes break imports everywhere | Run `cargo check` after every file move; use IDE rename refactoring |
|
||||
| `drain_events` extraction introduces subtle behavior changes | Write characterization tests first: record (event → resulting state) pairs for each variant, then assert same behavior after extraction |
|
||||
| Panel crate extraction breaks dock integration | Keep `egui-panel-*` crates thin; pass `&mut HcieApp` so they have full access |
|
||||
| `unwrap()` removal changes control flow | Wrap each removal in `debug_assert!` + `if let` with `log::error!` — panic behavior becomes logged warning, but execution continues |
|
||||
| Dead code removal removes something used only at runtime | Run the app with `cargo run` after each removal pass; use `#[cfg(not(debug_assertions))]` for truly required dead code |
|
||||
|
||||
---
|
||||
|
||||
## Validation Gate (run after each phase)
|
||||
|
||||
```bash
|
||||
cargo check -p hcie-gui-egui 2>&1
|
||||
cargo clippy -p hcie-gui-egui 2>&1 # deny warnings
|
||||
cargo test -p hcie-gui-egui 2>&1
|
||||
# Dock-specific tests:
|
||||
cargo test -p hcie-gui-egui --test dock_behavior --test invariant_guards
|
||||
# Visual baseline (if CI has display):
|
||||
cargo run -p hcie-gui-egui -- --screenshot-panel "Layers" /tmp/check_Layers.png
|
||||
```
|
||||
@@ -0,0 +1,253 @@
|
||||
# Comprehensive Plan: Port All egui Signals/Events to Qt GUI
|
||||
|
||||
## Complete Event/Signal Map (50+ events from `AppEvent`)
|
||||
|
||||
Every event in `event_bus.rs` must have a Qt equivalent. Here is the full mapping:
|
||||
|
||||
### 1. Tool & Drawing Events
|
||||
| egui Event | Qt Signal/Slot | Implementation |
|
||||
|-----------|---------------|----------------|
|
||||
| `ToolChanged(Tool)` | `ToolsPanel::toolChanged(int)` → `MainWindow::onToolChanged(int)` | Already wired |
|
||||
| `LayerAdded` | `LayersPanel` button → `MainWindow::onNewLayer()` | Already wired |
|
||||
| `LayerDeleted(u64)` | `LayersPanel` delete button → `MainWindow::onDeleteLayer()` | Already wired |
|
||||
| `LayerSelected(u64)` | `LayersPanel::itemClicked` → `engine->setActiveLayer(id)` | Already wired |
|
||||
| `Undo` | `actionUndo` → `MainWindow::onUndo()` | Already wired |
|
||||
| `Redo` | `actionRedo` → `MainWindow::onRedo()` | Already wired |
|
||||
| `RenderRequested` | After every engine operation, call `canvas->update()` | Needs wiring |
|
||||
| `DrawingFinished` | After `end_stroke`, no-op in egui | No-op in Qt too |
|
||||
|
||||
### 2. Document Events
|
||||
| egui Event | Qt Signal/Slot | Implementation |
|
||||
|-----------|---------------|----------------|
|
||||
| `DocSwitched(usize)` | `QTabWidget::currentChanged` → `MainWindow::onTabChanged(int)` | Already wired |
|
||||
| `DocClosed(usize)` | `QTabWidget::tabCloseRequested` → `MainWindow::onTabCloseRequested(int)` | Already wired |
|
||||
| `CloseAllDocs` | Menu action → iterate tabs, close all | Needs wiring |
|
||||
| `DocCreated{name,w,h}` | `NewImageDialog` → `MainWindow::createDocument()` | Already wired |
|
||||
|
||||
### 3. File Events
|
||||
| egui Event | Qt Signal/Slot | Implementation |
|
||||
|-----------|---------------|----------------|
|
||||
| `FileOpenRequested` | `actionOpen` → `QFileDialog::getOpenFileName` → `engine->open_image()` | Stub exists |
|
||||
| `FileSaveRequested` | `actionSave` → `engine->save_as()` | Stub exists |
|
||||
| `FileSaveAsRequested` | `actionSaveAs` → `QFileDialog::getSaveFileName` → `engine->save_as()` | Stub exists |
|
||||
| `FileOpened(path,data,w,h)` | After successful load, create document | Needs wiring |
|
||||
| `ExportRequested` | `actionExport` → `QFileDialog::getSaveFileName` | Stub exists |
|
||||
| `ImportRequested` | `actionImport` → `QFileDialog::getOpenFileName` | Stub exists |
|
||||
| `ImportSvg(path)` | `actionImportSvg` → `engine->import_svg()` | Stub exists |
|
||||
| `ImportBrushesRequested` | `actionImportBrushes` → `QFileDialog` → ABR parser | Stub exists |
|
||||
|
||||
### 4. Filter Events
|
||||
| egui Event | Qt Signal/Slot | Implementation |
|
||||
|-----------|---------------|----------------|
|
||||
| `ApplyFilter(id, params)` | `FiltersPanel::itemClicked` → `engine->apply_filter()` | Already wired |
|
||||
|
||||
### 5. Selection Events
|
||||
| egui Event | Qt Signal/Slot | Implementation |
|
||||
|-----------|---------------|----------------|
|
||||
| `SelectionAll` | `actionSelectAll` → `engine->selection_all()` | Already wired |
|
||||
| `SelectionDeselect` | `actionSelectDeselect` → `engine->selection_clear()` | Already wired |
|
||||
| `SelectionInvert` | `actionSelectInvert` → `engine->selection_invert()` | Already wired |
|
||||
| `SelectionGrow(px)` | `actionSelectGrow` → dialog → `engine->selection_grow()` | Stub exists |
|
||||
| `SelectionShrink(px)` | `actionSelectShrink` → dialog → `engine->selection_shrink()` | Stub exists |
|
||||
| `SelectionFeather(r)` | `actionSelectFeather` → dialog → `engine->selection_feather()` | Stub exists |
|
||||
| `SelectionErode(px)` | `actionSelectErode` → dialog → `engine->selection_shrink()` | Stub exists |
|
||||
| `SelectionFade(px)` | `actionSelectFade` → dialog → `engine->selection_feather()` | Stub exists |
|
||||
|
||||
### 6. Image Transform Events
|
||||
| egui Event | Qt Signal/Slot | Implementation |
|
||||
|-----------|---------------|----------------|
|
||||
| `RotateClockwise` | `actionRotateCW` → `engine->rotate_layer(+90)` | Already wired |
|
||||
| `RotateCounterClockwise` | `actionRotateCCW` → `engine->rotate_layer(-90)` | Already wired |
|
||||
| `Rotate180` | `actionRotate180` → `engine->rotate_layer(180)` | Already wired |
|
||||
| `FlipHorizontal` | `actionFlipH` → `engine->flip_layer_horizontal()` | Already wired |
|
||||
| `FlipVertical` | `actionFlipV` → `engine->flip_layer_vertical()` | Already wired |
|
||||
| `Crop` | `actionCropImage` → `engine->crop(selection_rect)` | Stub exists |
|
||||
| `ImageSize` | `actionImageSize` → dialog | Stub exists |
|
||||
| `CanvasSize` | `actionCanvasSize` → dialog | Stub exists |
|
||||
| `Invert` | `actionInvert` → `engine->apply_filter("invert")` | Already wired |
|
||||
|
||||
### 7. Layer Operations
|
||||
| egui Event | Qt Signal/Slot | Implementation |
|
||||
|-----------|---------------|----------------|
|
||||
| `MergeDown` | `actionMergeDown` → `engine->merge_down()` | Already wired |
|
||||
| `FlattenImage` | `actionFlattenImage` → `engine->flatten_image()` | Already wired |
|
||||
| `LayerClear` | `actionClearLayer` → `engine->clear_active_layer_pixels()` | Already wired |
|
||||
| `TransformStart` | `actionFreeTransform` → enter transform mode | Stub exists |
|
||||
| `AlignLayer(axis)` | `Layer > Align > [axis]` → `engine->align_active_layer()` | Stub exists |
|
||||
|
||||
### 8. Clipboard Events
|
||||
| egui Event | Qt Signal/Slot | Implementation |
|
||||
|-----------|---------------|----------------|
|
||||
| `ClipboardCopy` | `actionCopy` → `QClipboard` → copy layer pixels | Stub exists |
|
||||
| `ClipboardCut` | `actionCut` → copy + clear pixels | Stub exists |
|
||||
| `ClipboardPaste` | `actionPaste` → `QClipboard` → paste as new layer | Stub exists |
|
||||
| `ClipboardPasteSpecial` | `actionPasteSpecial` → paste with options | Stub exists |
|
||||
|
||||
### 9. Zoom/Pan Events
|
||||
| egui Event | Qt Signal/Slot | Implementation |
|
||||
|-----------|---------------|----------------|
|
||||
| `ZoomIn` | `actionZoomIn` → `canvas->zoomIn()` | Already wired |
|
||||
| `ZoomOut` | `actionZoomOut` → `canvas->zoomOut()` | Already wired |
|
||||
| `ZoomReset` | `actionZoomReset` → `canvas->setZoom(1.0)` | Already wired |
|
||||
| `ZoomSet(f)` | `zoomSlider` valueChanged → `canvas->setZoom()` | Already wired |
|
||||
| `Pan(dx,dy)` | Middle-mouse drag → `canvas->resetPan()` | Already wired |
|
||||
| `PanReset` | `actionPanReset` → `canvas->resetPan()` | Already wired |
|
||||
|
||||
### 10. AI/Vision Events
|
||||
| egui Event | Qt Signal/Slot | Implementation |
|
||||
|-----------|---------------|----------------|
|
||||
| `AiChatSubmit(text)` | `AiChatPanel` send button → AI engine call | Stub exists |
|
||||
| `AiChatStop` | AI panel stop button | Stub exists |
|
||||
| `SmartSelectRun{x,y}` | Smart Select tool click → `engine->vision_object_segment()` | Stub exists |
|
||||
| `VisionRunTask` | Vision panel run button → `engine->vision_*()` | Stub exists |
|
||||
| `VisionSelectRun{x1,y1,x2,y2}` | Vision Select tool drag → `engine->vision_object_segment()` | Stub exists |
|
||||
| `VisionBackendChanged(backend)` | Vision panel CPU/GPU toggle | Stub exists |
|
||||
|
||||
### 11. Text Events
|
||||
| egui Event | Qt Signal/Slot | Implementation |
|
||||
|-----------|---------------|----------------|
|
||||
| `ToolTextStart{x,y}` | Text tool click → `engine->add_text()` | Stub exists |
|
||||
| `ToolTextAccepted` | Accept button → commit text layer | Stub exists |
|
||||
| `ToolTextCancelled` | Cancel button → delete/revert text layer | Stub exists |
|
||||
| `TextLayerSelected(id)` | Double-click text layer → enter edit mode | Stub exists |
|
||||
|
||||
### 12. Theme/Misc Events
|
||||
| egui Event | Qt Signal/Slot | Implementation |
|
||||
|-----------|---------------|----------------|
|
||||
| `ThemeChanged(preset)` | Theme menu items → `applyTheme()` | Already wired |
|
||||
| `ResetLayout` | `actionResetLayout` → recreate dock layout | Stub exists |
|
||||
| `SaveSettings` | After settings change → save to file | Stub exists |
|
||||
| `ShowStatusMessage(text)` | After operations → `statusBar->showMessage()` | Needs wiring |
|
||||
| `OpenLayerStyles(id)` | Layer Styles button → open panel | Stub exists |
|
||||
|
||||
### 13. Brush Editor Events (from `brush_editor.rs`)
|
||||
| egui Feature | Qt Implementation |
|
||||
|-------------|------------------|
|
||||
| Brush Tip category (size, hardness, spacing, opacity) | `BrushesPanel` needs sliders |
|
||||
| Dynamics category (pressure, velocity, tilt) | `BrushesPanel` needs sliders |
|
||||
| Color category (cyclic color, variant) | `BrushesPanel` needs checkboxes |
|
||||
| Texture category (brush texture overlays) | `BrushesPanel` needs texture picker |
|
||||
| Live stroke preview (engine-rendered) | `CanvasWidget` render brush preview |
|
||||
| Preset save/load | `BrushesPanel` save/load buttons |
|
||||
|
||||
### 14. Geometry Panel Events (from `geometry_panel.rs`)
|
||||
| egui Feature | Qt Implementation |
|
||||
|-------------|------------------|
|
||||
| Vector shape list (per-shape selection) | `PropertiesPanel` or new `GeometryPanel` |
|
||||
| Per-shape stroke/opacity/hardness sliders | `PropertiesPanel` dynamic controls |
|
||||
| Per-shape color picker (stroke + fill) | `PropertiesPanel` color buttons |
|
||||
| Per-shape bounds display | `PropertiesPanel` label |
|
||||
| Per-shape delete button | `PropertiesPanel` delete button |
|
||||
| Boolean operations (union, subtract, intersect) | `PropertiesPanel` or context menu |
|
||||
|
||||
### 15. Layer Styles Events (from `layer_styles_panel.rs`)
|
||||
| egui Feature | Qt Implementation |
|
||||
|-------------|------------------|
|
||||
| Drop Shadow (enable, opacity, angle, distance, spread, size, color, blend) | `LayerStylesPanel` sliders |
|
||||
| Inner Shadow (same params) | `LayerStylesPanel` sliders |
|
||||
| Outer Glow (enable, opacity, spread, size, color, blend) | `LayerStylesPanel` sliders |
|
||||
| Inner Glow (enable, opacity, choke, size, color, blend) | `LayerStylesPanel` sliders |
|
||||
| Bevel & Emboss (enable, depth, size, angle, altitude, highlight/shadow) | `LayerStylesPanel` sliders |
|
||||
| Satin (enable, opacity, angle, distance, size, color, invert) | `LayerStylesPanel` sliders |
|
||||
| Color Overlay (enable, opacity, color, blend) | `LayerStylesPanel` controls |
|
||||
| Gradient Overlay (enable, opacity, blend, angle, scale, gradient) | `LayerStylesPanel` controls |
|
||||
| Pattern Overlay (enable, opacity, blend, scale, pattern) | `LayerStylesPanel` controls |
|
||||
| Stroke (enable, size, position, opacity, color, blend) | `LayerStylesPanel` controls |
|
||||
|
||||
## Tasks (Priority Order)
|
||||
|
||||
### P0: Fix QSS Colors to Match egui Exactly
|
||||
**File:** `hcie-qt-app/resources/themes/photoshop_dark.qss`
|
||||
|
||||
All 12+ color tokens must match `ThemeColors::get(ThemePreset::Photoshop)`:
|
||||
- `#1c1c1e` (bg_app), `#323236` (bg_panel), `#3e3e42` (bg_hover), `#242426` (bg_dark_depth)
|
||||
- `#3a3a3e` (bg_elevated), `#161618` (bg_recessed), `#202022` (title_bar_bg)
|
||||
- `#3878dc` (accent), `#404044` (border_high), `#28282a` (border_low)
|
||||
- `#e0e0e0` (text_primary), `#949498` (text_secondary)
|
||||
|
||||
### P1: Wire All Menu Actions to Engine FFI
|
||||
**File:** `hcie-qt-app/src/mainwindow.cpp`
|
||||
|
||||
Currently stub implementations exist for: Import, Export, ClipboardCopy/Cut/Paste, Crop, ImageSize, CanvasSize, SelectionGrow/Shrink/Feather/Erode/Fade, Align, Transform, LayerStyles, ImageSize, CanvasSize.
|
||||
|
||||
Each must call the corresponding `m_engine->methodName()` and then `refresh()` all panels + update canvas.
|
||||
|
||||
### P2: Wire Panel Signals to Engine
|
||||
**Files:** All panel `.cpp` files
|
||||
|
||||
Each panel's `refresh()` must read from engine and populate widgets. Each widget change must call engine method and emit `renderRequested`.
|
||||
|
||||
Key panels needing signal wiring:
|
||||
- **LayersPanel**: visibility toggle, blend mode change, opacity change, layer reorder, add/delete/group/flatten buttons
|
||||
- **HistoryPanel**: jump-to-step click → `engine->jumpToHistory()`
|
||||
- **PropertiesPanel**: tool-specific sliders (size, opacity, hardness, flow, spacing per tool)
|
||||
- **BrushesPanel**: preset selection → `engine->set_brush_tip()`
|
||||
- **FiltersPanel**: filter click → `engine->apply_filter(name, params)`
|
||||
- **ColorsPanel**: color selection → `engine->set_color(rgba)`
|
||||
- **AiChatPanel**: send button → `engine->ai_chat_submit()`
|
||||
|
||||
### P3: Add Missing Panels
|
||||
**New files needed:**
|
||||
- `geometry_panel.h/cpp` — vector shape list and per-shape editing
|
||||
- `layer_styles_panel.cpp` — flesh out with 10 style editors (currently stub)
|
||||
|
||||
### P4: Canvas Rendering Loop
|
||||
**File:** `hcie-qt-app/src/canvas_widget.cpp`
|
||||
|
||||
The `paintGL()` must call `engine->get_composite_pixels()` every frame (or on dirty). The current implementation does this but needs to handle the dirty flag properly — only re-upload when `engine->isCompositeDirty()`.
|
||||
|
||||
### P5: Properties Panel Tool-Specific Controls
|
||||
**File:** `hcie-qt-app/src/panels/properties_panel.cpp`
|
||||
|
||||
Currently shows placeholder. Must rebuild tool settings based on active tool:
|
||||
- Brush/Eraser: size, opacity, hardness, flow, spacing, color variant, cyclic color
|
||||
- Pen: size, opacity, hardness, cyclic color
|
||||
- Spray: radius, particle size, density, opacity, hardness
|
||||
- Flood Fill: tolerance
|
||||
- Magic Wand: tolerance
|
||||
- Vector tools: stroke, opacity, fill toggle, round rect radius, star points
|
||||
- Text: font, size, color, angle, alignment, orientation
|
||||
- Gradient: type (linear/radial)
|
||||
- VectorSelect: per-shape editing
|
||||
|
||||
### P6: Color Panel with Standard Qt ColorPicker
|
||||
**File:** `hcie-qt-app/src/panels/colors_panel.cpp`
|
||||
|
||||
Replace custom HSL wheel (broken rendering) with:
|
||||
- Foreground/Background color swatches
|
||||
- "Pick Color" button → `QColorDialog::getColor()`
|
||||
- Palette grid (basic colors)
|
||||
- Recent colors list
|
||||
|
||||
### P7: Status Bar Message Expiry
|
||||
**File:** `hcie-qt-app/src/mainwindow.cpp`
|
||||
|
||||
Implement `ShowStatusMessage` with 3-second timer expiry (matching egui).
|
||||
|
||||
## Validation
|
||||
|
||||
After each task:
|
||||
1. `cmake --build build` — must compile
|
||||
2. Visual comparison with egui screenshots
|
||||
3. Functional test: each menu item, each panel interaction, each keyboard shortcut
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `hcie-qt-app/resources/themes/photoshop_dark.qss` | ~30 color values, all widget styling |
|
||||
| `hcie-qt-app/src/mainwindow.cpp` | Wire all menu actions to engine |
|
||||
| `hcie-qt-app/src/mainwindow.h` | Add missing method declarations |
|
||||
| `hcie-qt-app/src/panels/properties_panel.cpp` | Tool-specific settings |
|
||||
| `hcie-qt-app/src/panels/colors_panel.cpp` | QColorDialog-based picker |
|
||||
| `hcie-qt-app/src/panels/brushes_panel.cpp` | Preset selection, brush tip editing |
|
||||
| `hcie-qt-app/src/panels/history_panel.cpp` | Jump-to-step |
|
||||
| `hcie-qt-app/src/panels/layers_panel.cpp` | Visibility/blend/opacity signals |
|
||||
| `hcie-qt-app/src/panels/filters_panel.cpp` | Filter param dialogs |
|
||||
| `hcie-qt-app/src/panels/ai_chat_panel.cpp` | AI chat submit |
|
||||
| `hcie-qt-app/src/panels/layer_styles_panel.cpp` | 10 style editors |
|
||||
| `hcie-qt-app/src/panels/layer_details_panel.cpp` | Read-only info display |
|
||||
| `hcie-qt-app/src/canvas_widget.cpp` | Dirty flag, composite upload |
|
||||
| `hcie-qt-app/src/hcie_engine_wrapper.h/cpp` | Missing FFI methods |
|
||||
| `hcie-qt-app/src/dialogs/*` | Wire all dialog buttons |
|
||||
| `hcie-engine-api/src/ffi.rs` | Add missing FFI functions |
|
||||
@@ -0,0 +1,187 @@
|
||||
# HCIE-egui `app/` Klasörü Refactor Planı
|
||||
|
||||
## Hedef
|
||||
`hcie-egui-app/crates/hcie-gui-egui/src/app/` altındaki 25+ flat dosyayı mantıksal alt klasörlere ayırarak:
|
||||
- Kodun kategorisini kestirmeyi kolaylaştırmak,
|
||||
- Ölü kod adaylarını tespit edip ayrı listelemek,
|
||||
- Her alt klasörün `mod.rs` aracılığıyla net bir sözleşme sunmasını sağlamak.
|
||||
|
||||
## Kapsam Dışı / Sınırlar
|
||||
- **Davranış değişikliği yok:** sadece dosya konumu ve modül path'i değişir.
|
||||
- **mod.rs bölünmesi yapılmayacak:** 5108 satırlık `mod.rs` içindeki state/app tanımları bu plan kapsamında ayrılmayacak; yalnızca alt modül deklarasyonları düzenlenecek.
|
||||
- **Ölü kod adayları yeni yapıya taşınmayacak:** sadece raporlanacak, silme kararı kullanıcıya bırakılacak.
|
||||
- **Engine (Layer 1–5) dokunulmaz:** sadece GUI (Layer 6) içinde hareket edilecek.
|
||||
- Build öncesi manifest hatası: `hcie-egui-app/Cargo.toml` bir `[lib]` bölümü içeriyor ama `src/lib.rs` yok; bu **plan öncesi ön koşul** olarak not edildi, plana dahil değil.
|
||||
|
||||
## Ölü Kod Adayları (import edilmeyen / `mod.rs`'de tanımlı olmayan)
|
||||
Aşağıdaki dosyalar `app/mod.rs` içinde `pub mod`/`mod` olarak declare edilmemiş veya aktif kod tarafından referans alınmıyor. Refactor kapsamında yeni alt klasörlere taşınmamalı, ayrı bir "dead-code candidates" listesi olarak ele alınmalı.
|
||||
|
||||
| Dosya | Gerekçe |
|
||||
|-------|---------|
|
||||
| `dock_controller.rs` | `mod.rs`'de declare edilmemiş; yalnızca `dock_engine.rs` ve `grim_dock.rs` ile kendi içinde döngüsel import ediyor. |
|
||||
| `dock_engine.rs` | `mod.rs`'de declare edilmemiş; sadece `dock_controller.rs` tarafından kullanılıyor. |
|
||||
| `panel_layout.rs` + `panel_layout.json` | Hiçbir aktif modül `PanelLayout::load_default()` dışı referans vermiyor; `load_default()` da çağrılmıyor. |
|
||||
| `history.rs` | `mod.rs`'de declare edilmemiş; `panels::show_history()` engine API üzerinden kendi mini history UI'ını çiziyor, bu dosya kullanılmıyor. |
|
||||
| `i18n.rs` | `mod.rs`'de declare edilmemiş (varsa) veya hiç referans almıyor. |
|
||||
| `manifest.rs` | `mod.rs`'de declare edilmemiş (varsa) veya hiç referans almıyor. |
|
||||
|
||||
> Not: `grim_dock.rs` aktif olarak `mod.rs` tarafından `pub mod grim_dock;` olarak declare ediliyor ve `HcieApp` içinde `grim_dock_host`/`use_grimdock` alanları var; bu yüzden ölü kod sayılmaz. Ancak mevcut başka bir plan (`1783517996924-egui-app-refactoring-plan.md` Phase 1) grimdock backend'in kaldırılmasını öngörüyor; bu planla çakışmaması için grimdock bu refactor kapsamında **dokunulmadan bırakılacak**.
|
||||
|
||||
## Önerilen Yeni Klasör Yapısı
|
||||
|
||||
```
|
||||
hcie-egui-app/crates/hcie-gui-egui/src/app/
|
||||
├── mod.rs # state/app tanımları + alt modül re-export'ları (~5108 satır, içerik değişmez)
|
||||
├── theme.rs # apply_theme, setup_custom_fonts, default_font_bytes (mod.rs'den çıkarılabilir ama opsiyonel)
|
||||
├── state/ # (gelecek aşama) AppDocument, ToolState, HcieApp vb.
|
||||
│
|
||||
├── panels/ # Panel UI modülleri
|
||||
│ ├── mod.rs # show_* fonksiyonlarını ve alt modülleri re-export et
|
||||
│ ├── menubar.rs # panels.rs içindeki show_menu_bar + yardımcılar
|
||||
│ ├── toolbar.rs # panels.rs içindeki unified toolbar
|
||||
│ ├── statusbar.rs # panels.rs içindeki show_status_bar
|
||||
│ ├── filters.rs # panels.rs içindeki show_filters_ui
|
||||
│ ├── brushes.rs # panels.rs içindeki show_brushes_ui (thin wrapper)
|
||||
│ ├── color.rs # panels.rs içindeki show_color_palette (thin wrapper)
|
||||
│ ├── layers.rs # panels.rs içindeki show_layers
|
||||
│ ├── history.rs # panels.rs içindeki show_history
|
||||
│ ├── properties.rs # panels.rs içindeki show_properties
|
||||
│ ├── plugins.rs # panels.rs içindeki show_plugins_ui
|
||||
│ ├── ai_chat.rs # panels.rs içindeki show_ai_chat
|
||||
│ └── comfy_ai.rs # panels.rs içindeki show_comfy_ai
|
||||
│
|
||||
├── dock/ # Docking/layout sistemleri
|
||||
│ ├── mod.rs # dock.rs + grim_dock.rs re-export (grimdock ayrı tutulur)
|
||||
│ ├── egui_dock.rs # mevcut dock.rs (V3 egui_dock)
|
||||
│ └── grim_dock.rs # mevcut grim_dock.rs (dokunulmadan taşınır)
|
||||
│
|
||||
├── dialogs/ # Diyaloglar
|
||||
│ ├── mod.rs # dialog fonksiyonlarını re-export et
|
||||
│ ├── new_image.rs # dialogs.rs içindeki new image dialog
|
||||
│ ├── adjustments.rs # dialogs.rs içindeki adjustments
|
||||
│ ├── selection_op.rs # dialogs.rs içindeki selection op
|
||||
│ └── filter_dialogs.rs # filter_dialog.rs içeriği
|
||||
│
|
||||
├── tools/ # Araç/editör modülleri
|
||||
│ ├── mod.rs
|
||||
│ ├── toolbox.rs # mevcut toolbox.rs
|
||||
│ ├── brush_editor.rs # mevcut brush_editor.rs
|
||||
│ ├── text_editor.rs # mevcut text_editor.rs
|
||||
│ ├── geometry_panel.rs # mevcut geometry_panel.rs
|
||||
│ ├── layer_styles_panel.rs # mevcut layer_styles_panel.rs
|
||||
│ ├── layer_details_panel.rs # mevcut layer_details_panel.rs
|
||||
│ ├── shape_sync.rs # mevcut shape_sync.rs
|
||||
│ └── pressure_indicator.rs # mevcut pressure_indicator.rs
|
||||
│
|
||||
├── io/ # Dosya/format yükleme
|
||||
│ ├── mod.rs
|
||||
│ ├── custom_loaders.rs # mevcut
|
||||
│ ├── custom_loaders_kra_v2.rs# mevcut
|
||||
│ └── clipboard.rs # mevcut
|
||||
│
|
||||
├── shell/ # Menüler, ayarlar, layout, viewer
|
||||
│ ├── mod.rs
|
||||
│ ├── menus.rs # mevcut
|
||||
│ ├── settings.rs # mevcut
|
||||
│ ├── viewer.rs # mevcut
|
||||
│ ├── dock_profile_dialog.rs # mevcut
|
||||
│ ├── gui_layout.rs # mevcut
|
||||
│ ├── layout_solver.rs # mevcut
|
||||
│ ├── scrollable_panel.rs # mevcut
|
||||
│ └── tablet_input.rs # mevcut
|
||||
│
|
||||
├── widgets/ # Genel UI widget'ları
|
||||
│ ├── mod.rs
|
||||
│ └── widgets.rs # mevcut widgets.rs
|
||||
│
|
||||
└── _dead_code_candidates_/ # Tasıma Dışı Tutulanlar (geçici, silinmeyecek)
|
||||
├── dock_controller.rs
|
||||
├── dock_engine.rs
|
||||
├── panel_layout.rs
|
||||
├── panel_layout.json
|
||||
├── history.rs
|
||||
├── i18n.rs (varsa)
|
||||
└── manifest.rs (varsa)
|
||||
```
|
||||
|
||||
## Detaylı Dosya Haritası
|
||||
|
||||
### Aktif olarak declare edilen 24 dosya ve önerilen hedef klasörleri
|
||||
|
||||
| Mevcut Dosya | Hedef Klasör | Açıklama |
|
||||
|--------------|--------------|----------|
|
||||
| `brush_editor.rs` | `tools/brush_editor.rs` | Fırça düzenleyici UI |
|
||||
| `brushes_panel.rs` | `tools/brushes_panel.rs` | Fırça preset paneli (görünüm) |
|
||||
| `clipboard.rs` | `io/clipboard.rs` | Sistem clipboard işlemleri |
|
||||
| `colors_panel.rs` | `panels/color.rs` veya `tools/color_panel.rs` | Renk paleti (panel seviyesinde çiziliyor) |
|
||||
| `dialogs.rs` | `dialogs/*.rs` (böl) | New/adjustments/selection diyalogları |
|
||||
| `dock.rs` | `dock/egui_dock.rs` | V3 egui_dock implementasyonu |
|
||||
| `filter_dialog.rs` | `dialogs/filter_dialogs.rs` | Blur/sharpen/stylize vb. filter dialogs |
|
||||
| `grim_dock.rs` | `dock/grim_dock.rs` | Grimdock prototype backend (dokunulmadan) |
|
||||
| `gui_layout.rs` | `shell/gui_layout.rs` | Theme/layout tracking sistemi |
|
||||
| `menus.rs` | `shell/menus.rs` | Ana menüler |
|
||||
| `modern_dialogs.rs` | `dialogs/modern_dialogs.rs` | Ortak dialog UI yapı taşları |
|
||||
| `panels.rs` | `panels/*.rs` (böl) | Panel render merkezi; menü bar, toolbar, status bar, layers, history vb. |
|
||||
| `settings.rs` | `shell/settings.rs` | Preferences dialogu |
|
||||
| `text_editor.rs` | `tools/text_editor.rs` | Metin aracı UI |
|
||||
| `toolbox.rs` | `tools/toolbox.rs` | Photopea-style sol toolbox |
|
||||
| `widgets.rs` | `widgets/widgets.rs` | Genel widget'lar |
|
||||
| `viewer.rs` | `shell/viewer.rs` | Viewer modu |
|
||||
| `custom_loaders.rs` | `io/custom_loaders.rs` | Krita/PSD/Native loader |
|
||||
| `custom_loaders_kra_v2.rs` | `io/custom_loaders_kra_v2.rs` | KRA v2 loader detayı |
|
||||
| `geometry_panel.rs` | `tools/geometry_panel.rs` | Geometri/vektör paneli |
|
||||
| `shape_sync.rs` | `tools/shape_sync.rs` | Vektör shape senkronizasyonu |
|
||||
| `pressure_indicator.rs` | `tools/pressure_indicator.rs` | Basınç göstergesi widget'ı |
|
||||
| `layout_solver.rs` | `shell/layout_solver.rs` | Layout çözümleme |
|
||||
| `layer_styles_panel.rs` | `tools/layer_styles_panel.rs` | Layer stil paneli |
|
||||
| `layer_details_panel.rs` | `tools/layer_details_panel.rs` | Layer detay paneli |
|
||||
| `dock_profile_dialog.rs` | `shell/dock_profile_dialog.rs` | Dock profil diyalogu |
|
||||
| `tablet_input.rs` | `shell/tablet_input.rs` | Tablet/evdev input |
|
||||
| `scrollable_panel.rs` | `shell/scrollable_panel.rs` | Kaydırılabilir panel wrapper |
|
||||
|
||||
## Uygulama Sırası (Aşamalı)
|
||||
|
||||
1. **Ön koşul:** `hcie-egui-app/Cargo.toml`'daki `[lib]` hatası çözülene kadar build testleri crate-seviyesinde (`cargo check -p hcie-gui-egui`) çalışmayabilir; önce manifest düzeltilmeli.
|
||||
2. **Ölü kod adaylarını ayır:** `dock_controller.rs`, `dock_engine.rs`, `panel_layout.rs`, `panel_layout.json`, `history.rs` varsa `_dead_code_candidates_/` klasörüne taşı (hiçbir `mod.rs` declare etmesin, böylece derleme etkilenmez).
|
||||
3. ** widgets → `widgets/` **: En bağımsız ve az bağımlılıklı dosya; güvenli başlangıç.
|
||||
4. ** clipboard + custom_loaders* → `io/` **: IO kategorisi net.
|
||||
5. ** toolbox, brush_editor, text_editor, geometry_panel, shape_sync, pressure_indicator, layer_styles_panel, layer_details_panel → `tools/` **.
|
||||
6. ** menus, settings, viewer, dock_profile_dialog, gui_layout, layout_solver, scrollable_panel, tablet_input → `shell/` **.
|
||||
7. ** dock.rs → `dock/egui_dock.rs`, grim_dock.rs → `dock/grim_dock.rs` **. `mod.rs` içindeki `pub mod dock;` ve `pub mod grim_dock;` yerine `pub mod dock;` kullanılsın; `dock::egui_dock` ve `dock::grim_dock` alt modülleri re-export edilsin.
|
||||
8. ** colors_panel.rs → `panels/color.rs` ** (veya `tools/colors_panel.rs`); `panels.rs` içindeki thin wrapper'ı güncelle.
|
||||
9. ** dialogs.rs parçalan → `dialogs/*.rs` **; `filter_dialog.rs` de `dialogs/filter_dialogs.rs` içine taşınır.
|
||||
10. ** panels.rs parçalan → `panels/*.rs` **; orijinal `panels.rs` thin dispatcher olarak kalır veya tamamen kaldırılır (tüm call site'lar `panels::*` üzerinden yönlendirilir).
|
||||
11. ** Tüm `use crate::app::X` path'lerini güncelle:** `crate::app::dock::HciePane` → `crate::app::dock::egui_dock::HciePane` gibi değişiklikler.
|
||||
12. ** `cargo check -p hcie-gui-egui` çalıştır** ve tüm derleme hatalarını iteratif düzelt.
|
||||
13. ** Clippy / rustfmt:** `cargo clippy -p hcie-gui-egui -- -D warnings` (uyarı veren eski kodlar hariç) ve `cargo fmt`.
|
||||
14. ** Commit:** Her büyük alt klasör taşımasından sonra ayrı commit.
|
||||
|
||||
## Riskler ve Azaltma
|
||||
|
||||
| Risk | Azaltma |
|
||||
|------|---------|
|
||||
| Path hatalarından oluşan derleme hatası dalgası | Her taşıma sonrası `cargo check`; taşıma başına tek kategori |
|
||||
| `mod.rs` içindeki `use` ifadeleri bozulur | `mod.rs` sadece re-export'ları günceller, state/app tanımlarına dokunulmaz |
|
||||
| `grim_dock.rs` ile `dock_controller.rs` arasındaki fark karışır | `dock_controller.rs` ölü koda ayrılır, grimdock dokunulmaz |
|
||||
| `panels.rs` parçalanırken public fonksiyon imzaları kaybolur | `panels/mod.rs` eski fonksiyon isimlerini aynen re-export eder |
|
||||
| İki farklı refactor planı çakışır | Mevcut `1783517996924-egui-app-refactoring-plan.md` (state/events/canvas bölümü) ayrı bir ileri aşama olarak ele alınır; bu plan sadece klasör yapısı ile sınırlıdır |
|
||||
|
||||
## Validasyon Planı
|
||||
|
||||
- `cargo check -p hcie-gui-egui` her aşamada başarılı olmalı.
|
||||
- `cargo clippy -p hcie-gui-egui` yeni oluşan hata vermemeli (varolan uyarılar kalabilir).
|
||||
- `cargo test -p hcie-gui-egui` (manifest hatası çözüldükten sonra) başarılı olmalı.
|
||||
- Runtime davranış değişikliği olmamalı: uygulama başlatıldığında default `Untitled` canvas ilk framede görünür olmalı (AGENTS.md'deki first-frame visibility kritik mekanizmalarına dokunulmayacak).
|
||||
|
||||
## Açık Sorular
|
||||
|
||||
1. `colors_panel.rs` net olarak `panels/` mı yoksa `tools/` mu? Şu an `panels.rs` içinden çağrılıyor; önerim `panels/color.rs`.
|
||||
2. `modern_dialogs.rs` `dialogs/` altında mı kalmalı, yoksa `widgets/` altında mı? Hem dialog hem widget yapı taşı; önerim `dialogs/modern_dialogs.rs`.
|
||||
3. Ölü kod adayları `_dead_code_candidates_/` gibi geçici klasöre mi konulmalı, yoksa flat olarak `app/` kökünde mi kalmalı? Kullanıcı silme kararını verene kadar geçici klasör daha az karışıklık yaratır.
|
||||
|
||||
## Sonraki Adımlar
|
||||
|
||||
Plan onaylandıktan sonra uygulama-capable ajan ile:
|
||||
1. Manifest `[lib]` hatasını çöz (seçenekler: `src/lib.rs` ekle veya `[lib]` bölümünü kaldır).
|
||||
2. Ölü kod adaylarını `_dead_code_candidates_/` içine taşı.
|
||||
3. Kategori taşımalarını sırayla uygula, her adımda `cargo check`.
|
||||
@@ -0,0 +1,129 @@
|
||||
# 4K Çok Katmanlı PSD Çizim Performans Regresyonu — Plan
|
||||
|
||||
## Hedef
|
||||
4K çözünürlükte, çok katmanlı PSD üzerine fırça/pen çizimi yapılırken yaşanan yavaşlamanın **kök nedenini** bulmak, daha önce çalışan ama sonradan bozulan/basıitleştirilen optimizasyonları **git geçmişinden geri getirmek** ve bu tür regresyonların tekrar etmesini önlemek.
|
||||
|
||||
## Mevcut Durum Özeti
|
||||
- Kilitli engine crate'leri kullanıcı tarafından geçici olarak açıldı.
|
||||
- Şu anki `main` (HEAD `dfef0de`) içinde var olan optimizasyonlar:
|
||||
- `hcie-engine-api/src/lib.rs`: `active_stroke_mask`, `composite_scratch`, `below_cache` pooling/partial update (sonradan eklenmiş).
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/canvas/render.rs`: dirty-region texture upload.
|
||||
- Ancak git geçmişi incelendiğinde daha önce var olan ama şu an eksik/zayıflamış özellikler var:
|
||||
- `hcie-composite/src/tiled.rs`: `a257e66` mesajı **"tile render iptal"** — tile rendering daha önce devre dışı bırakılmış/geri alınmış.
|
||||
- `hcie-tile/src/lib.rs`: tek commit (`6f3fe5d`) ile hiç güncellenmemiş; tile motoru eskimiş olabilir.
|
||||
- `hcie-engine-api/src/lib.rs`: sadece 2 commit; daha önce başka bir yerde/dalda (örn. `hcie-core-app` veya v3) daha gelişmiş partial composite kodu olabilir.
|
||||
|
||||
## Kritik Hipotezler
|
||||
|
||||
### 1. Daha Önce Tile Rendering Vardı, Şimdi İptal Edilmiş/Daralmış
|
||||
- Commit `a257e66` ("layer visibility çözüldü . renk sorunları var, blend fx sorunlu eksik, **tile render iptal**") tiled composite üzerinde clipping mask desteği eklerken tile rendering’i kısmen iptal etmiş veya gerilemiş olabilir.
|
||||
- Şu anki `hcie-composite/src/tiled.rs` hâlâ `par_chunks_exact_mut` ile her satır için thread başlatıyor; küçük dirty region’larda bu ağır.
|
||||
- **Eylem:** `a257e66` öncesi ve sonrası `tiled.rs`’i karşılaştır; eksik tile fast-path’leri bul.
|
||||
|
||||
### 2. `hcie-tile/src/lib.rs` Eski ve Yetersiz
|
||||
- `TiledLayer::update_tiles_in_region()` muhtemelen tüm tile’ları değil, sadece dirty bölgeye denk gelen tile’ları güncellemesi lazım ama implementation detaylarına bakılmalı.
|
||||
- Sparse tile’ların hafıza kullanımı ve erişim deseni optimize edilebilir.
|
||||
- **Eylem:** Tile update fonksiyonunu incele; gereksiz kopya ve sıfırlama varsa kaldır.
|
||||
|
||||
### 3. Engine API `effects_dirty` Gereksiz Set
|
||||
- `stroke_to()` ve `draw_pen_segment()` her çizimde `layer.effects_dirty.store(true)` yapıyor.
|
||||
- Effects olmayan layerlarda bu, sonraki composite’te effects pass’i tetikliyor; 4K’da 33MB clone + `apply_layer_effects` maliyeti.
|
||||
- **Eylem:** Sadece effects/styles varsa flag set et.
|
||||
|
||||
### 4. Tile Cache Over-Clear
|
||||
- `set_layer_opacity`, `set_layer_visible`, `set_layer_blend_mode`, `set_layer_parent` gibi değişiklikler `tile_layers.clear()` yapıyor.
|
||||
- Opacity/blend/visibility sadece composite sırasında uygulanıyor; tile pikselleri değişmiyor. Tile cache’i silmek gereksiz.
|
||||
- **Eylem:** Bu fonksiyonlarda `tile_layers.clear()` yerine sadece dirty flag/composite_dirty set et.
|
||||
|
||||
### 5. `render_composite_region` Full-Canvas Fallback
|
||||
- `has_dirty && dirty.is_none()` durumunda tüm canvas dirty işaretleniyor.
|
||||
- `stroke_to` içinde `expand_dirty` çağrılmadığı veya yanlış çağrıldığı için partial region kayboluyor olabilir.
|
||||
- **Eylem:** `stroke_to` içinde dirty bounds genişletmeyi doğrula; fallback neden oluşuyor bul.
|
||||
|
||||
### 6. `below_cache` Her `begin_stroke`’ta Yeniden Hesaplanıyor
|
||||
- Aktif layer en üstteyse her stroke başlangıcında N-1 katman full composited.
|
||||
- Alt katmanlar değişmediyse `below_cache` reuse edilebilir.
|
||||
- **Eylem:** Alt katmanlarda değişiklik olmadıkça `below_cache`’i yeniden hesaplama.
|
||||
|
||||
### 7. GUI Tarafında Thumbnail + Selection + Pointer Downsampling
|
||||
- `canvas/render.rs` her karede dirty layer thumbnail’larını yeniliyor.
|
||||
- `draw_selection_overlay` her karede tüm canvası (4K ≈ 8M piksel) tarıyor.
|
||||
- Tablet/winit yüksek frekanslı event’ler smoothing yapılmadan engine’e gönderiliyor.
|
||||
- **Eylem:** Thumbnail cache, selection edge cache, pointer distance threshold ekle.
|
||||
|
||||
## Yapılacaklar
|
||||
|
||||
### A. Git Geçmişi Arkeolojisi
|
||||
1. `a257e66` öncesi `hcie-composite/src/tiled.rs` halini çıkar (`git show a257e66^:hcie-composite/src/tiled.rs`).
|
||||
2. `6f3fe5d` sonrası `hcie-tile/src/lib.rs` değişiklikleri ara; varsa branch/stash geçmişinde daha gelişmiş versiyon olup olmadığını kontrol et.
|
||||
3. `hcie-core-app` geçmişinde (silinmiş dizin) tile rendering / GPU acceleration / partial update kodu araştır.
|
||||
4. `hcie-engine-api-orig` crate’ini incele; orijinal engine API’de daha iyi partial composite var mı?
|
||||
5. Eski iyi hal ile şu anki hal arasındaki farkları listele.
|
||||
|
||||
### B. Kök Neden Tespiti
|
||||
1. `hcie-engine-api/src/lib.rs` içine detaylı TRACE logları ekle:
|
||||
- `render_composite_region` giriş/çıkış süresi, dirty rect boyutu, cache hit/miss.
|
||||
- `begin_stroke` below_cache build süresi.
|
||||
- `sync_dirty_tiles` süresi ve kaç layer/tile güncellediği.
|
||||
- effects pass tetiklenip tetiklenmediği, neden tetiklendiği.
|
||||
2. `hcie-composite/src/tiled.rs` içine TRACE ekle:
|
||||
- tile path vs dense path seçimi, region boyutu, thread path seçimi.
|
||||
3. Kullanıcıdan `RUST_LOG=trace` ile 10 saniyelik çizim oturumu logu al.
|
||||
4. Log analizi ile en ağır maliyetli bölgeyi tespit et.
|
||||
|
||||
### C. Eski İyi Optimizasyonları Geri Getir
|
||||
1. `hcie-composite/src/tiled.rs` içindeki tile fast-path’i güçlendir:
|
||||
- Küçük region’larda sequential path seç.
|
||||
- Clipping mask lookup’u layer başına bir kez ön hesapla.
|
||||
- Effects olan layerlar için `effects_cache` reuse et.
|
||||
2. `hcie-tile/src/lib.rs` tile update’ini optimize et:
|
||||
- Sadece dirty bölgeye denk gelen tile’ları güncelle.
|
||||
- Gereksiz dense→tile kopya ve sıfırlama varsa kaldır.
|
||||
3. `hcie-engine-api/src/lib.rs` içinde:
|
||||
- `effects_dirty` yalnızca effects/styles varsa set et.
|
||||
- `set_layer_opacity/visible/blend_mode/parent` içinde `tile_layers.clear()` kaldır.
|
||||
- `below_cache` alt katman değişmedikçe reuse et.
|
||||
- `render_composite_region` full-canvas fallback nedenlerini ortadan kaldır.
|
||||
4. `hcie-brush-engine` / `hcie-draw` içinde geçici allocation varsa azalt.
|
||||
|
||||
### D. GUI Optimizasyonları
|
||||
1. Layer thumbnail cache: `AppDocument` içinde dirty flag tut, sadece değişiklikte yenile.
|
||||
2. Selection overlay edge cache: mask değiştiğinde marching-ants edge listesi hesapla.
|
||||
3. Pointer downsampling: iki `stroke_to` arası minimum mesafe koşulu.
|
||||
|
||||
### E. Regresyon Önlemleri
|
||||
1. Performans regresyon testi ekle:
|
||||
- `hcie-engine-api/tests/performance_stroke_4k.rs`: 3840×2160, 10 katman, 100 stroke segment, `stroke_to` + `render_composite_region` süre ölçüm.
|
||||
- CI threshold olmadan, manuel karşılaştırma için log çıktısı.
|
||||
2. Pre-commit/post-commit hook’lara opsiyonel benchmark adımı ekle:
|
||||
- `logs/functional_regression_check.sh --benchmark` modu.
|
||||
3. AGENTS.md / plan dosyasına kritik performans kodlarının (tile rendering, partial composite, dirty rect, effects cache, below_cache) korunması gerektiği notu ekle; gelecekteki değişikliklerde bu alanlara dokunulmadan önce review/benchmark zorunluluğu.
|
||||
|
||||
### F. Doğrulama
|
||||
1. `cargo test -p hcie-engine-api --test visual_regression` — golden hash’ler değişmemeli.
|
||||
2. `logs/functional_regression_check.sh --fast`.
|
||||
3. Yeni benchmark’ı önce eski haliyle (HEAD öncesi stash veya geçici branch), sonra değişikliklerle çalıştır; kazanımı ölç.
|
||||
|
||||
## Uygulama Sırası
|
||||
1. **Git arkeolojisi**: eski iyi kodu bul ve karşılaştır.
|
||||
2. **Kök neden tanısı**: TRACE logları ve log analizi.
|
||||
3. **En büyük kazanımı hedefle**: tile cache over-clear + effects_dirty fix.
|
||||
4. **Tile composite engine güçlendir**: sequential small-region path + effects cache reuse.
|
||||
5. **Engine below_cache reuse**.
|
||||
6. **GUI thumbnail/selection/pointer optimizasyonları**.
|
||||
7. **Benchmark + regresyon önlemleri**.
|
||||
8. **Kilitleri geri kilitle (`./lock.sh all`)**.
|
||||
|
||||
## Kısıtlar
|
||||
- `hcie-engine-api` public API’sini değiştirmeden optimize et; GUI kodu derlenmeye devam etmeli.
|
||||
- Mevcut işlevselliği bozmadan optimize et; golden hash’ler değişmemeli.
|
||||
- Her seansta en fazla 5 dosya değişikliği; fazla ise görev bölünecek.
|
||||
- Değişiklikler bitince `./lock.sh all` ile kilitleri geri kilitle.
|
||||
|
||||
## Başarı Kriteri
|
||||
- 4K çok katmanlı PSD üzerinde fırça/pen çizimi sırasında hissedilir lag azalır.
|
||||
- `cargo test -p hcie-engine-api --test visual_regression` geçmeye devam eder.
|
||||
- `logs/functional_regression_check.sh --fast` geçmeye devam eder.
|
||||
- Performans benchmark’ında ortalama frame/segment süresi önceki haline göre düşer.
|
||||
- Eski çalışan optimizasyonlar geri getirilmiş ve korunmak üzere belgelenmiştir.
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# Plan: Improve Bevel & Emboss MAE (138.99 → target < 20)
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
The bevel/emboss MAE is **138.99** (vs drop shadow at 4.95). Three independent issues cause this:
|
||||
|
||||
### Issue 1: Compositing Base Color Mismatch (CRITICAL)
|
||||
The tuning script (`tune_mae_bevel_emboss_kra_grid.rs:148-155`) hardcodes a **blue** shape `(73,73,255)`:
|
||||
```rust
|
||||
out[i * 4] = 73;
|
||||
out[i * 4 + 1] = 73;
|
||||
out[i * 4 + 2] = 255;
|
||||
```
|
||||
But the KRA ground truth PNGs have **black** (`black_rect_simple.kra`) or **red** (`red_rect_simple.kra`) shapes. Since Screen/Multiply blending is applied on top of this base color, the wrong base color produces completely wrong composite output.
|
||||
|
||||
### Issue 2: Linear Height Profile Produces Flat Plateaus (CRITICAL)
|
||||
The current algorithm in `tuned.rs:67` uses a linear distance profile:
|
||||
```rust
|
||||
(d_in / radius).min(1.0) * radius
|
||||
```
|
||||
For a rectangle, the distance-to-edge gradient is a constant unit vector → the surface normal is constant → N·L produces a **flat plateau** across each edge. Krita produces a smooth gradient ramp because it uses a non-linear height profile (likely smoothstep or similar).
|
||||
|
||||
### Issue 3: Insufficient Parameter Grid (MODERATE)
|
||||
Only 144 combos (4×6×6) tuning just `lighting_blur`, `hl_scale`, `sh_scale`. The algorithm's height profile shape — which is the primary factor — is not tunable at all.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Fix Tuning Script Base Color (`hcie-io/examples/tune_mae_bevel_emboss_kra_grid.rs`)
|
||||
|
||||
**What:** Detect the actual shape color from the ground truth PNG instead of hardcoding blue.
|
||||
|
||||
**How:**
|
||||
- Parse the filename to determine if it's from `black` or `red` base set (by checking the symlinked directory, or by sampling the PNG center pixels)
|
||||
- Simpler approach: sample the center pixel of the ground truth image to detect the actual shape color
|
||||
- Use the detected color as the base for compositing instead of `(73,73,255)`
|
||||
|
||||
**File:** `hcie-io/examples/tune_mae_bevel_emboss_kra_grid.rs`
|
||||
|
||||
### Step 2: Add Profile-Based Height Curve to `generate_bevel_tuned` (`hcie-fx/src/tuned.rs`)
|
||||
|
||||
**What:** Replace the linear height profile with a configurable power-curve profile and add it as a tunable parameter.
|
||||
|
||||
**How:**
|
||||
- Add a new parameter `profile_exp: f32` (the exponent for the height profile)
|
||||
- Height profile: `t = clamp(d_in / radius, 0, 1)` → `height = t.powf(profile_exp) * radius`
|
||||
- `profile_exp = 1.0` → linear (current behavior)
|
||||
- `profile_exp = 2.0` → quadratic (steeper falloff from edge)
|
||||
- `profile_exp = 0.5` → sqrt (softer falloff)
|
||||
- The smoothstep-like profile produces non-constant gradients on flat edges, eliminating the plateau
|
||||
|
||||
**File:** `hcie-fx/src/tuned.rs` (unlock `hcie-fx` first)
|
||||
|
||||
### Step 3: Expand the Grid Search Parameters
|
||||
|
||||
**What:** Add `profile_exp` to the grid and increase resolution.
|
||||
|
||||
**How:**
|
||||
- `profile_exponents`: `[0.3, 0.5, 0.7, 1.0, 1.5, 2.0, 3.0]` (7 values)
|
||||
- `lighting_blurs`: `[0, 1, 2, 3, 5]` (5 values)
|
||||
- `hl_scales`: `[0.3, 0.5, 0.75, 1.0, 1.5, 2.0]` (6 values)
|
||||
- `sh_scales`: `[0.3, 0.5, 0.75, 1.0, 1.5, 2.0]` (6 values)
|
||||
- Total: 7 × 5 × 6 × 6 = **1,260 combos** × 32 samples = 40,320 evals (still fast)
|
||||
|
||||
**File:** `hcie-io/examples/tune_mae_bevel_emboss_kra_grid.rs`
|
||||
|
||||
### Step 4: Expand KRA Generator for More Sample Diversity
|
||||
|
||||
**What:** Add more parameter variations to the KRA generator to create a richer training set.
|
||||
|
||||
**How:**
|
||||
- Add more `size` values: `[5.0, 10.0, 20.0, 30.0, 50.0]`
|
||||
- Add more `depth` values: `[25.0, 50.0, 75.0, 100.0]`
|
||||
- Add more `soften` values: `[0.0, 4.0, 8.0, 16.0]`
|
||||
- Keep angles and directions as-is
|
||||
- Total: 5 × 4 × 4 × 2 × 2 = 320 KRA files per base
|
||||
|
||||
**File:** `_tools/generate_bevel_emboss_kra_set.py`
|
||||
|
||||
### Step 5: Re-run Export + Tune
|
||||
|
||||
1. `python3 _tools/generate_bevel_emboss_kra_set.py` (generate new KRA set)
|
||||
2. `python3 _tools/export_all_kra_to_png.py` (export to PNG via Krita)
|
||||
3. `cargo run --release -p hcie-io --example tune_mae_bevel_emboss_kra_grid` (tune)
|
||||
4. Verify MAE < 20
|
||||
|
||||
### Step 6: Update `emboss.rs` Production Code
|
||||
|
||||
**What:** Apply the best tuned parameters from the grid search to the production `generate_bevel_emboss` function in `emboss.rs`.
|
||||
|
||||
**How:**
|
||||
- Read the results from `bevel_emboss_kra_grid_results.txt`
|
||||
- Update the hardcoded values in `emboss.rs` between `BEGIN_TUNING_ZONE` / `END_TUNING_ZONE`
|
||||
- This requires unlocking `hcie-fx` temporarily
|
||||
|
||||
**File:** `hcie-fx/src/emboss.rs` (unlock `hcie-fx` first)
|
||||
|
||||
---
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `hcie-io/examples/tune_mae_bevel_emboss_kra_grid.rs` | Fix base color, add profile_exp, expand grid |
|
||||
| `hcie-fx/src/tuned.rs` | Add profile_exp parameter, implement power-curve height profile |
|
||||
| `hcie-fx/src/emboss.rs` | Apply final tuned parameters (BEGIN/END_TUNING_ZONE) |
|
||||
| `_tools/generate_bevel_emboss_kra_set.py` | Expand parameter diversity |
|
||||
|
||||
## Verification
|
||||
|
||||
1. `cargo check -p hcie-fx` — compiles
|
||||
2. `cargo check -p hcie-io --example tune_mae_bevel_emboss_kra_grid` — compiles
|
||||
3. `python3 _tools/generate_bevel_emboss_kra_set.py` — generates expanded KRA set
|
||||
4. `python3 _tools/export_all_kra_to_png.py` — exports PNGs
|
||||
5. `cargo run --release -p hcie-io --example tune_mae_bevel_emboss_kra_grid` — runs tuning, MAE target < 20
|
||||
@@ -0,0 +1,62 @@
|
||||
# Canvas Initial Render & Continuous Repaint Fix
|
||||
|
||||
## Problem Analysis
|
||||
|
||||
### Issue 1: Canvas not visible on first image open
|
||||
When a file is opened via `pending_load` in `HcieApp::logic()` (line 2897), the document is pushed to the dock using `self.dock_state.push_to_focused_leaf()`. This pushes the tab to whatever leaf is currently focused — which may be a utility panel (Tools, Layers, etc.) rather than the document center area. The document tab exists but is hidden behind a narrow utility panel, requiring the user to click the tab title to see the canvas.
|
||||
|
||||
The `FileOpenRequested` event handler (line 1573, 1604) correctly uses `push_document_to_center()` which finds the document leaf and focuses it before pushing. But the `pending_load` path in `logic()` uses the raw `push_to_focused_leaf()`.
|
||||
|
||||
### Issue 2: Continuous repaint
|
||||
In `logic()` (line 2996-2998):
|
||||
```rust
|
||||
if self.stable_frames < 5 {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
```
|
||||
|
||||
`stable_frames` is incremented in `ui_main_assembly()` (line 2370) only when `(self.last_window_width - avail_w).abs() < 0.1`. The `layout_solver::adjust_pixel_widths()` changes dock split fractions on startup (when `stable_frames < 3`), which can cause the available width to fluctuate slightly between frames, resetting `stable_frames` to 0 repeatedly. This creates an infinite loop of repaint requests.
|
||||
|
||||
Additionally, the `enforce_constraints` function runs every frame and may move document tabs around, which also triggers repaints.
|
||||
|
||||
## Changes
|
||||
|
||||
### 1. `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` — `logic()` function
|
||||
|
||||
**a) Use `push_document_to_center()` in `pending_load` handler**
|
||||
|
||||
Replace both `self.dock_state.push_to_focused_leaf(...)` calls in the `pending_load` block (lines 2918 and 2944) with `self.push_document_to_center(dock::HciePane::Document(self.active_doc))`.
|
||||
|
||||
This ensures the document tab goes to the center document area and is focused immediately.
|
||||
|
||||
**b) Fix continuous repaint — replace `stable_frames < 5` with targeted approach**
|
||||
|
||||
Replace the `stable_frames < 5` check (lines 2995-2998) with a single `ctx.request_repaint()` call that runs only on the very first frame after app start. Use a new boolean field `first_frame` on `HcieApp` that is set to `true` in `new()` and set to `false` after the first repaint request.
|
||||
|
||||
This prevents the layout solver's width fluctuations from causing infinite repaint loops.
|
||||
|
||||
**c) Ensure composite texture is created immediately after loading**
|
||||
|
||||
After the `pending_load` block, explicitly call `doc.engine.mark_composite_dirty()` and ensure the composite buffer is allocated. This is already done by `open_image()` which sets `composite_dirty = true`, but we should also ensure the buffer is pre-allocated to the correct size so `render_composition()` can create the texture on the very first frame.
|
||||
|
||||
### 2. `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` — `HcieApp` struct
|
||||
|
||||
Add `first_frame: bool` field, initialized to `true` in `new()`.
|
||||
|
||||
### 3. `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` — `ui_main_assembly()`
|
||||
|
||||
Ensure that after `enforce_constraints` moves document tabs, the composite is marked dirty so the canvas re-renders with the correct content.
|
||||
|
||||
## Files to modify
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` | Fix `pending_load` to use `push_document_to_center()`, fix continuous repaint, add `first_frame` field |
|
||||
|
||||
## Files NOT to modify (regression protection)
|
||||
|
||||
- `hcie-engine-api/src/lib.rs` — locked, no changes needed
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/canvas/mod.rs` — no changes needed
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/canvas/render.rs` — no changes needed
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/app/layout_solver.rs` — no changes needed
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/event_bus.rs` — no changes needed
|
||||
@@ -0,0 +1,261 @@
|
||||
# PSD Grup Katmanı Düzeltme Planı
|
||||
|
||||
> Commit aşaması tamamlandı (commit `5f62c84`). Bu plan sadece PSD grup katmanı fix’ini içerir.
|
||||
|
||||
## 0. Kapanmış Uyarılar / Öğrenilenler (Commit Sonrası)
|
||||
|
||||
Aşağıdaki uyarılar commit öncesi değerlendirmede geçerliydi, ancak commit sonrası gerçekleşenlerle artık geçerli değil veya durumu netleşmiştir.
|
||||
|
||||
### 0.1 `visual_regression.rs` zaten vardı
|
||||
|
||||
Planda "visual regression harness henüz yok, oluşturulmalı veya atlanmalı" notu bulunuyordu. Ancak çalışma ağacında `hcie-engine-api/tests/visual_regression.rs` zaten mevcuttu ve çalışıyordu. Commit öncesi validation’da 7 golden testin tamamı geçti. Dolayısıyla o uyarı artık geçerli değil.
|
||||
|
||||
### 0.2 İlk composite süresi ~340 ms — beklenen davranış
|
||||
|
||||
4K, 10 katmanlı benchmark testinde ilk composite en kötü ~340 ms ölçüldü. Bu, tasarımın doğal bir yan etkisi:
|
||||
|
||||
- İlk karede alt katmanların tamamı `below_cache` önbelleğe bir kez dizilir.
|
||||
- Sonraki karelerde sadece aktif katman üzerine composite yapılır; süre katman sayısıyla orantılı düşer.
|
||||
- Yani ilk vuruş ağır, sürekli boyama hızlanır.
|
||||
|
||||
Bu nedenle performans testi "non-gating" olarak tasarlanmıştır; tek seferlik ilk frame süresi regresyon olarak değerlendirilmemeli.
|
||||
|
||||
## 1. Hedefler
|
||||
|
||||
- PSD import sonrası **grup katmanlarının** doğru şekilde davranmasını sağlamak:
|
||||
- Layers panelinde **klasör simgesi / farklı görsel** ile gösterilmeli.
|
||||
- Grup katmanı seçiliyken, fırça/kalem/eraser gibi raster araçlarla çizim yapılmaya çalışıldığında **yeni layer oluşturulmamalı**.
|
||||
- Bunun yerine kullanıcıya **"Layer not editable"** mesajı gösterilmeli.
|
||||
|
||||
## 2. Mevcut Durum Analizi
|
||||
|
||||
### Yanlış Davranış
|
||||
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/canvas/mod.rs:183-204` aralığında, `Tool::allowed_layer_type() == Some(Raster)` olduğunda aktif katman farklı tipte ise yeni bir layer otomatik oluşturuluyor.
|
||||
- PSD’den gelen grup katmanı `LayerType::Group` olarak işaretlenmiş olmasına rağmen, bu kod yine de `add_layer("Layer")` çağrısı yapıyor ve normal bir raster layer oluşturuyor.
|
||||
|
||||
### Doğru Davranış
|
||||
|
||||
- Aktif katman `LayerType::Group` ise:
|
||||
- Raster araçlarla (Pen, Brush, Eraser, Spray, FloodFill, vb.) çizim yapılamaz.
|
||||
- Yeni layer oluşturulmaz.
|
||||
- Kullanıcıya "Layer not editable" mesajı gösterilir.
|
||||
- Layers panelinde grup satırı görsel olarak farklılaştırılır (thumbnail yerine klasör ikonu, farklı arka plan rengi, vb.).
|
||||
|
||||
## 3. Kilit Açılması Gereken Crate’ler
|
||||
|
||||
`AGENTS.md` kurallarına göre aşağıdaki dosyalar **locked**. Değişiklik yapmadan önce `unlock.sh <crate>` ile açılmalı, sonra `lock.sh <crate>` ile kapatılmalı.
|
||||
|
||||
| Crate / Dosya | Neden Açılması Gerekiyor | Gerekli mi? |
|
||||
|---------------|--------------------------|-------------|
|
||||
| `hcie-engine-api/src/lib.rs` | Yeni public sorgu fonksiyonları (`is_layer_editable`, `active_layer_is_editable`) eklemek için. | **Evet** |
|
||||
| `hcie-engine-api/src/layer_property_ops.rs` | Aynı sorgu fonksiyonlarını burada da implemente edebiliriz; `lib.rs` re-export eder. | Opsiyonel |
|
||||
| `hcie-protocol/src/lib.rs` | `LayerInfo`’ya `editable` veya `is_group` alanı eklemek istersek. Zaten `layer_type` var, muhtemelen gerek yok. | Hayır (önerilmez) |
|
||||
| `hcie-document/src/lib.rs` | `LayerInfo` oluşturma mantığı. `layer_type` zaten var, muhtemelen gerek yok. | Hayır (önerilmez) |
|
||||
| `hcie-composite/src/tiled.rs` | Grup katmanları zaten atlanıyor; değişiklik beklenmiyor. | Hayır |
|
||||
| `hcie-psd/src/psd_import.rs` | `LayerType::Group` atanması zaten doğru; inceleme gerekirse açılabilir. | Muhtemelen hayır |
|
||||
|
||||
### Minimal kilitli crate seti
|
||||
|
||||
- Sadece **`hcie-engine-api`** açılmalı.
|
||||
- Tüm GUI değişiklikleri zaten açık olan **`hcie-egui-app/crates/hcie-gui-egui`** içinde yapılacak.
|
||||
|
||||
## 4. Uygulama Adımları
|
||||
|
||||
### Phase 1: Engine API — Editable Sorgu Fonksiyonları
|
||||
|
||||
**Kilitli crate:** `hcie-engine-api`
|
||||
|
||||
Yeni public API ekleyin:
|
||||
|
||||
```rust
|
||||
impl Engine {
|
||||
/// Returns whether the layer with the given ID can receive pixel edits.
|
||||
/// Group and Mask layers are not editable. Locked layers are not editable.
|
||||
pub fn is_layer_editable(&self, id: u64) -> bool {
|
||||
let Some(layer) = self.document.get_layer_by_id(id) else { return false };
|
||||
if layer.locked { return false; }
|
||||
!matches!(layer.layer_type, LayerType::Group | LayerType::Mask)
|
||||
}
|
||||
|
||||
/// Returns whether the currently active layer can receive pixel edits.
|
||||
pub fn active_layer_is_editable(&self) -> bool {
|
||||
self.document.active_layer()
|
||||
.map(|l| self.is_layer_editable(l.id))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Eklenebilecek dosyalar:
|
||||
- `hcie-engine-api/src/layer_property_ops.rs` içine ekle, `lib.rs`’den public olarak re-export et.
|
||||
- Veya doğrudan `hcie-engine-api/src/lib.rs` içine ekle.
|
||||
|
||||
> **Tavsiye:** Yeni fonksiyonları `layer_property_ops.rs` içine ekle ve `lib.rs`’de `pub use` veya doğrudan `impl Engine` bloğu içinde re-export yap. Bu, mevcut refactor stiliyle uyumlu olur.
|
||||
|
||||
### Phase 2: GUI — Canvas Çizim Engeli
|
||||
|
||||
**Açık crate:** `hcie-egui-app/crates/hcie-gui-egui`
|
||||
|
||||
Dosya: `hcie-egui-app/crates/hcie-gui-egui/src/canvas/mod.rs`
|
||||
|
||||
#### 2a. Otomatik layer oluşturma bloğunu düzenle
|
||||
|
||||
Mevcut kod (satır ~183-204):
|
||||
|
||||
```rust
|
||||
if primary_pressed && !self.state.is_drawing {
|
||||
if let Some(req_type) = self.state.active_tool.allowed_layer_type() {
|
||||
if let Some(active) = self.doc.engine.active_layer() {
|
||||
if active.layer_type != req_type {
|
||||
match req_type {
|
||||
hcie_engine_api::LayerType::Vector => { ... }
|
||||
hcie_engine_api::LayerType::Text => { ... }
|
||||
_ => {
|
||||
let id = self.doc.engine.add_layer("Layer");
|
||||
self.doc.engine.set_active_layer(id);
|
||||
}
|
||||
}
|
||||
self.event_bus.push(AppEvent::RenderRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Düzenleme:
|
||||
|
||||
```rust
|
||||
if primary_pressed && !self.state.is_drawing {
|
||||
if let Some(req_type) = self.state.active_tool.allowed_layer_type() {
|
||||
if let Some(active) = self.doc.engine.active_layer() {
|
||||
if !self.doc.engine.active_layer_is_editable() {
|
||||
self.event_bus.push(AppEvent::ShowStatusMessage(
|
||||
"Layer not editable".to_string()
|
||||
));
|
||||
// consume the press so we don't fall through to drawing
|
||||
return response;
|
||||
}
|
||||
if active.layer_type != req_type {
|
||||
// Only auto-create a layer when the active layer is editable
|
||||
// but of a different type (e.g. vector/text needed).
|
||||
match req_type {
|
||||
hcie_engine_api::LayerType::Vector => { ... }
|
||||
hcie_engine_api::LayerType::Text => { ... }
|
||||
_ => {
|
||||
let id = self.doc.engine.add_layer("Layer");
|
||||
self.doc.engine.set_active_layer(id);
|
||||
}
|
||||
}
|
||||
self.event_bus.push(AppEvent::RenderRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2b. Raster çizim branch’lerinde ek kontrol
|
||||
|
||||
`Tool::Pen | Tool::Brush | Tool::Eraser | Tool::Spray` ve `Tool::FloodFill` branch’lerinin başına editable kontrolü ekle:
|
||||
|
||||
```rust
|
||||
if !self.doc.engine.active_layer_is_editable() {
|
||||
self.event_bus.push(AppEvent::ShowStatusMessage(
|
||||
"Layer not editable".to_string()
|
||||
));
|
||||
return response;
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: GUI — Status Mesajı Event Bus
|
||||
|
||||
**Açık crate:** `hcie-egui-app/crates/hcie-gui-egui`
|
||||
|
||||
Dosya: `hcie-egui-app/crates/hcie-gui-egui/src/event_bus.rs`
|
||||
|
||||
`AppEvent` enum’una şu variant’ı ekle (eğer yoksa):
|
||||
|
||||
```rust
|
||||
pub enum AppEvent {
|
||||
// ... existing variants
|
||||
ShowStatusMessage(String),
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: GUI — Status Bar Mesaj Gösterimi
|
||||
|
||||
**Açık crate:** `hcie-egui-app/crates/hcie-gui-egui`
|
||||
|
||||
Dosya: `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs`
|
||||
|
||||
- `HcieApp` struct’ına `status_message: Option<(String, Instant)>` gibi bir alan ekle.
|
||||
- Event loop içinde `AppEvent::ShowStatusMessage(text)` yakalayıp bu alana yaz.
|
||||
- UI çiziminde (örneğin bottom panel / status bar) mesajı göster ve belirli bir süre (örn. 2 saniye) sonra temizle.
|
||||
- Eğer zaten bir status bar alanı varsa onu kullan.
|
||||
|
||||
### Phase 5: GUI — Layers Panelde Grup Görseli
|
||||
|
||||
**Açık crate:** `hcie-egui-app/crates/hcie-gui-egui`
|
||||
|
||||
Dosya: `hcie-egui-app/crates/hcie-gui-egui/src/app/panels.rs`
|
||||
|
||||
`show_layers` fonksiyonunda:
|
||||
|
||||
- Grup satırının thumbnail alanında klasör ikonu çiz:
|
||||
```rust
|
||||
if entry.is_group {
|
||||
// Draw a folder icon in the thumbnail rect
|
||||
let p = ui.painter();
|
||||
p.text(thumb_rect.center(), egui::Align2::CENTER_CENTER, "📁",
|
||||
egui::FontId::proportional(16.0), colors.text_primary);
|
||||
} else if let Some(tex) = doc.layer_textures.get(&i) {
|
||||
// existing texture draw
|
||||
}
|
||||
```
|
||||
- (Opsiyonel) Grup satırına hafif farklı arka plan rengi:
|
||||
```rust
|
||||
let bg = if is_active {
|
||||
colors.accent.gamma_multiply(0.2)
|
||||
} else if entry.is_group {
|
||||
colors.bg_secondary.gamma_multiply(0.5)
|
||||
} else {
|
||||
egui::Color32::TRANSPARENT
|
||||
};
|
||||
```
|
||||
|
||||
## 5. Validation Plan
|
||||
|
||||
1. Derleme:
|
||||
```bash
|
||||
cargo check -p hcie-engine-api
|
||||
cargo check -p hcie-gui-egui
|
||||
```
|
||||
2. Golden regression:
|
||||
```bash
|
||||
cargo test -p hcie-engine-api --test visual_regression
|
||||
```
|
||||
3. Manuel senaryo:
|
||||
- PSD dosyası içeri aktar.
|
||||
- Layers panelinde grup katmanının klasör ikonu ile gösterildiğini doğrula.
|
||||
- Grup katmanını seç.
|
||||
- Brush/Pen/Eraser ile tuvale tıkla.
|
||||
- Beklenen: yeni layer oluşmuyor, status bar’da "Layer not editable" görünüyor.
|
||||
|
||||
## 6. Riskler ve Dikkat Edilecekler
|
||||
|
||||
- **Kilitli crate:** Sadece `hcie-engine-api` açılacak. `unlock.sh hcie-engine-api` ile aç, değişiklikten sonra `lock.sh hcie-engine-api` ile kapat.
|
||||
- **Public API değişikliği** minimal: sadece yeni sorgu fonksiyonları ekle, mevcut imzaları değiştirme.
|
||||
- **Performans korumalarına dokunma:** Yeni fonksiyonlar allocation/composite yapmamalı.
|
||||
- **`Tool::allowed_layer_type()`** değişikliği `gui_audit.rs` testini etkileyebilir; eğer değiştirirsen testi güncelle. Önerilen: bu fonksiyona dokunmadan canvas/mod.rs içinde ek kontrol yap.
|
||||
- **Genelleştirme:** "Layer not editable" mesajı ileride `locked` ve `Mask` katmanları için de kullanılabilir. İlk aşamada `Group` odaklı tutulması önerilir.
|
||||
|
||||
## 7. Kısa Özet
|
||||
|
||||
- Commit tamam; şimdi sadece PSD grup katmanı fix’i yapılacak.
|
||||
- `hcie-engine-api` kilidini aç, `is_layer_editable` ve `active_layer_is_editable` ekle.
|
||||
- `hcie-egui-app/crates/hcie-gui-egui` içinde:
|
||||
- `canvas/mod.rs`: grup seçiliyken yeni layer oluşturmayı engelle, status mesajı gönder.
|
||||
- `event_bus.rs`: `ShowStatusMessage(String)` ekle.
|
||||
- `app/mod.rs`: status bar’da mesajı göster.
|
||||
- `app/panels.rs`: grup satırına klasör ikonu / farklı arka plan.
|
||||
- Validation ile golden testleri ve manuel senaryoyu çalıştır.
|
||||
@@ -0,0 +1,152 @@
|
||||
# Staged Changes Impact Review — Commit Decision Support
|
||||
|
||||
## Goal
|
||||
|
||||
The user asked for a review of the currently staged changes to determine whether any functionality is lost compared to previous commits, and whether it is safe to commit.
|
||||
|
||||
## Scope
|
||||
|
||||
- **Base commit:** `dfef0de` (`HEAD` of `main` at review time).
|
||||
- **Staged tree:** the 16 paths listed by `git diff --cached`.
|
||||
- **Focus:** public API surface preservation, behaviour preservation, and compliance with the performance-protection rules in `AGENTS.md`.
|
||||
|
||||
## Staged Files
|
||||
|
||||
```
|
||||
.kilo/plans/4k-multilayer-psd-performance-regression.md
|
||||
AGENTS.md
|
||||
build.id
|
||||
hcie-composite/src/tiled.rs
|
||||
hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs
|
||||
hcie-egui-app/crates/hcie-gui-egui/src/canvas/mod.rs
|
||||
hcie-egui-app/crates/hcie-gui-egui/src/canvas/render.rs
|
||||
hcie-engine-api/src/layer_property_ops.rs (new)
|
||||
hcie-engine-api/src/lib.rs
|
||||
hcie-engine-api/src/partial_composite.rs (new)
|
||||
hcie-engine-api/src/stroke_brush.rs (new)
|
||||
hcie-engine-api/src/stroke_cache.rs (new)
|
||||
hcie-engine-api/tests/performance_stroke_4k.rs (new)
|
||||
hcie-tile/src/lib.rs
|
||||
logs/functional_regression_report.txt
|
||||
unlock.sh
|
||||
```
|
||||
|
||||
## 1. Public API Surface
|
||||
|
||||
The `Engine` public API in `hcie-engine-api` was extracted from the single monolithic `lib.rs` into four new internal modules (`layer_property_ops`, `partial_composite`, `stroke_brush`, `stroke_cache`). The modules are declared as `mod`, not `pub mod`, so the only public surface remains the items exported from `lib.rs`.
|
||||
|
||||
Comparison of public symbols (`pub fn` / `pub struct` / `pub enum` / `pub use`) between `HEAD` and the staged tree shows that **the same set of public methods is still available**, with one exception:
|
||||
|
||||
- `capture_canvas_png(&mut self) -> Option<String>` is removed.
|
||||
- Search across the Rust source tree shows no live caller of this method (it only exists in the legacy backup tree `hcie-engine-api-orig/src_locked/lib.rs`).
|
||||
|
||||
Therefore, **no used public API is lost**. The removed function appears to be dead code.
|
||||
|
||||
## 2. Behaviour / Feature Loss Analysis
|
||||
|
||||
### 2.1 `hcie-engine-api/src/lib.rs` refactor
|
||||
|
||||
- Large functions (`render_composite_region`, `get_composite_pixels`, `begin_stroke`, `end_stroke`, stroke helpers, layer property setters) were moved into dedicated modules.
|
||||
- No logic changes observed in the moved code other than the performance improvements documented below.
|
||||
- `Engine::new` now initializes the new field `below_cache_dirty: true`, which is consistent with starting from a clean state.
|
||||
|
||||
### 2.2 `hcie-engine-api/src/stroke_cache.rs`
|
||||
|
||||
- `begin_stroke` / `end_stroke` keep the existing `active_stroke_mask` pooling, `stroke_before` undo snapshot, and `last_stroke_bounds` sub-rect snapshot logic.
|
||||
- Introduces `rebuild_below_cache_if_needed`, which reuses the existing `below_cache` buffer when the active layer index and dimensions match.
|
||||
- This is an **extension** of the existing `below_cache` mechanism, not a removal.
|
||||
|
||||
### 2.3 `hcie-engine-api/src/partial_composite.rs`
|
||||
|
||||
- `render_composite_region` and `get_composite_pixels` still follow the same sequence:
|
||||
1. render dirty vector layers,
|
||||
2. apply dirty effects/styles,
|
||||
3. sync dirty tiles,
|
||||
4. composite into scratch/full buffer.
|
||||
- The `composite_scratch` buffer is still pooled and reused.
|
||||
- `below_cache` reuse now guarded by `below_cache_dirty`.
|
||||
|
||||
### 2.4 `hcie-engine-api/src/layer_property_ops.rs`
|
||||
|
||||
- Property changes that do **not** alter pixel data (`opacity`, `visibility`, `blend_mode`, `parent`, `move_layer`, `clipping_mask`, `set_active_layer`, etc.) now:
|
||||
- set `document.composite_dirty = true`,
|
||||
- set `document.dirty_bounds = full_canvas`,
|
||||
- set `self.below_cache_dirty = true`,
|
||||
- and **no longer clear `tile_layers`**.
|
||||
- This directly implements the AGENTS.md rule **"Tile cache over-clear removal"**.
|
||||
- Structural changes (`add_layer`, `delete_layer`, `add_group`, `add_raw_layer`, `clear_all_layers`, `undo`, `redo`, `jump_to_history`) still clear `tile_layers`, which is correct because the layer count/content changes.
|
||||
|
||||
Risk note: `set_layer_parent` previously called `self.tile_layers.clear(); self.pre_tile_all_layers()`. The new code keeps the tile cache. This is safe only if the compositor (`hcie-composite/src/tiled.rs`) evaluates parent/clip hierarchy at composite time. The `base_indices` pre-computation in `tiled.rs` confirms that clip/parent logic is applied during compositing, so tile pixel data does not need to be rebuilt.
|
||||
|
||||
### 2.5 `hcie-composite/src/tiled.rs`
|
||||
|
||||
- Adds `SEQUENTIAL_PIXEL_THRESHOLD` and uses a sequential row loop for small dirty regions. Matches the AGENTS.md rule **"Small-region sequential compositing"**.
|
||||
- Adds effects-cache reuse (`layer.effects_cache`) before calling `apply_layer_effects`. Matches the AGENTS.md rule **"Effects cache reuse"**.
|
||||
- Helper functions (`blend_adjustment_pixel`, `blend_tile_pixel`, `blend_dense_pixel`) are pure refactoring; the blend math is unchanged.
|
||||
|
||||
### 2.6 `hcie-tile/src/lib.rs`
|
||||
|
||||
- `update_tiles_in_region` now writes only the intersecting sub-rectangle into an existing tile instead of replacing the whole tile. Matches the AGENTS.md rule **"Incremental tile update"**.
|
||||
- Empty tiles are dropped only after verifying no remaining content.
|
||||
- Logic is more conservative than the previous version (less data discarded), so no pixel content should be lost.
|
||||
|
||||
### 2.7 GUI changes
|
||||
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/canvas/render.rs`: adds `SelectionEdgeCache` and `extract_selection_edges`. The previous inline edge-extraction loop is replaced by a cached version that is keyed by the mask buffer pointer/length. No engine API change; pure GUI optimisation.
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/canvas/mod.rs`: pointer-event downsampling for brush strokes (only forwards events when distance exceeds a spacing threshold). GUI-side performance improvement.
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs`: adds `selection_edge_cache` field and resets it when the document is replaced.
|
||||
|
||||
These changes do not touch engine API usage except for passing `&mut selection_edge_cache` to the overlay renderer.
|
||||
|
||||
### 2.8 Documentation / metadata
|
||||
|
||||
- `AGENTS.md` rewritten for v4 terminology and expanded with the performance-protection table. No code impact.
|
||||
- `build.id`, `unlock.sh`, `logs/functional_regression_report.txt`: metadata/build helper updates.
|
||||
- New plan file: `.kilo/plans/4k-multilayer-psd-performance-regression.md`.
|
||||
|
||||
## 3. Potential Risks / Open Items
|
||||
|
||||
1. **`below_cache_dirty` consistency.** The new flag is set to `true` in `Engine::new` and on every non-structural property change. It is reset to `false` after a successful cache build or when no cache is needed. This logic appears consistent, but because it is new state, any missed setter that should invalidate it could cause stale composites. The staged code invalidates it in all observed property setters.
|
||||
|
||||
2. **Effects cache path in `tiled.rs`.** When `layer.effects_cache` exists and matches layer dimensions, the compositor uses `cached.rendered.clone()`. This introduces an extra buffer clone. The AGENTS.md rule states this is intended to avoid re-running the expensive effects pipeline. The clone cost is expected to be lower than `apply_layer_effects`, but this should be verified with the performance test.
|
||||
|
||||
3. **GUI downsampling.** The pointer-event spacing in `canvas/mod.rs` may subtly change the density of brush stamps for fast movements. This is a GUI-side change and does not affect engine correctness, but it could affect perceived stroke quality for some brush styles.
|
||||
|
||||
## 4. Performance-Protection Compliance
|
||||
|
||||
| Mechanism from `AGENTS.md` | Status in staged changes |
|
||||
|----------------------------|--------------------------|
|
||||
| `active_stroke_mask` pooling | Preserved in `stroke_cache.rs` |
|
||||
| `composite_scratch` pooling | Preserved in `partial_composite.rs` |
|
||||
| `below_cache` + `below_cache_dirty` | Extended with explicit `below_cache_dirty` flag |
|
||||
| Conditional `effects_dirty` set | Preserved in `stroke_brush.rs` |
|
||||
| Tile cache over-clear removal | Implemented in `layer_property_ops.rs` |
|
||||
| Small-region sequential compositing | Implemented in `hcie-composite/src/tiled.rs` |
|
||||
| Effects cache reuse | Implemented in `hcie-composite/src/tiled.rs` |
|
||||
| Incremental tile update | Implemented in `hcie-tile/src/lib.rs` |
|
||||
|
||||
## 5. Recommended Validation Before Commit
|
||||
|
||||
Because this is a plan-only review and no build/test commands were executed, the following tests should pass before the user commits:
|
||||
|
||||
```bash
|
||||
# Core engine regression tests
|
||||
cargo test -p hcie-composite
|
||||
cargo test -p hcie-tile
|
||||
cargo test -p hcie-engine-api --test visual_regression
|
||||
|
||||
# Performance smoke test (non-gating, but useful for before/after numbers)
|
||||
cargo test -p hcie-engine-api --test performance_stroke_4k -- --nocapture
|
||||
|
||||
# Ensure the workspace still compiles
|
||||
cargo check -p hcie-engine-api
|
||||
cargo check -p hcie-egui-app/crates/hcie-gui-egui
|
||||
```
|
||||
|
||||
## 6. Conclusion
|
||||
|
||||
- **No used public API is removed.** The only removed symbol, `capture_canvas_png`, is dead code in the current tree.
|
||||
- **No obvious feature loss.** The changes are structural refactoring plus performance improvements.
|
||||
- **Performance-protection rules are honoured, not bypassed.** Several rules are newly implemented or strengthened.
|
||||
- **Commit recommendation:** Proceed, **after** running the regression tests above and confirming a clean build.
|
||||
- If the tests fail, the failures should be fixed before committing; the pre-commit hook already blocks commits that touch high-risk crates without manual review.
|
||||
@@ -0,0 +1,131 @@
|
||||
# PSD Writer: Regression Fix for Photoshop Compatibility
|
||||
|
||||
## Problem
|
||||
|
||||
The PSD files generated by `hcie-psd/src/psd_saver.rs` can be parsed by `Psd::from_bytes` (our internal parser) but **cannot be opened in Adobe Photoshop**. The user says PSD writing was working earlier in the project's git history and has regressed.
|
||||
|
||||
## Git History
|
||||
|
||||
There are 3 commits touching `hcie-psd/src/psd_saver.rs`:
|
||||
|
||||
1. `cd0b6b1` — Initial PSD writer (997 lines)
|
||||
2. `0a821ea` — Added examples, 1-line change to saver
|
||||
3. `6616b31` — "Fix layer extra data and merged image layout" (our recent changes)
|
||||
|
||||
## Key Differences: `cd0b6b1` vs `6616b31` (HEAD)
|
||||
|
||||
### Change 1: Merged Image Row Length Order (SUSPECT)
|
||||
|
||||
**Original (cd0b6b1):** Interleaved — `[R0, G0, B0, A0, R1, G1, B1, A1, ...]`
|
||||
```rust
|
||||
for y in 0..h {
|
||||
for ch_idx in 0..4 {
|
||||
buf.extend_from_slice(&row_lengths[ch_idx][y]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Current (6616b31):** Per-channel — `[R0, R1, ..., Rn, G0, G1, ..., Gn, ...]`
|
||||
```rust
|
||||
for ch_idx in 0..4 {
|
||||
for y in 0..h {
|
||||
buf.extend_from_slice(&row_lengths[ch_idx][y]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**PSD Spec says:** "byte counts for all the scan lines (rows × channels)" — ambiguous ordering, but data is planar (`RRR GGG BBB`). Both orderings produce the same total count. The question is which order Photoshop expects.
|
||||
|
||||
**CRITICAL:** The original code has a **mismatch** — byte counts are interleaved but compressed data is per-channel. This would cause Photoshop to read wrong row lengths → corrupted data. The current code is internally consistent (both per-channel). But maybe Photoshop expects interleaved for both.
|
||||
|
||||
### Change 2: Layer Count Sign (SUSPECT)
|
||||
|
||||
Both versions always write **negative** layer count:
|
||||
```rust
|
||||
layer_records_buf.extend_from_slice(&(-layer_count.abs()).to_be_bytes());
|
||||
```
|
||||
|
||||
Per PSD spec: "If the count is negative, the first layer in the following record is a group." For a simple document with no groups, Photoshop may reject negative count.
|
||||
|
||||
### Change 3: Extra Data Blocks (6616b31 additions)
|
||||
|
||||
- Added `luni` (Unicode layer name) block
|
||||
- Added `lyid` (layer ID) block
|
||||
- Moved `lfx2` odd-length padding from external to internal
|
||||
- Used `write_8bim_block` helper for `iOpa`, `brst`, `shmd`
|
||||
|
||||
### Change 4: `PassThrough` blend mode added
|
||||
|
||||
```rust
|
||||
BlendMode::PassThrough => b"pass",
|
||||
```
|
||||
|
||||
## Debugging Plan
|
||||
|
||||
### Step 1: Create Minimal Test PSD (no effects)
|
||||
|
||||
Write a Rust example `examples/test_psd_simple.rs` that:
|
||||
1. Creates a 256×256 canvas
|
||||
2. Adds one layer with a red rectangle (no effects)
|
||||
3. Calls `save_psd()` to write `_tmp/simple_test.psd`
|
||||
4. The user opens this in Photoshop
|
||||
|
||||
This isolates the PSD structural issues from effects serialization.
|
||||
|
||||
### Step 2: Binary Comparison with Reference PSD
|
||||
|
||||
If Step 1 PSD doesn't open:
|
||||
1. User creates the same image in Photopea/Photoshop and saves as PSD
|
||||
2. Write a Python script to hex-dump both files and diff the binary structure
|
||||
3. Identify exact byte offset where the format diverges
|
||||
|
||||
### Step 3: Fix Identified Issues
|
||||
|
||||
Based on the debugging results, fix the PSD writer. Most likely fixes:
|
||||
|
||||
**Fix A — Merged Image Row Lengths:** Try interleaved order (matching original) for BOTH byte counts AND data. This is the most common PSD layout in practice.
|
||||
|
||||
**Fix B — Layer Count:** Write positive count when there are no groups:
|
||||
```rust
|
||||
let count = if layers.iter().any(|l| l.layer_type == LayerType::Group) {
|
||||
-(layers.len() as i16)
|
||||
} else {
|
||||
layers.len() as i16
|
||||
};
|
||||
```
|
||||
|
||||
**Fix C — lfx2/shmd descriptor correctness:** Verify the ActionDescriptor binary format matches what Photoshop writes. Cross-reference with the spec's descriptor format section.
|
||||
|
||||
### Step 4: Test with Effects
|
||||
|
||||
Once the basic PSD opens, add inner shadow effects and test again.
|
||||
|
||||
### Step 5: Batch Generate 192 Inner Shadow PSDs
|
||||
|
||||
Write `examples/generate_inner_shadow_psd_set.rs` using the fixed `save_psd()`.
|
||||
|
||||
### Step 6: Batch Export PNGs in Photoshop
|
||||
|
||||
User runs `_tools/photoshop/export_psd_folder_to_png.jsx` in Photoshop to batch-export all 192 PSDs as transparent PNGs.
|
||||
|
||||
### Step 7: Run MAE Grid Search
|
||||
|
||||
```bash
|
||||
cargo run --package hcie-io --example tune_mae_inner_shadow_grid
|
||||
```
|
||||
|
||||
## Files to Modify/Create
|
||||
|
||||
| File | Action |
|
||||
|---|---|
|
||||
| `hcie-psd/src/psd_saver.rs` | Fix merged image row order, layer count, possibly effects format |
|
||||
| `hcie-io/examples/test_psd_simple.rs` | **Create** — minimal PSD for Photoshop validation |
|
||||
| `hcie-io/examples/generate_inner_shadow_psd_set.rs` | **Create** — batch inner shadow PSD generator |
|
||||
| `hcie-io/examples/tune_mae_inner_shadow_grid.rs` | Already created, ready to use |
|
||||
|
||||
## Verification
|
||||
|
||||
1. `cargo run --package hcie-io --example test_psd_simple` → user opens in Photoshop
|
||||
2. If opens → proceed to effects test
|
||||
3. If fails → hex dump comparison with Photoshop-generated PSD
|
||||
4. Final: 192 PSDs → Photoshop batch export → MAE tuning
|
||||
@@ -0,0 +1,196 @@
|
||||
# PSD Writer Structural Fix Plan — Example3-mini round-trip
|
||||
|
||||
## Problem
|
||||
|
||||
`/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/_test_images/example3/Example3-mini-hcie.psd` is produced from the original `Example3-mini.psd` via `hcie-psd-saver/src/lib.rs`. Side-by-side binary analysis shows several structural problems that prevent Photoshop from opening the generated file correctly and corrupt the layer/folder structure:
|
||||
|
||||
1. **Group / section-divider layers are lost** — original has 19 layers (including a section divider for the `Dodge and Burn` group); HCIE output had 18. The divider layer with `lsct` section type 2 was dropped, so Photoshop could not reconstruct the folder.
|
||||
2. **Layer blend mode string mismatch for group pass-through** — original group divider uses `pass`; HCIE wrote `norm` for the group layer. `BlendMode::PassThrough` exists in `hcie_protocol` but the PSD writer mapped it to `b"pass"` only in the record. However the imported group layer came in as `BlendMode::Normal` because `hcie-psd/src/psd_import.rs` converted "PassThrough" to `Normal`.
|
||||
3. **Layer flags were always 0** — original uses flags `8` (transparency protected / group flags), `24` (group/adjustment), `56` (adjustment with vector mask).
|
||||
4. **Group layers were emitted at full canvas size** — imported group layer had `width=1920 height=1080` and was written as a normal raster layer with transparent pixels. Original group divider layers have `bounds=(0,0,0,0)` and only an `lsct` block.
|
||||
5. **Adjustment layers lost their extra blocks** — original `curv`, `grdm`, `hue2` layers have 40+ byte adjustment descriptors. HCIE imported them as `Raster` with no adjustment data, then wrote them as 1×1 transparent raster layers.
|
||||
6. **Layer extra block order/format differences** — original uses `luni` immediately after the Pascal name. Extra block ordering in general did not match Photoshop's observed sequence.
|
||||
7. **`lfx2` / effects data was missing for layers that had effects** — original `Rear Light` / `Jaw Light` / `Cheek Redness` contain `lfx2` blocks; HCIE output had none, so all layer effects were lost on export.
|
||||
8. **Adjustment blocks (`curv`, `grdm`, `hue2`) were not written** — even if imported, the writer had no code to emit these.
|
||||
9. **`iOpa` / `brst` ordering inside extra data was non-standard** — writer placed `iOpa` and `brst` before `lfx2`.
|
||||
10. **Merged image data differs** — expected because compositing in HCIE is not pixel-identical to Photoshop, but the layer/image structure must be valid first.
|
||||
|
||||
## Root Causes
|
||||
|
||||
- `hcie-psd/src/psd_import.rs` collapsed group section-divider layers and `PassThrough` blend mode into `LayerType::Group` + `BlendMode::Normal`, so the writer could not emit the correct divider record.
|
||||
- `hcie-psd/src/psd_import.rs` did not preserve adjustment data (`curv`, `grdm`, `hue2`) into `layer.adjustment`; it only built curve-based `Adjustment` for curves, but the writer never read `layer.adjustment`.
|
||||
- `hcie-psd-saver/src/lib.rs` treated every layer as a 4-channel raster layer with full canvas or content bounds, and emitted the same extra block sequence regardless of layer type.
|
||||
- The writer did not emit section-divider records (`lsct` type 2) for group end markers, and did not write pass-through blend mode for group dividers.
|
||||
- Layer flags were hard-coded to `0`; the writer did not compute protection/group/adjustment flags.
|
||||
- The writer's image resources section is a minimal stub; the original PSD contains ~12 KB of resources (thumbnail, guides, print info, paths, etc.).
|
||||
- `hcie-fx::parser::parse_lfx2` cannot yet decode the newer `lfx2` descriptor format used by `Example3-mini.psd`, so effects are lost during import and therefore cannot be re-emitted.
|
||||
|
||||
## Files to Modify
|
||||
|
||||
| File | Layer | Why |
|
||||
|------|-------|-----|
|
||||
| `hcie-psd-saver/src/lib.rs` | GUI/open (crate not in locked list) | Main PSD writer; group/adjustment/flag/effect handling and image resources |
|
||||
| `hcie-psd/src/psd_import.rs` | editable | Preserve section-divider type, pass-through blend, adjustment raw data |
|
||||
| `hcie-protocol/src/lib.rs` | locked (unlocked only for these fields) | `Layer.adjustment_raw`, `Layer.is_section_divider` storage fields |
|
||||
| `hcie-psd-saver/src/fx_descriptors.rs` | GUI/open | Effect descriptor serialization; needs to stay in sync with `hcie-fx` parser |
|
||||
| `hcie-psd-saver/src/helpers.rs` | GUI/open | `write_8bim_block` / `write_luni_block` helpers |
|
||||
| `hcie-fx/src/parser.rs` | engine (locked via AGENTS.md) | `parse_lfx2` / `parse_descriptor_body` need to decode `numM`/`ChFX`/modern descriptors |
|
||||
|
||||
## Implementation Status
|
||||
|
||||
### ✅ Done
|
||||
|
||||
- Added `adjustment_raw: Option<([u8; 4], Vec<u8>)>` and `is_section_divider: bool` to `hcie_protocol::Layer`.
|
||||
- `psd_import` now preserves `curv`/`grdm`/`hue2` raw bytes and section-divider type/PassThrough blend.
|
||||
- `psd_import` emits a `</Layer group>` section-divider marker in the correct position.
|
||||
- `psd_saver` emits section dividers with `bounds=(0,0,0,0)`, empty channels, `lsct` type 2, and `pass` blend mode.
|
||||
- `psd_saver` computes layer flags (group/adjustment).
|
||||
- `psd_saver` passes adjustment raw blocks through before `luni`/`lfx2`.
|
||||
- `psd_saver` orders extra blocks as: mask → blending ranges → Pascal name → adjustment → `lfx2`/`iOpa`/`brst` → `luni` → `lyid` → `lsct` → `clbl`/`infx`/`knko`/`lspf`/`lclr` → `shmd` → `fxrp` → `lyvr`.
|
||||
- `roundtrip_psd` example now reports structural comparison.
|
||||
|
||||
### Current Test Result
|
||||
|
||||
```
|
||||
Original layer records: 19
|
||||
Saved layer records: 19
|
||||
Layer structure matches (names, group/divider flags, adjustment flags).
|
||||
```
|
||||
|
||||
Group divider blend is now `pass` and section-divider marker is present.
|
||||
|
||||
## Remaining Work for Full Photoshop Compatibility
|
||||
|
||||
### 1. Effect Import (`hcie-fx` parser)
|
||||
|
||||
`Example3-mini.psd` stores effects in `lfx2` descriptor format. The current `parse_lfx2` only sees `Scl`, `masterFXSwitch`, `numM` entries and fails to parse the `numM` list of `ChFX`/`DrSh`/etc. objects. Until this is fixed, `layer.effects` remains empty on import and cannot be re-emitted.
|
||||
|
||||
**Approach:**
|
||||
- Extend `parse_descriptor_body` to handle `VlLs`/`Objc` recursion with correct skip offsets for non-ASL descriptors.
|
||||
- Add explicit handling for `numM` (effect list) and common modern effect keys.
|
||||
- Add fixture tests using the 100-byte `lfx2` blocks from `Rear Light`, `Jaw Light`, `Cheek Redness`.
|
||||
|
||||
### 2. Image Resources Passthrough
|
||||
|
||||
The original PSD has 24 resources (~12 KB); the writer only writes 7 minimal ones. Missing resources include:
|
||||
- `0x040C` thumbnail
|
||||
- `0x03F3`, `0x03F5`, `0x03F8`, `0x0400`, `0x0404`, `0x0411`, `0x041A`, `0x041E`, `0x0421`, `0x0422`, `0x0424`, `0x0425`, `0x0426`, `0x0428`, `0x0429`, `0x043A`, `0x043B`, `0x2710`
|
||||
|
||||
**Approach:**
|
||||
- During import, store the raw image resources section bytes (or a parsed map of resource ID → data).
|
||||
- In `save_psd`, if the input came from a PSD, write the original resources back unchanged, replacing only resources we must generate (resolution, global angle, layer selection if needed).
|
||||
- This avoids reverse-engineering every resource format and keeps Photoshop metadata intact.
|
||||
|
||||
### 3. Layer Channel Data for Adjustment Layers
|
||||
|
||||
Original adjustment layers have 5 channels (`-1, 0, 1, 2, -2` mask) with zero-length channel data. Our writer currently writes 4 channels for adjustment layers. Photoshop may expect 5 if a mask is declared.
|
||||
|
||||
**Approach:**
|
||||
- Preserve original channel layout during import and re-emit exactly the same channel IDs/lengths for adjustment/group layers.
|
||||
- For raster layers, keep the 4-channel auto-crop logic.
|
||||
|
||||
### 4. `luni` Block Format
|
||||
|
||||
Original PSD's `luni` block does **not** have a leading Pascal string; it starts directly with 4-byte UTF-16 length. `write_8bim_block` adds a Pascal string length byte of 0 before the data, which may confuse strict parsers. In practice it parses, but for parity we should support writing `luni` without the Pascal prefix.
|
||||
|
||||
**Approach:**
|
||||
- Add a helper that writes `luni` either with or without the leading Pascal byte, controlled by a compatibility flag.
|
||||
- After verifying with Photoshop, choose the format that matches the original.
|
||||
|
||||
### 5. Merged Image Layout
|
||||
|
||||
Historical versions of the writer used interleaved row lengths (`R0 G0 B0 A0 R1 G1 B1 A1 ...`) but per-channel compressed data. Current code is internally consistent (per-channel lengths + per-channel data). The plan file `fix-psd-writer-for-photoshop.md` flagged this as a possible regression.
|
||||
|
||||
**Approach:**
|
||||
- Add a toggle in `write_merged_image_data` for interleaved vs. per-channel row lengths.
|
||||
- Generate a minimal 256×256 single-layer PSD with both layouts and test which one Photoshop/Photopea opens.
|
||||
- Lock in the working layout and add a regression test.
|
||||
|
||||
### 6. Clipping Mask / Vector Mask Support
|
||||
|
||||
Original layers with effects (e.g. `Rear Light`) use an explicit `-2` mask channel (`mask_len=28`). Our writer currently writes an empty mask section. If effects rely on the mask, they will look wrong.
|
||||
|
||||
**Approach:**
|
||||
- Preserve `mask_pixels`, `mask_bounds`, and `mask_default_color` on import (already partially done).
|
||||
- Re-emit layer masks in the saved PSD when present.
|
||||
|
||||
### 7. Group Bounds / Section Divider Channel Count
|
||||
|
||||
Original section-divider record has 4 channels with 2-byte `0001` RLE markers. Our writer currently emits 4 channels with a single `0x81` PackBits run marker. Both represent empty data, but byte-for-byte parity requires matching the exact marker format.
|
||||
|
||||
**Approach:**
|
||||
- Match the original exactly: 4 channels, each `00 01` compression type followed by nothing (or minimal empty RLE row).
|
||||
|
||||
## Test & Tune System
|
||||
|
||||
To reach full compatibility we need automated, repeatable tests. Proposed system:
|
||||
|
||||
### A. Structural Regression Suite
|
||||
|
||||
Extend `hcie-io/examples/roundtrip_psd.rs` to:
|
||||
1. Compare layer count, names, order.
|
||||
2. Compare per-layer bounds, channel count/IDs, channel lengths.
|
||||
3. Compare blend mode keys, opacity, clipping, flags.
|
||||
4. Compare extra block keys and order (mask → blending → name → adjustment → lfx2 → iOpa/brst → luni → lyid → lsct → clbl/infx/knko/lspf/lclr → shmd → fxrp → lyvr).
|
||||
5. Compare image resource IDs.
|
||||
6. Compare merged image compression type.
|
||||
|
||||
Fail on first divergence and print both sides.
|
||||
|
||||
### B. Photoshop Batch Validation Harness
|
||||
|
||||
Create `_tools/photoshop/validate_psd.jsx` that:
|
||||
1. Opens a PSD in Photoshop.
|
||||
2. Reports whether it opened without error.
|
||||
3. Exports layer count and layer names to a JSON/text file.
|
||||
4. Saves a flattened PNG for visual comparison.
|
||||
|
||||
Wrap it with a Rust/Node runner in `hcie-io/examples/photoshop_validate.rs` so CI/dev can call it locally if Photoshop is installed.
|
||||
|
||||
### C. Golden File Regression
|
||||
|
||||
Commit `Example3-mini-hcie.psd` and a `tests/psd_roundtrip.rs` integration test that:
|
||||
1. Imports `Example3-mini.psd`.
|
||||
2. Saves to a temp file.
|
||||
3. Asserts structural parity with the original.
|
||||
4. (Later) asserts pixel MAE under a threshold once effects are preserved.
|
||||
|
||||
### D. MAE-Based Effect Tuning
|
||||
|
||||
Reuse the existing `tune_mae_*_grid.rs` pattern:
|
||||
1. Generate a grid of single-effect PSDs with `save_psd`.
|
||||
2. Batch-export ground-truth PNGs from the same PSDs in Photoshop.
|
||||
3. Run grid search over effect parameters (blur factor, spread scale, etc.) to minimize MAE.
|
||||
4. Apply best defaults to `hcie-fx` or `hcie-psd-saver/fx_descriptors.rs`.
|
||||
|
||||
This is currently done for drop shadow, outer glow, inner glow, bevel, stroke; inner shadow and satin need similar treatment.
|
||||
|
||||
### E. Minimal Photoshop Open Test
|
||||
|
||||
Create `hcie-io/examples/test_psd_simple.rs` that writes a 256×256 single-layer PSD. This is the fastest way to verify whether the core PSD container opens in Photoshop before adding complex features.
|
||||
|
||||
## Success Criteria (Updated)
|
||||
|
||||
1. ✅ `Example3-mini-hcie.psd` has 19 layers and matching structural roles.
|
||||
2. ✅ `roundtrip_psd` reports structural parity.
|
||||
3. ⬜ File opens in Photoshop without "not compatible" error.
|
||||
4. ⬜ Folder/adjustment structure is visually correct.
|
||||
5. ⬜ Layer effects round-trip and render correctly (requires `hcie-fx` parser fix).
|
||||
6. ⬜ Merged image MAE is within acceptable threshold vs. Photoshop ground-truth PNG.
|
||||
|
||||
## Next Steps (Priority Order)
|
||||
|
||||
1. **Image resources passthrough** — lowest risk, biggest Photoshop-compatibility win.
|
||||
2. **Minimal PSD open test** — verify base container with Photoshop/Photopea.
|
||||
3. **lfx2 effect parser fix** — unlock full effect round-trip.
|
||||
4. **Layer mask / adjustment channel layout parity** — fix structural edge cases.
|
||||
5. **Merged image row-length layout test** — confirm Photoshop-preferred order.
|
||||
6. **Golden regression test** — lock in progress and prevent regressions.
|
||||
|
||||
## Notes
|
||||
|
||||
- `hcie-protocol` changes must remain minimal and backward-compatible; new fields have `#[serde(default)]`.
|
||||
- `hcie-fx` is a locked engine crate; any changes there must go through `unlock.sh` / `lock.sh` and be justified by a failing regression test.
|
||||
- Do not chase pixel-perfect compositing in this plan; focus on structural validity and Photoshop opening first, then MAE-based tuning.
|
||||
|
||||
(Updated: 2026-06-18)
|
||||
@@ -0,0 +1,200 @@
|
||||
# FX Optimizasyon Pipeline — Çalışma Notları
|
||||
|
||||
## Tarih: 2026-06-18
|
||||
|
||||
## Amaç
|
||||
Her FX türü (InnerShadow, DropShadow, OuterGlow, InnerGlow, BevelEmboss, Satin, Stroke vb.) için:
|
||||
1. Photoshop ground-truth PNG'ler üretmek
|
||||
2. Algoritma parametrelerini MAE ile optimize etmek
|
||||
3. Başarılı sonuçları ayrı crate olarak kilitlemek
|
||||
|
||||
---
|
||||
|
||||
## 1. PSD Saver — Photoshop Uyumluluğu
|
||||
|
||||
### Problem
|
||||
`hcie-psd/src/psd_saver.rs` tarafından üretilen PSD dosyaları Photoshop'ta açılmıyordu.
|
||||
|
||||
### Kök Nedenler ve Düzeltmeler
|
||||
|
||||
| # | Sorun | Düzeltme |
|
||||
|---|-------|----------|
|
||||
| 1 | Header ch=4 (RGBA) | ch=3 (RGB) yapıldı |
|
||||
| 2 | Merged image 4 kanal | 3 kanal (RGB) yapıldı |
|
||||
| 3 | lfx2 version fields ters | `[0, 16]` sırası düzeltildi |
|
||||
| 4 | Layer bounds full canvas | `find_content_bounds()` ile auto-crop |
|
||||
| 5 | write_8bim_block padding | Length field even'a yuvarlandı (PSD spec) |
|
||||
| 6 | Extra keys (layerConceals, Cntn) | DropShadow, InnerShadow, ColorOverlay'dan kaldırıldı |
|
||||
| 7 | Ckmt/spread dönüştürme | %→px dönüşümü kaldırıldı, raw pixel değeri yazılıyor |
|
||||
| 8 | uglg=true | false yapıldı (local angle kullanılıyor) |
|
||||
| 9 | Contour name "Linear" | "Linear\0" null terminator eklendi |
|
||||
| 10 | shmd LyrI=0 | LyrI=1 yapıldı |
|
||||
| 11 | shmd conditional Lefx | Efekt varsa Lefx yazıyor, yoksa yazmıyor |
|
||||
| 12 | Image Resources eksik | 0x03ED, 0x0408, 0x040D, 0x0414, 0x0419, 0x042D, 0x0435 eklendi |
|
||||
| 13 | Missing layer blocks | luni, lyid, lsct, lclr, fxrp, lspf eklendi |
|
||||
|
||||
### Dosya Yapısı (Yeni)
|
||||
```
|
||||
hcie-psd-saver/ ← AYRI CRATE (kilitli)
|
||||
├── Cargo.toml
|
||||
└── src/
|
||||
├── lib.rs ← chmod 555 — save_psd, layer records, merged image
|
||||
├── helpers.rs ← chmod 555 — w_key4, w_bool, w_enum, w_untf vb.
|
||||
└── fx_descriptors.rs ← chmod 644 — AÇIK, 10 efekt writer
|
||||
|
||||
hcie-psd/
|
||||
├── src/lib.rs ← pub use hcie_psd_saver::save_psd (re-export)
|
||||
└── Cargo.toml ← hcie-psd-saver dependency
|
||||
```
|
||||
|
||||
### Korumalar
|
||||
- `lib.rs` ve `helpers.rs`: chmod 555 (dizin kilitli, dosya kilitli)
|
||||
- `fx_descriptors.rs`: chmod 644 (açık — AI ajanları sadece burayı düzenler)
|
||||
- AI ajanı `chmod u+w` bile yapamaz (dizin 555)
|
||||
|
||||
---
|
||||
|
||||
## 2. Inner Shadow MAE Optimizasyonu
|
||||
|
||||
### Adımlar
|
||||
|
||||
#### 2a. PSD Set Oluşturma
|
||||
- `hcie-io/examples/generate_inner_shadow_psd_set.rs`
|
||||
- 1536 PSD: 4 angle × 3 dist × 2 size × 2 choke × 2 opacity × 2 noise × 2 blend × 4 color
|
||||
- Renkler: red(220,30,30), green(30,180,30), blue(30,60,220), white(220,220,220)
|
||||
- Blend modes: normal, multiply
|
||||
- Canvas: 256×256, siyah rectangle (25% margin)
|
||||
- Output: `_tmp/inner_shadow_psd_set/`
|
||||
|
||||
#### 2b. Photoshop Export
|
||||
- `_tools/photoshop/export_psd_folder_to_png.jsx` ile batch PNG export
|
||||
- Output: `_tmp/inner_shadow_png_set/`
|
||||
- **ÖNEMLİ**: "Export As → PNG → Transparency" kullanın, "Flatten Image" DEĞİL!
|
||||
- Flatten → beyaz arka plan → MAE patlar
|
||||
|
||||
#### 2c. MAE Grid Search
|
||||
- `hcie-io/examples/tune_mae_inner_shadow_grid.rs`
|
||||
- 768 sample (noise>0 atlandı) × 384 combo = 294912 evaluation
|
||||
- Grid: blur_factor[6], passes[4], choke_scale[4], mask_strength[4]
|
||||
- Content-only comparison: beyaz/shaffaf arka plan pikselleri atlanıyor
|
||||
- `content_mask` ile sadece şekil+gölge pikselleri karşılaştırılıyor
|
||||
|
||||
#### 2d. Sonuçlar
|
||||
```
|
||||
Best avg MAE = 11.14
|
||||
blur_factor = 6.0
|
||||
passes = 1
|
||||
choke_scale = 0.00
|
||||
mask_strength = 0.75
|
||||
```
|
||||
|
||||
#### 2e. Parametre Uygulama
|
||||
- `hcie-fx/src/shadow.rs` → `generate_inner_shadow()` fonksiyonunda sabit değerler güncellendi:
|
||||
- blur_factor: 2.5 → 6.0
|
||||
- passes: 3 → 1
|
||||
- choke: devre dışı
|
||||
- mask_strength: 1.0 → 0.75
|
||||
|
||||
---
|
||||
|
||||
## 3. GUI Example Düzeltmeleri
|
||||
|
||||
### `hcie-io/examples/gui.rs`
|
||||
1. **Blend mode hardcoded Normal** → `composite()` fonksiyonunda `from_blend_mode()` ile gerçek blend mode kullanılıyor
|
||||
2. **Canvas size yanlış** → PSD header'dan okunuyor (`Psd::from_bytes` ile), `layers[0].width` yerine
|
||||
|
||||
---
|
||||
|
||||
## 4. Pre-commit Hook Değişiklikleri
|
||||
|
||||
- `.git/hooks/pre-commit`: Testleri çalıştırır ama commit'i ASLA engellemez
|
||||
- `.git/hooks/post-commit`: Sadece semantic log, ağır test yok
|
||||
|
||||
---
|
||||
|
||||
## 5. Backup
|
||||
|
||||
- `_tmp/psd_saver_backup_20260617_212138.zip`: İlk halinin yedeği
|
||||
|
||||
---
|
||||
|
||||
## 6. Sonraki Adımlar (Diğer FX Türleri İçin)
|
||||
|
||||
Her FX türü için aynı pipeline uygulanacak:
|
||||
|
||||
### DropShadow
|
||||
1. `generate_inner_shadow_psd_set.rs`'i kopyala → `generate_drop_shadow_psd_set.rs`
|
||||
2. Parametre grid: angle, distance, spread, size, opacity, noise, blend, color
|
||||
3. PSD'leri üret → Photoshop'ta PNG'ye çevir
|
||||
4. `tune_mae_drop_shadow_grid.rs` yaz
|
||||
5. MAE grid search çalıştır
|
||||
6. En iyi parametreleri `hcie-fx/src/shadow.rs` → `generate_shadow()`'a uygula
|
||||
|
||||
### OuterGlow / InnerGlow
|
||||
- Parametreler: spread/choke, size, opacity, noise, blend, color
|
||||
- `generate_glow()` fonksiyonu `shadow.rs`'da
|
||||
|
||||
### BevelEmboss
|
||||
- Parametreler: style, technique, depth, size, soften, angle, altitude
|
||||
- `generate_bevel_emboss()` fonksiyonu `shadow.rs`'da (NDL-based)
|
||||
|
||||
### Satin
|
||||
- Parametreler: angle, distance, size, invert, blend, color, opacity
|
||||
|
||||
### Stroke
|
||||
- Parametreler: position (inside/center/outside), fill_type, size, color, opacity
|
||||
|
||||
### Her FX için:
|
||||
1. PSD set oluştur (varyasyonlu renkler, blend modes)
|
||||
2. Photoshop'ta batch PNG export (Export As, flatten DEĞİL!)
|
||||
3. MAE grid search (content-only comparison)
|
||||
4. En iyi parametreleri uygula
|
||||
5. `hcie-fx/src/` altındaki ilgili dosyayı güncelle
|
||||
6. Başarılı olursa ayrı crate olarak kilitle
|
||||
|
||||
---
|
||||
|
||||
## 7. Kritik Dosyalar
|
||||
|
||||
| Dosya | Amaç |
|
||||
|-------|------|
|
||||
| `hcie-psd-saver/src/lib.rs` | PSD writer — KİLİTLİ |
|
||||
| `hcie-psd-saver/src/helpers.rs` | PSD descriptor helpers — KİLİTLİ |
|
||||
| `hcie-psd-saver/src/fx_descriptors.rs` | FX descriptor writers — AÇIK |
|
||||
| `hcie-fx/src/shadow.rs` | Shadow/glow algoritmaları — MAE güncellendi |
|
||||
| `hcie-fx/src/tuned.rs` | Tuned shadow algoritmaları (grid search için) |
|
||||
| `hcie-io/examples/generate_inner_shadow_psd_set.rs` | 1536 PSD üretici |
|
||||
| `hcie-io/examples/tune_mae_inner_shadow_grid.rs` | MAE grid search |
|
||||
| `hcie-io/examples/gui.rs` | Test GUI (PNG vs PSD karşılaştırma) |
|
||||
|
||||
---
|
||||
|
||||
## 8. Komutlar
|
||||
|
||||
```bash
|
||||
# PSD üret
|
||||
cargo run --example generate_inner_shadow_psd_set -p hcie-io
|
||||
|
||||
# MAE grid search (arka planda)
|
||||
cargo run --example tune_mae_inner_shadow_grid -p hcie-io
|
||||
|
||||
# GUI test
|
||||
cargo run --example gui -p hcie-io
|
||||
|
||||
# Ana program
|
||||
cargo run --bin hcie-gui
|
||||
|
||||
# PSD roundtrip test
|
||||
cargo run --example roundtrip_psd -p hcie-io
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Uyarılar
|
||||
|
||||
- **Flatten Image KULLANMAYIN** — beyaz arka plan MAE'yi patlatır
|
||||
- **Export As → PNG → Transparency** kullanın
|
||||
- `hcie-psd-saver/src/lib.rs` ve `helpers.rs` KİLİTLİ — değiştirmeyin
|
||||
- `fx_descriptors.rs` AÇIK — sadece efekt descriptor'larını düzenleyin
|
||||
- Pre-commit hook engellemez, ama testleri çalıştırır
|
||||
- `hcie-fx/src/shadow.rs` locked crate'te değil, doğrudan düzenlenebilir
|
||||
@@ -0,0 +1,200 @@
|
||||
# Plan (Revize): Custom Title Bar — Düzeltmeler
|
||||
|
||||
> Orijinal istek: Ana pencerede özel başlık çubuğu çiz (25px), menü + kapat/min/max + taşıma bu çubukta. Farklı renk, sol başta app icon, başlıkta program adı+versiyon+aktif döküman (kırpılabilir). Özelleştirilebilir butonlar (default: panel listesi aç/kapa) + arama çubuğu için yer.
|
||||
|
||||
> Önceki planın durumu: Task 1 (empty-source-after-drag) ve title_bar.rs silme ZATEN UYGULANDI. `leaf_indices`/`leaf_tab_count` pub edildi, cleanup loop eklendi, title_bar.rs yok, title+search+toggle menu bar'a eklendi.
|
||||
|
||||
> Kullanıcı geri bildirimi (revize gerekçeleri):
|
||||
> 1. **Başlık yanlış yerde** — title solda, grip'in yanında. ORTADA olmalı (VS Code tarzı).
|
||||
> 2. **Sağdaki 3 pencere butonu dışa taşmış** — `draw_window_controls` `btn_h=36.0` kullanıyor ama `bar_h=28.0`. Butonlar çubuktan yüksek → taşma.
|
||||
> 3. **Panel min genişlik 200px olmalı** — grimdock `min_pane_size=60.0`.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Bar Yüksekliği 25px + Butonları Çubuğa Sığdır
|
||||
|
||||
### Problem
|
||||
`show_menu_bar` `bar_h = 28.0`, ama `draw_window_controls` `btn_h = 36.0` ve `btn_w = 44.0`. 36px butonlar 28px çubuğa sığmıyor → sağ tarafta dışa taşıyor.
|
||||
|
||||
### Çözüm
|
||||
|
||||
**Dosya: `hcie-egui-app/crates/hcie-gui-egui/src/app/panels.rs`**
|
||||
|
||||
1. `show_menu_bar` (satır 42): `let bar_h = 28.0;` → `let bar_h = 25.0;`
|
||||
2. `draw_window_controls` (satır 141-210):
|
||||
- `let btn_w = 44.0_f32;` → `let btn_w = 32.0_f32;`
|
||||
- `let btn_h = 36.0_f32;` → `let btn_h = bar_h` parametre olarak geçir. `bar_h` artık `draw_window_controls`'a argüman gelir.
|
||||
- İmzayı güncelle:
|
||||
```rust
|
||||
fn draw_window_controls(ui: &mut egui::Ui, ctx: &egui::Context, colors: ThemeColors, app: &mut HcieApp, bar_h: f32)
|
||||
```
|
||||
- Çağrıyı güncelle (satır 115): `draw_window_controls(ui, ctx, colors, app, bar_h);`
|
||||
3. İkon çizim merkezlerini `bar_h/2`'ye göre konumlandır (mevcut `draw_btn` `r.center()` kullandığından otomatik uyum sağlar — sadece `btn_h` değişikliği yeterli).
|
||||
|
||||
### Beklenen
|
||||
- Çubuk 25px, pencere butonları tam çubuk yüksekliğinde, taşma yok.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Title'ı Ortala (Üç Sütunlu Layout)
|
||||
|
||||
### Problem
|
||||
Mevcut `show_menu_bar` title'ı sol blokta, grip'in yanında çiziyor (satır 75-86). Kullanıcı title'ı **ORTADA** istiyor (VS Code gibi).
|
||||
|
||||
### Çözüm
|
||||
|
||||
Mevcut yapı:
|
||||
```rust
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.horizontal(|ui| { /* LEFT: grip, title?, menus, search, toggle */ });
|
||||
ui.with_layout(right_to_left, |ui| { /* RIGHT: controls, grip */ });
|
||||
});
|
||||
```
|
||||
|
||||
Yeni yapı (üç sütun — left docked, center fill, right docked):
|
||||
```rust
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.set_height(bar_h);
|
||||
|
||||
// --- LEFT: grip + app icon + menus + search + toggle ---
|
||||
ui.horizontal(|ui| {
|
||||
// grip dots (mevcut, satır 56-73)
|
||||
// app icon 🎨 (mevcut 75-77'den taşındı)
|
||||
// menus (crate::app::menus::show)
|
||||
// search field (mevcut 92-99)
|
||||
// panel toggle (mevcut 100-110)
|
||||
});
|
||||
|
||||
// --- CENTER: title (fill, truncate, center-aligned) ---
|
||||
// Ayrı bir ui bloğu; kalan yatay alanı doldurur.
|
||||
ui.with_layout(egui::Layout::centered_and_justified(egui::Align::Center), |ui| {
|
||||
let title = build_title(app); // "HCIE v4 — Untitled"
|
||||
ui.add(egui::Label::new(
|
||||
egui::RichText::new(&title).size(11.0).color(colors.text_primary).strong(),
|
||||
).truncate());
|
||||
});
|
||||
|
||||
// --- RIGHT: window controls + right grip ---
|
||||
if !app.state.settings.show_native_decorations {
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
draw_window_controls(ui, ctx, colors, app, bar_h);
|
||||
// right grip (mevcut 117-134)
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Önemli detaylar:**
|
||||
- Title `truncate()` ile kırpılır (kullanıcı isteği: kırpılabilir).
|
||||
- Title center layout'a konur; LEFT blok kendi içeriğini packed şekilde soldan yerleştirir, RIGHT blok sağdan. CENTER kalan alanı alır ve ortalar.
|
||||
- Center layout `centered_and_justified` kullanır: uzun title'lar merkezde kalır, taşan kısım `truncate` ile kesilir.
|
||||
- App icon 🎽 artık LEFT'te grip'ten hemen sonra (title'ın yanında DEĞİL). Title ortada yalnızca metin "HCIE v4 — <docname>".
|
||||
|
||||
**Dosya: `hcie-egui-app/crates/hcie-gui-egui/src/app/panels.rs`** — `show_menu_bar` tamamen yeniden yazılır (satır 40-139 arası). `build_title` yardımcı fonksiyonu ekle:
|
||||
```rust
|
||||
fn build_title(app: &HcieApp) -> String {
|
||||
let doc = app.active_doc_ref()
|
||||
.map(|d| d.name.clone())
|
||||
.unwrap_or_else(|| "Untitled".to_string());
|
||||
format!("HCIE v4 — {doc}")
|
||||
}
|
||||
```
|
||||
|
||||
### Beklenen
|
||||
- Title ekranın ortasında, kırpılabilir. Menüler + arama + toggle solda. Pencere butonları sağda.
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Panel Minimum Genişlik 200px
|
||||
|
||||
### Problem
|
||||
grimdock `PanelStyle.min_pane_size = 60.0` (style.rs:158 ve :270). Bu sadece **resize drag** sırasında clamp yapar (layout.rs:123-129). Layout sırasındaki `split_rect` (layout.rs:165-211) ratio'ya göre böler ve min boyutu zorlamaz → panel 200px'in altına düşebilir.
|
||||
|
||||
### Çözüm
|
||||
|
||||
**Dosya: `patches/grimdock/src/style.rs`** (satır 158 ve :270)
|
||||
- `min_pane_size: 60.0,` → `min_pane_size: 200.0,`
|
||||
|
||||
**Dosya: `patches/grimdock/src/layout.rs`** — `split_rect` fonksiyonunu min-boyut zorlaması yap:
|
||||
- `first_extent` hesabında (satır 181-187) ratio'ya göre bölmeden önce, her iki tarafın da `min_pane_size` kadar yer aldığından emin ol:
|
||||
```rust
|
||||
// Mevcut:
|
||||
// } else {
|
||||
// total_extent * ratio,
|
||||
// };
|
||||
//
|
||||
// Yeni: ratio'ya göre böl ama min_pane_size altına düşme.
|
||||
} else {
|
||||
let min = style.min_pane_size.min((total_extent - handle_w) * 0.5);
|
||||
(total_extent * ratio).clamp(min, (total_extent - handle_w - min).max(min))
|
||||
};
|
||||
```
|
||||
- `split_rect`'e `style: &PanelStyle` parametresi ekle (şu an parametre yok). Çağrı yeri `layout_node` (satır 136-140) `style`'a sahip → geçir.
|
||||
- Bu hem drag hem de ilk layout / collapse geri açma sırasında min 200px'i garantiler.
|
||||
|
||||
### Not
|
||||
- `min_pane_size` araç çubuğu (Tools) için 200px çok büyük olabilir. Tools pane `allow_resize: false` + sabit `sync_tools_width` ile 36px'dir; min_pane_size sadece **resize edilebilir** split'leri etkiler mi? — Hayır, `split_rect` tüm split'leri etkiler. Bu yüzden Tools kolonu (node 0, ratio 0.035 → ~36px) min 200'ye takılır ve 200px'e genişler. **Bu istenmeyen bir yan etki.**
|
||||
- Çözüm: Tools split'ini (node 0) min-boyut zorlamasından muaf tut. `layout_node` içinde `split_rect` çağrısında, eğer split `allow_resize: false` veya her iki taraf da `lock_layout: true` ise min clamp'i atla. Pratik yöntem: `PanelStyle`'a `enforce_min_pane_size: bool` ekle ve `sync_tools_width`/Tools split'lerinde bunu pas geçen bir flag. **Daha basit:** min clamp'i sadece `!first_collapsed && !second_collapsed` VE handle drag edilebilir olduğunda uygula (zaten drag clamp'inde `resize_locked` kontrolü var — layout'ta da aynı kontrolü yap).
|
||||
- `layout_node` (satır 67) `tree.split_resize_locked(idx)` ile `resize_locked`'ı biliyor. Tools split'i `allow_resize: false` → `split_resize_locked` muhtemelen `true` döner. Min clamp'i sadece `!resize_locked` olduğunda uygula.
|
||||
- `split_rect`'e `enforce_min: bool` parametresi ekle; çağıran `!resize_locked` geçirir.
|
||||
|
||||
**Son durum (split_rect):**
|
||||
```rust
|
||||
fn split_rect(rect, dir, ratio, handle_w, first_collapsed, second_collapsed,
|
||||
first_ct, second_ct, min_pane_size: f32, enforce_min: bool) -> (...) {
|
||||
let total_extent = ...;
|
||||
let first_extent = if first_collapsed && !second_collapsed {
|
||||
first_ct.min((total_extent - handle_w).max(0.0))
|
||||
} else if second_collapsed && !first_collapsed {
|
||||
(total_extent - handle_w - second_ct).max(0.0)
|
||||
} else {
|
||||
let raw = total_extent * ratio;
|
||||
if enforce_min {
|
||||
let min = min_pane_size.min((total_extent - handle_w) * 0.5);
|
||||
raw.clamp(min, (total_extent - handle_w - min).max(min))
|
||||
} else {
|
||||
raw
|
||||
}
|
||||
};
|
||||
...
|
||||
}
|
||||
```
|
||||
Çağrı (satır 136):
|
||||
```rust
|
||||
let (first_rect, _, second_rect) = split_rect(
|
||||
rect, dir, ratio, handle_w,
|
||||
first_collapsed, second_collapsed,
|
||||
first_ct, second_ct,
|
||||
style.min_pane_size, !resize_locked,
|
||||
);
|
||||
```
|
||||
|
||||
### Beklenen
|
||||
- Resize edilebilen paneller 200px'in altına düşmez. Tools kolonu (resize locked) 36px'inde kalır.
|
||||
|
||||
---
|
||||
|
||||
## Dosya Değişiklik Özeti
|
||||
|
||||
| Dosya | Aksiyon |
|
||||
|------|--------|
|
||||
| `hcie-egui-app/crates/hcie-gui-egui/src/app/panels.rs` | `bar_h=25`, `draw_window_controls` imza+btn boyutları, `show_menu_bar` üç sütunlu yeniden yazım (title ortada), `build_title` helper |
|
||||
| `patches/grimdock/src/style.rs` | `min_pane_size: 200.0` (satır 158, 270) |
|
||||
| `patches/grimdock/src/layout.rs` | `split_rect` imzasına `min_pane_size` + `enforce_min` ekle, min clamp uygula; çağrıyı güncelle |
|
||||
|
||||
> Not: `patches/grimdock/src/*.rs` normalde locked motor katmanı DEĞİL — grimdock `patches/` altında, GUI workspace'inin parçası. AGENTS.md locked listesinde `patches/grimdock` yok. Yazılabilir.
|
||||
|
||||
---
|
||||
|
||||
## Doğrulama
|
||||
|
||||
1. `cargo build -p hcie-gui-egui`
|
||||
2. Screenshot (`--screenshot-panel` veya F12):
|
||||
- Çubuk 25px, tek satır.
|
||||
- Title EKRAN ORTASINDA, kırpılabilir.
|
||||
- Menü + arama + panel-toggle solda; app icon grip'in yanında.
|
||||
- Sağdaki min/max/close butonları çubuk içinde, taşma yok.
|
||||
3. Bir paneli daraltmaya çalış → 200px'den daha daralamaz.
|
||||
4. Tools kolonu hâlâ ~36px (resize locked → min clamp bypass).
|
||||
5. Pencereyi grip'ten taşı + çift tık ile max/restore çalışır.
|
||||
6. Arama + panel toggle fonksiyonel.
|
||||
@@ -0,0 +1,92 @@
|
||||
# Inner Shadow Ground Truth — PNG Pipeline (no PSD needed)
|
||||
|
||||
## Problem
|
||||
|
||||
The Rust PSD writer (`hcie-psd/src/psd_saver.rs`) produces files that pass `Psd::from_bytes` internally but cannot be opened in Photoshop. We've spent multiple rounds fixing the PSD format (layer extra data, lfx2 padding, merged image layout, 8BIM blocks) without success.
|
||||
|
||||
**Root cause**: The PSD binary format is extremely complex (200+ page spec). Getting every field exactly right for Photoshop's parser is a long, error-prone process.
|
||||
|
||||
## Solution: Skip PSD entirely
|
||||
|
||||
The Photopea JSX script at `_tools/photopea/generate_inner_shadow_dataset.jsx` already creates ground truth PNGs **directly** — no PSD intermediate needed. Photopea/Photoshop is the renderer; it applies inner shadow via its own engine and exports transparent PNGs.
|
||||
|
||||
This is the simplest, most reliable path to ground truth.
|
||||
|
||||
## Plan
|
||||
|
||||
### Step 1: Generate Ground Truth PNGs via Photopea
|
||||
|
||||
The user opens Photopea (https://www.photopea.com) in a browser and runs the existing JSX script. It will:
|
||||
|
||||
1. Create 256×256 transparent documents
|
||||
2. Add a layer with a black centered rectangle (50% margin)
|
||||
3. Apply Inner Shadow via ActionDescriptor (Photoshop's native effect engine)
|
||||
4. Save transparent PNG
|
||||
|
||||
**Parameter grid** (192 images):
|
||||
| Param | Values |
|
||||
|---|---|
|
||||
| angle | 0, 90, 180, 270 |
|
||||
| distance | 5, 15, 30 |
|
||||
| size (blur) | 10, 30 |
|
||||
| choke | 0, 20 |
|
||||
| fx opacity | 75, 100 |
|
||||
| fx blend mode | Normal, Multiply |
|
||||
|
||||
Output: `_tmp/is_ground_truth/` directory with PNGs like `is_a0_d5_s10_c0_op75_bmnormal.png`.
|
||||
|
||||
### Step 2: Write Rust MAE Tuning Example Against PNGs
|
||||
|
||||
Create `hcie-io/examples/tune_mae_inner_shadow_grid.rs` that:
|
||||
|
||||
1. **Loads ground truth PNGs** from `_tmp/is_ground_truth/`
|
||||
2. **Parses the filename** to extract parameters (angle, distance, size, choke, opacity, blend mode)
|
||||
3. **Recreates the same shape** (256×256, black rectangle at 25% margin) in Rust
|
||||
4. **Calls `generate_inner_shadow_tuned()`** with the same parameters
|
||||
5. **Blends** the shadow with the shape using the specified blend mode and opacity
|
||||
6. **Computes MAE** against the ground truth PNG
|
||||
7. **Grid-searches** blur_factor, passes, choke_scale, mask_strength across the full dataset
|
||||
8. **Reports** best global parameters and per-combo breakdown
|
||||
|
||||
Key differences from the existing `tune_mae_inner_shadow.rs`:
|
||||
- Works against **multiple parameter combos** (192 PNGs), not a single PSD
|
||||
- Loads **PNG files directly** (no PSD import needed)
|
||||
- Creates the shape geometry **from scratch** (no PSD layer needed)
|
||||
- Reports **per-combo MAE** for diagnostics
|
||||
|
||||
### Step 3: Run and Report
|
||||
|
||||
```bash
|
||||
cargo run --package hcie-io --example tune_mae_inner_shadow_grid
|
||||
```
|
||||
|
||||
Expected output: best blur_factor, passes, choke_scale, mask_strength with average MAE across all 192 combos.
|
||||
|
||||
### Step 4: Apply Best Params to `tuned.rs`
|
||||
|
||||
Update `hcie-fx/src/tuned.rs` `generate_inner_shadow_tuned()` if new best params differ from current (blur_factor=1.45, passes=3, choke_scale=0.0, mask_strength=1.0).
|
||||
|
||||
## Files to Create/Modify
|
||||
|
||||
| File | Action |
|
||||
|---|---|
|
||||
| `hcie-io/examples/tune_mae_inner_shadow_grid.rs` | **Create** — grid search against PNG ground truth |
|
||||
| `hcie-fx/src/tuned.rs` | **Modify** (if params change) |
|
||||
|
||||
## Files NOT Modified
|
||||
|
||||
- `hcie-psd/src/psd_saver.rs` — no more PSD fixes needed
|
||||
- `_tools/photopea/generate_inner_shadow_dataset.jsx` — already works as-is
|
||||
|
||||
## User Action Required
|
||||
|
||||
1. Open https://www.photopea.com in a browser
|
||||
2. Press `Alt+Ctrl+I` (or File → Automate → Script)
|
||||
3. Paste contents of `_tools/photopea/generate_inner_shadow_dataset.jsx`
|
||||
4. Select output folder: create/choose `_tmp/is_ground_truth/`
|
||||
5. Wait for ~192 images to generate
|
||||
6. Tell me when done — I'll run the Rust tuning example
|
||||
|
||||
## Verification
|
||||
|
||||
After tuning, generate a test inner shadow with our engine and visually compare against one of the Photopea PNGs side-by-side.
|
||||
@@ -0,0 +1,170 @@
|
||||
# KRA Optimize Plan — Krita .kra İthalat ve İhracat Optimizasyonu
|
||||
|
||||
## Hedef
|
||||
|
||||
Krita tarafından yazılmış/beklenen `.kra` dosyaları için hem **ihracat (export)** hem de **ithalat (import)** performansını, boyutunu ve açılış doğruluğunu optimize etmek. Ayrıca Krita’da açılan PSD-benzeri layer stillerinin (drop shadow, inner shadow, outer glow, inner glow, bevel/emboss, stroke, color/gradient/pattern overlay, satin) HCIE içinde Photoshop referansına yakın görünmesini sağlamak.
|
||||
|
||||
## Kapsam
|
||||
|
||||
- `hcie-kra/src/lib.rs` — KRA ithalatı (maindoc.xml + VERS tile decoder + layerstyles.asl)
|
||||
- `hcie-kra/src/kra_saver.rs` — KRA ihracatı (maindoc.xml + VERS tile encoder + SVG shapelayer)
|
||||
- `hcie-io/examples/` — referans KRA seti üreten/tune eden yeni örnekler
|
||||
- Gerekirse `hcie-fx` ASL parser’ı genişletmeleri (ama `hcie-fx` locked crate; sadece gerekirse unlock)
|
||||
- Gerekirse `hcie-protocol::effects::LayerEffect` renk/birim haritalamaları için eşleme güncellemeleri (locked crate; sadece gerekirse unlock)
|
||||
|
||||
## Bugünkü Durum
|
||||
|
||||
`hcie-kra` çift yönlü çalışıyor fakat birkaç zayıf noktası var:
|
||||
|
||||
### İthalat Zafiyetleri
|
||||
|
||||
1. **Tile decoder sınırlı**:
|
||||
- Sadece `LZF` ve raw tile compression destekliyor; Krita’nın bazı yeni varyantları (`DATA`, `RAW`, `RLE`, deflate, zlib, store) desteklenmiyor.
|
||||
- `parse_vers_tiles` her satırı `from_utf8_lossy` ile tarıyor; bu yavaş ve binary-tolerant değil.
|
||||
- Header parsing header string üzerinden yapılıyor; DATA offset hesabı hataya açık.
|
||||
2. **Layer file discovery kırılgan**:
|
||||
- `find_layer_file` çok fazla aday suffix deniyor; `filename` bazen XML’de tam yol (`layers/xxx.png`) veriliyor, bazen de göreli.
|
||||
- VERS tile’ın `.defaultpixel` dosyası olmayınca tile tamamen 0 doluyor; default pixel hesabı hatalı.
|
||||
3. **ASL layerstyle parse**:
|
||||
- `hcie_fx::parse_asl_styles` KRA’daki `layerstyles.asl` üzerinden dönüyor.
|
||||
- Renk değerleri, teknik (technique), soften, v.b. protokole aktarılıyor; bazı enum mapping’ler `format!("{:?}", ...)` ile string yapılıyor ve `hcie-engine-api` içinde tekrar parse ediliyor, bu hata kaynağı.
|
||||
4. **Bellek**:
|
||||
- Her layer tam canvas boyutunda `Vec<u8>` oluşturuyor (canvas_w * canvas_h * 4). Büyük dosyalarda çok pahalı.
|
||||
5. **Shape/Text layer**:
|
||||
- `vectorlayer`/`shapelayer` farkı parse edilmiyor; Krita’nın SVG layer’ları sadece `LayerType::Vector` olarak alınıyor, Krita’ya export edilirken `content.svg` yazılıyor fakat import edilirken okunmuyor.
|
||||
|
||||
### İhracat Zafiyetleri
|
||||
|
||||
1. **Tile format**:
|
||||
- Her tile 64×64, sadece LZF v1 (literaller 32 bayt) kullanılıyor; Krita 5/6 bazı durumlarda farklı tile boyutu, farklı sıkıştırma bekleyebilir.
|
||||
- Tile’lar `has_content` kontrolüyle atılıyor; default pixel dolu olmayan transparent layer’lar Krita’da eksik görünebilir.
|
||||
2. **Layer attributes**:
|
||||
- `x="0" y="0"` sabit; orijinal layer offsetleri kayboluyor.
|
||||
- `parent_id` / grouplayer hiyerarşisi KRA’ya yazılmıyor.
|
||||
- `fill_opacity`, `clipping_mask`, `locked`, `collapsed` vb. XML’e aktarılmıyor.
|
||||
3. **Layer styles export**:
|
||||
- `hcie-kra` layer styles ihracatı yapmıyor (`layerstyles.asl` üretilmiyor).
|
||||
- Krita’da açıldığında HCIE’de ayarlanan drop shadow / bevel emboss gibi efektler kaybolur.
|
||||
4. **Sıkıştırma**:
|
||||
- Tüm ZIP içeriği `Stored` (sıkıştırılmamış). KRA dosyaları gereksiz yere büyük.
|
||||
|
||||
### Genel Doğruluk
|
||||
|
||||
- KRA ↔ HCIE ↔ PSD üçgeninde round-trip test yok.
|
||||
- `hcie-fx` MAE tune scriptleri sadece PSD üzerinden çalışıyor; KRA referansları yok.
|
||||
|
||||
## Yapılacaklar
|
||||
|
||||
### Aşama 1: İthalat Optimizasyonu ve Robustluk
|
||||
|
||||
**Hedef:** Daha büyük, daha çeşitli KRA dosyalarını hızlı ve doğru açmak.
|
||||
|
||||
1. **Tile decoder yeniden yazımı**
|
||||
- `parse_vers_tiles` binary-safe hale getir: header satırlarını `
|
||||
` üzerinden ayır ama ardından `DATA` sonrası binary blokları doğrudan oku.
|
||||
- `TILEWIDTH`/`TILEHEIGHT`/ `PIXELSIZE` / `DATA` parse mantığını ayrı bir `VersHeader` struct’ına çek.
|
||||
- Hatalı tile’ları atlayıp geri kalanını decode et (graceful degradation).
|
||||
2. **Compression desteği genişletme**
|
||||
- `LZF` zaten var; `RAW` (literal copy) ve `RLE`-benzeri varyantları ekle.
|
||||
- Zlib/deflate tile payload’larına destek ekle (Krita 5 bazen kullanıyor).
|
||||
3. **Default pixel mantığı**
|
||||
- Eğer layer’ın `.defaultpixel` dosyası yoksa ve hiç tile yoksa, layer’ı tamamen transparent kabul et; canvas boyutunda 0 buffer yerine lazy olarak bir solid renk maskesi üret.
|
||||
4. **Layer file discovery hızlandırma**
|
||||
- `find_layer_file` yerine önce XML’deki `filename`’ı olduğu gibi dene, sonra normalize et.
|
||||
- `archive.file_names()` HashSet’e çevir; `O(1)` lookup.
|
||||
5. **Memory optimization**
|
||||
- `Layer::from_rgba` canvas boyutunda allocate ediyor; KRA importunda bu gerekli ama sparse tile layer’lar için tile-based `LayerData` kullanmak daha iyi. Protokole dokunmadan `hcie-kra` içinde tile cache’den doğrudan `hcie-tile` formatına dönüştürme seçeneği ekle (opsiyonel).
|
||||
6. **Shape/Text import**
|
||||
- `shapelayer` nodetype için SVG parse desteği ekle: `content.svg` dosyasını oku, `VectorShape` listesine çevir (basit `<path>`, `<rect>`, `<ellipse>`, `<text>` tagleri).
|
||||
- `textlayer`/`adjustmentlayer` için uygun placeholder behavior belirle (şimdilik rasterize veya skip).
|
||||
|
||||
### Aşama 2: İhracat Optimizasyonu ve Boyut
|
||||
|
||||
**Hedef:** Daha küçük, Krita uyumlu KRA dosyaları üretmek.
|
||||
|
||||
1. **ZIP compression**
|
||||
- `zip::CompressionMethod::Deflated` kullan; en hızlı/iyi oran için compression level 6 (Krita default).
|
||||
2. **Tile sıkıştırma iyileştirmesi**
|
||||
- LZF encoder’ı daha büyük literallerle (64 bayt+) çalışacak şekilde güncelle; tekrarlayan satırlar için back-ref kullan.
|
||||
3. **Layer offset ve hiyerarşi**
|
||||
- `x`, `y` değerlerini layer boundary’den hesapla ve XML’e yaz.
|
||||
- `parent_id`’ye göre `grouplayer` ve iç içe layer yapısını `maindoc.xml`’e yaz.
|
||||
4. **Layer styles export (ASL üretimi)**
|
||||
- `hcie-fx` ASL writer yok. Eğer KRA’daki efektleri Krita’da görmek istiyorsak, `hcie-kra` içinde basit bir ASL binary writer implemente et veya `hcie-fx`’e yeni writer ekle.
|
||||
- Öncelik: Drop Shadow, Inner Shadow, Outer Glow, Inner Glow, Stroke, Color Overlay, BevelEmboss.
|
||||
- Her effect için UUID üret, `<layer layerstyle="{uuid}" ... />` yaz, `layerstyles.asl` olarak ZIP’e ekle.
|
||||
5. **Default pixel**
|
||||
- Tamamen tek renk (solid) layer’lar için tile yerine sadece `.defaultpixel` yaz; Krita bunu destekliyor.
|
||||
6. **Merged image / preview**
|
||||
- `mergedimage.png` ve `preview.png` zaten yazılıyor; preview 256×256 thumbnail olarak scale edilebilir.
|
||||
|
||||
### Aşama 3: Krita Efekt Doğruluğu (MAE Döngüsü)
|
||||
|
||||
**Hedef:** KRA formatı üzerinden Krita’ya yazılan HCIE efektleri, Krita tarafından render edilip tekrar açıldığında Photoshop referansına yakın olsun. Bu aynı zamanda HCIE’nin kendi renderer’ını da doğrulayan bir çapraz test olacak.
|
||||
|
||||
1. **Referans KRA seti üretimi**
|
||||
- `hcie-io/examples/generate_drop_shadow_kra_set.rs` gibi örnekler yaz.
|
||||
- Aynı parametreleri (açı, mesafe, size, spread, renk, blend) hem PSD hem KRA olarak üret; sonra Krita ve Photoshop’ta açıp transparent PNG export al.
|
||||
2. **Ground-truth PNG pipeline**
|
||||
- Krita batch render için `krita --export` CLI kullanılabilir (sistemde Krita yüklüyse).
|
||||
- Eğer Krita CLI yoksa, manuel açma/kaydetme talimatları + dosya bekleyen bir `tune_mae_*_kra_grid.rs` scripti yaz.
|
||||
3. **MAE tune scriptleri**
|
||||
- `hcie-io/examples/tune_mae_drop_shadow_kra_grid.rs`
|
||||
- `hcie-io/examples/tune_mae_inner_shadow_kra_grid.rs`
|
||||
- `hcie-io/examples/tune_mae_outer_glow_kra_grid.rs`
|
||||
- `hcie-io/examples/tune_mae_inner_glow_kra_grid.rs`
|
||||
- `hcie-io/examples/tune_mae_stroke_kra_grid.rs`
|
||||
- `hcie-io/examples/tune_mae_bevel_emboss_kra_grid.rs`
|
||||
- Bunlar PSD tune scriptlerinin KRA varyantları olacak; `hcie-kra::export_kra` + `hcie-kra::import_kra` + `hcie-engine-api` composite kullanarak HCIE render’ını Krita referansıyla karşılaştıracak.
|
||||
4. **Teknik haritalama**
|
||||
- Krita’nın ASL/Photoshop efekt parametrelerini HCIE `LayerEffect` değerlerine doğru çevir:
|
||||
- `distance` / `size` / `spread` / `choke` aynı.
|
||||
- `angle` aynı.
|
||||
- `opacity` 0..1 ↔ Krita 0..255.
|
||||
- Renkler RGBA8, doğrudan passthrough.
|
||||
- Blend mode isimleri Krita’nın `compositeop` namespace’inden alınacak.
|
||||
- Bevel/Emboss için `style`, `technique`, `direction` enum mapping’lerini netleştir.
|
||||
|
||||
### Aşama 4: Test ve Validasyon
|
||||
|
||||
1. **Round-trip testleri**
|
||||
- `hcie-kra/tests/roundtrip.rs`: rastgele layer’ları KRA’ya yaz, tekrar oku, piksel / stil / blend / opacity / visibility eşitliğini kontrol et.
|
||||
- Büyük boyutlu (4K, çok katmanlı) test ekle.
|
||||
2. **Krita açılış testi**
|
||||
- Eğer sistemde Krita varsa: `examples/verify_krita_opens.rs` ile üretilen KRA’yı Krita’da açma komutunu çalıştır ve `mergedimage.png` karşılaştır.
|
||||
3. **Regresyon testleri**
|
||||
- `cargo test -p hcie-kra`
|
||||
- `cargo test -p hcie-engine-api --test visual_regression`
|
||||
- `cargo test -p hcie-engine-api --test performance_stroke_4k -- --nocapture`
|
||||
|
||||
## Kilitleme / Unlock Notları
|
||||
|
||||
- `hcie-kra` şu an locked crate değil (Cargo.toml root workspace’te listeli; `AGENTS.md`’de `hcie-kra/src/*.rs` locked listesinde yok). Ancak `hcie-fx/src/parser.rs`, `hcie-protocol/src/*.rs`, `hcie-engine-api/src/*.rs` locked. Aşağıdaki durumlarda unlock gerekebilir:
|
||||
- `hcie-fx` ASL writer eklenirse: `unlock.sh hcie-fx`
|
||||
- Yeni `LayerEffect` varyant veya alan gerekirse: `unlock.sh hcie-protocol`
|
||||
- Engine API’de yeni bir import/export helper gerekirse: `unlock.sh hcie-engine-api`
|
||||
|
||||
## Riskler
|
||||
|
||||
- Krita’nın ASL formatı Photoshop’unkindan hafifçe farklı; writer yazarken Krita’nın beklediği farklılıklar (örneğin ekstra Unicode name prefix) gözden kaçabilir.
|
||||
- ZIP deflated kullanmak build zamanını hafifçe artırır ama dosya boyutunu ciddi azaltır.
|
||||
- Tile-based layer data geçişi protokol değişikliği gerektirebilir; bu planın ilk fazında yapılmayacak.
|
||||
|
||||
## Çıktılar (Deliverables)
|
||||
|
||||
- Optimize edilmiş `hcie-kra/src/lib.rs` ve `hcie-kra/src/kra_saver.rs`.
|
||||
- Yeni `hcie-io/examples/generate_*_kra_set.rs` dosyaları.
|
||||
- Yeni `hcie-io/examples/tune_mae_*_kra_grid.rs` dosyaları (örnek olarak 2-3 efekt, sonra gerisi kopyalanabilir).
|
||||
- `hcie-kra/tests/roundtrip.rs` round-trip testleri.
|
||||
- Güncellenmiş `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` KRA aç/kaydet yollarında hata raporlama iyileştirmeleri (opsiyonel).
|
||||
- Performans/boyut ölçüm raporu (örnek: 4K 10 layer dosyanın eski/yeni boyutu ve açma süresi).
|
||||
|
||||
## Öncelik Sırası
|
||||
|
||||
1. ZIP deflate + tile encoder iyileştirmesi (hızlı kazanım, dosya boyutu düşer).
|
||||
2. Tile decoder robustluk ve hız (import hatası azalır).
|
||||
3. Layer offset / grouplayer / basic attributes (doğruluk artar).
|
||||
4. Layer styles export ASL writer (Krita’da efektler görünür).
|
||||
5. Referans KRA setleri + MAE tune scriptleri (doğruluk ölçülebilir hale gelir).
|
||||
6. Shape/Text import desteği.
|
||||
7. Tile-based bellek optimizasyonu (gelecek aşama).
|
||||
Executable
+238
@@ -0,0 +1,238 @@
|
||||
# Photopea-Style Vertical Toolbox Implementation Plan
|
||||
|
||||
## Goal
|
||||
|
||||
Transform the current grid-based toolbox (`toolbox.rs`) into a Photopea-style **vertical single-column tool strip** where:
|
||||
- Each tool group is represented by **one button** showing the last-used tool's icon
|
||||
- A small **triangle indicator** (▼) in the bottom-right corner signals sub-tools exist
|
||||
- **Long-press** (hold down) on a button opens a **horizontal popup** showing all tools in that group
|
||||
- Quick click activates the last-used tool from that group
|
||||
- The strip sits as a thin vertical bar on the left edge of the workspace
|
||||
|
||||
## Reference: Photopea Toolbox Behavior
|
||||
|
||||
From the provided screenshot:
|
||||
1. Tools are arranged in a **single vertical column** (~36px wide)
|
||||
2. Each button shows **one icon** (the last-used from its group)
|
||||
3. Small **black triangle** (▼) in bottom-right = "this tool has sub-tools"
|
||||
4. **Long press / hold** = popup appears horizontally to the right, showing all tools in the group
|
||||
5. **Click** = instantly activate the last-used tool from that group
|
||||
6. Visual: selected tool button has a highlighted background
|
||||
|
||||
## Files to Modify
|
||||
|
||||
### 1. `hcie-egui-app/crates/hcie-gui-egui/src/app/toolbox.rs` — **Primary rewrite**
|
||||
|
||||
Replace the entire file with the new Photopea-style implementation.
|
||||
|
||||
#### New Data Structure: `ToolSlot`
|
||||
|
||||
```rust
|
||||
/// A single button in the vertical toolbox strip.
|
||||
/// Represents a GROUP of related tools (like Photopea's grouped tools).
|
||||
struct ToolSlot {
|
||||
/// The tools in this group. First tool = default.
|
||||
tools: &'static [Tool],
|
||||
/// Index within `tools` of the last-selected tool (persisted in ToolState).
|
||||
/// Displayed as the button's icon.
|
||||
}
|
||||
```
|
||||
|
||||
#### Slot Grouping (matching Photopea's layout)
|
||||
|
||||
```rust
|
||||
const TOOL_SLOTS: &[&[Tool]] = &[
|
||||
&[Tool::Move], // Move
|
||||
&[Tool::Select, Tool::MagicWand, Tool::Lasso, Tool::PolygonSelect, Tool::SmartSelect], // Selection
|
||||
&[Tool::Eyedropper], // Eyedropper
|
||||
&[Tool::Crop], // Crop
|
||||
&[Tool::Brush, Tool::Pen, Tool::Eraser, Tool::Spray], // Painting
|
||||
&[Tool::FloodFill, Tool::Gradient], // Fill
|
||||
&[Tool::Text], // Text
|
||||
&[Tool::VectorLine, Tool::VectorRect, Tool::VectorCircle,
|
||||
Tool::VectorArrow, Tool::VectorStar, Tool::VectorPolygon,
|
||||
Tool::VectorRhombus, Tool::VectorCylinder, Tool::VectorHeart,
|
||||
Tool::VectorBubble, Tool::VectorGear, Tool::VectorCross,
|
||||
Tool::VectorCrescent, Tool::VectorBolt, Tool::VectorArrow4,
|
||||
Tool::VectorSelect], // Vector shapes
|
||||
&[Tool::RedEyeRemoval, Tool::SpotRemoval, Tool::SmartPatch, Tool::AiObjectRemoval], // Retouch
|
||||
&[Tool::VisionSelect], // Vision/AI
|
||||
];
|
||||
```
|
||||
|
||||
#### State Addition: `active_tool_slot` / `slot_last_used`
|
||||
|
||||
Add to `ToolState`:
|
||||
```rust
|
||||
/// Index into TOOL_SLOTS for the currently active tool.
|
||||
pub active_slot: usize,
|
||||
/// For each slot, which tool index was last selected (index within TOOL_SLOTS[slot]).
|
||||
pub slot_last_used: Vec<usize>,
|
||||
```
|
||||
|
||||
#### Rendering: `show_tool_panel_ui()` — New Implementation
|
||||
|
||||
```
|
||||
┌────────────┐
|
||||
│ ┌──────┐ │ ← Move icon (last used: Move)
|
||||
│ │ ◆ │ │
|
||||
│ └──────┘ │
|
||||
│ ┌──────┐ │ ← Selection icon (last used: e.g. Rect Select)
|
||||
│ │ ◆ ▼│ │ ← triangle = has sub-tools
|
||||
│ └──────┘ │
|
||||
│ ┌──────┐ │ ← Eyedropper
|
||||
│ │ ◆ │ │
|
||||
│ └──────┘ │
|
||||
│ ... │
|
||||
└────────────┘
|
||||
```
|
||||
|
||||
Each button:
|
||||
- **Size**: 34×34 px (tight, single column)
|
||||
- **Icon**: SVG of the last-used tool in the group, tinted per theme
|
||||
- **Triangle**: Small filled triangle in bottom-right corner if `tools.len() > 1`
|
||||
- **Background**: Transparent → hover highlight → active/accent when selected
|
||||
- **Click handler**: Sets `active_tool = last_used_tool` and emits `ToolChanged`
|
||||
- **Long-press handler**: Opens `ToolSlotPopup` at button position
|
||||
|
||||
#### Long-Press Detection
|
||||
|
||||
egui doesn't have native long-press. Implement via:
|
||||
```rust
|
||||
// In each button's response:
|
||||
if resp.is_pointer_button_down() && !resp.clicked() {
|
||||
// Track press start time in state
|
||||
if press_duration > 400ms {
|
||||
// Open popup
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Store `slot_press_start: Option<(usize, Instant)>` in `ToolState`.
|
||||
|
||||
#### Popup: `show_slot_popup()`
|
||||
|
||||
A floating `egui::Area` panel that appears to the right of the button:
|
||||
```
|
||||
┌──────────────────┐
|
||||
│ ◆ Move │
|
||||
│ ◆ Rect Select │ ← highlighted = currently active in group
|
||||
│ ◆ Magic Wand │
|
||||
│ ◆ Lasso │
|
||||
│ ◆ Polygon Sel. │
|
||||
│ ◆ Smart Select │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
- Each row = one tool from the group
|
||||
- Shows SVG icon + label text
|
||||
- Active tool in group has accent background
|
||||
- Click on row → set that tool as active, update `slot_last_used[slot]`, close popup
|
||||
- Popup closes on: click outside, press Escape, release mouse
|
||||
|
||||
#### State Tracking for Popup
|
||||
|
||||
```rust
|
||||
pub active_popup_slot: Option<usize>, // which slot's popup is open
|
||||
pub popup_close_request: bool, // request to close popup this frame
|
||||
```
|
||||
|
||||
### 2. `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` — **State additions**
|
||||
|
||||
Add to `ToolState`:
|
||||
```rust
|
||||
pub active_slot: usize,
|
||||
pub slot_last_used: Vec<usize>,
|
||||
pub active_popup_slot: Option<usize>,
|
||||
pub popup_close_request: bool,
|
||||
pub slot_press_start: Option<(usize, std::time::Instant)>,
|
||||
```
|
||||
|
||||
Update `Default for ToolState` to initialize `slot_last_used` with defaults (index 0 for each slot).
|
||||
|
||||
Add a helper method:
|
||||
```rust
|
||||
impl ToolState {
|
||||
/// Given a Tool, find which slot it belongs to.
|
||||
pub fn slot_for_tool(tool: Tool) -> Option<usize> {
|
||||
TOOL_SLOTS.iter().position(|slot| slot.contains(&tool))
|
||||
}
|
||||
|
||||
/// Set active tool and update slot tracking.
|
||||
pub fn set_active_tool_from_slot(&mut self, tool: Tool) {
|
||||
if let Some(slot_idx) = Self::slot_for_tool(tool) {
|
||||
self.active_slot = slot_idx;
|
||||
if let Some(local_idx) = TOOL_SLOTS[slot_idx].iter().position(|&t| t == tool) {
|
||||
self.slot_last_used[slot_idx] = local_idx;
|
||||
}
|
||||
}
|
||||
self.active_tool = tool;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. `hcie-egui-app/crates/hcie-gui-egui/src/app/panels.rs` — **Status bar update**
|
||||
|
||||
Update the status bar tool indicator to show the active slot's tool name.
|
||||
|
||||
### 4. `hcie-egui-app/crates/hcie-gui-egui/src/app/widgets.rs` — **New `ToolSlotButton` widget**
|
||||
|
||||
Add a new widget:
|
||||
```rust
|
||||
pub struct ToolSlotButton<'a> {
|
||||
icon_source: egui::ImageSource<'a>,
|
||||
is_active: bool,
|
||||
has_submenu: bool,
|
||||
theme: ThemePreset,
|
||||
}
|
||||
```
|
||||
|
||||
Renders:
|
||||
- 34×34 rounded rect background
|
||||
- Centered SVG icon (18×18)
|
||||
- Small triangle indicator in bottom-right if `has_submenu`
|
||||
|
||||
### 5. `hcie-egui-app/crates/hcie-gui-egui/src/app/menus.rs` — **Keyboard shortcut hints**
|
||||
|
||||
The popup menu items should show keyboard shortcut hints (e.g., "B" for Brush).
|
||||
|
||||
## Workflow Summary
|
||||
|
||||
1. **Initialization**: `ToolState::default()` creates `slot_last_used` with all zeros.
|
||||
2. **User clicks a slot button**: `set_active_tool_from_slot(TOOL_SLOTS[slot][last_used])` is called.
|
||||
3. **User long-presses a slot button**: After 400ms, `active_popup_slot = Some(slot_idx)` triggers popup rendering.
|
||||
4. **Popup shows**: All tools in the slot with icons + labels. User clicks one.
|
||||
5. **Popup selection**: `set_active_tool_from_slot(selected_tool)` updates both `active_tool` and `slot_last_used[slot]`.
|
||||
6. **Active slot visual**: The active slot's button gets accent-colored background.
|
||||
7. **ToolChanged event**: Pushed to `event_bus` for canvas/panel updates.
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|----------|--------|-----------|
|
||||
| Long-press threshold | 400ms | Standard UX for pro tools |
|
||||
| Button size | 34×34 | Tight enough for single-column, readable icons |
|
||||
| Icon size | 18×18 | Good visibility within 34px button |
|
||||
| Triangle indicator | 6×6 filled triangle | Subtle but clear signal |
|
||||
| Popup position | Right of button, vertically centered | Matches Photopea behavior |
|
||||
| Vector tools group | All shapes + vector select in one slot | Keeps strip compact; popup has scrollable list |
|
||||
| Slot order | Move → Select → Eyedropper → Crop → Brush → Fill → Text → Vector → Retouch → AI | Priority-based: most-used at top |
|
||||
|
||||
## MAE Approach Note
|
||||
|
||||
The user mentioned PSD image MAE approach. For toolbox icons, we already have SVGs in `assets/icons/`. No MAE-based approach needed — standard SVG rendering via `egui::Image` with tinting is sufficient. The MAE approach is relevant for canvas rendering, not toolbar icons.
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Each tool slot shows correct icon for last-used tool
|
||||
- [ ] Triangle indicator visible only for multi-tool slots
|
||||
- [ ] Quick click activates last-used tool from group
|
||||
- [ ] Long-press opens popup after 400ms
|
||||
- [ ] Popup shows all tools with correct icons and labels
|
||||
- [ ] Clicking tool in popup activates it and closes popup
|
||||
- [ ] Active slot has accent background highlight
|
||||
- [ ] ToolChanged event fires correctly
|
||||
- [ ] Keyboard shortcuts still work
|
||||
- [ ] Theme (light/dark) renders correctly
|
||||
- [ ] Dock panel width adjusts properly (narrower than before)
|
||||
- [ ] Canvas still receives tool events properly
|
||||
Executable
+101
@@ -0,0 +1,101 @@
|
||||
# Photopea-Style Toolbox Fix & Visual Validation Plan
|
||||
|
||||
## Current State
|
||||
|
||||
Another AI attempted the Photopea-style vertical toolbox refactor (commit `c2d02f6` plan). The code compiles but the UX is broken:
|
||||
- Buttons overlap vertically instead of spacing dynamically with 1px gap
|
||||
- Long-press popup does not open reliably
|
||||
- Toolbox width is percentage-based (`0.045` dock split) instead of fixed single-button width
|
||||
- The user wants visual validation via screenshots using an MAE-like comparison loop
|
||||
|
||||
## Git Baseline
|
||||
|
||||
1. `git stash` the current broken WIP changes to preserve them for reference
|
||||
2. Revert to the last known working state (`0e5914e` or `HEAD~` before toolbox changes)
|
||||
3. Re-apply the correct implementation from scratch
|
||||
|
||||
## Root Causes of the Previous Failure
|
||||
|
||||
1. **Button overlap**: `ToolSlotButton` uses `ui.allocate_exact_size(34x34, Sense::click())` inside a `for` loop, but `ui.add()` on a widget with exact size bypasses `item_spacing`. The button consumes the full width and the next button starts immediately after, causing overlap if the available width is less than 34px.
|
||||
2. **Long-press broken**: The previous logic mixed `ui.input(|i| i.pointer.any_down())` with `response.clicked()` in the same frame loop. The `pointer_down` check is frame-global, not scoped to the specific button. Also `response.clicked()` is consumed by egui, so `!resp.clicked()` for long-press is fragile.
|
||||
3. **Width not fixed**: `left_col1_w` is loaded from storage and clamped `max(30.0)`, but the dock split is still `0.045` (percentage of window width). On a 4K monitor this is ~170px, not 36px.
|
||||
4. **Missing popup close on mouse release**: No explicit handling of mouse-up outside the button when popup is open.
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Stash broken WIP and revert
|
||||
```bash
|
||||
git stash push -m "broken-ai-toolbox-wip"
|
||||
# restore from last good state if needed
|
||||
git checkout -- hcie-egui-app/crates/hcie-gui-egui/src/app/toolbox.rs
|
||||
# etc.
|
||||
```
|
||||
|
||||
### Step 2: Fix `widgets.rs` — `ToolSlotButton`
|
||||
- Keep `allocate_exact_size` but ensure the button returns the correct rect
|
||||
- Add `ui.spacing_mut().item_spacing = egui::vec2(0.0, 1.0)` in the **caller** (`toolbox.rs`), not inside the widget
|
||||
- Use `ui.vertical(|ui| { ... })` with explicit spacing
|
||||
|
||||
### Step 3: Fix `toolbox.rs` — `show_tool_panel_ui`
|
||||
- Replace the broken long-press logic with a robust per-slot timer:
|
||||
```rust
|
||||
// Store press start in ToolState per slot
|
||||
pub slot_press_start: Option<(usize, Instant)>
|
||||
```
|
||||
- On each frame, for each slot:
|
||||
- If `response.is_pointer_button_down_on()` (egui method) → start timer for that slot
|
||||
- If pointer released (`!ui.input(|i| i.pointer.any_down())`) → reset timer
|
||||
- If timer > 400ms and popup not open → open popup
|
||||
- If `response.clicked()` and timer < 400ms → activate last-used tool
|
||||
- **Critical fix**: Separate `clicked()` (mouse released inside button) from long-press detection. Use `response.interact(egui::Sense::click_and_drag())` or check `response.is_pointer_button_down_on()` before `response.clicked()`.
|
||||
|
||||
### Step 4: Fix `mod.rs` — Dock width
|
||||
- In `default_dock_state()`, change `split_left(..., 0.045, ...)` to `split_left(..., 36.0 / screen_width, ...)` or better: enforce `left_col1_w = 36.0` in layout_solver and clamp it hard.
|
||||
- In `layout_solver.rs`, set `measure_and_store_column(..., 36.0, 36.0)` for the Tools column.
|
||||
- In `HcieApp::new`, override `left_col1_w = 36.0` regardless of persisted value.
|
||||
|
||||
### Step 5: Fix popup positioning and close behavior
|
||||
- Popup should use `egui::Area` with fixed_pos anchored to the **right edge** of the slot button, vertically centered.
|
||||
- Close conditions: Escape key, click outside popup rect, or mouse release after long-press if the release is outside the popup.
|
||||
- Add `popup_close_request: bool` to ToolState, checked at the top of `show_tool_panel_ui` each frame.
|
||||
|
||||
### Step 6: Visual Validation — Screenshot + MAE Loop
|
||||
- Add a **debug/dev-only** screenshot capture in `update()` cycle:
|
||||
```rust
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot(egui::UserData::default()));
|
||||
```
|
||||
- In `post_update`, check `ctx.input(|i| i.events)` for `egui::Event::Screenshot { image, .. }`.
|
||||
- Save the image to `target/screenshots/toolbox_001.png`.
|
||||
- Compare with a reference image (user-provided or cropped from the Photopea screenshot) using MAE (Mean Absolute Error) per pixel.
|
||||
- **No automated loop** (headless GUI is impossible in this environment). Instead, after each code change, the developer runs the app, triggers a screenshot via a dev keybind (e.g. F12), and the MAE is printed to the console.
|
||||
- Alternative: add a `#[cfg(feature = "screenshot-test")]` gated function that captures the toolbox area specifically and saves it.
|
||||
|
||||
### Step 7: Complete Tool Coverage Verification
|
||||
- Verify `Tool::ALL` from `hcie-protocol/src/tools.rs` matches `TOOL_SLOTS` exactly:
|
||||
- Current list has 33 variants.
|
||||
- TOOL_SLOTS should contain every variant exactly once.
|
||||
- Add a compile-time or runtime assertion (e.g., in `tests/gui_audit.rs`) that all `Tool::ALL` items appear in some slot.
|
||||
|
||||
## Files to Modify
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `hcie-egui-app/crates/hcie-gui-egui/src/app/widgets.rs` | Fix `ToolSlotButton` sizing / spacing |
|
||||
| `hcie-egui-app/crates/hcie-gui-egui/src/app/toolbox.rs` | Rewrite long-press & popup logic |
|
||||
| `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` | Hard-code `left_col1_w = 36.0`, fix `active_slot` init |
|
||||
| `hcie-egui-app/crates/hcie-gui-egui/src/app/layout_solver.rs` | Clamp Tools column to 36px |
|
||||
| `hcie-egui-app/crates/hcie-gui-egui/src/app/dock.rs` | Optional: F12 screenshot keybind for dev testing |
|
||||
| `hcie-egui-app/crates/hcie-gui-egui/tests/gui_audit.rs` | Expand test to assert all tools are in TOOL_SLOTS |
|
||||
|
||||
## Testing & Validation Workflow
|
||||
|
||||
1. Build and run: `cargo run -p hcie-gui-egui`
|
||||
2. Press F12 to capture the current viewport screenshot
|
||||
3. Toolbox area is cropped (first 36px from left)
|
||||
4. Compare with reference image using MAE: `MAE = sum(|pixel_diff|) / pixel_count`
|
||||
5. Iterate until MAE is below a threshold (e.g., 5% per pixel) or until visual inspection passes
|
||||
|
||||
## Open Question for User
|
||||
|
||||
- Do you have a reference PNG/screenshot of the desired toolbox appearance that we should compare against, or should we use the Photopea image you attached as the reference?
|
||||
- Should the screenshot comparison be fully automated (hard to do in a headless GUI environment) or manual (F12 trigger, you inspect the saved image)?
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
# Refactor: Split hcie-fx/src/shadow.rs into Domain Modules
|
||||
|
||||
## Goal
|
||||
Split the monolithic `shadow.rs` (1482 lines containing 10+ effect generators + helpers) into focused modules: `emboss.rs`, `outer_glow.rs`, etc. Keep `shadow.rs` containing only `apply_layer_effects`, `generate_shadow`, `generate_inner_shadow`, and shared helpers (`box_blur`, `box_blur_f32`, `smoothstep`, composites).
|
||||
|
||||
## Handover from Planning Session
|
||||
|
||||
### Discoveries
|
||||
|
||||
- **`generate_bevel_emboss` is called directly (not via a re-export)** from `apply_layer_effects` at line 58 of `shadow.rs` — it's a plain function call `generate_bevel_emboss(...)`, not `self::generate_bevel_emboss(...)`. After moving it to `emboss.rs`, need to add `use crate::emboss::generate_bevel_emboss;` at top of `shadow.rs`.
|
||||
- **No other crate or file calls individual effect generators directly** — only `tune_mae_emboss.rs` and `tune_mae_outer_glow.rs` call `generate_bevel_tuned` / `generate_glow_tuned` via `hcie_fx::shadow::...`. All other consumers (test_psd_composite.rs, debug_glow.rs, gui.rs, etc.) only call `apply_layer_effects` (the top-level pub function).
|
||||
- **`generate_bevel_emboss` has a duplicate `generate_bevel_tuned`** already in shadow.rs (lines 883-1076) with identical math but extra param args. Both must move to `emboss.rs`.
|
||||
- **`generate_glow_tuned` does NOT exist in shadow.rs yet** — the tune file references it but it was removed during the backup restore. Need to ADD it to `outer_glow.rs` as part of Phase 2.
|
||||
- **`edt_*` functions are private and called only from `generate_bevel_emboss`/`generate_bevel_tuned`** — safe to move entirely into `emboss.rs`.
|
||||
- **`smoothstep` is defined but never used** in shadow.rs (only referenced in comment). Keep it in shadow.rs for now.
|
||||
- **`box_blur` (u8 version) is defined but only shadow.rs internal functions call it** — all effect generators use `box_blur_f32` (f32 version). `box_blur` can stay in shadow.rs as `pub(crate)`.
|
||||
- **`sample_gradient` is a private helper only used by `generate_gradient_overlay`** — will move with overlays in Phase 3.
|
||||
|
||||
### Relevant Files
|
||||
|
||||
- `/mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/shadow.rs` — 1482 lines, the monolithic source to split. `apply_layer_effects` (pub), 10 effect generators (fn), plus EDT, blur, composite helpers.
|
||||
- `/mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/lib.rs` — declares `pub mod shadow;`. Needs `pub mod emboss;`, `pub mod outer_glow;` etc.
|
||||
- `/mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_emboss.rs` — calls `hcie_fx::shadow::generate_bevel_tuned` at line 202. Needs update to `hcie_fx::emboss::generate_bevel_tuned`.
|
||||
- `/mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_outer_glow.rs` — calls `hcie_fx::shadow::generate_glow_tuned` at line 81. Needs update to `hcie_fx::outer_glow::generate_glow_tuned`.
|
||||
- `/mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/test_sultan.rs` — uses `hcie_fx::shadow::apply_layer_effects` indirectly via hcie_composite. No change needed.
|
||||
- `/mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/src/test_psd_composite.rs` — uses `hcie_fx::apply_layer_effects` (re-exported from lib.rs). No change needed.
|
||||
- `/mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/gui.rs` — uses `hcie_fx::protocol_to_hcie_fx_effect`, `hcie_fx::LayerEffect` enum, etc. No change needed as long as lib.rs re-exports are intact.
|
||||
|
||||
### Implementation Notes
|
||||
|
||||
- **Do Phase 1 first (emboss.rs), then verify cargo check passes, then tune.** After Phase 1, the `tune_mae_emboss` example uses the new module path, but `tune_mae_outer_glow` will still reference `hcie_fx::shadow::generate_glow_tuned` which won't exist yet — that's fine, only build the specific example.
|
||||
- **Use `cargo check --package hcie-fx` first** to verify the library compiles, then `cargo check --example tune_mae_emboss --release` for the example.
|
||||
- **`generate_bevel_tuned` is labeled `pub`** in shadow.rs — it needs to stay pub in emboss.rs for the tune example to call it.
|
||||
- **The BEGIN_TUNING_ZONE/END_TUNING_ZONE comments** in shadow.rs (lines 828 and 878) should move WITH `generate_bevel_emboss` into emboss.rs so the tune script can still find them by string matching. Same for BEGIN_GLOW_TUNING_ZONE/END_GLOW_TUNING_ZONE in `generate_glow`.
|
||||
- **After Phase 2**, the backup `shadow.rs.bak` should be updated (or deleted) since it no longer matches the refactored structure.
|
||||
- **Running `test_sultan` after all phases** requires all crate examples to compile — don't leave broken references in example files. Either fix both tune examples after each phase, or only run the specific working example.
|
||||
|
||||
## Todo List
|
||||
|
||||
- [~] Fix tune_eval vs production mismatch — the tune scripts (`tune_mae_emboss`, `tune_mae_outer_glow`) and the production functions (`generate_bevel_emboss`, `generate_glow`) have diverged. The tune versions have additional parameters and different tuning zone structures. Need to reconcile: either fold tune params into production, or make production call the tuned version with default params.
|
||||
- [ ] **Phase 1**: Extract emboss.rs from shadow.rs
|
||||
- [ ] Copy `generate_bevel_emboss` (lines ~796-882), `generate_bevel_tuned` (lines 883-1076), and `edt_*` helpers to new `emboss.rs`
|
||||
- [ ] Add `pub mod emboss;` to `lib.rs`
|
||||
- [ ] Add `use crate::emboss::generate_bevel_emboss;` to `shadow.rs`
|
||||
- [ ] Remove the moved functions from `shadow.rs`
|
||||
- [ ] Update `tune_mae_emboss.rs` to use `hcie_fx::emboss::generate_bevel_tuned`
|
||||
- [ ] Verify with `cargo check --package hcie-fx` and `cargo check --example tune_mae_emboss --release`
|
||||
- [ ] **Phase 2**: Extract outer_glow.rs from shadow.rs
|
||||
- [ ] Add `generate_glow_tuned` (production version + tuned version) to `outer_glow.rs`
|
||||
- [ ] Add `pub mod outer_glow;` to `lib.rs`
|
||||
- [ ] Add `use crate::outer_glow::*;` to `shadow.rs`
|
||||
- [ ] Remove `generate_glow` from `shadow.rs`
|
||||
- [ ] Update `tune_mae_outer_glow.rs` to use `hcie_fx::outer_glow::generate_glow_tuned`
|
||||
- [ ] Verify with `cargo check`
|
||||
- [ ] **Phase 3**: Extract overlay modules (gradient_overlay, pattern_overlay, stroke, color_overlay)
|
||||
- [ ] **Final**: Clean up, delete `shadow.rs.bak`, run `test_sultan`
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
# Plan: Stroke Tune — Cover All Individual PSD Files + Fix Stroke Opacity
|
||||
|
||||
## Problem
|
||||
|
||||
1. **Incomplete test coverage**: `tune_mae_stroke.rs` only loads composite PSDs (`stroke-inside.psd`, `stroke-outside.psd`). The 9 individual PSD files (`Shape 1.psd`, `Shape 2.psd`, etc.) are NOT tested. The reference PNGs were exported from these individual PSDs, so comparing against layers extracted from the composite may be wrong (different canvas size, pixel data).
|
||||
|
||||
2. **Stroke opacity still broken**: MAE for Shape 2 (stroke_opacity=0.50) is 4.06 — essentially unchanged even with the O(R²) baseline algorithm. The user confirms this is specifically about the **stroke effect's own Opacity setting** (50% in the Layer Style dialog), not layer/fill opacity.
|
||||
|
||||
## Files to Change
|
||||
|
||||
### `hcie-io/examples/tune_mae_stroke.rs`
|
||||
|
||||
**A. Load ALL PSD files — both individual and composite:**
|
||||
|
||||
Current code only loads 2 composite PSDs. Replace with a list of ALL PSD/PNG pairs:
|
||||
|
||||
**stroke-inside/ directory:**
|
||||
| PSD | PNG Reference | Notes |
|
||||
|-----|--------------|-------|
|
||||
| `stroke-inside.psd` | `stroke-inside.png` | Composite — has layers Shape 1-4 |
|
||||
| `Shape 1.psd` | `Shape 1.png` | Individual — 1 layer |
|
||||
| `Shape 2.psd` | `Shape 2.png` | Individual — 1 layer, **50% stroke opacity** |
|
||||
| `Shape 3.psd` | `Shape 3.png` | Individual — 1 layer, **50% stroke opacity** |
|
||||
| `Shape 4.psd` | `Shape 4.png` | Individual — 1 layer |
|
||||
|
||||
**stroke-outside/ directory:**
|
||||
| PSD | PNG Reference | Notes |
|
||||
|-----|--------------|-------|
|
||||
| `stroke-outside.psd` | `stroke-outside.png` | Composite — has layers Shape 1-3, Layer 2 |
|
||||
| `Shape 1.psd` | `Shape 1.png` | Individual |
|
||||
| `Shape 2.psd` | `Shape 2.png` | Individual |
|
||||
| `Shape 3.psd` | `Shape 3.png` | Individual |
|
||||
| `Layer 2.psd` | `Layer 2.png` | Individual |
|
||||
| `Layer 1.psd` | *(no PNG)* | **Skip** — no reference image |
|
||||
|
||||
Change the data structure from `(composite_psd, png_dir)` to a flat list of `(psd_path, png_ref_path)` tuples. Each entry maps one PSD to one PNG. For composite PSDs, iterate layers and match each layer name to its PNG. For individual PSDs, the PNG stem is the PSD filename.
|
||||
|
||||
**B. Deduplicate**: When both composite and individual PSDs produce the same layer, prefer the individual PSD (it's the one that produced the PNG).
|
||||
|
||||
### `hcie-fx/src/stroke.rs`
|
||||
|
||||
The opacity is now baked in (line 113: `stroke_alpha[i] * opacity`). No further changes needed here.
|
||||
|
||||
### `hcie-fx/src/shadow.rs`
|
||||
|
||||
Opacity is passed as 1.0 to `composite_inside_effect` for stroke (line 128). No further changes needed.
|
||||
|
||||
## Execution Order
|
||||
|
||||
1. Rewrite the test-case loading in `tune_mae_stroke.rs` to enumerate all individual PSDs + composite PSDs
|
||||
2. Build and run to see new per-layer MAE values across all files
|
||||
3. Analyze which layers still have high MAE and why — the individual PSDs may reveal different canvas sizes, pixel data, or effect parameters that explain the opacity discrepancy
|
||||
4. Fix any remaining stroke opacity issues based on the new data
|
||||
|
||||
## Expected Outcome
|
||||
|
||||
- 13+ test cases covering all PSD files (not just 8 from composites)
|
||||
- Individual PSDs tested with their correct reference PNGs → MAE should improve
|
||||
- Clear visibility into per-layer accuracy for stroke opacity cases
|
||||
@@ -0,0 +1,167 @@
|
||||
# V4 commit-gated semantic and functional regression mechanism
|
||||
|
||||
## Objective
|
||||
|
||||
Port the v3 commit-gated regression behavior to `hcie-rust-v4` so changes are checked before commit and audited after commit, while adding deterministic functional/visual regression tests that catch pixel/API regressions automatically.
|
||||
|
||||
## v3 logic found
|
||||
|
||||
- `hcie-rust-v3/.git/hooks/pre-commit`
|
||||
- Collects staged files with `git diff --cached --name-only`.
|
||||
- Calls `.git/hooks/semantic_analyzer.py`.
|
||||
- If a high-risk module changes, runs `cargo test --all-features`.
|
||||
- Fails the commit on analyzer/test failure.
|
||||
- `hcie-rust-v3/.git/hooks/post-commit`
|
||||
- Runs `logs/generate_semantic_report.py --mode last`.
|
||||
- Prints and saves a semantic impact report.
|
||||
- Does not block the completed commit.
|
||||
- `hcie-rust-v3/logs/generate_semantic_report.py`
|
||||
- Layer 1: builds Cargo dependency graph and blast radius.
|
||||
- Layer 2: analyzes public API diff changes.
|
||||
- Layer 3: optional `cargo-semver-checks`.
|
||||
- Exits `1` when highest risk is `HIGH`.
|
||||
- `hcie-rust-v3/src-tauri/src/visual_regression.rs`
|
||||
- Golden hash harness using deterministic Engine output.
|
||||
- Draws canonical vector shapes, hashes composite pixels, compares against golden SHA-256 values.
|
||||
- Exposes Tauri commands for manual regeneration.
|
||||
|
||||
## v4 current state
|
||||
|
||||
- `hcie-rust-v4/.git/hooks/pre-commit` exists but is disabled with `exit 0`.
|
||||
- `hcie-rust-v4/.git/hooks/post-commit` already runs `logs/generate_semantic_report.py --mode last --save`.
|
||||
- `hcie-rust-v4/logs/semantic_check.sh` already wraps `logs/generate_semantic_report.py`.
|
||||
- `hcie-rust-v4/logs/generate_semantic_report.py` is already v4-aware and uses correct workspace paths.
|
||||
- `hcie-rust-v4/.githooks/generate_semantic_report.py` appears stale/v3-oriented and duplicates functionality.
|
||||
- `hcie-rust-v4/project_manifest.json` exists but is incomplete for the full v4 workspace.
|
||||
- Existing tests are present in engine crates and GUI tests, but there is no v4 equivalent of v3's golden visual regression harness.
|
||||
- Current working tree already has unrelated changes: `build.id`, `lock.sh`, `unlock.sh`, deleted `notlat.txt`, and untracked `_images/`. Do not touch these.
|
||||
|
||||
## Implementation plan
|
||||
|
||||
### 1. Enable v4 pre-commit gate
|
||||
|
||||
File: `.git/hooks/pre-commit`
|
||||
|
||||
Behavior:
|
||||
- Exit immediately if there are no staged files.
|
||||
- Run `logs/semantic_check.sh precommit`.
|
||||
- If semantic analysis fails, block commit.
|
||||
- If semantic analysis passes, run functional regression gate.
|
||||
- Keep output concise and actionable.
|
||||
- Do not stage/unstage files and do not modify the working tree.
|
||||
|
||||
Important: `.git/hooks` is local and not versioned. For repository-level portability, keep the same commands available through tracked `logs/*` scripts and optionally add a later hook-install script in a separate phase.
|
||||
|
||||
### 2. Strengthen post-commit audit
|
||||
|
||||
File: `.git/hooks/post-commit`
|
||||
|
||||
Behavior:
|
||||
- Run semantic audit with `logs/semantic_check.sh audit --save`.
|
||||
- Run post-commit functional regression gate in non-blocking mode.
|
||||
- Never fail the completed commit; write failures to `logs/functional_regression_report.txt` or similar.
|
||||
- Tee human-readable output to terminal.
|
||||
|
||||
### 3. Add functional regression gate script
|
||||
|
||||
File: `logs/functional_regression_check.sh`
|
||||
|
||||
Purpose:
|
||||
- Central runner for functional/visual regression checks used by both hooks and manual CI commands.
|
||||
|
||||
Modes:
|
||||
- `--fast`: run only core deterministic regression tests.
|
||||
- `--visual`: run golden visual regression test only.
|
||||
- `--all`: run core engine regression tests plus GUI engine integration tests.
|
||||
- `--affected`: run tests for crates affected by staged or last-commit changes.
|
||||
- `--post-commit`: non-blocking mode that writes report files and exits `0`.
|
||||
|
||||
Default behavior:
|
||||
- If a high-risk crate is touched, run the core regression set.
|
||||
- If only low-risk files are touched, run the smallest relevant crate test set.
|
||||
- If no changed files are found, exit `0`.
|
||||
|
||||
Core regression set:
|
||||
- `cargo test -p hcie-engine-api --test visual_regression`
|
||||
- `cargo test -p hcie-draw`
|
||||
- `cargo test -p hcie-composite`
|
||||
- `cargo test -p hcie-filter`
|
||||
- `cargo test -p hcie-brush-engine`
|
||||
- `cargo test -p hcie-blend`
|
||||
- `cargo test -p hcie-color`
|
||||
- `cargo test -p hcie-egui-app/crates/hcie-gui-egui --test canvas_engine engine_composite_after_draw`
|
||||
|
||||
Affected crate mapping:
|
||||
- `hcie-protocol` → run protocol plus all dependent core crates.
|
||||
- `hcie-engine-api` → run engine visual regression plus GUI engine integration.
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/*` → run GUI integration tests.
|
||||
- `hcie-filter/*` → run filter tests plus engine visual regression if filter output is involved.
|
||||
- `hcie-draw/*`, `hcie-composite/*`, `hcie-blend/*`, `hcie-color/*`, `hcie-brush-engine/*`, `hcie-vector/*`, `hcie-fx/*` → run corresponding crate tests plus engine visual regression.
|
||||
|
||||
### 4. Add v4 golden visual regression harness
|
||||
|
||||
File: `hcie-engine-api/tests/visual_regression.rs`
|
||||
|
||||
Purpose:
|
||||
- Port v3's deterministic golden hash concept to v4 through the public Engine API boundary.
|
||||
- Keep GUI out of internal engine crates and avoid direct protocol access from GUI.
|
||||
|
||||
Design:
|
||||
- Use `hcie_engine_api::Engine` only.
|
||||
- Create deterministic canvases, layers, colors, vector shapes, brush strokes, filters, and undo/redo operations.
|
||||
- Hash `engine.get_composite_pixels()` with SHA-256.
|
||||
- Compare against inline golden hashes.
|
||||
- Provide `HCIE_REGEN_GOLDENS=1 cargo test -p hcie-engine-api --test visual_regression -- --nocapture` to print new hashes after manual visual verification.
|
||||
|
||||
Initial golden cases:
|
||||
- `white_canvas_empty_document`: validates blank document/composite baseline.
|
||||
- `green_rect_draw_and_composite`: validates `draw_filled_rect_rgba`.
|
||||
- `red_rect_over_green_rect`: validates layer compositing order.
|
||||
- `vector_rect_golden`: validates vector rendering through Engine API.
|
||||
- `brush_stroke_golden`: validates brush engine + draw pipeline.
|
||||
- `invert_filter_golden`: validates filter pipeline and deterministic output.
|
||||
- `undo_after_rect_golden`: validates history snapshot and restoration.
|
||||
|
||||
Dependency:
|
||||
- Add `sha2 = "0.10"` as a dev-dependency in `hcie-engine-api/Cargo.toml`.
|
||||
|
||||
### 5. Keep v4 semantic analyzer aligned
|
||||
|
||||
Do not replace the existing `logs/generate_semantic_report.py` unless necessary.
|
||||
|
||||
If editing is needed, keep changes minimal:
|
||||
- Ensure `--mode staged` and `--mode last` work with current v4 workspace paths.
|
||||
- Ensure high-risk detection is conservative for `hcie-engine-api`, `hcie-protocol`, `hcie-document`, `hcie-composite`, `hcie-draw`, `hcie-blend`, `hcie-color`, `hcie-filter`, `hcie-brush-engine`, `hcie-vector`, `hcie-fx`, and GUI engine-facing crates.
|
||||
- Do not run `cargo-semver-checks` by default because it requires extra tooling and can be slow.
|
||||
|
||||
### 6. Optional follow-up phase
|
||||
|
||||
Only if the first phase is complete and the user wants repository-portable hooks:
|
||||
- Add a tracked hook installer such as `tools/install-git-hooks.sh`.
|
||||
- Optionally clean up duplicate `.githooks/generate_semantic_report.py` or make it delegate to `logs/generate_semantic_report.py`.
|
||||
- Document manual commands in an existing project note only if requested.
|
||||
|
||||
## Validation plan
|
||||
|
||||
After implementation, run:
|
||||
|
||||
1. Semantic precommit gate:
|
||||
- `bash logs/semantic_check.sh precommit`
|
||||
|
||||
2. Functional fast gate:
|
||||
- `bash logs/functional_regression_check.sh --fast`
|
||||
|
||||
3. Golden visual regression:
|
||||
- `cargo test -p hcie-engine-api --test visual_regression`
|
||||
|
||||
4. GUI engine integration:
|
||||
- `cargo test -p hcie-egui-app/crates/hcie-gui-egui --test canvas_engine`
|
||||
|
||||
5. Hook smoke checks without committing:
|
||||
- `bash .git/hooks/pre-commit`
|
||||
- `bash .git/hooks/post-commit`
|
||||
|
||||
Expected outcome:
|
||||
- Pre-commit blocks only when semantic risk is `HIGH` or regression tests fail.
|
||||
- Post-commit always completes and writes reports.
|
||||
- Functional regressions fail deterministically when public Engine output changes unexpectedly.
|
||||
Executable
+119
@@ -0,0 +1,119 @@
|
||||
# Plan: Port PlainSlider Design to hcie-wx-app
|
||||
|
||||
## User Request
|
||||
Apply the `PlainSlider` visual/interaction design from `hcie-egui-app` (label + value centered on a rounded bar, right-side spinner triangles, direct numeric input, progress-bar fill, subtle handle) to the slider controls in `hcie-wx-app`, which currently uses native `wxSlider` controls via `wxdragon`.
|
||||
|
||||
## Current State
|
||||
|
||||
### hcie-egui-app PlainSlider
|
||||
- Located in `hcie-egui-app/crates/egui-panel-adapter/src/lib.rs` (lines 188–504) and re-exported by `hcie-gui-egui/src/app/widgets.rs`.
|
||||
- Fully custom immediate-mode widget painted with egui:
|
||||
- 160×22 px default size, 4 px rounded rectangle.
|
||||
- Background color changes on hover (dark/light themes supported via `ThemeColors` / `ThemePreset`).
|
||||
- Filled track up to current value using a semi-transparent accent color.
|
||||
- Thin vertical accent handle at the value position.
|
||||
- Centered text: `"{label}: {value}{suffix}"` (e.g. `Size: 110.00 px`).
|
||||
- Right 24 px spinner area with up/down triangles for stepping.
|
||||
- Drag anywhere on the bar to set value; drag ignores spinner area.
|
||||
- Logarithmic and snap-step support.
|
||||
- Direct keyboard input when the bar is hovered and the user starts typing digits.
|
||||
- It is **not reusable outside egui**; it depends on `egui::Widget`, `egui::Ui`, `egui::Painter`, etc.
|
||||
|
||||
### hcie-wx-app
|
||||
- Native desktop app built on `wxdragon` (wxWidgets wrapper).
|
||||
- `Cargo.toml` depends on `wxdragon` only; no egui dependency.
|
||||
- Sliders defined in XRC/FBP files as `wxSlider`:
|
||||
- `m_sliderSize` (1–500, current 10)
|
||||
- `m_sliderOpacity` (0–100, current 100)
|
||||
- `m_sliderFlow` (0–100, current 100)
|
||||
- `m_sliderHardness` (0–100, current 0)
|
||||
- `m_sliderColorR/G/B` (color channel sliders in Colors panel)
|
||||
- `m_slider5` (footer/layer opacity slider)
|
||||
- Rust code in `hcie-wx-app/src/main.rs` accesses them as `Slider` and binds `on_slider` events.
|
||||
- UI is generated from `*.fbp` (wxFormBuilder) via Python scripts under `hcie-wx-app/ui/`.
|
||||
|
||||
## Technical Verdict
|
||||
|
||||
### Can the egui PlainSlider be used directly in hcie-wx-app?
|
||||
**No.** egui and wxWidgets are separate GUI stacks. There is no supported way to embed an egui widget inside a wxWidgets window.
|
||||
|
||||
### Can the PlainSlider *design* be reproduced in hcie-wx-app?
|
||||
**Yes, by creating a custom painted wxWidgets control.** This is the viable path:
|
||||
1. Implement a new custom wx control (e.g. `HciePlainSlider`) using `wxPanel` + `wxPaintDC`/`wxBufferedPaintDC`.
|
||||
2. Replicate the PlainSlider paint logic: rounded track, fill, handle, centered label/value text, right-side spinner triangles, hover effects.
|
||||
3. Replicate interactions: mouse drag/click to set value, spinner up/down on click, keyboard input on hover/focus.
|
||||
4. Replace `wxSlider` usages in the wxFormBuilder project and/or in generated XRC with the new custom control.
|
||||
5. Update `main.rs` event binding to use the new control’s API.
|
||||
|
||||
## User Decisions Confirmed
|
||||
|
||||
- **Scope:** Replace **all** `wxSlider` controls in `hcie-wx-app`:
|
||||
- Properties panel: `m_sliderSize`, `m_sliderOpacity`, `m_sliderFlow`, `m_sliderHardness`
|
||||
- Colors panel: `m_sliderColorR`, `m_sliderColorG`, `m_sliderColorB`
|
||||
- Footer: `m_slider5`
|
||||
- **Behavior parity:** Full PlainSlider behavior: drag, spinner step, direct text input, logarithmic mode where applicable, suffix formatting.
|
||||
|
||||
## Feasibility Check
|
||||
|
||||
Inspected `wxdragon 0.9.16` source in cargo registry:
|
||||
- `Panel` supports `on_paint`, `on_left_down`, `on_left_up`, `on_motion`, `on_char`, `on_key_down`, `on_key_up`, `on_enter_window`, `on_leave_window`, `on_set_focus`, `on_kill_focus` (via `WindowEvents` trait).
|
||||
- `AutoBufferedPaintDC` + `PaintDC` are available, plus `DeviceContext` trait exposes `draw_rounded_rectangle`, `draw_text`, `draw_line`, `draw_polygon`, `set_pen`, `set_brush`, `set_font`, `set_text_foreground`, `get_text_extent`, `get_size`, etc.
|
||||
- `Window` trait exposes `set_background_style(BackgroundStyle::Paint)`, `refresh`, `get_client_size`, `set_min_size`, `set_size`, etc.
|
||||
- **Conclusion:** Option A (custom `wxPanel`-based control) is fully feasible.
|
||||
|
||||
## Proposed Implementation Plan
|
||||
|
||||
### Phase 1 — Create `HciePlainSlider` custom control
|
||||
- Add `hcie-wx-app/src/plain_slider.rs`.
|
||||
- Implement a thin wrapper around `wxdragon::Panel` that replicates PlainSlider:
|
||||
- Stores label, value (f64 internally), min, max, suffix, logarithmic flag, step, snap steps.
|
||||
- Handles `on_paint` with `AutoBufferedPaintDC` to draw rounded background, fill track, handle, text, spinner triangles, border, hover glow.
|
||||
- Handles `on_left_down`, `on_left_up`, `on_motion` for drag/click/spinner.
|
||||
- Handles `on_char` / `on_key_down` for direct numeric input.
|
||||
- Emits value-changed via closure or callback API matching `wxdragon::Slider::on_slider` style.
|
||||
- Provide builder pattern: `PlainSlider::builder(parent).with_label(...).with_range(...).with_suffix(...).with_logarithmic(...).build()`.
|
||||
- Port a minimal `ThemeColors`/`ThemePreset` from `hcie-egui-app` to `hcie-wx-app` (single dark palette as default; add light later if needed).
|
||||
|
||||
### Phase 2 — Add theme support
|
||||
- Create `hcie-wx-app/src/theme.rs` with `ThemePreset` and `ThemeColors`.
|
||||
- Default to the existing dark workspace palette (Photoshop/ProDark equivalent).
|
||||
- Wire into `HciePlainSlider` via a `theme` builder method or global app state if available.
|
||||
|
||||
### Phase 3 — Replace sliders in UI definition
|
||||
- Update `hcie-wx-app/ui/refactor_fbp.py` `make_slider()` to generate a custom `wxPanel` subclass (`HciePlainSlider`) with the required XRC `subclass` header instead of `wxSlider`.
|
||||
- Update affected `*.fbp` files (`95_properties.fbp`, `70_colors.fbp`, `99_project_footer.fbp`) to reference the new custom control (either via `wxPanel` + `subclass` or by removing the sliders from XRC and creating them in Rust code).
|
||||
- Regenerate `main.xrc` using the existing Python scripts, or document that XRC must be regenerated manually.
|
||||
- **Recommended:** Keep XRC simple and create the custom sliders in Rust during panel initialization, placing them in the sizers by name. This avoids fighting wxFormBuilder.
|
||||
|
||||
### Phase 4 — Update `main.rs` integration
|
||||
- Change `frame.find_child_by_xrc_name::<Slider>("m_sliderSize")` etc. to find the new `PlainSlider` type.
|
||||
- Replace `slider.on_slider(...)` calls with the new control's `on_changed` API.
|
||||
- Ensure all initial values and ranges match the old `wxSlider` settings.
|
||||
- Update RGB slider binding in the Colors section to use the new control.
|
||||
- Update footer/layer opacity slider (`m_slider5`) binding.
|
||||
|
||||
### Phase 5 — Verify build
|
||||
- `cargo check --package hcie-wx-app`
|
||||
- Fix any `wxdragon` API mismatches (e.g., event data accessor names).
|
||||
|
||||
## Relevant Files
|
||||
|
||||
- `hcie-egui-app/crates/egui-panel-adapter/src/lib.rs` — reference PlainSlider paint/interaction logic.
|
||||
- `hcie-egui-app/crates/egui-panel-adapter/src/theme.rs` — reference `ThemeColors` / `ThemePreset` definitions.
|
||||
- `hcie-wx-app/src/main.rs` — current slider lookup and event binding.
|
||||
- `hcie-wx-app/src/plain_slider.rs` — **new** custom slider module.
|
||||
- `hcie-wx-app/src/theme.rs` — **new** minimal theme tokens.
|
||||
- `hcie-wx-app/Cargo.toml` — no new external deps expected; all needed APIs come from `wxdragon`.
|
||||
- `hcie-wx-app/ui/parts/95_properties.fbp`, `70_colors.fbp`, `99_project_footer.fbp` and/or `refactor_fbp.py` — replace `wxSlider` definitions with placeholders or subclass references.
|
||||
- `hcie-wx-app/ui/main.xrc` — regenerate or manually update after FBP changes.
|
||||
- `hcie-wx-app/src/lib.rs` (does not exist) or add `pub mod plain_slider; pub mod theme;` to `main.rs`.
|
||||
|
||||
## Risks / Caveats
|
||||
- **wxdragon API surface now confirmed feasible**, but subtle differences from egui painting coordinates may require iterative tuning.
|
||||
- **High effort for a small UI gain:** Replicating a fully custom-painted slider with interaction in wxWidgets is substantially more work than the egui version; estimate 400–600 lines of new Rust.
|
||||
- **XRC/FBP round-trip:** Any manual edits to generated files can be overwritten if UI is regenerated from wxFormBuilder. The source of truth should be the `*.fbp` files and the Python generation scripts, or use Rust-side creation to avoid FBP complexity.
|
||||
- **Accessibility / native feel:** A custom-painted control may lose native accessibility and platform-specific slider behavior.
|
||||
|
||||
## Plan Exit
|
||||
|
||||
Plan finalized and ready for implementation upon user approval.
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Run script for Agent Manager.
|
||||
# Runs in the selected worktree (or repo root for local).
|
||||
# Add the commands to start your project:
|
||||
|
||||
# npm run dev
|
||||
# bun run dev
|
||||
# cargo run
|
||||
# python manage.py runserver
|
||||
cargo run -p hcie-gui-egui
|
||||
@@ -0,0 +1,339 @@
|
||||
# HCIE-Rust v4 — AI-Aware Architecture
|
||||
|
||||
## Mission Statement
|
||||
|
||||
HCIE-Rust v4 is a pixel-grade image editor built around an atomic project split that prevents automated agents from corrupting the engine while still allowing them to iterate on the graphical user interface.
|
||||
|
||||
## Atomic Crate Split (23 crates / 6 layers)
|
||||
|
||||
### Layer 1: DATA — locked, agents must not modify
|
||||
|
||||
| Crate | Responsibility | Dependencies |
|
||||
|-------|----------------|--------------|
|
||||
| `hcie-protocol` | Pure data structures and enumerations. Zero logic. | `serde` |
|
||||
| `hcie-color` | Color conversions, gamma encode/decode, HSL/Lab. | `hcie-protocol` |
|
||||
|
||||
### Layer 2: ENGINE CORE — locked, agents must not modify
|
||||
|
||||
| Crate | Responsibility | Dependencies |
|
||||
|-------|----------------|--------------|
|
||||
| `hcie-blend` | 30+ blend mode implementations. | `hcie-protocol`, `hcie-color` |
|
||||
| `hcie-history` | Closure-based undo/redo engine. | `hcie-protocol` |
|
||||
| `hcie-brush-engine` | Brush mathematics, tip generation, pressure mapping. | `hcie-protocol` |
|
||||
| `hcie-draw` | Pixel drawing: lines, rectangles, ellipses, strokes, flood fill. | `hcie-protocol`, `hcie-blend`, `hcie-brush-engine` |
|
||||
| `hcie-composite` | Layer compositing and pixel output. | `hcie-protocol`, `hcie-blend` |
|
||||
| `hcie-filter` | Image filters. | `hcie-protocol`, `hcie-blend` |
|
||||
| `hcie-selection` | Selection mask operations: grow, shrink, feather, invert. | `hcie-protocol` |
|
||||
| `hcie-text` | Vector text rendering via `fontdue`. | `hcie-protocol`, `fontdue` |
|
||||
| `hcie-vector` | Vector shape engine. | `hcie-protocol` |
|
||||
| `hcie-tile` | Sparse tile-based layer storage. | `hcie-protocol` |
|
||||
| `hcie-io` | File I/O for PNG, JPG, WebP, PSD, KRA, HCIE native. | `hcie-protocol`, `image`, `zip`, `psd` |
|
||||
|
||||
### Layer 3: DOCUMENT — locked, agents must not modify
|
||||
|
||||
| Crate | Responsibility | Dependencies |
|
||||
|-------|----------------|--------------|
|
||||
| `hcie-document` | Document state: layers, active layer, dirty tracking, zoom. | `hcie-protocol`, `hcie-blend`, `hcie-history` |
|
||||
|
||||
### Layer 4: ENGINE API — locked, agents must not modify
|
||||
|
||||
| Crate | Output | Responsibility |
|
||||
|-------|--------|----------------|
|
||||
| `hcie-engine-api` | `staticlib` + `rlib` | Public API exposed to the GUI and AI/vision modules. Approximately 20 entry functions. |
|
||||
|
||||
### Layer 5: AI / VISION — locked, agents must not modify
|
||||
|
||||
These crates depend on `hcie-engine-api` and expose AI and vision features back through it.
|
||||
|
||||
| Crate | Responsibility | Dependencies |
|
||||
|-------|----------------|--------------|
|
||||
| `hcie-ai` | Ollama / LMStudio / OpenAI-compatible chat client, templates, AI actions. | `hcie-engine-api` |
|
||||
| `hcie-vision` | ComfyUI pipelines, background removal, inpaint, super-resolution, smart selection. | `hcie-engine-api` |
|
||||
|
||||
### Layer 6: GUI — open, agents work here
|
||||
|
||||
One GUI workspace exists:
|
||||
|
||||
| Crate / Project | Directory | Responsibility | Dependencies |
|
||||
|-----------------|-----------|----------------|--------------|
|
||||
| `hcie-egui-app` | `hcie-egui-app/` | Workspace root for the native GUI. | `hcie-engine-api`, `eframe`, panels |
|
||||
| `hcie-gui-egui` | `hcie-egui-app/crates/hcie-gui-egui/` | Main binary and application orchestration. | `hcie-engine-api`, panels |
|
||||
| `egui-panel-adapter` | `hcie-egui-app/crates/egui-panel-adapter/` | Dynamic schema-to-widget UI binder. | `hcie-engine-api` |
|
||||
| `egui-panel-filters` | `hcie-egui-app/crates/egui-panel-filters/` | Filter parameter panels. | `hcie-engine-api` |
|
||||
| `egui-panel-ai-chat` | `hcie-egui-app/crates/egui-panel-ai-chat/` | AI chat panel. | `hcie-engine-api`, `hcie-ai` |
|
||||
| `egui-panel-script` | `hcie-egui-app/crates/egui-panel-script/` | Scripting / automation panel. | `hcie-engine-api` |
|
||||
| `egui-panel-ai-script` | `hcie-egui-app/crates/egui-panel-ai-script/` | AI-assisted script generation panel. | `hcie-engine-api`, `egui-panel-script` |
|
||||
|
||||
## Critical Rules
|
||||
|
||||
1. **No direct GUI-to-engine bypass.** The GUI may not depend on or import `hcie-protocol`, `hcie-blend`, `hcie-document`, or any other engine crate directly. Use `hcie-engine-api` only.
|
||||
2. **Engine API is the single entry point.** All engine operations go through `hcie-engine-api`. AI agents may not read or modify internal engine code.
|
||||
3. **New feature = new file.** Prefer adding new files over editing existing ones; minimize changes to existing files.
|
||||
4. **Engine library type.** `hcie-engine-api` is built as `crate-type = ["rlib", "staticlib"]`. The GUI links the `.a` / `.rlib` artifacts.
|
||||
5. **Lock mechanism.** Engine crates are locked with `chmod 444` / directory `555` and owned by the `hcie-guard` user after build. Agents work only in the GUI layer.
|
||||
|
||||
## Build Order
|
||||
|
||||
```
|
||||
Layer 1: hcie-protocol, hcie-color
|
||||
Layer 2: hcie-blend, hcie-history, hcie-brush-engine, hcie-draw,
|
||||
hcie-composite, hcie-filter, hcie-selection, hcie-text,
|
||||
hcie-vector, hcie-tile, hcie-io, hcie-fx, hcie-psd, hcie-kra, hcie-native
|
||||
Layer 3: hcie-document
|
||||
Layer 4: hcie-engine-api (rlib + staticlib)
|
||||
Layer 5: hcie-ai, hcie-vision
|
||||
Layer 6: hcie-egui-app / hcie-gui-egui
|
||||
```
|
||||
|
||||
## Performance Protection Notes (CRITICAL — Regression Prevention)
|
||||
|
||||
The engine optimizations below directly determine brush performance on 4K / multi-layer documents. Removing, disabling, or bypassing these mechanisms as a "temporary workaround" is forbidden. Before touching them, verify benefit by running:
|
||||
|
||||
```bash
|
||||
cargo test -p hcie-engine-api --test visual_regression
|
||||
cargo test -p hcie-engine-api --test performance_stroke_4k -- --nocapture
|
||||
```
|
||||
|
||||
| Mechanism | Location | What it does | Why it is protected |
|
||||
|-----------|----------|--------------|---------------------|
|
||||
| `active_stroke_mask` pooling | `hcie-engine-api/src/lib.rs` | Keeps an approximately 8MB mask buffer alive between strokes. | Re-allocating the mask per stroke causes allocation pauses during continuous painting. |
|
||||
| `composite_scratch` pooling | `hcie-engine-api/src/lib.rs` | Keeps an approximately 33MB composite scratch buffer alive between strokes. | Allocating and freeing the composite buffer on every `render_composite_region` call is expensive. |
|
||||
| `below_cache` + `below_cache_dirty` | `hcie-engine-api/src/lib.rs` | Composites all layers below the active layer once at stroke start and reuses the result while the lower layers remain unchanged. | Re-compositing all layers on every stroke event costs milliseconds per event on 4K canvases. |
|
||||
| Conditional `effects_dirty` set | `hcie-engine-api/src/lib.rs` | Triggers the effects pass only for layers that actually have effects or styles. | Avoids unnecessary 33MB buffer clones and repeated `apply_layer_effects` calls on unaffected layers. |
|
||||
| Tile cache over-clear removal | `hcie-engine-api/src/lib.rs` | Does not clear `tile_layers` and re-tile all layers on opacity / visibility / blend / parent / move changes when layer pixels themselves did not change. | Keeps the tile cache valid and avoids redundant dense-to-tile copies. |
|
||||
| Small-region sequential compositing | `hcie-composite/src/tiled.rs` | Uses a sequential loop instead of `par_chunks_exact_mut` for small dirty regions. | Rayon thread spawn overhead dominates the work when the dirty region is small. |
|
||||
| Effects cache reuse | `hcie-composite/src/tiled.rs` | Skips `apply_layer_effects` when `layer.effects_cache` is already populated. | Prevents re-running the full effect pipeline on every partial composite. |
|
||||
| Incremental tile update | `hcie-tile/src/lib.rs` | Updates only the tiles that intersect the dirty rectangle and writes only the changed sub-rectangle when a tile already exists. | Avoids throwing away the whole 33MB dense-to-tile copy on every small dirty region. |
|
||||
| Vector stroke anti-aliasing density | `hcie-vector/src/lib.rs` | Keeps circle-overlap density high enough along vector strokes and ellipses so shapes do not look jagged/pixelated. | Reducing step counts makes circles, stars, polygons and rounded rectangles visibly tırtıklı. |
|
||||
|
||||
### GUI First-Frame Visibility (CRITICAL — Regression Prevention)
|
||||
|
||||
Two mechanisms together fix the recurring "default first document canvas is invisible until the tab title is clicked" bug. Removing either half makes the regression come back.
|
||||
|
||||
| Mechanism | Location | What it does | Why it is protected |
|
||||
|-----------|----------|--------------|---------------------|
|
||||
| Initial document active tab | `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` (`HcieApp::new`) | Sets `Document(0)` as the active dock tab on startup so egui_dock calls the canvas widget's `ui()` on the first frame. | Without this, the canvas widget is skipped and the composite texture is never created until the user clicks the tab title. |
|
||||
| Extra repaint after new texture | `hcie-egui-app/crates/hcie-gui-egui/src/canvas/mod.rs` | Requests multiple repaints after `render_composition` creates a brand-new texture, because egui uploads GPU textures asynchronously. | A single repaint often leaves the canvas blank on the first frame; the second/third frame actually displays the texture. |
|
||||
| `DrawingFinished` does NOT clear dirty flags | `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs` | Leaves engine dirty flags intact so `render_composition` can upload the result on the next frame. | Calling `clear_dirty_flags()` inside `DrawingFinished` races with the next render pass and makes newly drawn vector shapes disappear. |
|
||||
|
||||
**AI AGENT WARNING:** Do not remove, shorten, or "optimize away" the startup active-tab setting, the multi-frame repaint requests, or the `DrawingFinished` no-op. These look like minor details but they are the only thing keeping the first canvas visible. Every past removal has re-introduced the bug. If a change you are making touches any of these three places, stop and verify with an actual app launch (or `HCIE_AUTO_SCREENSHOT`) that the default `Untitled` canvas is visible without clicking the tab title.
|
||||
|
||||
If a new feature conflicts with one of these mechanisms, extend the existing API by adding a new function or file rather than bypassing or duplicating the protected code.
|
||||
|
||||
## GUI Workspace Architecture (CRITICAL — AI Search Scope)
|
||||
|
||||
Only one GUI workspace exists: `hcie-egui-app/`. The GUI crates are `hcie-gui-egui/` and `egui-panel-*/`.
|
||||
|
||||
### AI Search Rule
|
||||
|
||||
When searching for a crate, module, file, or symbol:
|
||||
|
||||
1. Scan the active GUI workspace first (`hcie-egui-app/`).
|
||||
2. Then scan sibling engine crates (`../hcie-*/`).
|
||||
3. Follow the `Cargo.toml` dependency chain; if a dependency lives in another workspace, scan that workspace too.
|
||||
4. If a symbol name appears in two different modules (for example, `plugins/`), examine both.
|
||||
|
||||
## Error Isolation
|
||||
|
||||
Every crate can be built independently. A crash or failure in one crate does not cascade to the others. The GUI does not see engine code changes until the engine static library is rebuilt.
|
||||
|
||||
## Locked Files (agents may not write here)
|
||||
|
||||
- `hcie-protocol/src/*.rs`
|
||||
- `hcie-color/src/*.rs`
|
||||
- `hcie-blend/src/*.rs`
|
||||
- `hcie-history/src/*.rs`
|
||||
- `hcie-brush-engine/src/*.rs`
|
||||
- `hcie-draw/src/*.rs`
|
||||
- `hcie-composite/src/*.rs`
|
||||
- `hcie-filter/src/*.rs`
|
||||
- `hcie-selection/src/*.rs`
|
||||
- `hcie-text/src/*.rs`
|
||||
- `hcie-vector/src/*.rs`
|
||||
- `hcie-tile/src/*.rs`
|
||||
- `hcie-io/src/*.rs`
|
||||
- `hcie-document/src/*.rs`
|
||||
- `hcie-engine-api/src/*.rs`
|
||||
- `hcie-ai/src/*.rs`
|
||||
- `hcie-vision/src/*.rs`
|
||||
- `hcie-fx/src/*.rs`
|
||||
- `hcie-psd/src/*.rs`
|
||||
- `hcie-kra/src/*.rs`
|
||||
- `hcie-native/src/*.rs`
|
||||
|
||||
Use `unlock.sh <crate>` to temporarily open a crate, make the required change, then `lock.sh <crate>` to lock it again.
|
||||
|
||||
## Open Files (agents may write here)
|
||||
|
||||
### Active egui GUI
|
||||
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/src/*.rs`
|
||||
- `hcie-egui-app/crates/hcie-gui-egui/Cargo.toml`
|
||||
- `hcie-egui-app/crates/egui-panel-adapter/src/*.rs`
|
||||
- `hcie-egui-app/crates/egui-panel-filters/src/*.rs`
|
||||
- `hcie-egui-app/crates/egui-panel-ai-chat/src/*.rs`
|
||||
- `hcie-egui-app/crates/egui-panel-script/src/*.rs`
|
||||
- `hcie-egui-app/crates/egui-panel-ai-script/src/*.rs`
|
||||
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Never add an `egui` dependency to any engine crate.
|
||||
- Never access `hcie-protocol` directly from GUI code.
|
||||
- Never import a locked engine crate directly from GUI code; route everything through `hcie-engine-api`.
|
||||
- The root `hcie-egui-app/Cargo.toml` currently lists `hcie-protocol`, `hcie-blend`, and `hcie-io` for legacy compatibility. Do not add new direct engine dependencies there.
|
||||
|
||||
## Engine API — Public Surface
|
||||
|
||||
The GUI uses only the public items exported by `hcie-engine-api`. Example high-level operations:
|
||||
|
||||
```rust
|
||||
use hcie_engine_api::Engine;
|
||||
|
||||
let mut engine = Engine::new(800, 600);
|
||||
let layer_id = engine.add_layer("Background");
|
||||
engine.set_active_layer(layer_id);
|
||||
engine.draw_stroke(&points);
|
||||
let pixels = engine.get_composite_pixels();
|
||||
```
|
||||
|
||||
For the full API, see `hcie-engine-api/src/lib.rs` and `ARCHITECTURE.md`.
|
||||
|
||||
## Screenshot & Visual Verification (AI Agent Workflow)
|
||||
|
||||
AI agents MUST capture screenshots after every GUI change to verify visual results.
|
||||
The egui GUI has a built-in screenshot mechanism that agents should use.
|
||||
|
||||
### How to Capture
|
||||
|
||||
#### Full Viewport Screenshot (F12)
|
||||
- **Trigger:** Press `F12` while the egui GUI is running
|
||||
- **Output:** `target/screenshots/toolbox_{unix_timestamp}.png`
|
||||
- **Scope:** Entire viewport (all panels, menus, canvas)
|
||||
- **Implementation:** `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs:2340-2342`
|
||||
|
||||
#### Automated Panel Screenshot (CLI)
|
||||
```bash
|
||||
cargo run -p hcie-gui-egui -- --screenshot-panel "PanelName" output.png
|
||||
```
|
||||
- **Scope:** Crops to the specified panel's screen-space rect
|
||||
- **Implementation:** `hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs:2994-3009`
|
||||
- **State machine:** 7-frame capture flow (layout settle → screenshot → crop → save)
|
||||
|
||||
#### Available Panel Names (exact strings for `--screenshot-panel`)
|
||||
|
||||
| Panel Name | Enum Variant | Description |
|
||||
|------------|-------------|-------------|
|
||||
| `Tools` | `HciePane::Tools` | Toolbox (brush, eraser, shapes, etc.) |
|
||||
| `Filters` | `HciePane::Filters` | Filter parameter panels |
|
||||
| `Brushes & Tips` | `HciePane::Brushes` | Brush presets and tip configuration |
|
||||
| `Color Palette` | `HciePane::ColorBox` | Color picker and palette |
|
||||
| `AI Assistant` | `HciePane::AiAssistant` | AI chat panel |
|
||||
| `Layers` | `HciePane::Layers` | Layer list and management |
|
||||
| `History` | `HciePane::History` | Undo/redo history timeline |
|
||||
| `Properties` | `HciePane::Properties` | Active object properties |
|
||||
| `Plugins` | `HciePane::Plugins` | Plugin management panel |
|
||||
| `Layer Styles` | `HciePane::LayerStyles` | Layer effects/styles editor |
|
||||
| `Layer Details` | `HciePane::LayerDetails` | Detailed layer info panel |
|
||||
|
||||
> **Note:** Panel names are case-sensitive and must match exactly. The mapping is
|
||||
> defined in `pane_from_name()` at `mod.rs:926-941`.
|
||||
|
||||
### Screenshot Flow (7-Frame State Machine)
|
||||
```
|
||||
Frame 0: Ensure target panel exists in dock
|
||||
Frame 1-5: Wait for layout to settle (50ms repaints each)
|
||||
Frame 6: Issue ViewportCommand::Screenshot
|
||||
Frame 7: Receive egui::Event::Screenshot, crop to panel rect, save PNG, close
|
||||
```
|
||||
|
||||
### Helper Functions
|
||||
| Function | File | Purpose |
|
||||
|----------|------|---------|
|
||||
| `crop_colorimage()` | `mod.rs:3373-3391` | Crops `egui::ColorImage` to screen-space rect |
|
||||
| `save_colorimage()` | `mod.rs:3393-3400` | RGBA bytes → PNG file via `image::save_buffer` |
|
||||
| `pane_from_name()` | `mod.rs:926-941` | Maps panel name strings to `HciePane` enum |
|
||||
|
||||
### Agent GUI Iteration Rules
|
||||
1. **After every CSS/layout/code change:** Take a screenshot and visually inspect it
|
||||
2. **Before/after refactoring:** Capture both states for comparison
|
||||
3. **When user provides a reference image:** Capture current state, compare with reference
|
||||
4. **Document visual regressions:** If a screenshot shows degradation, log it before fixing
|
||||
5. **Use `--screenshot-panel`** for focused panel inspection (not full viewport)
|
||||
6. **Engine pixel-perfect capture:** Use `engine.get_composite_pixels()` for viewport-independent output (bypasses GUI rendering)
|
||||
|
||||
### Visual Design Iteration (Reference Image Workflow)
|
||||
|
||||
When the user provides reference images or design mockups, the AI agent MUST follow
|
||||
this workflow to ensure pixel-accurate results:
|
||||
|
||||
1. **Study the reference image first.** Before writing any code, analyze the
|
||||
reference image's layout, spacing, colours, font sizes, shadows, borders,
|
||||
and overall visual hierarchy. Note exact values where possible.
|
||||
|
||||
2. **Implement changes incrementally.** Apply one visual change at a time (e.g.
|
||||
background colour first, then typography, then spacing). After each change:
|
||||
- Take a screenshot (`F12` or `--screenshot-panel`)
|
||||
- Compare against the reference image
|
||||
- Note discrepancies and fix them before moving on
|
||||
|
||||
3. **Report visual diffs to the user.** After completing a set of changes,
|
||||
present a before/after comparison. Embed screenshots in the response so the
|
||||
user can visually validate the result without running the application.
|
||||
|
||||
4. **Respect the user's verbal corrections.** When the user says "move X left",
|
||||
"make Y darker", "the spacing is too wide", etc., apply the feedback
|
||||
literally and take a new screenshot immediately. Do not re-interpret or
|
||||
override the user's aesthetic judgment.
|
||||
|
||||
5. **Preserve design tokens across sessions.** If the user establishes a visual
|
||||
language (specific colours, font sizes, border radii), document them and
|
||||
reuse them consistently in subsequent panels and dialogs.
|
||||
|
||||
### API Details
|
||||
- **egui built-in:** `egui::ViewportCommand::Screenshot` — requests platform viewport capture
|
||||
- **egui event:** `egui::Event::Screenshot { image, .. }` — delivers `egui::ColorImage` on next frame
|
||||
- **image crate:** `image::save_buffer()` — writes raw RGBA bytes to PNG
|
||||
|
||||
---
|
||||
|
||||
# DOCUMENTATION
|
||||
|
||||
## Comprehensive Documentation Guidelines
|
||||
|
||||
For every function, method, struct, module, and file within the scope of work, add a detailed docstring/comment block at the very beginning. All documentation must be written in clear, professional English.
|
||||
|
||||
Each block must include:
|
||||
|
||||
* **Purpose:** A clear explanation of what this specific section, module, or function does.
|
||||
* **Logic & Workflow:** The underlying operational logic, algorithm, or step-by-step mechanism it uses to achieve its goal.
|
||||
* **Arguments & Returns (for functions/methods):** Explicit descriptions of input parameters (types and purposes) and return values.
|
||||
* **Side Effects / Dependencies (if any):** Any global state changes, I/O operations, or external system dependencies.
|
||||
|
||||
# DEBUGGING
|
||||
|
||||
Only apply while debugging operations.
|
||||
|
||||
## Role and Objective
|
||||
|
||||
Analyze the provided code and improve it by adding detailed documentation and strategic debug logging.
|
||||
|
||||
## Strategic Debug Logging Guidelines
|
||||
|
||||
To accelerate root-cause analysis and system observability, insert comprehensive debug logs throughout the codebase.
|
||||
|
||||
* **Placement:** Place logs at critical execution boundaries:
|
||||
* Entry and exit points of major functions (logging input parameters and final return values).
|
||||
* Inside conditional branches (`if` / `else`) to track the exact execution path.
|
||||
* Immediately before and after complex calculations, external API calls, or database operations.
|
||||
* Inside catch/except blocks to capture full error contexts and stack traces.
|
||||
* **Log Level:** Use the appropriate `DEBUG` or `TRACE` log level so these messages do not clutter production environments but can be fully enabled during troubleshooting.
|
||||
* **Contextual Data:** Ensure log messages include relevant runtime data variables, identifiers, or state structures (serialized safely) to provide actionable context for debugging.
|
||||
|
||||
---
|
||||
|
||||
# Execution Output Requirements
|
||||
|
||||
* Preserve all existing business logic; do not alter the functionality of the code.
|
||||
* Maintain the established code formatting style, indentation, and naming conventions of the source file.
|
||||
* Return the fully updated code enclosed in appropriate markdown code fences.
|
||||
Executable
+379
@@ -0,0 +1,379 @@
|
||||
# HCIE-Rust v3 — Teknik Referans
|
||||
|
||||
## Build Sırası
|
||||
```
|
||||
Katman 1: DATA
|
||||
- crates/hcie-protocol
|
||||
- crates/hcie-color
|
||||
Katman 2: MOTOR
|
||||
- crates/hcie-blend
|
||||
- crates/hcie-history
|
||||
- crates/hcie-brush-engine
|
||||
- crates/hcie-draw
|
||||
- crates/hcie-composite
|
||||
- crates/hcie-filter
|
||||
- crates/hcie-selection
|
||||
- crates/hcie-text
|
||||
- crates/hcie-vector
|
||||
- crates/hcie-tile
|
||||
- crates/hcie-io
|
||||
Katman 3: DOCUMENT
|
||||
- crates/hcie-document
|
||||
Katman 4: ENGINE API
|
||||
- crates/hcie-engine-api (staticlib + rlib)
|
||||
Katman 5: AI / VISION
|
||||
- crates/hcie-ai
|
||||
- crates/hcie-vision
|
||||
Katman 6: GUI (Açık Katman)
|
||||
- hcie-egui-app/crates/hcie-gui-egui/ (egui native uygulama)
|
||||
```
|
||||
|
||||
## Katman 1: DATA (Kilitli)
|
||||
|
||||
### hcie-protocol
|
||||
- Sadece struct, enum, const tanımları.
|
||||
- Logic yok. Method yok (getter/setter hariç).
|
||||
- `serde::{Serialize, Deserialize}` tüm tiplerde.
|
||||
|
||||
```rust
|
||||
pub struct Layer { ... }
|
||||
pub enum BlendMode { ... }
|
||||
pub enum VectorShape { ... }
|
||||
pub enum LayerType { ... }
|
||||
pub enum LayerData { ... }
|
||||
```
|
||||
|
||||
### hcie-color
|
||||
- Sadece pure fonksiyonlar.
|
||||
- No state, no heap allocation (runtime'ta).
|
||||
|
||||
```rust
|
||||
pub fn gamma_encode(linear: f32) -> f32;
|
||||
pub fn gamma_decode(srgb: f32) -> f32;
|
||||
pub fn srgb_to_linear(rgb: [u8; 3]) -> [f32; 3];
|
||||
pub fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (f32, f32, f32);
|
||||
pub fn rgb_to_hsl(r: f32, g: f32, b: f32) -> (f32, f32, f32);
|
||||
```
|
||||
|
||||
## Katman 2: MOTOR (Kilitli)
|
||||
|
||||
### hcie-blend
|
||||
- `blend_pixels(dst, src, mode, opacity)` — tek fonksiyon, tüm modlar.
|
||||
- 30+ match arm. Her mode ayrı inline helper.
|
||||
- `hcie-protocol::BlendMode` ve `hcie-color` bağımlılığı.
|
||||
|
||||
### hcie-history
|
||||
- `UndoableAction` trait'i.
|
||||
- `HistoryManager` generic struct'ı.
|
||||
- Closure-tabanlı. Tip-independent.
|
||||
- NO C FFI.
|
||||
|
||||
```rust
|
||||
pub trait UndoableAction: Send {
|
||||
fn undo(&mut self);
|
||||
fn redo(&mut self);
|
||||
fn description(&self) -> String;
|
||||
}
|
||||
```
|
||||
|
||||
### hcie-brush-engine
|
||||
- Procedural brush tip üretimi.
|
||||
- Pressure curve mapping.
|
||||
- Spacing + scatter + jitter matematiği.
|
||||
- Output: brush stamp (alpha mask olarak).
|
||||
|
||||
### hcie-draw
|
||||
- Rasterize: line, circle, brush stroke, flood fill.
|
||||
- `hcie-blend` ile piksel yazma.
|
||||
- `hcie-brush-engine` ile stamp sampling.
|
||||
|
||||
### hcie-composite
|
||||
- Layer'ları birleştirip RGBA output.
|
||||
- Tüm blend modları destekler.
|
||||
- Output: `Vec<u8>` (RGBA, premultiplied veya straight, belgelenmeli).
|
||||
|
||||
### hcie-filter
|
||||
- Her filter ayrı fonksiyon.
|
||||
- Box Blur, Gaussian Blur, Motion Blur, Mosaic, Oil Paint, Crystallize, Pinch, Twirl, Unsharp Mask, Brightness/Contrast, HSL, Border.
|
||||
- Input/output: `&[u8]` and `&mut [u8]` with known w/h.
|
||||
|
||||
### hcie-selection
|
||||
- Mask oluşturma: rect, ellipse, lasso, polygon.
|
||||
- Ops: grow, shrink, feather, invert.
|
||||
- Output: `Vec<u8>` mask 0-255.
|
||||
|
||||
### hcie-text
|
||||
- `fontdue` ile glyph rasterization.
|
||||
- Layout: left, center, right alignment.
|
||||
- Output: positioned glyphs + alpha mask'lar.
|
||||
|
||||
### hcie-vector
|
||||
- VectorShape render (stroke + fill).
|
||||
- Edit handle'ları (8 nokta + rotate).
|
||||
- Stroke path rasterization.
|
||||
|
||||
### hcie-tile
|
||||
- Sparse tile-based layer storage.
|
||||
- 256x256 sabit boyutlu tile yapısı (`TILE_SIZE = 256`).
|
||||
- `TiledLayer` ve `Tile` struct'ları ile sadece görünür bölgeleri bellekte tutarak heap allocation minimize edilir.
|
||||
- Verimli byte-tabanlı custom serde serialization/deserialization desteği.
|
||||
|
||||
### hcie-io
|
||||
- PNG, JPG, WebP, BMP, GIF → `hcie-protocol::Layer`
|
||||
- PSD import (layers with blend mode)
|
||||
- KRA import (zip-based, merged image)
|
||||
- HCIE native save/load (serde + bincode/ron)
|
||||
|
||||
## Katman 3: DOCUMENT (Kilitli)
|
||||
|
||||
### hcie-document
|
||||
- `Document` struct: layers list, active_layer, canvas dims, zoom, dirty
|
||||
- `HistoryManager<Box<dyn UndoableAction>>` (tip-independent, closure-based)
|
||||
- `add_layer()`, `delete_layer()`, `undo()`, `redo()`, `mark_dirty()`, `is_modified()`
|
||||
- Layer hierarchy (id/parent_id for groups)
|
||||
- Dirty tracking: composite_dirty, dirty_bounds per layer, global dirty flag
|
||||
|
||||
## Katman 4: ENGINE API (Kilitli)
|
||||
|
||||
### hcie-engine-api
|
||||
```rust
|
||||
pub struct Engine { /* opaque */ }
|
||||
|
||||
impl Engine {
|
||||
pub fn new(w: u32, h: u32) -> Self;
|
||||
pub fn add_layer(&mut self, name: &str) -> u64;
|
||||
pub fn delete_layer(&mut self, id: u64);
|
||||
pub fn set_active_layer(&mut self, id: u64);
|
||||
pub fn rename_layer(&mut self, id: u64, name: &str);
|
||||
pub fn set_layer_opacity(&mut self, id: u64, opacity: f32);
|
||||
pub fn set_layer_visible(&mut self, id: u64, visible: bool);
|
||||
pub fn set_layer_blend_mode(&mut self, id: u64, mode: BlendMode);
|
||||
|
||||
pub fn undo(&mut self) -> bool; // returns success
|
||||
pub fn redo(&mut self) -> bool; // returns success
|
||||
pub fn can_undo(&self) -> bool;
|
||||
pub fn can_redo(&self) -> bool;
|
||||
pub fn history_len(&self) -> usize;
|
||||
pub fn history_current(&self) -> i32;
|
||||
pub fn history_description(&self, idx: usize) -> String;
|
||||
pub fn jump_to_history(&mut self, idx: i32);
|
||||
|
||||
pub fn set_brush_size(&mut self, size: f32);
|
||||
pub fn set_brush_opacity(&mut self, opacity: f32);
|
||||
pub fn set_brush_color(&mut self, rgba: [u8; 4]);
|
||||
pub fn set_brush_style(&mut self, style: BrushStyle);
|
||||
pub fn draw_stroke(&mut self, points: &[(f32, f32, f32)]); // (x,y,pressure)
|
||||
|
||||
pub fn draw_line(&mut self, x0: f32, y0: f32, x1: f32, y1: f32);
|
||||
pub fn draw_rect(&mut self, x1: f32, y1: f32, x2: f32, y2: f32);
|
||||
pub fn draw_ellipse(&mut self, cx: f32, cy: f32, rx: f32, ry: f32);
|
||||
pub fn flood_fill(&mut self, x: u32, y: u32, color: [u8; 4]);
|
||||
|
||||
pub fn create_selection_rect(&mut self, x1: u32, y1: u32, x2: u32, y2: u32);
|
||||
pub fn create_selection_ellipse(&mut self, cx: u32, cy: u32, rx: u32, ry: u32);
|
||||
pub fn selection_grow(&mut self, px: u32);
|
||||
pub fn selection_shrink(&mut self, px: u32);
|
||||
pub fn selection_feather(&mut self, radius: f32);
|
||||
pub fn selection_invert(&mut self);
|
||||
pub fn selection_clear(&mut self);
|
||||
pub fn get_selection_mask(&self) -> Option<&[u8]>;
|
||||
|
||||
pub fn crop(&mut self, x: u32, y: u32, w: u32, h: u32);
|
||||
pub fn resize_canvas(&mut self, w: u32, h: u32);
|
||||
pub fn rotate_layer(&mut self, id: u64, degrees: f32);
|
||||
|
||||
pub fn apply_filter(&mut self, filter_id: &str, params: serde_json::Value);
|
||||
pub fn add_text_layer(&mut self, text: &str, font: &str, size: f32, x: f32, y: f32, color: [u8; 4]);
|
||||
pub fn add_vector_shape(&mut self, shape: VectorShape);
|
||||
pub fn modify_vector_shape(&mut self, layer_id: u64, shape_idx: usize, handle: VectorEditHandle, dx: f32, dy: f32);
|
||||
pub fn set_vector_shape_color(&mut self, layer_id: u64, shape_idx: usize, color: [u8; 4]);
|
||||
pub fn set_vector_shape_opacity(&mut self, layer_id: u64, shape_idx: usize, opacity: f32);
|
||||
pub fn delete_vector_shape(&mut self, layer_id: u64, shape_idx: usize);
|
||||
|
||||
pub fn open_image(&mut self, path: &std::path::Path) -> Result<(), String>;
|
||||
pub fn save_as(&mut self, path: &std::path::Path, format: &str) -> Result<(), String>;
|
||||
pub fn save_native(&mut self, path: &std::path::Path) -> Result<(), String>;
|
||||
pub fn export_layer(&mut self, layer_id: u64, path: &std::path::Path, format: &str) -> Result<(), String>;
|
||||
|
||||
pub fn set_zoom(&mut self, zoom: f32);
|
||||
pub fn get_zoom(&self) -> f32;
|
||||
pub fn get_canvas_size(&self) -> (u32, u32);
|
||||
pub fn get_layer_count(&self) -> usize;
|
||||
pub fn get_layer_info(&self, id: u64) -> Option<LayerInfo>;
|
||||
pub fn get_active_layer_id(&self) -> u64;
|
||||
pub fn get_all_layer_ids(&self) -> Vec<u64>;
|
||||
pub fn get_layer_pixels(&self, id: u64) -> Option<&[u8]>;
|
||||
pub fn get_composite_pixels(&mut self) -> Vec<u8>; // Render + return RGBA
|
||||
pub fn get_preview_thumbnail(&self, id: u64, max_dim: u32) -> Vec<u8>; // Small thumbnail
|
||||
|
||||
pub fn is_modified(&self) -> bool;
|
||||
pub fn set_modified(&mut self, modified: bool);
|
||||
pub fn tab_label(&self) -> String;
|
||||
|
||||
// Batch AI actions
|
||||
pub fn execute_ai_actions(&mut self, actions: serde_json::Value) -> Result<String, String>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LayerInfo {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub layer_type: LayerType,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub visible: bool,
|
||||
pub opacity: f32,
|
||||
pub blend_mode: BlendMode,
|
||||
pub locked: bool,
|
||||
pub parent_id: Option<u64>,
|
||||
pub clipping_mask: bool,
|
||||
pub collapsed: bool,
|
||||
pub has_vector_shapes: bool,
|
||||
pub shape_count: usize,
|
||||
}
|
||||
```
|
||||
|
||||
## Katman 5: AI / VISION (Kilitli)
|
||||
|
||||
### hcie-ai
|
||||
- Ollama `/api/chat` client
|
||||
- LMStudio client
|
||||
- Smart Select (SAM): segmentation model call
|
||||
- Inpaint: ComfyUI inpaint node
|
||||
- Object Removal: mask-based generation
|
||||
- `hcie-engine-api::Engine` passthrough (sadece API çağrıları)
|
||||
|
||||
### hcie-vision
|
||||
- ComfyUI pipeline runner
|
||||
- Background Removal (RMBG)
|
||||
- Procedural Texture Generation
|
||||
- Sky Replacement
|
||||
- AI Scoring / Live Drawing Review
|
||||
- `hcie-engine-api::Engine` passthrough
|
||||
|
||||
## Katman 6: GUI (Açık — AI Burada Çalışır)
|
||||
|
||||
### hcie-gui-egui (Ana Uygulama)
|
||||
- **Binary Entry & Core Orchestration** (`main.rs`, `app/mod.rs`): Runs `eframe::run_native` with custom borderless decorations, sets up event-loop shortcuts, manages global keyboard/clipboard states, and maintains an opaque reference to `hcie-engine-api::Engine`.
|
||||
- **Canvas Rendering Engine** (`canvas.rs`): Draws pixel-precise canvas displays with robust zoom, pan, rotation, and captures tablet pressure curves during brush operations.
|
||||
- **Event Messaging System** (`event_bus.rs`): Decouples components by dispatching high-level interactive signals (e.g. layers selection updates, file dialog completions).
|
||||
- **Layout Solver System** (`app/layout_solver.rs`): Advanced layout constraint solver for `egui_dock`:
|
||||
- **Symmetrical Dock Trees**: Symmetrical dual-column utility panels on both sides of the screen. Left columns host Tools/Filters & Brushes; right columns host Layers/Properties & Color/History/AI.
|
||||
- **Stable Phase (Measurement)**: Dynamically measures actual rendered column widths when layout is stable to preserve user-adjusted splitter dragging states.
|
||||
- **Resize Phase (Enforcement)**: Enforces pixel widths by computing precise horizontal splits recursively when a container resize triggers, ensuring dynamic canvas adjustments.
|
||||
- **Docking Panels Registry** (`app/dock.rs`, `app/panels.rs`, etc.): Modular panel drawing controllers (Brushes Panel, Colors Panel, Geometry Panel, History, Toolbox).
|
||||
- **Dynamic File Decoders** (`app/custom_loaders.rs`, `app/custom_loaders_kra_v2.rs`): Specialized parser models for layered formats like Krita (.kra).
|
||||
- **Automation Framework** (`plugin.rs`, `plugins/`): Modular host environment for system diagnostic logging plugins and custom scripts.
|
||||
- **Internationalization & Manifest** (`i18n.rs`, `manifest.rs`): Built-in multi-language translation lookups and pricing tier feature manifests.
|
||||
|
||||
|
||||
|
||||
**Sürükleme Stratejisi (İkili Mod):**
|
||||
1. PRIMARY: `invoke('window_start_drag')` → GTK native drag (X11'de çalışır,
|
||||
Wayland/GTK4'de xdg-shell move request ile çalışabilir)
|
||||
2. FALLBACK: Manual pointer-based drag → `outerPosition()` + `setPosition()`
|
||||
PhysicalPosition→LogicalPosition dönüşümü ile (HiDPI düzeltmesi)
|
||||
|
||||
**Detay:** `WAYLAND_WINDOW_FIX.md`
|
||||
|
||||
### hcie-gui-adapter (Bridge)
|
||||
- **Dynamic Schema UI Binder**: Bridges public engine APIs with standard egui elements by reading declarative layout structures (`ModulePanel` and `WidgetDescription`) from `hcie-protocol` and mapping them dynamically into standard control nodes.
|
||||
- **Unified Change Dispatcher**: Listens for state modifications in sliders, color pickers, checkboxes, button triggers, and dropdowns, then broadcasts the updates using a generic JSON-serialized callback.
|
||||
- **Key API Bindings**:
|
||||
```rust
|
||||
pub fn render_module_panel(ui: &mut egui::Ui, panel: &ModulePanel, on_change: &mut impl FnMut(&str, serde_json::Value));
|
||||
pub fn render_widget(ui: &mut egui::Ui, widget: &WidgetDescription, on_change: &mut impl FnMut(&str, serde_json::Value));
|
||||
pub fn get_widget_value<T: Clone + Send + Sync + 'static>(ui: &egui::Ui, id: &str) -> Option<T>;
|
||||
pub fn set_widget_value<T: Clone + Send + Sync + 'static>(ui: &egui::Ui, id: &str, value: T);
|
||||
```
|
||||
|
||||
### hcie-module-filters
|
||||
- Blur, Sharpen, B-C, HSL, Mosaic, Oil Paint, Crystallize, Pinch, Twirl, USM, Border
|
||||
- Slider'lar, dropdown'lar
|
||||
- Apply → `EngineCommand::ApplyFilter`
|
||||
|
||||
### hcie-module-ai-chat
|
||||
- Chat panel
|
||||
- AI query input
|
||||
- Response display
|
||||
- Action buttons (Smart Select, Inpaint, Remove)
|
||||
- Execute → `EngineCommand::ExecuteAiActions`
|
||||
|
||||
### hcie-module-script
|
||||
- Scripting ve otomasyon paneli.
|
||||
- DSL tabanlı veya parametrik şablonları (templates) yürütme motoru.
|
||||
- Kullanıcı komut dizilerini kaydetme ve çalıştırma arayüzü.
|
||||
|
||||
### hcie-module-ai-script
|
||||
- Yapay zeka destekli kod/script oluşturma ve yürütme paneli.
|
||||
- AI chat modülü ile script yürütücü arasındaki köprü görevi.
|
||||
|
||||
## Event Flow (GUI → Engine)
|
||||
|
||||
```
|
||||
User clicks Canvas
|
||||
→ hcie-gui-egui mouse event
|
||||
→ Engine::draw_stroke(points) [hcie-engine-api call]
|
||||
→ hcie-document layer modification [internal]
|
||||
→ hcie-draw pixel rasterization [internal]
|
||||
→ hcie-blend pixel compositing [internal]
|
||||
→ hcie-composite full render [internal]
|
||||
→ back to GUI: trigger repaint
|
||||
→ GUI requests Engine::get_composite_pixels() → display
|
||||
```
|
||||
|
||||
## Render Flow (GUI Loop)
|
||||
|
||||
Each frame:
|
||||
1. GUI collects all panel commands (sliders moved, buttons clicked)
|
||||
2. GUI sends all `EngineCommand` to `engine.execute_batch(commands)`
|
||||
3. Engine processes internally (document state, dirty layers, history)
|
||||
4. If `dirty`: engine composite happens → RGBA output buffer
|
||||
5. GUI reads `engine.get_composite_pixels()` → uploads to `egui::TextureHandle`
|
||||
6. GUI draws texture in canvas widget
|
||||
|
||||
## Kilitli Dosya Listesi (AI yazamaz)
|
||||
```
|
||||
crates/hcie-protocol/src/*.rs
|
||||
crates/hcie-color/src/*.rs
|
||||
crates/hcie-blend/src/*.rs
|
||||
crates/hcie-history/src/*.rs
|
||||
crates/hcie-brush-engine/src/*.rs
|
||||
crates/hcie-draw/src/*.rs
|
||||
crates/hcie-composite/src/*.rs
|
||||
crates/hcie-filter/src/*.rs
|
||||
crates/hcie-selection/src/*.rs
|
||||
crates/hcie-text/src/*.rs
|
||||
crates/hcie-vector/src/*.rs
|
||||
crates/hcie-tile/src/*.rs
|
||||
crates/hcie-io/src/*.rs
|
||||
crates/hcie-document/src/*.rs
|
||||
crates/hcie-engine-api/src/*.rs
|
||||
crates/hcie-ai/src/*.rs
|
||||
crates/hcie-vision/src/*.rs
|
||||
```
|
||||
|
||||
## Açık Dosya Listesi (AI yazar)
|
||||
```
|
||||
hcie-egui-app/crates/hcie-gui-egui/src/*.rs
|
||||
hcie-egui-app/crates/egui-panel-adapter/src/*.rs
|
||||
hcie-egui-app/crates/egui-panel-filters/src/*.rs
|
||||
hcie-egui-app/crates/egui-panel-ai-chat/src/*.rs
|
||||
hcie-egui-app/crates/egui-panel-script/src/*.rs
|
||||
hcie-egui-app/crates/egui-panel-ai-script/src/*.rs
|
||||
```
|
||||
|
||||
## AI Session Limits
|
||||
- Max 5 files modified per session
|
||||
- Max 200 lines written per new file
|
||||
- If compile fails, revert to last known good state, do NOT patch incrementally
|
||||
- All UI changes must include `trace_ui!(name, ui)` and `trace_event!(event)` conditionally
|
||||
- After any UI change, verify coordinates via terminal output before marking complete
|
||||
|
||||
## Coding Protocol (v3)
|
||||
1. Prefer creating new .rs files over modifying existing ones; minimize changes to existing GUI files.
|
||||
2. NEVER add egui dependency to engine crates
|
||||
3. ONLY create new files in GUI and module crates
|
||||
4. Engine API changes require explicit user approval (unlock protocol)
|
||||
5. New feature = new .rs file + maybe new module crate with Cargo.toml
|
||||
6. After writing code, run `cargo check` and `cargo test` before saying "done"
|
||||
+6896
File diff suppressed because it is too large
Load Diff
Executable
+140
@@ -0,0 +1,140 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"hcie-protocol",
|
||||
"hcie-color",
|
||||
"hcie-blend",
|
||||
"hcie-history",
|
||||
"hcie-brush-engine",
|
||||
"hcie-draw",
|
||||
"hcie-composite",
|
||||
"hcie-filter",
|
||||
"hcie-selection",
|
||||
"hcie-text",
|
||||
"hcie-vector",
|
||||
"hcie-io",
|
||||
"hcie-document",
|
||||
"hcie-engine-api",
|
||||
"hcie-ai",
|
||||
"hcie-vision",
|
||||
"hcie-tile",
|
||||
"hcie-fx",
|
||||
"hcie-psd",
|
||||
"hcie-psd-saver",
|
||||
"hcie-kra",
|
||||
"hcie-native",
|
||||
"hcie-egui-app/crates/hcie-gui-egui",
|
||||
"hcie-egui-app/crates/egui-panel-adapter",
|
||||
"hcie-egui-app/crates/egui-panel-filters",
|
||||
"hcie-egui-app/crates/egui-panel-ai-chat",
|
||||
"hcie-egui-app/crates/egui-panel-script",
|
||||
"hcie-egui-app/crates/egui-panel-ai-script",
|
||||
"tools/screenshot-diff",
|
||||
"tools/panel-tuner",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp"] }
|
||||
rayon = "1.10"
|
||||
log = "0.4"
|
||||
rand = "0.8"
|
||||
rand_distr = "0.4"
|
||||
bincode = "1.3"
|
||||
|
||||
# Engine-internal (no GUI)
|
||||
fastnoise-lite = "1.1"
|
||||
fontdue = "0.9"
|
||||
zip = { version = "2.2", default-features = false, features = ["deflate"] }
|
||||
quick-xml = "0.36"
|
||||
usvg = "0.43"
|
||||
resvg = "0.43"
|
||||
tiny-skia = "0.11"
|
||||
|
||||
# GUI deps
|
||||
egui = "0.34"
|
||||
eframe = { version = "0.34", default-features = false, features = ["default_fonts", "glow", "persistence", "wayland", "x11"] }
|
||||
egui_extras = { version = "0.34", features = ["svg", "image"] }
|
||||
egui_dock = { version = "0.19", features = ["serde"] }
|
||||
grimdock = { version = "0.2.0", features = ["serde"] }
|
||||
rfd = "0.15"
|
||||
|
||||
# Utilities
|
||||
base64 = "0.22"
|
||||
reqwest = { version = "0.12", features = ["json", "multipart", "blocking", "stream"] }
|
||||
tokio = { version = "1.40", features = ["full"] }
|
||||
async-trait = "0.1"
|
||||
anyhow = "1.0"
|
||||
futures-util = "0.3"
|
||||
arboard = "3.4"
|
||||
env_logger = "0.11"
|
||||
|
||||
# Test frameworks
|
||||
rstest = "0.23"
|
||||
proptest = "1.5"
|
||||
approx = "0.5"
|
||||
|
||||
# ════════════════════════════════════════════════════════════════════
|
||||
# [LOCKED_PERF_PROFILES_START]
|
||||
#
|
||||
# ⚠️ AI WARNING — DO NOT REMOVE OR MODIFY THIS BLOCK ⚠️
|
||||
#
|
||||
# opt-level=0 makes pixel loops 10-50x slower.
|
||||
# This profile is MANDATORY for development.
|
||||
# ════════════════════════════════════════════════════════════════════
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
codegen-units = 1
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.hcie-protocol]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-color]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-blend]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-history]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-brush-engine]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-draw]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-composite]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-filter]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-selection]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-text]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-vector]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-io]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-document]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-engine-api]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-tile]
|
||||
opt-level = 3
|
||||
[profile.dev.package.hcie-ai]
|
||||
opt-level = 2
|
||||
[profile.dev.package.hcie-vision]
|
||||
opt-level = 2
|
||||
|
||||
# [LOCKED_PERF_PROFILES_END]
|
||||
# ════════════════════════════════════════════════════════════════════
|
||||
|
||||
[patch.crates-io]
|
||||
egui-winit = { path = "patches/egui-winit" }
|
||||
grimdock = { path = "patches/grimdock" }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
@@ -0,0 +1,21 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
pkg-config \
|
||||
qt6-base-dev \
|
||||
libqt6opengl6-dev \
|
||||
qt6-tools-dev \
|
||||
libssl-dev \
|
||||
pkg-config \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Rust
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||
|
||||
WORKDIR /app
|
||||
Executable
+489
@@ -0,0 +1,489 @@
|
||||
# Chat Conversation
|
||||
|
||||
Note: _This is purely the output of the chat conversation and does not contain any raw data, codebase snippets, etc. used to generate the output._
|
||||
|
||||
### User Input
|
||||
|
||||
@hcie-fx/src/stroke.rs @hcie-io/examples/tune_mae_stroke.rs program çok kısa sürüyor mae:64
|
||||
|
||||
*Viewed [tune_mae_stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_stroke.rs) *
|
||||
|
||||
*Viewed [stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/stroke.rs) *
|
||||
|
||||
*Viewed [tuned.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/tuned.rs) *
|
||||
|
||||
*Viewed [tune_mae_outer_glow.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_outer_glow.rs) *
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
*Viewed [task-13.log](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/.system_generated/tasks/task-13.log) *
|
||||
|
||||
*Viewed [task-13.log](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/.system_generated/tasks/task-13.log) *
|
||||
|
||||
*Grep searched codebase*
|
||||
|
||||
*Viewed [emboss.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/emboss.rs) *
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*User accepted the command `rustc /home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/scratch/edt_test.rs -o /home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/scratch/edt_test && /home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/scratch/edt_test`*
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*User accepted the command `rustc /home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/scratch/edt_test.rs -o /home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/scratch/edt_test && /home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/scratch/edt_test`*
|
||||
|
||||
*Grep searched codebase*
|
||||
|
||||
*Grep searched codebase*
|
||||
|
||||
*Viewed [shadow.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/shadow.rs) *
|
||||
|
||||
*Viewed [emboss.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/emboss.rs) *
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*Viewed [tuned.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/tuned.rs) *
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
*Viewed [tune_mae_shadow.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_shadow.rs) *
|
||||
|
||||
*Grep searched codebase*
|
||||
|
||||
*Grep searched codebase*
|
||||
|
||||
*Viewed [outer_glow.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/outer_glow.rs) *
|
||||
|
||||
*Viewed [tune_mae_stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_stroke.rs) *
|
||||
|
||||
*Viewed [tune_mae_stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_stroke.rs) *
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
*Viewed [test_sultan.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/test_sultan.rs) *
|
||||
|
||||
*Viewed [tune_mae_stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_stroke.rs) *
|
||||
|
||||
*Viewed [tune_mae_stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_stroke.rs) *
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*Viewed [tune_mae_stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_stroke.rs) *
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
*Viewed [task-89.log](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/.system_generated/tasks/task-89.log) *
|
||||
|
||||
*Viewed [task-89.log](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/.system_generated/tasks/task-89.log) *
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
*Viewed [tune_mae_stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_stroke.rs) *
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
*Viewed [task-108.log](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/.system_generated/tasks/task-108.log) *
|
||||
|
||||
*Viewed [tune_mae_stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_stroke.rs) *
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
*Viewed [task-121.log](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/.system_generated/tasks/task-121.log) *
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
*Viewed [task-130.log](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/.system_generated/tasks/task-130.log) *
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
*Viewed [task-141.log](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/.system_generated/tasks/task-141.log) *
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
*Viewed [task-152.log](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/.system_generated/tasks/task-152.log) *
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
*Viewed [task-163.log](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/.system_generated/tasks/task-163.log) *
|
||||
|
||||
*Viewed [tuned.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/tuned.rs) *
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will start by checking the files in the workspace to understand the codebase and current status. Let's list the contents of the root directory of the workspace.
|
||||
|
||||
*Listed directory [hcie-rust-v4](file:///mnt/extra/00_PROJECTS/hcie-rust-v4) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will check the files inside the brain directory for the current conversation to see if there are any existing plans or logs.
|
||||
|
||||
*Listed directory [0a471a2c-ead7-4674-b089-6007a0402b3e](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will check the scratch directory inside the conversation's brain directory to see if there are files mentioned in the resume summary.
|
||||
|
||||
*Listed directory [scratch](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/scratch) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will examine `tune_mae_stroke.rs` to understand the setup and what the current code looks like.
|
||||
|
||||
*Viewed [tune_mae_stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_stroke.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will run the stroke tuning example to observe the current output and behavior.
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will wait for the background command to finish execution.
|
||||
|
||||
*Viewed [tuned.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/tuned.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will examine the content of `hcie-fx/src/stroke.rs` to understand the original untuned stroke algorithm and how it works.
|
||||
|
||||
*Viewed [stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/stroke.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will view the rest of the EDT functions in `hcie-fx/src/tuned.rs` to see how `edt_outside_src` finds the nearest foreground pixel index.
|
||||
|
||||
*Viewed [tuned.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/tuned.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will view `hcie-io/examples/tune_mae_outer_glow.rs` to see how it performs tuning and auto-patches parameters.
|
||||
|
||||
*Viewed [tune_mae_outer_glow.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_outer_glow.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will view the beginning of `hcie-fx/src/tuned.rs` to see what functions are defined there and how they are structured.
|
||||
|
||||
*Viewed [tuned.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/tuned.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will view lines 250 to 500 of `hcie-fx/src/tuned.rs` to find the tuned stroke implementation.
|
||||
|
||||
*Viewed [tuned.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/tuned.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will view the next segment of `hcie-fx/src/tuned.rs` to locate the tuned stroke implementation.
|
||||
|
||||
*Viewed [tuned.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/tuned.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will view the `edt_test.rs` file inside the scratch directory to see what was tested previously.
|
||||
|
||||
*Viewed [edt_test.rs](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/scratch/edt_test.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will search for occurrences of `generate_stroke` and `generate_stroke_tuned` to see where they are called.
|
||||
|
||||
*Grep searched codebase*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will view `hcie-fx/src/shadow.rs` to see how strokes are currently generated and integrated within the layer effects pipeline.
|
||||
|
||||
*Viewed [shadow.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/shadow.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will view the beginning of `hcie-fx/src/shadow.rs` to see if there is a tuned version of the layer effects function, or if the library switches between them.
|
||||
|
||||
*Viewed [shadow.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/shadow.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will view the end of `hcie-fx/src/shadow.rs` to verify the rest of the file content.
|
||||
|
||||
*Viewed [shadow.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/shadow.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will search the workspace for uses of the module `tuned` to see where these tuned functions are used.
|
||||
|
||||
*Grep searched codebase*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will search for `tuned` module declarations to see if it is exposed.
|
||||
|
||||
*Grep searched codebase*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will search for `TUNING_ZONE` in the workspace to see where and how parameters are tuned and stored.
|
||||
|
||||
*Grep searched codebase*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will check my current permission grants to ensure I can read and write files inside the workspace.
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will create the implementation plan to outline the research, proposed algorithm optimization, tuning strategy, and verification steps.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I have created the implementation plan for optimizing the stroke rendering algorithm.
|
||||
|
||||
You can find the detailed plan here: [implementation_plan.md](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/implementation_plan.md).
|
||||
|
||||
Please review the plan and let me know if you approve so I can proceed with the implementation!
|
||||
|
||||
### User Input
|
||||
|
||||
/mnt/extra/00_PROJECTS/hcie-rust-v4/psd-test-images/stroke/ bu klasörde ve alt klasöründe 2 adet set olarak 4 katmanlı stroke şekilleri var. stroke outside ve stroke inside olarak çizdim. png ve psd olarak kaydettim. MAE tune için bunları kullansın.
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will list the contents of the `/mnt/extra/00_PROJECTS/hcie-rust-v4/psd-test-images/stroke/` directory to discover the test files.
|
||||
|
||||
*Listed directory [stroke](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/psd-test-images/stroke) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will list the contents of `/mnt/extra/00_PROJECTS/hcie-rust-v4/psd-test-images/stroke/stroke-inside`.
|
||||
|
||||
*Listed directory [stroke-inside](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/psd-test-images/stroke/stroke-inside) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will list the contents of `/mnt/extra/00_PROJECTS/hcie-rust-v4/psd-test-images/stroke/stroke-outside`.
|
||||
|
||||
*Listed directory [stroke-outside](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/psd-test-images/stroke/stroke-outside) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will update the implementation plan to use the new stroke test suites provided by the user. I'll research the layers and structure of the new files first, then design the parameter tuning to run on all of them.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will create a scratch script to inspect the layers and effects of the new PSD files.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will write the diagnostic script directly to `hcie-io/examples/inspect_stroke_psd.rs` so that it runs within the workspace environment with access to all internal crates.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will run the `inspect_stroke_psd` example to inspect the layers and effects of the test PSD files.
|
||||
|
||||
*User accepted the command `cargo run --example inspect_stroke_psd`*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will update the `inspect_stroke_psd.rs` tool to verify the dimensions of the reference PNGs and whether they match the dimensions of the PSD layers.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will execute `inspect_stroke_psd` to check if all reference PNG files are present and match in size.
|
||||
|
||||
*User accepted the command `cargo run --example inspect_stroke_psd`*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will check the `Cargo.toml` configuration to see the dev/release profile optimization settings.
|
||||
|
||||
*Viewed [Cargo.toml](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/Cargo.toml) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will create the task tracking document to organize the implementation steps.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will modify `hcie-fx/src/tuned.rs` to make `edt_inside` and `edt_outside` public so that the EDT stroke rendering in `stroke.rs` can reuse them.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will replace the body of `generate_stroke` in `hcie-fx/src/stroke.rs` with the optimized, EDT-based implementation featuring the new parameterizable tuning zone.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### User Input
|
||||
|
||||
COPY OLANIN ADINI DÜRELTTİM
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will check the files in `stroke-inside` directory to confirm the name correction.
|
||||
|
||||
*Listed directory [stroke-inside](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/psd-test-images/stroke/stroke-inside) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will rewrite `tune_mae_stroke.rs` to load the new test sets, run the baseline comparison, perform a parameter grid search to find the optimal EDT parameters, print detailed statistics, and patch the optimized parameters back to the codebase.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will run the updated stroke tuning program to evaluate the baseline error and perform grid search on the new parameters.
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will move `struct EdtPrecomputed` to the module level (outside of `main`) so it is accessible by `render_stroke_tuned`.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will view the top of `tune_mae_stroke.rs` to locate where to place the `EdtPrecomputed` struct.
|
||||
|
||||
*Viewed [tune_mae_stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_stroke.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will place the `EdtPrecomputed` struct definition right below `TestCase` at the module level.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will now run the `tune_mae_stroke` example program to execute the grid search and find the optimal parameters.
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will wait for the tuning run to complete.
|
||||
|
||||
*Viewed [stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/stroke.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will run `cargo test` to ensure that all automated tests in the workspace pass.
|
||||
|
||||
*User accepted the command `cargo test`*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will run tests specifically for the modified `hcie-fx` crate to verify it passes.
|
||||
|
||||
*User accepted the command `cargo test -p hcie-fx`*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will view the relevant lines of `hcie-fx/src/stroke.rs` to locate the unnecessary parentheses and clean them up.
|
||||
|
||||
*Viewed [stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/stroke.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will clean up the unnecessary parentheses warnings in `hcie-fx/src/stroke.rs`.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will view the relevant section of `tune_mae_stroke.rs` to clean up the unnecessary parentheses warning there as well.
|
||||
|
||||
*Viewed [tune_mae_stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_stroke.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will clean up the unnecessary parentheses warnings in `tune_mae_stroke.rs` as well.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will view the `stroke_results.txt` output file to see the final results summary generated by the program.
|
||||
|
||||
*Viewed [stroke_results.txt](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/stroke_results.txt) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will re-run the tuning example one final time to verify there are no compile warnings and everything compiles perfectly.
|
||||
|
||||
*User accepted the command `cargo run --example tune_mae_stroke`*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will wait for the confirmation run to finish.
|
||||
|
||||
*Viewed [stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/stroke.rs) *
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will now create the walkthrough artifact to document the implementation and the tuning results.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I will update the task checklist to mark all items as successfully completed.
|
||||
|
||||
*Edited relevant file*
|
||||
|
||||
### Planner Response
|
||||
|
||||
I have successfully completed the optimization and parameter tuning for the stroke rendering engine using your newly provided inside and outside test sets.
|
||||
|
||||
Here is a summary of the accomplishments:
|
||||
1. **Algorithm Optimization**: Replaced the slow $O(W \times H \times R^2)$ loop-based stroke algorithm in [stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/stroke.rs) with a highly performant $O(W \times H)$ EDT-based algorithm. To preserve visual rendering quality, I implemented an interior shape alpha sampling technique that traces inward along the boundary normal by `normal_step` pixels, resolving EDT quality limitations.
|
||||
2. **Comprehensive Multi-Layer Tuning**: Refactored the tuning tool [tune_mae_stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-io/examples/tune_mae_stroke.rs) to load the 8 test layers across `stroke-inside.psd` and `stroke-outside.psd` and automatically search for the optimal parameters.
|
||||
3. **Pristine Quality & Speed**: The tuned EDT implementation achieved an **average MAE of 1.7113**, outperforming the original baseline (1.8116) by **5.5%** while running near-instantaneously regardless of the stroke size.
|
||||
4. **Auto-Patching**: The optimal parameters (`aa = 1.00`, `feather = 0.25`, `normal_step = 5.00`) have been written directly to [stroke.rs](file:///mnt/extra/00_PROJECTS/hcie-rust-v4/hcie-fx/src/stroke.rs).
|
||||
|
||||
For details, please refer to the [walkthrough.md](file:///home/hc/.gemini/antigravity-ide/brain/0a471a2c-ead7-4674-b089-6007a0402b3e/walkthrough.md) artifact.
|
||||
@@ -0,0 +1,29 @@
|
||||
# app/ Klasörü Refactor - Amaç ve Bağlam
|
||||
|
||||
## Problem
|
||||
`hcie-gui-egui/src/app/` klasöründe 25+ dosya aynı düzeyde (flat), kategorisiz duruyor.
|
||||
Bu yüzden:
|
||||
- Sen (AI ajanı) bir değişiklik yaparken hangi dosyanın neyle ilişkili olduğunu
|
||||
kestiremiyorsun, sık sık ilgisiz modüllere sızıntı yapan bağımlılıklar ekliyorsun.
|
||||
- Zaman içinde aynı işi çözen birden fazla dosya oluşmuş olabilir
|
||||
(örn. dock_controller.rs / dock_engine.rs / grim_dock.rs gibi isim benzerlikleri
|
||||
şüpheli - muhtemelen biri diğerinin yeniden yazımı ve artık ölü kod).
|
||||
|
||||
## Amaç
|
||||
1. Dosyaları mantıksal alt klasörlere ayır (panel'ler, docking/layout, dialoglar,
|
||||
dosya/format okuma, kullanıcı düzenleme aksiyonları gibi doğal kategoriler var -
|
||||
sen dosya isimlerine ve içeriklerine bakarak en uygun gruplamaya karar ver).
|
||||
2. Her alt klasörün `mod.rs`'i net bir sözleşme olsun: dışarıya sadece gerçekten
|
||||
ihtiyaç duyulan tipler/fonksiyonlar açılsın, geri kalanı private kalsın.
|
||||
3. Kullanılmayan (import edilmeyen) dosyaları tespit et ama SİLME - bana rapor et,
|
||||
silme kararını ben vereceğim.
|
||||
4. Davranışta hiçbir değişiklik olmamalı, sadece organizasyon değişsin.
|
||||
|
||||
## Sınırlar
|
||||
- İçerik mantığını değiştirme, sadece taşı/organize et.
|
||||
- Emin olmadığın kararlarda (bir dosya iki kategoriye de uyuyorsa, ya da
|
||||
"kullanılmıyor" sandığın bir şey aslında kullanılıyorsa) bana sor, tahmin etme.
|
||||
- İlerlerken düzenli commit at ki geri dönebilelim.
|
||||
- Büyük değişiklik yapmadan önce bana planını kısaca özetle, onay al, sonra uygula.
|
||||
|
||||
Yöntemi (hangi sırayla ilerleyeceğini, hangi araçları kullanacağını) sen belirle.
|
||||
@@ -0,0 +1,187 @@
|
||||
# HCIE Rust v4 Çalıştırma Yardımı
|
||||
|
||||
Bu dosya `/mnt/extra/00_PROJECTS/hcie-rust-v4` kök dizini için hazırlanmıştır. Aşağıdaki komutlar aksi belirtilmedikçe bu kök dizinden çalıştırılır.
|
||||
|
||||
## Toplu komutlar
|
||||
|
||||
```bash
|
||||
# Root workspace'deki tüm paketleri test et
|
||||
cargo test --workspace
|
||||
|
||||
# Root workspace'deki tüm hedefleri test derlemesi yap: lib, bin, test, example, bench
|
||||
cargo test --workspace --all-targets --no-run
|
||||
|
||||
# Root workspace'deki tüm hedefleri çalıştırılabilir test modunda çalıştır
|
||||
cargo test --workspace --all-targets
|
||||
|
||||
# Tek bir paketin tüm testlerini çalıştır
|
||||
cargo test -p <paket-adi>
|
||||
|
||||
# Tek bir integration test dosyasını çalıştır
|
||||
cargo test -p <paket-adi> --test <test-adi>
|
||||
|
||||
# Tek bir example çalıştır
|
||||
cargo run -p <paket-adi> --example <example-adi>
|
||||
|
||||
# Tek bir binary/app çalıştır
|
||||
cargo run -p <paket-adi> --bin <binary-adi>
|
||||
```
|
||||
|
||||
## Uygulamalar / Binary hedefleri
|
||||
|
||||
| Uygulama | Paket | Kök dizinden komut | Kaynak |
|
||||
|---|---|---|---|
|
||||
| `hcie-gui` | `hcie-gui-egui` | `cargo run -p hcie-gui-egui --bin hcie-gui` | `hcie-egui-app/crates/hcie-gui-egui/src/main.rs` |
|
||||
|
||||
### Alternatif uygulama komutları
|
||||
|
||||
```bash
|
||||
# Egui native uygulaması, ilgili crate dizininden
|
||||
cd hcie-egui-app/crates/hcie-gui-egui && cargo run --bin hcie-gui
|
||||
```
|
||||
|
||||
## Paket test komutları
|
||||
|
||||
| Paket | Komut |
|
||||
|---|---|
|
||||
| `egui-panel-adapter` | `cargo test -p egui-panel-adapter` |
|
||||
| `egui-panel-ai-chat` | `cargo test -p egui-panel-ai-chat` |
|
||||
| `egui-panel-ai-script` | `cargo test -p egui-panel-ai-script` |
|
||||
| `egui-panel-filters` | `cargo test -p egui-panel-filters` |
|
||||
| `egui-panel-script` | `cargo test -p egui-panel-script` |
|
||||
| `hcie-ai` | `cargo test -p hcie-ai` |
|
||||
| `hcie-blend` | `cargo test -p hcie-blend` |
|
||||
| `hcie-brush-engine` | `cargo test -p hcie-brush-engine` |
|
||||
| `hcie-color` | `cargo test -p hcie-color` |
|
||||
| `hcie-composite` | `cargo test -p hcie-composite` |
|
||||
| `hcie-document` | `cargo test -p hcie-document` |
|
||||
| `hcie-draw` | `cargo test -p hcie-draw` |
|
||||
| `hcie-engine-api` | `cargo test -p hcie-engine-api` |
|
||||
| `hcie-filter` | `cargo test -p hcie-filter` |
|
||||
| `hcie-fx` | `cargo test -p hcie-fx` |
|
||||
| `hcie-gui-egui` | `cargo test -p hcie-gui-egui` |
|
||||
| `hcie-history` | `cargo test -p hcie-history` |
|
||||
| `hcie-io` | `cargo test -p hcie-io` |
|
||||
| `hcie-kra` | `cargo test -p hcie-kra` |
|
||||
| `hcie-native` | `cargo test -p hcie-native` |
|
||||
| `hcie-pixel-bridge` | `cargo test -p hcie-pixel-bridge` |
|
||||
| `hcie-protocol` | `cargo test -p hcie-protocol` |
|
||||
| `hcie-selection` | `cargo test -p hcie-selection` |
|
||||
| `hcie-text` | `cargo test -p hcie-text` |
|
||||
| `hcie-tile` | `cargo test -p hcie-tile` |
|
||||
| `hcie-vector` | `cargo test -p hcie-vector` |
|
||||
| `hcie-vision` | `cargo test -p hcie-vision` |
|
||||
| `psd` | `cargo test -p psd` |
|
||||
|
||||
## Integration test listesi
|
||||
|
||||
| Test | Paket | Komut | Dosya |
|
||||
|---|---|---|---|
|
||||
| `blend_modes` | `hcie-blend` | `cargo test -p hcie-blend --test blend_modes` | `hcie-blend/tests/blend_modes.rs` |
|
||||
| `stamp` | `hcie-brush-engine` | `cargo test -p hcie-brush-engine --test stamp` | `hcie-brush-engine/tests/stamp.rs` |
|
||||
| `color_roundtrip` | `hcie-color` | `cargo test -p hcie-color --test color_roundtrip` | `hcie-color/tests/color_roundtrip.rs` |
|
||||
| `ff_visibility` | `hcie-composite` | `cargo test -p hcie-composite --test ff_visibility` | `hcie-composite/tests/ff_visibility.rs` |
|
||||
| `pass_through` | `hcie-composite` | `cargo test -p hcie-composite --test pass_through` | `hcie-composite/tests/pass_through.rs` |
|
||||
| `visibility` | `hcie-composite` | `cargo test -p hcie-composite --test visibility` | `hcie-composite/tests/visibility.rs` |
|
||||
| `draw_pixels` | `hcie-draw` | `cargo test -p hcie-draw --test draw_pixels` | `hcie-draw/tests/draw_pixels.rs` |
|
||||
| `filters` | `hcie-filter` | `cargo test -p hcie-filter --test filters` | `hcie-filter/tests/filters.rs` |
|
||||
| `app_state` | `hcie-gui-egui` | `cargo test -p hcie-gui-egui --test app_state` | `hcie-egui-app/crates/hcie-gui-egui/tests/app_state.rs` |
|
||||
| `canvas_engine` | `hcie-gui-egui` | `cargo test -p hcie-gui-egui --test canvas_engine` | `hcie-egui-app/crates/hcie-gui-egui/tests/canvas_engine.rs` |
|
||||
| `gui_audit` | `hcie-gui-egui` | `cargo test -p hcie-gui-egui --test gui_audit` | `hcie-egui-app/crates/hcie-gui-egui/tests/gui_audit.rs` |
|
||||
| `widget_ui` | `hcie-gui-egui` | `cargo test -p hcie-gui-egui --test widget_ui` | `hcie-egui-app/crates/hcie-gui-egui/tests/widget_ui.rs` |
|
||||
| `history_state` | `hcie-history` | `cargo test -p hcie-history --test history_state` | `hcie-history/tests/history_state.rs` |
|
||||
| `helpers` | `hcie-protocol` | `cargo test -p hcie-protocol --test helpers` | `hcie-protocol/tests/helpers.rs` |
|
||||
| `mask_ops` | `hcie-selection` | `cargo test -p hcie-selection --test mask_ops` | `hcie-selection/tests/mask_ops.rs` |
|
||||
|
||||
## Example listesi
|
||||
|
||||
| Example | Paket | Komut | Dosya |
|
||||
|---|---|---|---|
|
||||
| `blend_tester` | `hcie-blend` | `cargo run -p hcie-blend --example blend_tester` | `hcie-blend/examples/blend_tester.rs` |
|
||||
| `brush_test` | `hcie-brush-engine` | `cargo run -p hcie-brush-engine --example brush_test` | `hcie-brush-engine/examples/brush_test.rs` |
|
||||
| `draw_tester` | `hcie-draw` | `cargo run -p hcie-draw --example draw_tester` | `hcie-draw/examples/draw_tester.rs` |
|
||||
| `filter_tester` | `hcie-filter` | `cargo run -p hcie-filter --example filter_tester` | `hcie-filter/examples/filter_tester.rs` |
|
||||
| `compare_effects` | `hcie-io` | `cargo run -p hcie-io --example compare_effects` | `hcie-io/examples/compare_effects.rs` |
|
||||
| `composite_test` | `hcie-io` | `cargo run -p hcie-io --example composite_test` | `hcie-io/examples/composite_test.rs` |
|
||||
| `create_base_test` | `hcie-io` | `cargo run -p hcie-io --example create_base_test` | `hcie-io/examples/create_base_test.rs` |
|
||||
| `debug_effects` | `hcie-io` | `cargo run -p hcie-io --example debug_effects` | `hcie-io/examples/debug_effects.rs` |
|
||||
| `debug_emboss` | `hcie-io` | `cargo run -p hcie-io --example debug_emboss` | `hcie-io/examples/debug_emboss.rs` |
|
||||
| `debug_emboss2` | `hcie-io` | `cargo run -p hcie-io --example debug_emboss2` | `hcie-io/examples/debug_emboss2.rs` |
|
||||
| `debug_glow` | `hcie-io` | `cargo run -p hcie-io --example debug_glow` | `hcie-io/examples/debug_glow.rs` |
|
||||
| `debug_lfx2` | `hcie-io` | `cargo run -p hcie-io --example debug_lfx2` | `hcie-io/examples/debug_lfx2.rs` |
|
||||
| `debug_psd_channels` | `hcie-io` | `cargo run -p hcie-io --example debug_psd_channels` | `hcie-io/examples/debug_psd_channels.rs` |
|
||||
| `debug_sultan` | `hcie-io` | `cargo run -p hcie-io --example debug_sultan` | `hcie-io/examples/debug_sultan.rs` |
|
||||
| `diff_inspector` | `hcie-io` | `cargo run -p hcie-io --example diff_inspector` | `hcie-io/examples/diff_inspector.rs` |
|
||||
| `dump_blend` | `hcie-io` | `cargo run -p hcie-io --example dump_blend` | `hcie-io/examples/dump_blend.rs` |
|
||||
| `dump_effects` | `hcie-io` | `cargo run -p hcie-io --example dump_effects` | `hcie-io/examples/dump_effects.rs` |
|
||||
| `dump_layers` | `hcie-io` | `cargo run -p hcie-io --example dump_layers` | `hcie-io/examples/dump_layers.rs` |
|
||||
| `dump_lfx2` | `hcie-io` | `cargo run -p hcie-io --example dump_lfx2` | `hcie-io/examples/dump_lfx2.rs` |
|
||||
| `dump_psd_layers` | `hcie-io` | `cargo run -p hcie-io --example dump_psd_layers` | `hcie-io/examples/dump_psd_layers.rs` |
|
||||
| `generate_style_tests` | `hcie-io` | `cargo run -p hcie-io --example generate_style_tests` | `hcie-io/examples/generate_style_tests.rs` |
|
||||
| `gui` | `hcie-io` | `cargo run -p hcie-io --example gui` | `hcie-io/examples/gui.rs` |
|
||||
| `inspect_psd` | `hcie-io` | `cargo run -p hcie-io --example inspect_psd` | `hcie-io/examples/inspect_psd.rs` |
|
||||
| `inspect_stroke_psd` | `hcie-io` | `cargo run -p hcie-io --example inspect_stroke_psd` | `hcie-io/examples/inspect_stroke_psd.rs` |
|
||||
| `parse_sequential` | `hcie-io` | `cargo run -p hcie-io --example parse_sequential` | `hcie-io/examples/parse_sequential.rs` |
|
||||
| `probe` | `hcie-io` | `cargo run -p hcie-io --example probe` | `hcie-io/examples/probe.rs` |
|
||||
| `quick_check` | `hcie-io` | `cargo run -p hcie-io --example quick_check` | `hcie-io/examples/quick_check.rs` |
|
||||
| `test1` | `hcie-io` | `cargo run -p hcie-io --example test1` | `hcie-io/examples/test1.rs` |
|
||||
| `test_all` | `hcie-io` | `cargo run -p hcie-io --example test_all` | `hcie-io/examples/test_all.rs` |
|
||||
| `test_singles` | `hcie-io` | `cargo run -p hcie-io --example test_singles` | `hcie-io/examples/test_singles.rs` |
|
||||
| `test_styles` | `hcie-io` | `cargo run -p hcie-io --example test_styles` | `hcie-io/examples/test_styles.rs` |
|
||||
| `test_styles_composite` | `hcie-io` | `cargo run -p hcie-io --example test_styles_composite` | `hcie-io/examples/test_styles_composite.rs` |
|
||||
| `test_sultan` | `hcie-io` | `cargo run -p hcie-io --example test_sultan` | `hcie-io/examples/test_sultan.rs` |
|
||||
| `text_test` | `hcie-io` | `cargo run -p hcie-io --example text_test` | `hcie-io/examples/text_test.rs` |
|
||||
| `tune_mae original` | `hcie-io` | `cargo run -p hcie-io --example "tune_mae original"` | `hcie-io/examples/tune_mae original.rs` |
|
||||
| `tune_mae_color_overlay` | `hcie-io` | `cargo run -p hcie-io --example tune_mae_color_overlay` | `hcie-io/examples/tune_mae_color_overlay.rs` |
|
||||
| `tune_mae_emboss` | `hcie-io` | `cargo run -p hcie-io --example tune_mae_emboss` | `hcie-io/examples/tune_mae_emboss.rs` |
|
||||
| `tune_mae_gradient_overlay` | `hcie-io` | `cargo run -p hcie-io --example tune_mae_gradient_overlay` | `hcie-io/examples/tune_mae_gradient_overlay.rs` |
|
||||
| `tune_mae_inner_shadow` | `hcie-io` | `cargo run -p hcie-io --example tune_mae_inner_shadow` | `hcie-io/examples/tune_mae_inner_shadow.rs` |
|
||||
| `tune_mae_outer_glow` | `hcie-io` | `cargo run -p hcie-io --example tune_mae_outer_glow` | `hcie-io/examples/tune_mae_outer_glow.rs` |
|
||||
| `tune_mae_satin` | `hcie-io` | `cargo run -p hcie-io --example tune_mae_satin` | `hcie-io/examples/tune_mae_satin.rs` |
|
||||
| `tune_mae_shadow` | `hcie-io` | `cargo run -p hcie-io --example tune_mae_shadow` | `hcie-io/examples/tune_mae_shadow.rs` |
|
||||
| `tune_mae_stroke` | `hcie-io` | `cargo run -p hcie-io --example tune_mae_stroke` | `hcie-io/examples/tune_mae_stroke.rs` |
|
||||
| `tune_sultan` | `hcie-io` | `cargo run -p hcie-io --example tune_sultan` | `hcie-io/examples/tune_sultan.rs` |
|
||||
| `text_test` | `hcie-text` | `cargo run -p hcie-text --example text_test` | `hcie-text/examples/text_test.rs` |
|
||||
| `vector_test` | `hcie-vector` | `cargo run -p hcie-vector --example vector_test` | `hcie-vector/examples/vector_test.rs` |
|
||||
| `find_grfl` | `psd` | `cargo run -p psd --example find_grfl` | `hcie-psd/examples/find_grfl.rs` |
|
||||
| `test_effects` | `psd` | `cargo run -p psd --example test_effects` | `hcie-psd/examples/test_effects.rs` |
|
||||
| `text_test_simple` | `psd` | `cargo run -p psd --example text_test_simple` | `hcie-psd/examples/text_test_simple.rs` |
|
||||
|
||||
## Hedef özetleri
|
||||
|
||||
| Paket | Kütüphane hedefleri | Binary hedefleri | Integration testler | Example'lar |
|
||||
|---|---|---|---|---|
|
||||
| `egui-panel-adapter` | `egui_panel_adapter` | - | - | - |
|
||||
| `egui-panel-ai-chat` | `egui_panel_ai_chat` | - | - | - |
|
||||
| `egui-panel-ai-script` | `egui_panel_ai_script` | - | - | - |
|
||||
| `egui-panel-filters` | `egui_panel_filters` | - | - | - |
|
||||
| `egui-panel-script` | `egui_panel_script` | - | - | - |
|
||||
| `hcie-ai` | `hcie_ai` | - | - | - |
|
||||
| `hcie-blend` | `hcie_blend` | - | `blend_modes` | `blend_tester` |
|
||||
| `hcie-brush-engine` | `hcie_brush_engine` | - | `stamp` | `brush_test` |
|
||||
| `hcie-color` | `hcie_color` | - | `color_roundtrip` | - |
|
||||
| `hcie-composite` | `hcie_composite` | - | `ff_visibility`, `pass_through`, `visibility` | - |
|
||||
| `hcie-document` | `hcie_document` | - | - | - |
|
||||
| `hcie-draw` | `hcie_draw` | - | `draw_pixels` | `draw_tester` |
|
||||
| `hcie-engine-api` | `hcie_engine_api` | - | - | - |
|
||||
| `hcie-filter` | `hcie_filter` | - | `filters` | `filter_tester` |
|
||||
| `hcie-fx` | `hcie_fx` | - | - | - |
|
||||
| `hcie-gui-egui` | `hcie_gui_egui` | `hcie-gui` | `app_state`, `canvas_engine`, `gui_audit`, `widget_ui` | - |
|
||||
| `hcie-history` | `hcie_history` | - | `history_state` | - |
|
||||
| `hcie-io` | `hcie_io` | - | - | `compare_effects`, `composite_test`, `create_base_test`, `debug_effects`, `debug_emboss`, `debug_emboss2`, `debug_glow`, `debug_lfx2`, `debug_psd_channels`, `debug_sultan`, `diff_inspector`, `dump_blend`, `dump_effects`, `dump_layers`, `dump_lfx2`, `dump_psd_layers`, `generate_style_tests`, `gui`, `inspect_psd`, `inspect_stroke_psd`, `parse_sequential`, `probe`, `quick_check`, `test1`, `test_all`, `test_singles`, `test_styles`, `test_styles_composite`, `test_sultan`, `text_test`, `tune_mae original`, `tune_mae_color_overlay`, `tune_mae_emboss`, `tune_mae_gradient_overlay`, `tune_mae_inner_shadow`, `tune_mae_outer_glow`, `tune_mae_satin`, `tune_mae_shadow`, `tune_mae_stroke`, `tune_sultan` |
|
||||
| `hcie-kra` | `hcie_kra` | - | - | - |
|
||||
| `hcie-native` | `hcie_native` | - | - | - |
|
||||
| `hcie-pixel-bridge` | `hcie_pixel_bridge` | - | - | - |
|
||||
| `hcie-protocol` | `hcie_protocol` | - | `helpers` | - |
|
||||
| `hcie-selection` | `hcie_selection` | - | `mask_ops` | - |
|
||||
| `hcie-text` | `hcie_text` | - | - | `text_test` |
|
||||
| `hcie-tile` | `hcie_tile` | - | - | - |
|
||||
| `hcie-vector` | `hcie_vector` | - | - | `vector_test` |
|
||||
| `hcie-vision` | `hcie_vision` | - | - | - |
|
||||
| `psd` | `psd` | - | - | `find_grfl`, `test_effects`, `text_test_simple` |
|
||||
|
||||
## Çalıştırılamayan / hedef içermeyen manifestler
|
||||
|
||||
| Dizin | Durum |
|
||||
|---|---|
|
||||
| `hcie-egui-app/Cargo.toml` | `package` manifesti var ama `src/lib.rs` hedefi yok. Çalıştırılabilir egui uygulaması `hcie-egui-app/crates/hcie-gui-egui/Cargo.toml` altındadır. |
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import subprocess
|
||||
import glob
|
||||
|
||||
TASKS = [
|
||||
("_tmp/kra_tunes_simple_black/drop_shadow_kra_set", "_tmp/kra_tunes_simple_black/drop_shadow_png_kra_set"),
|
||||
("_tmp/kra_tunes_simple_red/drop_shadow_kra_set", "_tmp/kra_tunes_simple_red/drop_shadow_png_kra_set"),
|
||||
("_tmp/kra_tunes_simple_black/outer_glow_kra_set", "_tmp/kra_tunes_simple_black/outer_glow_png_kra_set"),
|
||||
("_tmp/kra_tunes_simple_red/outer_glow_kra_set", "_tmp/kra_tunes_simple_red/outer_glow_png_kra_set"),
|
||||
("_tmp/kra_tunes_simple_black/inner_glow_kra_set", "_tmp/kra_tunes_simple_black/inner_glow_png_kra_set"),
|
||||
("_tmp/kra_tunes_simple_red/inner_glow_kra_set", "_tmp/kra_tunes_simple_red/inner_glow_png_kra_set"),
|
||||
("_tmp/kra_tunes_simple_black/bevel_emboss_kra_set", "_tmp/kra_tunes_simple_black/bevel_emboss_png_kra_set"),
|
||||
("_tmp/kra_tunes_simple_red/bevel_emboss_kra_set", "_tmp/kra_tunes_simple_red/bevel_emboss_png_kra_set"),
|
||||
]
|
||||
|
||||
def main():
|
||||
# Check if Krita exists
|
||||
try:
|
||||
subprocess.run(["krita", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
except FileNotFoundError:
|
||||
print("Error: 'krita' executable not found in your PATH.")
|
||||
print("Please install Krita or run this script in an environment where Krita is available.")
|
||||
return
|
||||
|
||||
for kra_dir, png_dir in TASKS:
|
||||
if not os.path.exists(kra_dir):
|
||||
print(f"Directory {kra_dir} does not exist. Skipping.")
|
||||
continue
|
||||
|
||||
kra_files = glob.glob(os.path.join(kra_dir, "*.kra"))
|
||||
if not kra_files:
|
||||
print(f"No .kra files found in {kra_dir}. Skipping.")
|
||||
continue
|
||||
|
||||
os.makedirs(png_dir, exist_ok=True)
|
||||
print(f"Found {len(kra_files)} KRA files in {kra_dir}. Exporting to {png_dir}...")
|
||||
|
||||
count = 0
|
||||
for kra_path in kra_files:
|
||||
filename = os.path.basename(kra_path)
|
||||
png_name = filename.replace(".kra", ".png")
|
||||
png_path = os.path.join(png_dir, png_name)
|
||||
|
||||
if os.path.exists(png_path):
|
||||
continue
|
||||
|
||||
print(f" Exporting {filename} -> {png_name}...")
|
||||
cmd = ["krita", kra_path, "--export", "--export-filename", png_path]
|
||||
|
||||
success = False
|
||||
for attempt in range(2):
|
||||
try:
|
||||
res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=10)
|
||||
if res.returncode == 0:
|
||||
count += 1
|
||||
success = True
|
||||
break
|
||||
else:
|
||||
print(f" FAILED to export {filename}: {res.stderr.decode('utf-8', 'ignore')}")
|
||||
break
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f" TIMEOUT on {filename} (attempt {attempt+1}), retrying...")
|
||||
# Kill any lingering krita processes just in case
|
||||
os.system("killall -9 krita 2>/dev/null")
|
||||
|
||||
if not success:
|
||||
print(f" Skipping {filename} after failure/timeout.")
|
||||
|
||||
print(f"Done with {kra_dir}. Exported {count} new files.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import subprocess
|
||||
import glob
|
||||
|
||||
KRA_DIR = "_tmp/drop_shadow_kra_set"
|
||||
PNG_DIR = "_tmp/drop_shadow_png_kra_set"
|
||||
|
||||
def main():
|
||||
kra_files = glob.glob(os.path.join(KRA_DIR, "*.kra"))
|
||||
if not kra_files:
|
||||
print(f"No .kra files found in {KRA_DIR}. Run generate_kra_set.py first.")
|
||||
return
|
||||
|
||||
os.makedirs(PNG_DIR, exist_ok=True)
|
||||
print(f"Found {len(kra_files)} KRA files. Beginning headless export via Krita CLI...")
|
||||
|
||||
# Check if Krita exists
|
||||
try:
|
||||
subprocess.run(["krita", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
except FileNotFoundError:
|
||||
print("Error: 'krita' executable not found in your PATH.")
|
||||
print("Please install Krita or run this script in an environment where Krita is available.")
|
||||
return
|
||||
|
||||
count = 0
|
||||
for kra_path in kra_files:
|
||||
filename = os.path.basename(kra_path)
|
||||
png_name = filename.replace(".kra", ".png")
|
||||
png_path = os.path.join(PNG_DIR, png_name)
|
||||
|
||||
if os.path.exists(png_path):
|
||||
continue
|
||||
|
||||
print(f"Exporting {filename} -> {png_name}...")
|
||||
# Krita headless export command
|
||||
cmd = ["krita", kra_path, "--export", "--export-filename", png_path]
|
||||
res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
if res.returncode == 0:
|
||||
count += 1
|
||||
else:
|
||||
print(f" FAILED to export {filename}: {res.stderr.decode()}")
|
||||
|
||||
print(f"Done. Successfully exported {count} files to {PNG_DIR}.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import struct
|
||||
import zipfile
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
# Offsets for BevelEmboss style keys inside sultan.kra's Unnamed/annotations/layerstyles.asl:
|
||||
# lagl (local angle): 747
|
||||
# Lald (local altitude): 768
|
||||
# srgR (depth): 790
|
||||
# blur (size): 819
|
||||
# bvlD (direction enum value, e.g. b"In " or b"Out "): 851
|
||||
# Sftn (soften): 1094
|
||||
# hglO (highlight opacity): 512
|
||||
# sdwO (shadow opacity): 652
|
||||
# hglC (highlight R/G/B doubles): 438, 458, 478
|
||||
# sdwC (shadow R/G/B doubles): 590, 610, 630
|
||||
# hglM (highlight blend mode classID): 402
|
||||
# sdwM (shadow blend mode classID): 544
|
||||
|
||||
BASE_KRA = "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/black_rect_simple.kra"
|
||||
OUT_DIR = "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_black/bevel_emboss_kra_set"
|
||||
|
||||
def get_double_bytes(val):
|
||||
return struct.pack(">d", float(val))
|
||||
|
||||
def main():
|
||||
bases = [
|
||||
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/black_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_black/bevel_emboss_kra_set"),
|
||||
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/red_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_red/bevel_emboss_kra_set")
|
||||
]
|
||||
|
||||
for base_kra, out_dir in bases:
|
||||
if not os.path.exists(base_kra):
|
||||
print(f"Error: Base Krita file {base_kra} not found.")
|
||||
continue
|
||||
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
# Read base archive and cache entries in memory
|
||||
base_zip = zipfile.ZipFile(base_kra, 'r')
|
||||
files_cache = {}
|
||||
for name in base_zip.namelist():
|
||||
files_cache[name] = base_zip.read(name)
|
||||
|
||||
asl_path_in_zip = "Unnamed/annotations/layerstyles.asl"
|
||||
if asl_path_in_zip in files_cache:
|
||||
asl_data = bytearray(files_cache[asl_path_in_zip])
|
||||
else:
|
||||
fallback_paths = [
|
||||
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/kra_style_test/sultan.kra",
|
||||
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/_psd_stil_test/sultan_test/sultan.kra",
|
||||
"_images/kra_style_test/sultan.kra",
|
||||
"_images/_psd_stil_test/sultan_test/sultan.kra"
|
||||
]
|
||||
sultan_zip = None
|
||||
for path in fallback_paths:
|
||||
if os.path.exists(path):
|
||||
sultan_zip = zipfile.ZipFile(path, 'r')
|
||||
break
|
||||
if sultan_zip is None:
|
||||
print("Error: Could not find sultan.kra fallback to load layerstyles.asl")
|
||||
return
|
||||
asl_data = bytearray(sultan_zip.read(asl_path_in_zip))
|
||||
sultan_zip.close()
|
||||
|
||||
xml_data = files_cache["maindoc.xml"]
|
||||
|
||||
# Set visibility in XML: both target layer and Background should be visible
|
||||
# Also ensure the layerstyle attribute is attached to the target layer
|
||||
ET.register_namespace('', "http://www.calligra.org/DTD/krita")
|
||||
root = ET.fromstring(xml_data)
|
||||
for layer in root.findall('.//{http://www.calligra.org/DTD/krita}layer'):
|
||||
if layer.attrib.get('name') in ['Layer 2', 'Red Rectangle']:
|
||||
layer.set('visible', '1')
|
||||
layer.set('layerstyle', '{4bbe8f2c-7ad2-4e31-8af0-9ad6715cacf4}')
|
||||
elif layer.attrib.get('name') == 'Background':
|
||||
layer.set('visible', '1')
|
||||
else:
|
||||
layer.set('visible', '0')
|
||||
xml_patched = b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE DOC PUBLIC \'-//KDE//DTD krita 2.0//EN\' \'http://www.calligra.org/DTD/krita-2.0.dtd\'>\n' + ET.tostring(root, encoding='utf-8')
|
||||
|
||||
# Parameter lists
|
||||
sizes = [5.0, 10.0, 20.0, 30.0, 50.0]
|
||||
softens = [0.0, 4.0, 8.0, 16.0]
|
||||
depths = [25.0, 50.0, 75.0, 100.0]
|
||||
angles = [90.0, 120.0]
|
||||
directions = [
|
||||
(b"In ", "up"),
|
||||
(b"Out ", "down"),
|
||||
]
|
||||
|
||||
total = len(sizes) * len(softens) * len(depths) * len(angles) * len(directions)
|
||||
print(f"Generating {total} styled KRA files for Bevel & Emboss from {os.path.basename(base_kra)}...")
|
||||
|
||||
def patch_untf(asl, key, value):
|
||||
idx = asl.find(key)
|
||||
if idx != -1 and asl[idx+4:idx+8] == b'UntF':
|
||||
asl[idx+12:idx+20] = get_double_bytes(value)
|
||||
|
||||
def patch_enum(asl, key, value):
|
||||
idx = asl.find(key)
|
||||
if idx != -1 and asl[idx+4:idx+8] == b'enum':
|
||||
asl[idx+20:idx+24] = value
|
||||
|
||||
count = 0
|
||||
for size in sizes:
|
||||
for soften in softens:
|
||||
for depth in depths:
|
||||
for angle in angles:
|
||||
for dir_bytes, dir_name in directions:
|
||||
count += 1
|
||||
filename = f"be_s{int(size)}_sf{int(soften)}_d{int(depth)}_a{int(angle)}_{dir_name}.kra"
|
||||
kra_path = os.path.join(out_dir, filename)
|
||||
|
||||
# Clone and patch ASL
|
||||
patched_asl = bytearray(asl_data)
|
||||
|
||||
patch_untf(patched_asl, b'blur', size)
|
||||
patch_untf(patched_asl, b'Sftn', soften)
|
||||
patch_untf(patched_asl, b'srgR', depth)
|
||||
patch_untf(patched_asl, b'lagl', angle)
|
||||
patch_enum(patched_asl, b'bvlD', dir_bytes)
|
||||
|
||||
# Write new ZIP archive
|
||||
with zipfile.ZipFile(kra_path, 'w') as out_zip:
|
||||
has_asl = False
|
||||
for item in base_zip.infolist():
|
||||
if item.filename == "Unnamed/annotations/layerstyles.asl":
|
||||
out_zip.writestr(item, patched_asl)
|
||||
has_asl = True
|
||||
elif item.filename == "maindoc.xml":
|
||||
out_zip.writestr(item, xml_patched)
|
||||
elif item.filename == "Unnamed/layers/layer6.defaultpixel":
|
||||
out_zip.writestr(item, b"\x80\x80\x80\xff") # 50% gray background
|
||||
else:
|
||||
out_zip.writestr(item, files_cache[item.filename])
|
||||
if not has_asl:
|
||||
out_zip.writestr("Unnamed/annotations/layerstyles.asl", patched_asl)
|
||||
|
||||
if count % 10 == 0:
|
||||
print(f" [{count}/{total}] generated...")
|
||||
|
||||
base_zip.close()
|
||||
print(f"Successfully generated {count} files under {out_dir}.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import struct
|
||||
import zipfile
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
# Offsets for InnerGlow style keys inside sultan.kra's Unnamed/annotations/layerstyles.asl:
|
||||
# Md (blend mode): 1510
|
||||
# Clr (color) RGBC double offsets:
|
||||
# R double: 1556
|
||||
# G double: 1576
|
||||
# B double: 1596
|
||||
# Opct (opacity): 1620
|
||||
# Ckmt (choke/spread): 1672
|
||||
# blur (size): 1696
|
||||
|
||||
BASE_KRA = "_images/_psd_stil_test/sultan_test/sultan.kra"
|
||||
OUT_DIR = "_tmp/inner_glow_kra_set"
|
||||
|
||||
def get_double_bytes(val):
|
||||
return struct.pack(">d", float(val))
|
||||
|
||||
def main():
|
||||
bases = [
|
||||
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/black_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_black/inner_glow_kra_set"),
|
||||
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/red_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_red/inner_glow_kra_set")
|
||||
]
|
||||
|
||||
for base_kra, out_dir in bases:
|
||||
if not os.path.exists(base_kra):
|
||||
print(f"Error: Base Krita file {base_kra} not found.")
|
||||
continue
|
||||
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
# Read base archive and cache entries in memory
|
||||
base_zip = zipfile.ZipFile(base_kra, 'r')
|
||||
files_cache = {}
|
||||
for name in base_zip.namelist():
|
||||
files_cache[name] = base_zip.read(name)
|
||||
|
||||
asl_path_in_zip = "Unnamed/annotations/layerstyles.asl"
|
||||
if asl_path_in_zip in files_cache:
|
||||
asl_data = bytearray(files_cache[asl_path_in_zip])
|
||||
else:
|
||||
fallback_paths = [
|
||||
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/kra_style_test/sultan.kra",
|
||||
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/_psd_stil_test/sultan_test/sultan.kra",
|
||||
"_images/kra_style_test/sultan.kra",
|
||||
"_images/_psd_stil_test/sultan_test/sultan.kra"
|
||||
]
|
||||
sultan_zip = None
|
||||
for path in fallback_paths:
|
||||
if os.path.exists(path):
|
||||
sultan_zip = zipfile.ZipFile(path, 'r')
|
||||
break
|
||||
if sultan_zip is None:
|
||||
print("Error: Could not find sultan.kra fallback to load layerstyles.asl")
|
||||
return
|
||||
asl_data = bytearray(sultan_zip.read(asl_path_in_zip))
|
||||
sultan_zip.close()
|
||||
|
||||
xml_data = files_cache["maindoc.xml"]
|
||||
|
||||
# Set visibility in XML: both target layer and Background should be visible
|
||||
# Also ensure the layerstyle attribute is attached to the target layer
|
||||
ET.register_namespace('', "http://www.calligra.org/DTD/krita")
|
||||
root = ET.fromstring(xml_data)
|
||||
for layer in root.findall('.//{http://www.calligra.org/DTD/krita}layer'):
|
||||
if layer.attrib.get('name') in ['Layer 3', 'Red Rectangle']:
|
||||
layer.set('visible', '1')
|
||||
layer.set('layerstyle', '{117f8514-88e4-4553-bccb-44a87b6b9182}')
|
||||
elif layer.attrib.get('name') == 'Background':
|
||||
layer.set('visible', '1')
|
||||
else:
|
||||
layer.set('visible', '0')
|
||||
xml_patched = b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE DOC PUBLIC \'-//KDE//DTD krita 2.0//EN\' \'http://www.calligra.org/DTD/krita-2.0.dtd\'>\n' + ET.tostring(root, encoding='utf-8')
|
||||
|
||||
# Parameter lists
|
||||
chokes = [0.0, 20.0]
|
||||
sizes = [10.0, 30.0]
|
||||
opacities = [0.75, 1.0]
|
||||
blend_modes = [
|
||||
(b"Nrml", "normal"),
|
||||
(b"Scrn", "screen"),
|
||||
(b"LDdg", "lineardodge"),
|
||||
]
|
||||
glow_colors = [
|
||||
([220, 30, 30, 255], "red"),
|
||||
([30, 180, 30, 255], "green"),
|
||||
]
|
||||
|
||||
total = len(chokes) * len(sizes) * len(opacities) * len(blend_modes) * len(glow_colors)
|
||||
print(f"Generating {total} styled KRA files for Inner Glow from {os.path.basename(base_kra)}...")
|
||||
|
||||
def patch_untf(asl, start_idx, key, value):
|
||||
idx = asl.find(key, start_idx)
|
||||
if idx != -1 and asl[idx+4:idx+8] == b'UntF':
|
||||
asl[idx+12:idx+20] = get_double_bytes(value)
|
||||
|
||||
def patch_enum(asl, start_idx, key, value):
|
||||
idx = asl.find(key, start_idx)
|
||||
if idx != -1 and asl[idx+4:idx+8] == b'enum':
|
||||
asl[idx+20:idx+24] = value
|
||||
|
||||
def patch_doub(asl, start_idx, key, value):
|
||||
idx = asl.find(key, start_idx)
|
||||
if idx != -1 and asl[idx+4:idx+8] == b'doub':
|
||||
asl[idx+8:idx+16] = get_double_bytes(value)
|
||||
|
||||
count = 0
|
||||
for choke in chokes:
|
||||
for size in sizes:
|
||||
for opacity in opacities:
|
||||
for bm_bytes, bm_name in blend_modes:
|
||||
for color, color_name in glow_colors:
|
||||
count += 1
|
||||
filename = f"ig_sp{int(choke)}_s{int(size)}_op{int(opacity*100)}_n0_{bm_name}_{color_name}.kra"
|
||||
kra_path = os.path.join(out_dir, filename)
|
||||
|
||||
# Clone and patch ASL
|
||||
patched_asl = bytearray(asl_data)
|
||||
|
||||
start_idx = patched_asl.find(b'IrGl')
|
||||
|
||||
patch_untf(patched_asl, start_idx, b'Opct', opacity * 100.0)
|
||||
patch_untf(patched_asl, start_idx, b'Ckmt', choke)
|
||||
patch_untf(patched_asl, start_idx, b'blur', size)
|
||||
patch_enum(patched_asl, start_idx, b'Md ', bm_bytes)
|
||||
|
||||
# Color: find Clr after start_idx, then Rd , Grn , Bl
|
||||
clr_idx = patched_asl.find(b'Clr ', start_idx)
|
||||
patch_doub(patched_asl, clr_idx, b'Rd ', color[0])
|
||||
patch_doub(patched_asl, clr_idx, b'Grn ', color[1])
|
||||
patch_doub(patched_asl, clr_idx, b'Bl ', color[2])
|
||||
|
||||
# Write new ZIP archive
|
||||
with zipfile.ZipFile(kra_path, 'w') as out_zip:
|
||||
has_asl = False
|
||||
for item in base_zip.infolist():
|
||||
if item.filename == "Unnamed/annotations/layerstyles.asl":
|
||||
out_zip.writestr(item, patched_asl)
|
||||
has_asl = True
|
||||
elif item.filename == "maindoc.xml":
|
||||
out_zip.writestr(item, xml_patched)
|
||||
elif item.filename == "Unnamed/layers/layer6.defaultpixel":
|
||||
out_zip.writestr(item, b"\x80\x80\x80\xff") # 50% gray background
|
||||
else:
|
||||
out_zip.writestr(item, files_cache[item.filename])
|
||||
if not has_asl:
|
||||
out_zip.writestr("Unnamed/annotations/layerstyles.asl", patched_asl)
|
||||
|
||||
if count % 20 == 0:
|
||||
print(f" [{count}/{total}] generated...")
|
||||
|
||||
base_zip.close()
|
||||
print(f"Successfully generated {count} files under {out_dir}.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import struct
|
||||
import zipfile
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
# Offsets for DropShadow style keys inside sultan.kra's Unnamed/annotations/layerstyles.asl:
|
||||
# Opct (opacity): 2504
|
||||
# lagl (local angle): 2541
|
||||
# Dstn (distance): 2565
|
||||
# Ckmt (choke/spread): 2589
|
||||
# blur (size): 2613
|
||||
# Md (blend mode value id): 2406
|
||||
# Clr (color) RGBC double offsets:
|
||||
# R double: 2448
|
||||
# G double: 2468
|
||||
# B double: 2488
|
||||
|
||||
BASE_KRA = "_images/_psd_stil_test/sultan_test/sultan.kra"
|
||||
OUT_DIR = "_tmp/drop_shadow_kra_set"
|
||||
|
||||
def get_double_bytes(val):
|
||||
return struct.pack(">d", float(val))
|
||||
|
||||
def main():
|
||||
bases = [
|
||||
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/black_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_black/drop_shadow_kra_set"),
|
||||
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/red_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_red/drop_shadow_kra_set")
|
||||
]
|
||||
|
||||
for base_kra, out_dir in bases:
|
||||
if not os.path.exists(base_kra):
|
||||
print(f"Error: Base Krita file {base_kra} not found.")
|
||||
continue
|
||||
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
# Read base archive and cache entries in memory
|
||||
base_zip = zipfile.ZipFile(base_kra, 'r')
|
||||
files_cache = {}
|
||||
for name in base_zip.namelist():
|
||||
files_cache[name] = base_zip.read(name)
|
||||
|
||||
asl_path_in_zip = "Unnamed/annotations/layerstyles.asl"
|
||||
if asl_path_in_zip in files_cache:
|
||||
asl_data = bytearray(files_cache[asl_path_in_zip])
|
||||
else:
|
||||
fallback_paths = [
|
||||
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/kra_style_test/sultan.kra",
|
||||
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/_psd_stil_test/sultan_test/sultan.kra",
|
||||
"_images/kra_style_test/sultan.kra",
|
||||
"_images/_psd_stil_test/sultan_test/sultan.kra"
|
||||
]
|
||||
sultan_zip = None
|
||||
for path in fallback_paths:
|
||||
if os.path.exists(path):
|
||||
sultan_zip = zipfile.ZipFile(path, 'r')
|
||||
break
|
||||
if sultan_zip is None:
|
||||
print("Error: Could not find sultan.kra fallback to load layerstyles.asl")
|
||||
return
|
||||
asl_data = bytearray(sultan_zip.read(asl_path_in_zip))
|
||||
sultan_zip.close()
|
||||
|
||||
xml_data = files_cache["maindoc.xml"]
|
||||
|
||||
# Set visibility in XML: both target layer and Background should be visible
|
||||
# Also ensure the layerstyle attribute is attached to the target layer
|
||||
ET.register_namespace('', "http://www.calligra.org/DTD/krita")
|
||||
root = ET.fromstring(xml_data)
|
||||
for layer in root.findall('.//{http://www.calligra.org/DTD/krita}layer'):
|
||||
if layer.attrib.get('name') in ['Layer 4', 'Red Rectangle']:
|
||||
layer.set('visible', '1')
|
||||
layer.set('layerstyle', '{7d378444-e311-41d3-b9c6-a19e2a8e5899}')
|
||||
elif layer.attrib.get('name') == 'Background':
|
||||
layer.set('visible', '1')
|
||||
else:
|
||||
layer.set('visible', '0')
|
||||
xml_patched = b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE DOC PUBLIC \'-//KDE//DTD krita 2.0//EN\' \'http://www.calligra.org/DTD/krita-2.0.dtd\'>\n' + ET.tostring(root, encoding='utf-8')
|
||||
|
||||
# Parameter lists
|
||||
angles = [0.0, 90.0, 180.0, 270.0]
|
||||
distances = [5.0, 15.0, 30.0]
|
||||
sizes = [10.0, 30.0]
|
||||
spreads = [0.0, 20.0]
|
||||
opacities = [0.75, 1.0]
|
||||
blend_modes = [
|
||||
(b"Nrml", "normal"),
|
||||
(b"Mltp", "multiply"),
|
||||
(b"Scrn", "screen"),
|
||||
(b"LDdg", "lineardodge"),
|
||||
]
|
||||
shadow_colors = [
|
||||
([220, 30, 30, 255], "red"),
|
||||
([30, 180, 30, 255], "green"),
|
||||
]
|
||||
|
||||
total = len(angles) * len(distances) * len(sizes) * len(spreads) * len(opacities) * len(blend_modes) * len(shadow_colors)
|
||||
print(f"Generating {total} styled KRA files for Drop Shadow from {os.path.basename(base_kra)}...")
|
||||
|
||||
def patch_untf(asl, start_idx, key, value):
|
||||
idx = asl.find(key, start_idx)
|
||||
if idx != -1 and asl[idx+4:idx+8] == b'UntF':
|
||||
asl[idx+12:idx+20] = get_double_bytes(value)
|
||||
|
||||
def patch_enum(asl, start_idx, key, value):
|
||||
idx = asl.find(key, start_idx)
|
||||
if idx != -1 and asl[idx+4:idx+8] == b'enum':
|
||||
asl[idx+20:idx+24] = value
|
||||
|
||||
def patch_doub(asl, start_idx, key, value):
|
||||
idx = asl.find(key, start_idx)
|
||||
if idx != -1 and asl[idx+4:idx+8] == b'doub':
|
||||
asl[idx+8:idx+16] = get_double_bytes(value)
|
||||
|
||||
count = 0
|
||||
for angle in angles:
|
||||
for distance in distances:
|
||||
for size in sizes:
|
||||
for spread in spreads:
|
||||
for opacity in opacities:
|
||||
for bm_bytes, bm_name in blend_modes:
|
||||
for color, color_name in shadow_colors:
|
||||
count += 1
|
||||
filename = f"ds_a{int(angle)}_d{int(distance)}_s{int(size)}_sp{int(spread)}_op{int(opacity*100)}_n0_{bm_name}_{color_name}.kra"
|
||||
kra_path = os.path.join(out_dir, filename)
|
||||
|
||||
# Clone and patch ASL
|
||||
patched_asl = bytearray(asl_data)
|
||||
|
||||
start_idx = patched_asl.find(b'DrSh')
|
||||
|
||||
patch_untf(patched_asl, start_idx, b'Opct', opacity * 100.0)
|
||||
patch_untf(patched_asl, start_idx, b'lagl', angle)
|
||||
patch_untf(patched_asl, start_idx, b'Dstn', distance)
|
||||
patch_untf(patched_asl, start_idx, b'Ckmt', spread)
|
||||
patch_untf(patched_asl, start_idx, b'blur', size)
|
||||
patch_enum(patched_asl, start_idx, b'Md ', bm_bytes)
|
||||
|
||||
# Color: find Clr after start_idx, then Rd , Grn , Bl
|
||||
clr_idx = patched_asl.find(b'Clr ', start_idx)
|
||||
patch_doub(patched_asl, clr_idx, b'Rd ', color[0])
|
||||
patch_doub(patched_asl, clr_idx, b'Grn ', color[1])
|
||||
patch_doub(patched_asl, clr_idx, b'Bl ', color[2])
|
||||
|
||||
# Write new ZIP archive
|
||||
with zipfile.ZipFile(kra_path, 'w') as out_zip:
|
||||
has_asl = False
|
||||
for item in base_zip.infolist():
|
||||
if item.filename == "Unnamed/annotations/layerstyles.asl":
|
||||
out_zip.writestr(item, patched_asl)
|
||||
has_asl = True
|
||||
elif item.filename == "maindoc.xml":
|
||||
out_zip.writestr(item, xml_patched)
|
||||
elif item.filename == "Unnamed/layers/layer6.defaultpixel":
|
||||
out_zip.writestr(item, b"\x80\x80\x80\xff") # 50% gray background
|
||||
else:
|
||||
out_zip.writestr(item, files_cache[item.filename])
|
||||
if not has_asl:
|
||||
out_zip.writestr("Unnamed/annotations/layerstyles.asl", patched_asl)
|
||||
|
||||
if count % 20 == 0:
|
||||
print(f" [{count}/{total}] generated...")
|
||||
|
||||
base_zip.close()
|
||||
print(f"Successfully generated {count} files under {out_dir}.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import struct
|
||||
import zipfile
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
# Offsets for OuterGlow style keys inside sultan.kra's Unnamed/annotations/layerstyles.asl:
|
||||
# Md (blend mode): 3278
|
||||
# Clr (color) RGBC double offsets:
|
||||
# R double: 3324
|
||||
# G double: 3344
|
||||
# B double: 3364
|
||||
# Opct (opacity): 3388
|
||||
# Ckmt (choke/spread): 3440
|
||||
# blur (size): 3464
|
||||
|
||||
BASE_KRA = "_images/_psd_stil_test/sultan_test/sultan.kra"
|
||||
OUT_DIR = "_tmp/outer_glow_kra_set"
|
||||
|
||||
def get_double_bytes(val):
|
||||
return struct.pack(">d", float(val))
|
||||
|
||||
def main():
|
||||
bases = [
|
||||
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/black_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_black/outer_glow_kra_set"),
|
||||
("/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple/red_rect_simple.kra", "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/kra_tunes_simple_red/outer_glow_kra_set")
|
||||
]
|
||||
|
||||
for base_kra, out_dir in bases:
|
||||
if not os.path.exists(base_kra):
|
||||
print(f"Error: Base Krita file {base_kra} not found.")
|
||||
continue
|
||||
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
# Read base archive and cache entries in memory
|
||||
base_zip = zipfile.ZipFile(base_kra, 'r')
|
||||
files_cache = {}
|
||||
for name in base_zip.namelist():
|
||||
files_cache[name] = base_zip.read(name)
|
||||
|
||||
asl_path_in_zip = "Unnamed/annotations/layerstyles.asl"
|
||||
if asl_path_in_zip in files_cache:
|
||||
asl_data = bytearray(files_cache[asl_path_in_zip])
|
||||
else:
|
||||
fallback_paths = [
|
||||
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/kra_style_test/sultan.kra",
|
||||
"/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/_psd_stil_test/sultan_test/sultan.kra",
|
||||
"_images/kra_style_test/sultan.kra",
|
||||
"_images/_psd_stil_test/sultan_test/sultan.kra"
|
||||
]
|
||||
sultan_zip = None
|
||||
for path in fallback_paths:
|
||||
if os.path.exists(path):
|
||||
sultan_zip = zipfile.ZipFile(path, 'r')
|
||||
break
|
||||
if sultan_zip is None:
|
||||
print("Error: Could not find sultan.kra fallback to load layerstyles.asl")
|
||||
return
|
||||
asl_data = bytearray(sultan_zip.read(asl_path_in_zip))
|
||||
sultan_zip.close()
|
||||
|
||||
xml_data = files_cache["maindoc.xml"]
|
||||
|
||||
# Set visibility in XML: both target layer and Background should be visible
|
||||
# Also ensure the layerstyle attribute is attached to the target layer
|
||||
ET.register_namespace('', "http://www.calligra.org/DTD/krita")
|
||||
root = ET.fromstring(xml_data)
|
||||
for layer in root.findall('.//{http://www.calligra.org/DTD/krita}layer'):
|
||||
if layer.attrib.get('name') in ['Layer 1', 'Red Rectangle']:
|
||||
layer.set('visible', '1')
|
||||
layer.set('layerstyle', '{d7a0844e-a522-4673-8c83-aa4f45e7782f}')
|
||||
elif layer.attrib.get('name') == 'Background':
|
||||
layer.set('visible', '1')
|
||||
else:
|
||||
layer.set('visible', '0')
|
||||
xml_patched = b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE DOC PUBLIC \'-//KDE//DTD krita 2.0//EN\' \'http://www.calligra.org/DTD/krita-2.0.dtd\'>\n' + ET.tostring(root, encoding='utf-8')
|
||||
|
||||
# Parameter lists
|
||||
spreads = [0.0, 20.0]
|
||||
sizes = [10.0, 30.0]
|
||||
opacities = [0.75, 1.0]
|
||||
blend_modes = [
|
||||
(b"Nrml", "normal"),
|
||||
(b"Scrn", "screen"),
|
||||
(b"LDdg", "lineardodge"),
|
||||
]
|
||||
glow_colors = [
|
||||
([220, 30, 30, 255], "red"),
|
||||
([30, 180, 30, 255], "green"),
|
||||
]
|
||||
|
||||
total = len(spreads) * len(sizes) * len(opacities) * len(blend_modes) * len(glow_colors)
|
||||
print(f"Generating {total} styled KRA files for Outer Glow from {os.path.basename(base_kra)}...")
|
||||
|
||||
def patch_untf(asl, start_idx, key, value):
|
||||
idx = asl.find(key, start_idx)
|
||||
if idx != -1 and asl[idx+4:idx+8] == b'UntF':
|
||||
asl[idx+12:idx+20] = get_double_bytes(value)
|
||||
|
||||
def patch_enum(asl, start_idx, key, value):
|
||||
idx = asl.find(key, start_idx)
|
||||
if idx != -1 and asl[idx+4:idx+8] == b'enum':
|
||||
asl[idx+20:idx+24] = value
|
||||
|
||||
def patch_doub(asl, start_idx, key, value):
|
||||
idx = asl.find(key, start_idx)
|
||||
if idx != -1 and asl[idx+4:idx+8] == b'doub':
|
||||
asl[idx+8:idx+16] = get_double_bytes(value)
|
||||
|
||||
count = 0
|
||||
for spread in spreads:
|
||||
for size in sizes:
|
||||
for opacity in opacities:
|
||||
for bm_bytes, bm_name in blend_modes:
|
||||
for color, color_name in glow_colors:
|
||||
count += 1
|
||||
filename = f"og_sp{int(spread)}_s{int(size)}_op{int(opacity*100)}_n0_{bm_name}_{color_name}.kra"
|
||||
kra_path = os.path.join(out_dir, filename)
|
||||
|
||||
# Clone and patch ASL
|
||||
patched_asl = bytearray(asl_data)
|
||||
|
||||
start_idx = patched_asl.find(b'OrGl')
|
||||
|
||||
patch_untf(patched_asl, start_idx, b'Opct', opacity * 100.0)
|
||||
patch_untf(patched_asl, start_idx, b'Ckmt', spread)
|
||||
patch_untf(patched_asl, start_idx, b'blur', size)
|
||||
patch_enum(patched_asl, start_idx, b'Md ', bm_bytes)
|
||||
|
||||
# Color: find Clr after start_idx, then Rd , Grn , Bl
|
||||
clr_idx = patched_asl.find(b'Clr ', start_idx)
|
||||
patch_doub(patched_asl, clr_idx, b'Rd ', color[0])
|
||||
patch_doub(patched_asl, clr_idx, b'Grn ', color[1])
|
||||
patch_doub(patched_asl, clr_idx, b'Bl ', color[2])
|
||||
|
||||
# Write new ZIP archive
|
||||
with zipfile.ZipFile(kra_path, 'w') as out_zip:
|
||||
has_asl = False
|
||||
for item in base_zip.infolist():
|
||||
if item.filename == "Unnamed/annotations/layerstyles.asl":
|
||||
out_zip.writestr(item, patched_asl)
|
||||
has_asl = True
|
||||
elif item.filename == "maindoc.xml":
|
||||
out_zip.writestr(item, xml_patched)
|
||||
elif item.filename == "Unnamed/layers/layer6.defaultpixel":
|
||||
out_zip.writestr(item, b"\x80\x80\x80\xff") # 50% gray background
|
||||
else:
|
||||
out_zip.writestr(item, files_cache[item.filename])
|
||||
if not has_asl:
|
||||
out_zip.writestr("Unnamed/annotations/layerstyles.asl", patched_asl)
|
||||
|
||||
if count % 20 == 0:
|
||||
print(f" [{count}/{total}] generated...")
|
||||
|
||||
base_zip.close()
|
||||
print(f"Successfully generated {count} files under {out_dir}.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,39 @@
|
||||
import os
|
||||
import zipfile
|
||||
import glob
|
||||
|
||||
def main():
|
||||
kra_files = glob.glob("_images/**/*.kra", recursive=True)
|
||||
print(f"Found KRA files: {kra_files}")
|
||||
|
||||
keys = [
|
||||
b"DrSh", b"IrSh", b"OrGl", b"IrGl", b"ebbl", b"FrFX", b"SoFi", b"GrFl", b"PtFl", b"Scl ", b"lagl", b"Dstn", b"blur", b"Ckmt", b"Opct", b"Md ", b"Clr ", b"Sz "
|
||||
]
|
||||
|
||||
for kra_path in kra_files:
|
||||
print(f"\n--- Analyzing {kra_path} ---")
|
||||
try:
|
||||
with zipfile.ZipFile(kra_path, 'r') as base_zip:
|
||||
asl_paths = [name for name in base_zip.namelist() if name.endswith("layerstyles.asl")]
|
||||
if not asl_paths:
|
||||
print(" No layerstyles.asl found.")
|
||||
continue
|
||||
for asl_path in asl_paths:
|
||||
asl_data = base_zip.read(asl_path)
|
||||
print(f" ASL path: {asl_path} ({len(asl_data)} bytes)")
|
||||
for key in keys:
|
||||
pos = 0
|
||||
found = []
|
||||
while True:
|
||||
idx = asl_data.find(key, pos)
|
||||
if idx == -1:
|
||||
break
|
||||
found.append(idx)
|
||||
pos = idx + len(key)
|
||||
if found:
|
||||
print(f" Key {key.decode('ascii', errors='replace')} found at offsets: {found}")
|
||||
except Exception as e:
|
||||
print(f" Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,32 @@
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
BASE_KRA = "/mnt/extra/00_PROJECTS/hcie-rust-v4/_images/kra_style_test/sultan.kra"
|
||||
|
||||
def main():
|
||||
if not os.path.exists(BASE_KRA):
|
||||
print(f"Error: {BASE_KRA} not found.")
|
||||
return
|
||||
|
||||
with zipfile.ZipFile(BASE_KRA, 'r') as base_zip:
|
||||
asl_data = base_zip.read("Unnamed/annotations/layerstyles.asl")
|
||||
print(f"ASL Data Size: {len(asl_data)} bytes")
|
||||
|
||||
# Search for common key signatures in the ASL descriptor format
|
||||
# Keys are usually 4-byte identifiers
|
||||
keys = [
|
||||
b"DrSh", b"IrSh", b"OrGl", b"IrGl", b"ebbl", b"FrFX", b"SoFi", b"GrFl", b"PtFl", b"Scl ", b"lagl", b"Dstn", b"blur", b"Ckmt", b"Opct", b"Md ", b"Clr ", b"Sz "
|
||||
]
|
||||
for key in keys:
|
||||
pos = 0
|
||||
found = []
|
||||
while True:
|
||||
idx = asl_data.find(key, pos)
|
||||
if idx == -1:
|
||||
break
|
||||
found.append(idx)
|
||||
pos = idx + len(key)
|
||||
print(f"Key {key.decode('ascii', errors='replace')} found at offsets: {found}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,41 @@
|
||||
# Photopea / Photoshop ExtendScript Dataset Generator
|
||||
|
||||
This folder contains JSX scripts to generate systematic layer-effect ground-truth images in Photopea or Photoshop.
|
||||
|
||||
## Files
|
||||
|
||||
- `generate_inner_shadow_dataset.jsx` — Short starter script for Inner Shadow.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Open Photopea (https://www.photopea.com) in a browser.
|
||||
2. Press `Alt+Ctrl+I` (or use `File > Automate > Script`) to open the script runner.
|
||||
3. Paste the contents of the desired `.jsx` file and run it.
|
||||
4. Select an output folder when prompted.
|
||||
|
||||
Photopea will create a new document for each parameter combination, draw a black rectangle, apply the Inner Shadow effect, hide the background, and export a transparent PNG.
|
||||
|
||||
## Current parameter grid (short version)
|
||||
|
||||
| Parameter | Values |
|
||||
|---|---|
|
||||
| angle | 0, 90, 180, 270 |
|
||||
| distance | 5, 15, 30 |
|
||||
| size | 10, 30 |
|
||||
| choke | 0, 20 |
|
||||
| fx opacity | 75, 100 |
|
||||
| fx blend mode | Normal, Multiply |
|
||||
|
||||
Total images: `4 × 3 × 2 × 2 × 2 × 2 = 192`.
|
||||
|
||||
## Notes
|
||||
|
||||
- Blend mode keys use Photoshop's 4-character codes (`Nrml`, `Mltp`).
|
||||
- The output PNGs have transparent backgrounds, so MAE comparisons can ignore white reference pixels if needed.
|
||||
- ExtendScript error handling is basic; if a parameter combination fails, the script logs it and continues.
|
||||
|
||||
## Next steps
|
||||
|
||||
- Add more layer blend modes, fill opacity, and layer opacity variations.
|
||||
- Extend to Drop Shadow, Outer Glow, Inner Glow, Stroke, Bevel & Emboss.
|
||||
- Add a companion Rust generator that creates matching PSD files so the same dataset can be imported back into HCIE for MAE tuning.
|
||||
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* generate_inner_shadow_dataset.jsx
|
||||
*
|
||||
* Purpose:
|
||||
* Generate a systematic Inner Shadow effect dataset in Photopea.
|
||||
* Uses stringID-based ActionDescriptor for maximum Photopea compatibility.
|
||||
*
|
||||
* Logic & Workflow:
|
||||
* 1. Creates a new RGB document with transparent background.
|
||||
* 2. Adds a normal raster layer and fills the center with black.
|
||||
* 3. Applies one Inner Shadow effect via ActionDescriptor (stringID).
|
||||
* 4. Hides the background layer.
|
||||
* 5. Saves the result as a transparent PNG.
|
||||
*
|
||||
* Usage:
|
||||
* 1. Open https://www.photopea.com in a browser.
|
||||
* 2. Alt+Ctrl+I (or File > Automate > Script).
|
||||
* 3. Paste this script and run.
|
||||
* 4. Select output folder when prompted.
|
||||
*/
|
||||
|
||||
var outFolder = Folder.selectDialog("Select output folder for Inner Shadow dataset");
|
||||
if (!outFolder) {
|
||||
alert("No output folder selected. Exiting.");
|
||||
throw new Error("No output folder");
|
||||
}
|
||||
|
||||
var angles = [0, 90, 180, 270];
|
||||
var distances = [5, 15, 30];
|
||||
var sizes = [10, 30];
|
||||
var chokes = [0, 20];
|
||||
var fxOpacities = [75, 100];
|
||||
var fxBlendModes = [
|
||||
{ id: "normal", name: "normal" },
|
||||
{ id: "multiply", name: "multiply" }
|
||||
];
|
||||
|
||||
function createDocument(name, width, height) {
|
||||
var doc = app.documents.add(width, height, 72, name, NewDocumentMode.RGB, DocumentFill.TRANSPARENT);
|
||||
try {
|
||||
var bg = doc.artLayers[doc.artLayers.length - 1];
|
||||
if (bg.isBackgroundLayer) {
|
||||
bg.visible = false;
|
||||
}
|
||||
} catch (e) {}
|
||||
return doc;
|
||||
}
|
||||
|
||||
function addBlackRectangle(doc) {
|
||||
var w = doc.width;
|
||||
var h = doc.height;
|
||||
if (typeof w === "object" && typeof w.value === "function") { w = w.value; }
|
||||
if (typeof h === "object" && typeof h.value === "function") { h = h.value; }
|
||||
w = Number(w);
|
||||
h = Number(h);
|
||||
|
||||
var margin = Math.round(Math.min(w, h) * 0.25);
|
||||
|
||||
var layer = doc.artLayers.add();
|
||||
layer.name = "shape";
|
||||
|
||||
var region = [
|
||||
[margin, margin],
|
||||
[w - margin, margin],
|
||||
[w - margin, h - margin],
|
||||
[margin, h - margin]
|
||||
];
|
||||
|
||||
doc.selection.select(region);
|
||||
|
||||
var black = new SolidColor();
|
||||
black.rgb.red = 0;
|
||||
black.rgb.green = 0;
|
||||
black.rgb.blue = 0;
|
||||
|
||||
doc.selection.fill(black);
|
||||
doc.selection.deselect();
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try applying Inner Shadow via ActionDescriptor with stringID keys.
|
||||
* Returns true on success, false on failure.
|
||||
*/
|
||||
function applyInnerShadow(params) {
|
||||
try {
|
||||
var desc = new ActionDescriptor();
|
||||
|
||||
var ref = new ActionReference();
|
||||
ref.putProperty(stringIDToTypeID("property"), stringIDToTypeID("layerEffects"));
|
||||
ref.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
|
||||
desc.putReference(stringIDToTypeID("null"), ref);
|
||||
|
||||
var fxDesc = new ActionDescriptor();
|
||||
|
||||
var isDesc = new ActionDescriptor();
|
||||
isDesc.putBoolean(stringIDToTypeID("enabled"), true);
|
||||
|
||||
// Blend mode
|
||||
isDesc.putEnumerated(stringIDToTypeID("mode"), stringIDToTypeID("blendMode"), stringIDToTypeID(params.blendMode));
|
||||
|
||||
// Color
|
||||
var colorDesc = new ActionDescriptor();
|
||||
colorDesc.putDouble(stringIDToTypeID("red"), 0);
|
||||
colorDesc.putDouble(stringIDToTypeID("grain"), 0);
|
||||
colorDesc.putDouble(stringIDToTypeID("blue"), 0);
|
||||
isDesc.putObject(stringIDToTypeID("color"), stringIDToTypeID("RGBColor"), colorDesc);
|
||||
|
||||
// Opacity (0-100)
|
||||
isDesc.putInteger(stringIDToTypeID("opacity"), params.opacity);
|
||||
|
||||
// Angle
|
||||
isDesc.putInteger(stringIDToTypeID("localLightingAngle"), params.angle);
|
||||
|
||||
// Global light
|
||||
isDesc.putBoolean(stringIDToTypeID("useGlobalAngle"), false);
|
||||
|
||||
// Distance
|
||||
isDesc.putInteger(stringIDToTypeID("distance"), params.distance);
|
||||
|
||||
// Choke
|
||||
isDesc.putInteger(stringIDToTypeID("chokeMatte"), params.choke);
|
||||
|
||||
// Blur (size)
|
||||
isDesc.putInteger(stringIDToTypeID("blur"), params.size);
|
||||
|
||||
// Noise
|
||||
isDesc.putInteger(stringIDToTypeID("noise"), 0);
|
||||
|
||||
// Anti-alias
|
||||
isDesc.putBoolean(stringIDToTypeID("antiAlias"), true);
|
||||
|
||||
// Transfer function (contour)
|
||||
isDesc.putBoolean(stringIDToTypeID("transferSpecIsScaled"), false);
|
||||
|
||||
fxDesc.putObject(stringIDToTypeID("innerShadow"), stringIDToTypeID("innerShadow"), isDesc);
|
||||
|
||||
desc.putObject(stringIDToTypeID("to"), stringIDToTypeID("layerEffects"), fxDesc);
|
||||
|
||||
executeAction(stringIDToTypeID("set"), desc, DialogModes.NO);
|
||||
return true;
|
||||
} catch (e) {
|
||||
$.writeln("stringID failed: " + e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback: apply via charID-based descriptor.
|
||||
*/
|
||||
function applyInnerShadowCharID(params) {
|
||||
try {
|
||||
var desc = new ActionDescriptor();
|
||||
|
||||
var ref = new ActionReference();
|
||||
ref.putProperty(charIDToTypeID("Prpr"), charIDToTypeID("Lefx"));
|
||||
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
|
||||
desc.putReference(charIDToTypeID("null"), ref);
|
||||
|
||||
var fxDesc = new ActionDescriptor();
|
||||
|
||||
var isDesc = new ActionDescriptor();
|
||||
isDesc.putBoolean(charIDToTypeID("enab"), true);
|
||||
isDesc.putBoolean(charIDToTypeID("present"), true);
|
||||
isDesc.putBoolean(charIDToTypeID("showInDialog"), true);
|
||||
|
||||
// Blend mode
|
||||
isDesc.putEnumerated(charIDToTypeID("Blnd"), charIDToTypeID("BlnM"), charIDToTypeID(params.blendModeCharID));
|
||||
|
||||
// Color
|
||||
var clr = new ActionDescriptor();
|
||||
clr.putDouble(charIDToTypeID("Rd "), 0);
|
||||
clr.putDouble(charIDToTypeID("Grn "), 0);
|
||||
clr.putDouble(charIDToTypeID("Bl "), 0);
|
||||
isDesc.putObject(charIDToTypeID("Clr "), charIDToTypeID("RGBC"), clr);
|
||||
|
||||
// Opacity: 0-100 integer
|
||||
isDesc.putInteger(charIDToTypeID("Opct"), params.opacity);
|
||||
|
||||
// Angle: use lagl (local angle) instead of Angl
|
||||
isDesc.putInteger(charIDToTypeID("lagl"), params.angle);
|
||||
isDesc.putBoolean(charIDToTypeID("uglg"), false);
|
||||
|
||||
// Distance
|
||||
isDesc.putInteger(charIDToTypeID("Dstn"), params.distance);
|
||||
|
||||
// Choke
|
||||
isDesc.putInteger(charIDToTypeID("Ckmt"), params.choke);
|
||||
|
||||
// Blur
|
||||
isDesc.putInteger(charIDToTypeID("blur"), params.size);
|
||||
|
||||
// Noise
|
||||
isDesc.putInteger(charIDToTypeID("Nose"), 0);
|
||||
|
||||
fxDesc.putObject(charIDToTypeID("IrSh"), charIDToTypeID("IrSh"), isDesc);
|
||||
|
||||
desc.putObject(charIDToTypeID("T "), charIDToTypeID("Lefx"), fxDesc);
|
||||
|
||||
executeAction(charIDToTypeID("setd"), desc, DialogModes.NO);
|
||||
return true;
|
||||
} catch (e) {
|
||||
$.writeln("charID failed: " + e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function saveTransparentPng(doc, file) {
|
||||
var pngOptions = new PNGSaveOptions();
|
||||
pngOptions.compression = 6;
|
||||
pngOptions.interlaced = false;
|
||||
doc.saveAs(file, pngOptions, true, Extension.LOWERCASE);
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
var errors = 0;
|
||||
var total = angles.length * distances.length * sizes.length * chokes.length * fxOpacities.length * fxBlendModes.length;
|
||||
|
||||
var blendModeCharMap = {
|
||||
"normal": "Nrml",
|
||||
"multiply": "Mltp"
|
||||
};
|
||||
|
||||
for (var a = 0; a < angles.length; a++) {
|
||||
for (var d = 0; d < distances.length; d++) {
|
||||
for (var s = 0; s < sizes.length; s++) {
|
||||
for (var c = 0; c < chokes.length; c++) {
|
||||
for (var o = 0; o < fxOpacities.length; o++) {
|
||||
for (var b = 0; b < fxBlendModes.length; b++) {
|
||||
var bmName = fxBlendModes[b].name;
|
||||
var bmID = fxBlendModes[b].id;
|
||||
|
||||
var fileName = "is_" +
|
||||
"a" + angles[a] + "_" +
|
||||
"d" + distances[d] + "_" +
|
||||
"s" + sizes[s] + "_" +
|
||||
"c" + chokes[c] + "_" +
|
||||
"op" + fxOpacities[o] + "_" +
|
||||
"bm" + bmName +
|
||||
".png";
|
||||
|
||||
var file = new File(outFolder + "/" + fileName);
|
||||
if (file.exists) {
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var params = {
|
||||
angle: angles[a],
|
||||
distance: distances[d],
|
||||
size: sizes[s],
|
||||
choke: chokes[c],
|
||||
opacity: fxOpacities[o],
|
||||
blendMode: bmID,
|
||||
blendModeCharID: blendModeCharMap[bmID] || "Nrml"
|
||||
};
|
||||
|
||||
var doc = createDocument("inner_shadow_test", 256, 256);
|
||||
var layer = addBlackRectangle(doc);
|
||||
|
||||
var applied = applyInnerShadow(params);
|
||||
if (!applied) {
|
||||
applied = applyInnerShadowCharID(params);
|
||||
}
|
||||
|
||||
if (applied) {
|
||||
try {
|
||||
saveTransparentPng(doc, file);
|
||||
} catch (e2) {
|
||||
$.writeln("Save error: " + fileName + " — " + e2);
|
||||
errors++;
|
||||
}
|
||||
} else {
|
||||
$.writeln("FAILED to apply effect: " + fileName);
|
||||
errors++;
|
||||
}
|
||||
|
||||
doc.close(SaveOptions.DONOTSAVECHANGES);
|
||||
|
||||
count++;
|
||||
if (count % 10 === 0) {
|
||||
$.writeln("Progress: " + count + " / " + total + " (errors: " + errors + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alert("Done. Generated " + count + " images, " + errors + " errors.\n" + outFolder.fsName);
|
||||
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test Photopea inner shadow via CDP."""
|
||||
import json, time, subprocess, sys, os
|
||||
|
||||
CHROMIUM = "/snap/bin/chromium"
|
||||
PORT = 9555
|
||||
OUT_DIR = "/mnt/extra/00_PROJECTS/hcie-rust-v4/_tmp/photopea_test"
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
|
||||
def http_get(path):
|
||||
import urllib.request
|
||||
with urllib.request.urlopen(f"http://127.0.0.1:{PORT}{path}", timeout=10) as r:
|
||||
return json.loads(r.read())
|
||||
|
||||
class CDP:
|
||||
def __init__(self, ws_url):
|
||||
import websocket
|
||||
self.ws = websocket.create_connection(ws_url, timeout=60)
|
||||
self._id = 0
|
||||
|
||||
def send(self, method, params=None):
|
||||
self._id += 1
|
||||
msg = {"id": self._id, "method": method}
|
||||
if params:
|
||||
msg["params"] = params
|
||||
self.ws.send(json.dumps(msg))
|
||||
while True:
|
||||
resp = json.loads(self.ws.recv())
|
||||
if resp.get("id") == self._id:
|
||||
return resp
|
||||
|
||||
def eval_js(self, expr, await_promise=True, timeout_ms=45000):
|
||||
r = self.send("Runtime.evaluate", {
|
||||
"expression": expr,
|
||||
"awaitPromise": await_promise,
|
||||
"returnByValue": True,
|
||||
"timeout": timeout_ms,
|
||||
})
|
||||
val = r.get("result", {}).get("result", {})
|
||||
if "value" in val:
|
||||
return val["value"]
|
||||
return val
|
||||
|
||||
def close(self):
|
||||
try: self.ws.close()
|
||||
except: pass
|
||||
|
||||
def main():
|
||||
proc = subprocess.Popen([
|
||||
CHROMIUM,
|
||||
"--headless=new",
|
||||
"--no-sandbox",
|
||||
"--disable-gpu",
|
||||
"--disable-dev-shm-usage",
|
||||
f"--remote-debugging-port={PORT}",
|
||||
"--remote-allow-origins=*",
|
||||
"--window-size=1024,768",
|
||||
"about:blank",
|
||||
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
time.sleep(4)
|
||||
|
||||
try:
|
||||
targets = http_get("/json")
|
||||
ws_url = None
|
||||
for t in targets:
|
||||
if t.get("type") == "page":
|
||||
ws_url = t["webSocketDebuggerUrl"]
|
||||
break
|
||||
if not ws_url:
|
||||
print("FATAL: no page target")
|
||||
return 1
|
||||
|
||||
cdp = CDP(ws_url)
|
||||
print(f"Connected ({ws_url[:60]}...)")
|
||||
|
||||
print("[1] Navigating to photopea.com...")
|
||||
cdp.send("Page.navigate", {"url": "https://www.photopea.com"})
|
||||
|
||||
print("[2] Waiting for page load...")
|
||||
time.sleep(90)
|
||||
|
||||
r = cdp.eval_js('document.readyState', await_promise=False)
|
||||
print(f" readyState={r}")
|
||||
|
||||
# Check what globals exist
|
||||
print("[3] Probing global scope...")
|
||||
probe = cdp.eval_js("""
|
||||
JSON.stringify({
|
||||
hasApp: typeof app !== "undefined",
|
||||
hasApp2: typeof window.app !== "undefined",
|
||||
hasPhotoshop: typeof Photoshop !== "undefined",
|
||||
hasPhotoshopApp: typeof photoshop !== "undefined",
|
||||
bodyLen: document.body ? document.body.innerHTML.length : 0,
|
||||
scripts: document.scripts.length,
|
||||
title: document.title,
|
||||
iframes: document.querySelectorAll("iframe").length,
|
||||
})
|
||||
""", await_promise=False)
|
||||
print(f" globals: {probe}")
|
||||
|
||||
# Photopea uses a special mechanism - try postMessage approach
|
||||
# Photopea might listen for messages with specific format
|
||||
print("[4] Trying Photopea postMessage API...")
|
||||
|
||||
# Photopea's API: when loaded in an iframe, parent sends scripts via postMessage.
|
||||
# But we're ON the photopea.com page. Let's try app.executeScript or direct eval.
|
||||
r2 = cdp.eval_js("""
|
||||
(function() {
|
||||
// Try to find app in various scopes
|
||||
var found = [];
|
||||
try { if (window.app) found.push("window.app"); } catch(e) {}
|
||||
try { if (self.app) found.push("self.app"); } catch(e) {}
|
||||
try { if (globalThis.app) found.push("globalThis.app"); } catch(e) {}
|
||||
|
||||
// Check if there are any Photopea-specific globals
|
||||
var keys = Object.keys(window).filter(k =>
|
||||
k.toLowerCase().includes("photo") ||
|
||||
k.toLowerCase().includes("ppea") ||
|
||||
k.toLowerCase().includes("pp") ||
|
||||
k.toLowerCase().includes("app")
|
||||
);
|
||||
|
||||
return JSON.stringify({found: found, photoKeys: keys.slice(0, 20)});
|
||||
})()
|
||||
""", await_promise=False)
|
||||
print(f" probe result: {r2}")
|
||||
|
||||
# Try running a script via Photopea's mechanism
|
||||
# Photopea can receive scripts via URL hash: photopea.com/#script_here
|
||||
# But more importantly, it exposes app when loaded as intended
|
||||
print("[5] Trying hash-based script execution...")
|
||||
script = 'var doc = app.documents.add(256, 256, 72, "test", NewDocumentMode.RGB, DocumentFill.TRANSPARENT); app.echoToOE("OK");'
|
||||
import base64
|
||||
b64 = base64.b64encode(script.encode()).decode()
|
||||
cdp.send("Page.navigate", {"url": f"https://www.photopea.com#{script}"})
|
||||
time.sleep(15)
|
||||
|
||||
r3 = cdp.eval_js('typeof app !== "undefined" ? "app_FOUND" : "no_app"', await_promise=False)
|
||||
print(f" after hash nav: {r3}")
|
||||
|
||||
# Maybe we need to add --enable-webgl or similar
|
||||
# Let's try a completely different approach: use Photopea's RPC mechanism
|
||||
print("[6] Checking if Photopea uses MessagePort or ServiceWorker...")
|
||||
r4 = cdp.eval_js("""
|
||||
(function() {
|
||||
var info = {};
|
||||
try { info.workers = navigator.serviceWorker ? "available" : "not available"; } catch(e) { info.workers = "err:" + e; }
|
||||
try { info.sockets = typeof WebSocket !== "undefined" ? "available" : "not"; } catch(e) {}
|
||||
// Check for MessageChannel
|
||||
try {
|
||||
var ch = new MessageChannel();
|
||||
info.messageChannel = "available";
|
||||
} catch(e) { info.messageChannel = "err"; }
|
||||
|
||||
// Check canvas elements (Photopea renders to canvas)
|
||||
var canvases = document.querySelectorAll("canvas");
|
||||
info.canvasCount = canvases.length;
|
||||
info.canvasDims = [];
|
||||
canvases.forEach(function(c) { info.canvasDims.push(c.width + "x" + c.height); });
|
||||
|
||||
return JSON.stringify(info);
|
||||
})()
|
||||
""", await_promise=False)
|
||||
print(f" {r4}")
|
||||
|
||||
# Last resort: check the actual Photopea API docs
|
||||
# Photopea exposes `app` on window when the UI is loaded
|
||||
# It might need WebGL canvas to be created first
|
||||
# Let's check if there's a #pea-app or similar container
|
||||
r5 = cdp.eval_js("""
|
||||
(function() {
|
||||
var divs = document.querySelectorAll("div[id]");
|
||||
var ids = [];
|
||||
divs.forEach(function(d) { ids.push(d.id); });
|
||||
return JSON.stringify(ids.slice(0, 30));
|
||||
})()
|
||||
""", await_promise=False)
|
||||
print(f" div ids: {r5}")
|
||||
|
||||
print("\n=== CONCLUSION ===")
|
||||
print("Photopea loaded but `app` is NOT in the global JS scope.")
|
||||
print("This means Photopea's scripting API is only available via postMessage")
|
||||
print("when Photopea is embedded as an iframe in another page.")
|
||||
print("")
|
||||
print("The correct approach for batch generation is:")
|
||||
print(" - The user runs the JSX script manually in Photopea's Script runner")
|
||||
print(" - OR use a different tool (GIMP Script-Fu, Python PIL)")
|
||||
|
||||
return 0
|
||||
|
||||
except Exception as e:
|
||||
print(f"EXCEPTION: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
finally:
|
||||
try: cdp.close()
|
||||
except: pass
|
||||
try: proc.kill()
|
||||
except: pass
|
||||
try: proc.wait(timeout=5)
|
||||
except: pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main() or 0)
|
||||
@@ -0,0 +1,41 @@
|
||||
# Photoshop ExtendScript Dataset Generator
|
||||
|
||||
This folder contains JSX scripts to generate systematic layer-effect ground-truth images in Adobe Photoshop.
|
||||
|
||||
## Files
|
||||
|
||||
- `generate_inner_shadow_dataset.jsx` — Starter script for Inner Shadow.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Open Adobe Photoshop.
|
||||
2. Choose `File > Scripts > Browse...`.
|
||||
3. Select `generate_inner_shadow_dataset.jsx`.
|
||||
4. Choose an output folder when prompted.
|
||||
|
||||
Photoshop will create a new document for each parameter combination, draw a filled black rectangle, apply the Inner Shadow effect, and export a transparent PNG.
|
||||
|
||||
## Current parameter grid (short version)
|
||||
|
||||
| Parameter | Values |
|
||||
|---|---|
|
||||
| angle | 0, 90, 180, 270 |
|
||||
| distance | 5, 15, 30 |
|
||||
| size | 10, 30 |
|
||||
| choke | 0, 20 |
|
||||
| fx opacity | 75, 100 |
|
||||
| fx blend mode | Normal, Multiply |
|
||||
|
||||
Total images: `4 × 3 × 2 × 2 × 2 × 2 = 192`.
|
||||
|
||||
## Notes
|
||||
|
||||
- Blend mode keys use Photoshop's 4-character codes (`Nrml`, `Mltp`).
|
||||
- The output PNGs have transparent backgrounds, so MAE comparisons can ignore white reference pixels if needed.
|
||||
- Opacity is passed as 0-100 (e.g. 75 means 75%).
|
||||
- If a combination fails, the script alerts and continues with the next one.
|
||||
|
||||
## Next steps
|
||||
|
||||
- Add layer blend mode, layer opacity, and fill opacity variations.
|
||||
- Extend to Drop Shadow, Outer Glow, Inner Glow, Stroke, Bevel & Emboss, Color Overlay.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Photoshop Batch PNG Exporter
|
||||
|
||||
This script batch-exports PSD files to transparent PNGs.
|
||||
|
||||
## Files
|
||||
|
||||
- `export_psd_folder_to_png.jsx`
|
||||
|
||||
## Usage
|
||||
|
||||
1. Open Adobe Photoshop.
|
||||
2. Choose `File > Scripts > Browse...`.
|
||||
3. Select `export_psd_folder_to_png.jsx`.
|
||||
4. When prompted, choose the folder containing the PSD files generated by `generate_inner_shadow_psd_set.rs`.
|
||||
5. The script saves one PNG next to each PSD.
|
||||
|
||||
## Notes
|
||||
|
||||
- Existing PNGs are skipped, so you can re-run safely.
|
||||
- If any PSD fails to open or export, the script logs it and continues.
|
||||
- Output PNGs use transparent background.
|
||||
|
||||
## Photopea alternative
|
||||
|
||||
Photopea does not support transparent PNG export via JSX directly. For Photopea, open each PSD and use `File > Export As > PNG` with transparency enabled.
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* export_psd_folder_to_png.jsx
|
||||
*
|
||||
* Purpose:
|
||||
* Batch-open every PSD in a folder and export it as a transparent PNG.
|
||||
*
|
||||
* Logic & Workflow:
|
||||
* 1. Prompts the user to select a folder containing PSD files.
|
||||
* 2. Iterates over all *.psd files in that folder.
|
||||
* 3. Opens each PSD, saves it as PNG with transparency, then closes without saving.
|
||||
*
|
||||
* Arguments:
|
||||
* None (interactive). User selects input folder via dialog.
|
||||
*
|
||||
* Side Effects:
|
||||
* - Opens and closes many Photoshop documents.
|
||||
* - Writes PNG files next to the original PSDs.
|
||||
*/
|
||||
|
||||
var inFolder = Folder.selectDialog("Select folder containing PSD files");
|
||||
if (!inFolder) {
|
||||
alert("No folder selected. Exiting.");
|
||||
throw new Error("No folder");
|
||||
}
|
||||
|
||||
var psdFiles = inFolder.getFiles("*.psd");
|
||||
if (psdFiles.length === 0) {
|
||||
alert("No PSD files found in " + inFolder.fsName);
|
||||
throw new Error("No PSD files");
|
||||
}
|
||||
|
||||
var pngOptions = new PNGSaveOptions();
|
||||
pngOptions.compression = 6;
|
||||
pngOptions.interlaced = false;
|
||||
|
||||
for (var i = 0; i < psdFiles.length; i++) {
|
||||
var psdFile = psdFiles[i];
|
||||
var baseName = psdFile.name.replace(/\.psd$/i, "");
|
||||
var pngFile = new File(inFolder + "/" + baseName + ".png");
|
||||
|
||||
if (pngFile.exists) {
|
||||
$.writeln("Skipping existing: " + pngFile.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
var doc = app.open(psdFile);
|
||||
doc.saveAs(pngFile, pngOptions, true, Extension.LOWERCASE);
|
||||
doc.close(SaveOptions.DONOTSAVECHANGES);
|
||||
$.writeln("Exported: " + pngFile.name);
|
||||
} catch (e) {
|
||||
$.writeln("Error processing " + psdFile.name + ": " + e);
|
||||
alert("Error processing " + psdFile.name + ":\n" + e);
|
||||
}
|
||||
}
|
||||
|
||||
alert("Done. Processed " + psdFiles.length + " PSD files.");
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* export_psd_folder_to_png.jsx
|
||||
*
|
||||
* Purpose:
|
||||
* Batch-open every PSD in a folder and export it as a transparent PNG
|
||||
* into a separate output folder.
|
||||
*
|
||||
* Logic & Workflow:
|
||||
* 1. Prompts user for input PSD folder.
|
||||
* 2. Prompts user for output PNG folder (default: input + "_png" suffix).
|
||||
* 3. Iterates over all *.psd files.
|
||||
* 4. Opens each PSD, saves as PNG with transparency, closes without saving.
|
||||
*
|
||||
* Arguments:
|
||||
* None (interactive).
|
||||
*
|
||||
* Side Effects:
|
||||
* - Opens and closes many Photoshop documents.
|
||||
* - Writes PNG files into the output folder.
|
||||
*/
|
||||
|
||||
var inFolder = Folder.selectDialog("Select PSD input folder");
|
||||
if (!inFolder) {
|
||||
alert("No folder selected. Exiting.");
|
||||
throw new Error("No folder");
|
||||
}
|
||||
|
||||
var defaultOut = new Folder(inFolder.parent.fsName + "/" + inFolder.name + "_png");
|
||||
var outFolder = Folder.selectDialog("Select PNG output folder", defaultOut);
|
||||
if (!outFolder) {
|
||||
outFolder = defaultOut;
|
||||
}
|
||||
if (!outFolder.exists) {
|
||||
outFolder.create();
|
||||
}
|
||||
|
||||
var psdFiles = inFolder.getFiles("*.psd");
|
||||
if (psdFiles.length === 0) {
|
||||
alert("No PSD files found in " + inFolder.fsName);
|
||||
throw new Error("No PSD files");
|
||||
}
|
||||
|
||||
var pngOptions = new PNGSaveOptions();
|
||||
pngOptions.compression = 6;
|
||||
pngOptions.interlaced = false;
|
||||
|
||||
for (var i = 0; i < psdFiles.length; i++) {
|
||||
var psdFile = psdFiles[i];
|
||||
var baseName = psdFile.name.replace(/\.psd$/i, "");
|
||||
var pngFile = new File(outFolder.fsName + "/" + baseName + ".png");
|
||||
|
||||
if (pngFile.exists) {
|
||||
$.writeln("Skipping existing: " + pngFile.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
var doc = app.open(psdFile);
|
||||
// Ensure no background layer blocks transparency
|
||||
if (doc.backgroundLayer) {
|
||||
doc.activeLayer = doc.backgroundLayer;
|
||||
doc.activeLayer.remove();
|
||||
}
|
||||
doc.trim(TrimType.TRANSPARENT);
|
||||
doc.saveAs(pngFile, pngOptions, true, Extension.LOWERCASE);
|
||||
doc.close(SaveOptions.DONOTSAVECHANGES);
|
||||
$.writeln("Exported: " + pngFile.name);
|
||||
} catch (e) {
|
||||
$.writeln("Error processing " + psdFile.name + ": " + e);
|
||||
}
|
||||
}
|
||||
|
||||
alert("Done. Processed " + psdFiles.length + " PSD files.");
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* generate_inner_shadow_dataset.jsx
|
||||
*
|
||||
* Purpose:
|
||||
* Generate a systematic Inner Shadow effect dataset in Adobe Photoshop.
|
||||
*
|
||||
* Logic & Workflow:
|
||||
* 1. Creates a new transparent RGB document.
|
||||
* 2. Adds a raster layer and fills a centered rectangle with black.
|
||||
* 3. Applies one Inner Shadow effect via Photoshop ActionDescriptor.
|
||||
* 4. Saves the result as a transparent PNG.
|
||||
*
|
||||
* Arguments:
|
||||
* None (interactive). The user selects an output folder via dialog.
|
||||
*
|
||||
* Side Effects:
|
||||
* - Creates and closes many temporary Photoshop documents.
|
||||
* - Writes PNG files to the selected folder.
|
||||
*/
|
||||
|
||||
var outFolder = Folder.selectDialog("Select output folder for Inner Shadow dataset");
|
||||
if (!outFolder) {
|
||||
alert("No output folder selected. Exiting.");
|
||||
throw new Error("No output folder");
|
||||
}
|
||||
|
||||
var angles = [0, 90, 180, 270];
|
||||
var distances = [5, 15, 30];
|
||||
var sizes = [10, 30];
|
||||
var chokes = [0, 20];
|
||||
var fxOpacities = [75, 100];
|
||||
var fxBlendModes = [
|
||||
{ key: "Nrml", name: "normal" },
|
||||
{ key: "Mltp", name: "multiply" }
|
||||
];
|
||||
|
||||
function pxValue(v) {
|
||||
if (v === undefined || v === null) return 0;
|
||||
if (typeof v === "number") return v;
|
||||
if (typeof v.as === "function") return v.as("px");
|
||||
if (typeof v.value !== "undefined") return v.value;
|
||||
return Number(v);
|
||||
}
|
||||
|
||||
function createDocument(name, width, height) {
|
||||
try {
|
||||
return app.documents.add(width, height, 72, name, NewDocumentMode.RGB, DocumentFill.TRANSPARENT);
|
||||
} catch (e) {
|
||||
return app.documents.add(width, height, 72, name, NewDocumentMode.RGB);
|
||||
}
|
||||
}
|
||||
|
||||
function addBlackRectangle(doc) {
|
||||
var w = pxValue(doc.width);
|
||||
var h = pxValue(doc.height);
|
||||
var margin = Math.round(Math.min(w, h) * 0.25);
|
||||
|
||||
var layer = doc.artLayers.add();
|
||||
layer.name = "shape";
|
||||
|
||||
var region = [
|
||||
[margin, margin],
|
||||
[w - margin, margin],
|
||||
[w - margin, h - margin],
|
||||
[margin, h - margin]
|
||||
];
|
||||
|
||||
doc.selection.select(region);
|
||||
|
||||
var black = new SolidColor();
|
||||
black.rgb.red = 0;
|
||||
black.rgb.green = 0;
|
||||
black.rgb.blue = 0;
|
||||
|
||||
doc.selection.fill(black);
|
||||
doc.selection.deselect();
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
function blendModeTypeID(mode) {
|
||||
try { return charIDToTypeID(mode); }
|
||||
catch (e) { return stringIDToTypeID(mode); }
|
||||
}
|
||||
|
||||
function applyInnerShadow(params) {
|
||||
var desc = new ActionDescriptor();
|
||||
|
||||
var ref = new ActionReference();
|
||||
ref.putProperty(charIDToTypeID("Prpr"), charIDToTypeID("Lefx"));
|
||||
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
|
||||
desc.putReference(charIDToTypeID("null"), ref);
|
||||
|
||||
var fxDesc = new ActionDescriptor();
|
||||
|
||||
var isDesc = new ActionDescriptor();
|
||||
isDesc.putBoolean(charIDToTypeID("enab"), true);
|
||||
isDesc.putBoolean(charIDToTypeID("present"), true);
|
||||
isDesc.putBoolean(charIDToTypeID("showInDialog"), true);
|
||||
|
||||
isDesc.putEnumerated(charIDToTypeID("Blnd"), charIDToTypeID("BlnM"), blendModeTypeID(params.blendMode));
|
||||
|
||||
var clr = new ActionDescriptor();
|
||||
clr.putDouble(charIDToTypeID("Rd "), 0);
|
||||
clr.putDouble(charIDToTypeID("Grn "), 0);
|
||||
clr.putDouble(charIDToTypeID("Bl "), 0);
|
||||
isDesc.putObject(charIDToTypeID("Clr "), charIDToTypeID("RGBC"), clr);
|
||||
|
||||
isDesc.putInteger(charIDToTypeID("Opct"), params.opacity);
|
||||
isDesc.putInteger(charIDToTypeID("lagl"), params.angle);
|
||||
isDesc.putBoolean(charIDToTypeID("uglg"), false);
|
||||
isDesc.putInteger(charIDToTypeID("Dstn"), params.distance);
|
||||
isDesc.putInteger(charIDToTypeID("Ckmt"), params.choke);
|
||||
isDesc.putInteger(charIDToTypeID("blur"), params.size);
|
||||
isDesc.putInteger(charIDToTypeID("Nose"), 0);
|
||||
|
||||
fxDesc.putObject(charIDToTypeID("IrSh"), charIDToTypeID("IrSh"), isDesc);
|
||||
desc.putObject(charIDToTypeID("T "), charIDToTypeID("Lefx"), fxDesc);
|
||||
|
||||
executeAction(charIDToTypeID("setd"), desc, DialogModes.NO);
|
||||
}
|
||||
|
||||
function saveTransparentPng(doc, file) {
|
||||
var pngOptions = new PNGSaveOptions();
|
||||
pngOptions.compression = 6;
|
||||
pngOptions.interlaced = false;
|
||||
doc.saveAs(file, pngOptions, true, Extension.LOWERCASE);
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
var errors = 0;
|
||||
var total = angles.length * distances.length * sizes.length * chokes.length * fxOpacities.length * fxBlendModes.length;
|
||||
|
||||
for (var a = 0; a < angles.length; a++) {
|
||||
for (var d = 0; d < distances.length; d++) {
|
||||
for (var s = 0; s < sizes.length; s++) {
|
||||
for (var c = 0; c < chokes.length; c++) {
|
||||
for (var o = 0; o < fxOpacities.length; o++) {
|
||||
for (var b = 0; b < fxBlendModes.length; b++) {
|
||||
var params = {
|
||||
angle: angles[a],
|
||||
distance: distances[d],
|
||||
size: sizes[s],
|
||||
choke: chokes[c],
|
||||
opacity: fxOpacities[o],
|
||||
blendMode: fxBlendModes[b].key
|
||||
};
|
||||
|
||||
var fileName = "is_" +
|
||||
"a" + params.angle + "_" +
|
||||
"d" + params.distance + "_" +
|
||||
"s" + params.size + "_" +
|
||||
"c" + params.choke + "_" +
|
||||
"op" + params.opacity + "_" +
|
||||
"bm" + fxBlendModes[b].name +
|
||||
".png";
|
||||
|
||||
var file = new File(outFolder + "/" + fileName);
|
||||
if (file.exists) { count++; continue; }
|
||||
|
||||
var doc = createDocument("inner_shadow_test", 256, 256);
|
||||
var layer = addBlackRectangle(doc);
|
||||
|
||||
try {
|
||||
applyInnerShadow(params);
|
||||
saveTransparentPng(doc, file);
|
||||
} catch (e) {
|
||||
$.writeln("Error: " + fileName + " — " + e);
|
||||
errors++;
|
||||
} finally {
|
||||
doc.close(SaveOptions.DONOTSAVECHANGES);
|
||||
}
|
||||
|
||||
count++;
|
||||
if (count % 10 === 0) {
|
||||
$.writeln("Progress: " + count + " / " + total + " (errors: " + errors + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alert("Done. Generated " + count + " images, " + errors + " errors.\n" + outFolder.fsName);
|
||||
@@ -0,0 +1,283 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Minimal PSD writer — byte-exact per Adobe Photoshop File Formats Specification.
|
||||
Creates a 256x256 RGBA document with one layer (red rectangle).
|
||||
"""
|
||||
import struct, os
|
||||
|
||||
def be16(v): return struct.pack(">H", v)
|
||||
def be32(v): return struct.pack(">I", v)
|
||||
def bei16(v): return struct.pack(">h", v)
|
||||
|
||||
def packbits_compress(row):
|
||||
out = bytearray()
|
||||
n = len(row)
|
||||
i = 0
|
||||
while i < n:
|
||||
# find run
|
||||
run = 1
|
||||
while run < 128 and i + run < n and row[i+run] == row[i]:
|
||||
run += 1
|
||||
if run > 1:
|
||||
out.append((1 - run) & 0xFF)
|
||||
out.append(row[i])
|
||||
i += run
|
||||
else:
|
||||
# literal
|
||||
start = i
|
||||
lit = 0
|
||||
while lit < 128 and i + lit < n:
|
||||
nxt = 0
|
||||
if i + lit + 1 < n and row[i+lit] == row[i+lit+1]:
|
||||
r = 2
|
||||
while r < 128 and i+lit+r < n and row[i+lit+r] == row[i+lit]:
|
||||
r += 1
|
||||
nxt = r
|
||||
if nxt >= 3:
|
||||
break
|
||||
lit += 1
|
||||
if lit == 0:
|
||||
lit = 1
|
||||
out.append(lit - 1)
|
||||
out.extend(row[start:start+lit])
|
||||
i += lit
|
||||
return bytes(out)
|
||||
|
||||
def write_resource(buf, res_id, name, data):
|
||||
buf.extend(b"8BIM")
|
||||
buf.extend(be16(res_id))
|
||||
name_padded = name + b'\x00' if (1 + len(name)) % 2 == 1 else name
|
||||
buf.append(len(name))
|
||||
buf.extend(name)
|
||||
if (1 + len(name)) % 2 == 1:
|
||||
buf.append(0)
|
||||
buf.extend(be32(len(data)))
|
||||
buf.extend(data)
|
||||
if len(data) % 2 != 1:
|
||||
pass # even already
|
||||
# pad data to even
|
||||
if len(data) % 2 != 0:
|
||||
buf.append(0)
|
||||
|
||||
def write_pascal_padded4(buf, s):
|
||||
"""Write Pascal string padded to multiple of 4 bytes."""
|
||||
b = s.encode("ascii") if isinstance(s, str) else s
|
||||
buf.append(len(b))
|
||||
buf.extend(b)
|
||||
total = 1 + len(b)
|
||||
pad = (4 - total % 4) % 4
|
||||
buf.extend(b'\x00' * pad)
|
||||
|
||||
def write_tagged_block(buf, key, data):
|
||||
"""Write 8BIM tagged block."""
|
||||
buf.extend(b"8BIM")
|
||||
buf.extend(key)
|
||||
buf.extend(be32(len(data)))
|
||||
buf.extend(data)
|
||||
if len(data) % 2 != 0:
|
||||
buf.append(0)
|
||||
|
||||
def make_descriptor_string(s):
|
||||
"""Write a descriptor UTF-16 string: length(4) + chars(2*N)."""
|
||||
enc = s.encode("utf-16-be")
|
||||
return be32(len(enc) // 2) + enc
|
||||
|
||||
def main():
|
||||
W, H = 256, 256
|
||||
MARGIN = 64
|
||||
|
||||
# ── Create layer pixels ──
|
||||
rgba = bytearray(W * H * 4)
|
||||
for y in range(H):
|
||||
for x in range(W):
|
||||
i = (y * W + x) * 4
|
||||
if MARGIN <= x < W - MARGIN and MARGIN <= y < H - MARGIN:
|
||||
rgba[i] = 255 # R
|
||||
rgba[i+1] = 0 # G
|
||||
rgba[i+2] = 0 # B
|
||||
rgba[i+3] = 255 # A
|
||||
|
||||
# ── Header (26 bytes) ──
|
||||
header = bytearray()
|
||||
header.extend(b"8BPS")
|
||||
header.extend(be16(1)) # version
|
||||
header.extend(b'\x00' * 6) # reserved
|
||||
header.extend(be16(4)) # channels (RGBA)
|
||||
header.extend(be32(H)) # height
|
||||
header.extend(be32(W)) # width
|
||||
header.extend(be16(8)) # depth
|
||||
header.extend(be16(3)) # color mode (RGB)
|
||||
|
||||
# ── Color Mode Data ──
|
||||
color_mode = be32(0)
|
||||
|
||||
# ── Image Resources ──
|
||||
img_res = bytearray()
|
||||
# 0x03ED: Resolution info
|
||||
res_data = bytearray()
|
||||
res_data.extend(be32(72 << 16)) # hRes (72 DPI fixed)
|
||||
res_data.extend(be16(1)) # hResUnit (1=pixels/inch)
|
||||
res_data.extend(be16(1)) # vResUnit
|
||||
res_data.extend(be32(72 << 16)) # vRes
|
||||
res_data.extend(be16(1)) # widthUnit
|
||||
res_data.extend(be16(1)) # heightUnit
|
||||
write_resource(img_res, 0x03ED, b"", bytes(res_data))
|
||||
|
||||
# 0x0419: Global Angle
|
||||
write_resource(img_res, 0x0419, b"", be32(120))
|
||||
|
||||
# pad img_res to even
|
||||
if len(img_res) % 2 != 0:
|
||||
img_res.append(0)
|
||||
|
||||
# ── Layer & Mask Information ──
|
||||
# Build layer info section first
|
||||
layer_info = bytearray()
|
||||
|
||||
# Layer count (positive = no groups)
|
||||
layer_info.extend(bei16(1))
|
||||
|
||||
# Layer record
|
||||
layer_rec = bytearray()
|
||||
# Rect: top, left, bottom, right
|
||||
layer_rec.extend(be32(0)) # top
|
||||
layer_rec.extend(be32(0)) # left
|
||||
layer_rec.extend(be32(H)) # bottom
|
||||
layer_rec.extend(be32(W)) # right
|
||||
|
||||
# Channel count
|
||||
layer_rec.extend(be16(4))
|
||||
|
||||
# Channel info: (-1=alpha, 0=R, 1=G, 2=B)
|
||||
# We'll compute channel data lengths later
|
||||
ch_ids = [-1, 0, 1, 2]
|
||||
ch_data_list = []
|
||||
for ch_id in ch_ids:
|
||||
plane = bytearray()
|
||||
for y in range(H):
|
||||
for x in range(W):
|
||||
idx = (y * W + x) * 4
|
||||
if ch_id == -1:
|
||||
plane.append(rgba[idx + 3])
|
||||
elif ch_id == 0:
|
||||
plane.append(rgba[idx])
|
||||
elif ch_id == 1:
|
||||
plane.append(rgba[idx + 1])
|
||||
elif ch_id == 2:
|
||||
plane.append(rgba[idx + 2])
|
||||
ch_data_list.append(plane)
|
||||
|
||||
# Compress each channel
|
||||
ch_compressed = []
|
||||
for plane in ch_data_list:
|
||||
row_lengths = []
|
||||
compressed_rows = []
|
||||
for y in range(H):
|
||||
row = plane[y * W : (y + 1) * W]
|
||||
compressed = packbits_compress(row)
|
||||
row_lengths.append(len(compressed))
|
||||
compressed_rows.append(compressed)
|
||||
ch_compressed.append((row_lengths, compressed_rows))
|
||||
|
||||
# Channel data = compression(2) + row_lengths(H*2) + compressed_data
|
||||
ch_packed = []
|
||||
for row_lengths, compressed_rows in ch_compressed:
|
||||
buf = bytearray()
|
||||
buf.extend(be16(1)) # compression type = RLE
|
||||
for rl in row_lengths:
|
||||
buf.extend(be16(rl))
|
||||
for cr in compressed_rows:
|
||||
buf.extend(cr)
|
||||
ch_packed.append(bytes(buf))
|
||||
|
||||
# Write channel info records
|
||||
for i, ch_id in enumerate(ch_ids):
|
||||
layer_rec.extend(bei16(ch_id))
|
||||
layer_rec.extend(be32(len(ch_packed[i])))
|
||||
|
||||
# Blend mode
|
||||
layer_rec.extend(b"8BIMnorm")
|
||||
layer_rec.append(255) # opacity
|
||||
layer_rec.append(0) # clipping (base)
|
||||
layer_rec.append(0) # flags (visible)
|
||||
layer_rec.append(0) # filler
|
||||
|
||||
# Extra data
|
||||
extra = bytearray()
|
||||
# Layer mask data (4 bytes, length=0)
|
||||
extra.extend(be32(0))
|
||||
# Blending ranges (4 bytes length=40, then 40 bytes of data)
|
||||
extra.extend(be32(40))
|
||||
for _ in range(10):
|
||||
extra.extend(be16(0))
|
||||
extra.extend(be16(65535))
|
||||
# Layer name (Pascal string padded to 4)
|
||||
write_pascal_padded4(extra, "Red Rectangle")
|
||||
|
||||
layer_rec.extend(be32(len(extra)))
|
||||
layer_rec.extend(extra)
|
||||
|
||||
# Append channel data
|
||||
layer_info.extend(layer_rec)
|
||||
for ch_data in ch_packed:
|
||||
layer_info.extend(ch_data)
|
||||
|
||||
# Pad layer info to even
|
||||
if len(layer_info) % 2 != 0:
|
||||
layer_info.append(0)
|
||||
|
||||
# Global Layer Mask Info (empty, 4 bytes)
|
||||
glmi = be32(0)
|
||||
|
||||
# Layer & Mask Information section
|
||||
lami = bytearray()
|
||||
lami.extend(be32(len(layer_info) + len(glmi))) # length of rest
|
||||
lami.extend(layer_info)
|
||||
lami.extend(glmi)
|
||||
|
||||
# ── Merged Image Data ──
|
||||
merged = bytearray()
|
||||
merged.extend(be16(1)) # compression = RLE
|
||||
|
||||
# Row lengths for all channels (per-channel order: RRR...GGG...BBB...AAA...)
|
||||
for ch_offset in range(4): # R, G, B, A
|
||||
for y in range(H):
|
||||
row = bytes(rgba[(y * W + ch_offset)::4][:W]) # wrong, need proper extraction
|
||||
row = bytearray()
|
||||
for x in range(W):
|
||||
idx = (y * W + x) * 4 + ch_offset
|
||||
row.append(rgba[idx])
|
||||
compressed = packbits_compress(row)
|
||||
merged.extend(be16(len(compressed)))
|
||||
|
||||
# Compressed data for all channels
|
||||
for ch_offset in range(4):
|
||||
for y in range(H):
|
||||
row = bytearray()
|
||||
for x in range(W):
|
||||
idx = (y * W + x) * 4 + ch_offset
|
||||
row.append(rgba[idx])
|
||||
compressed = packbits_compress(row)
|
||||
merged.extend(compressed)
|
||||
|
||||
# ── Assemble PSD ──
|
||||
psd = bytearray()
|
||||
psd.extend(header)
|
||||
psd.extend(color_mode)
|
||||
psd.extend(be32(len(img_res)))
|
||||
psd.extend(img_res)
|
||||
psd.extend(lami)
|
||||
psd.extend(merged)
|
||||
|
||||
out_path = "_tmp/python_ref_test.psd"
|
||||
with open(out_path, "wb") as f:
|
||||
f.write(psd)
|
||||
print(f"Wrote {len(psd)} bytes to {out_path}")
|
||||
|
||||
# Also verify roundtrip
|
||||
assert psd[:4] == b"8BPS"
|
||||
assert len(psd) == 26 + 4 + 4 + len(img_res) + len(lami) + len(merged)
|
||||
print("Roundtrip check: OK")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE animation-metadata PUBLIC '-//KDE//DTD krita 1.1//EN' 'http://www.calligra.org/DTD/krita-1.1.dtd'>
|
||||
<animation-metadata xmlns="http://www.calligra.org/DTD/krita">
|
||||
<framerate type="value" value="24"/>
|
||||
<range to="100" type="timerange" from="0"/>
|
||||
<currentTime type="value" value="0"/>
|
||||
<export-settings>
|
||||
<sequenceFilePath type="value" value=""/>
|
||||
<sequenceBaseName type="value" value=""/>
|
||||
<sequenceInitialFrameNumber type="value" value="-1"/>
|
||||
</export-settings>
|
||||
</animation-metadata>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
VERSION 2
|
||||
TILEWIDTH 64
|
||||
TILEHEIGHT 64
|
||||
PIXELSIZE 4
|
||||
DATA 0
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE document-info PUBLIC '-//KDE//DTD document-info 1.1//EN' 'http://www.calligra.org/DTD/document-info-1.1.dtd'>
|
||||
<document-info xmlns="http://www.calligra.org/DTD/document-info">
|
||||
<about>
|
||||
<title></title>
|
||||
<description></description>
|
||||
<subject></subject>
|
||||
<abstract><![CDATA[]]></abstract>
|
||||
<keyword></keyword>
|
||||
<initial-creator>Unknown</initial-creator>
|
||||
<editing-cycles>1</editing-cycles>
|
||||
<editing-time>0</editing-time>
|
||||
<date>2026-06-17T17:50:18</date>
|
||||
<creation-date>2026-06-17T17:48:05</creation-date>
|
||||
<language></language>
|
||||
<license></license>
|
||||
</about>
|
||||
<author>
|
||||
<full-name></full-name>
|
||||
<creator-first-name></creator-first-name>
|
||||
<creator-last-name></creator-last-name>
|
||||
<initial></initial>
|
||||
<author-title></author-title>
|
||||
<position></position>
|
||||
<company></company>
|
||||
</author>
|
||||
</document-info>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE DOC PUBLIC '-//KDE//DTD krita 2.0//EN' 'http://www.calligra.org/DTD/krita-2.0.dtd'>
|
||||
<DOC xmlns="http://www.calligra.org/DTD/krita" editor="Krita" kritaVersion="5.2.11-prealpha" syntaxVersion="2.0">
|
||||
<IMAGE height="800" description="" name="Unnamed" mime="application/x-kra" profile="sRGB-elle-V2-srgbtrc.icc" width="800" x-res="72.0000000000058" y-res="72.0000000000058" colorspacename="RGBA">
|
||||
<layers>
|
||||
<layer intimeline="0" name="Layer 1" locked="0" collapsed="0" channelflags="" uuid="{e58100bc-fcda-43b3-ac7d-b834b7baacb6}" colorspacename="RGBA" filename="layer2" opacity="255" compositeop="normal" colorlabel="0" layerstyle="{d7a0844e-a522-4673-8c83-aa4f45e7782f}" channellockflags="" y="0" x="0" onionskin="0" visible="1" nodetype="paintlayer"/>
|
||||
<layer intimeline="0" name="Layer 4" locked="0" collapsed="0" channelflags="" uuid="{633fd10a-20f2-4a99-8e29-aa27df65df0e}" colorspacename="RGBA" filename="layer3" opacity="255" compositeop="normal" colorlabel="0" selected="true" layerstyle="{7d378444-e311-41d3-b9c6-a19e2a8e5899}" channellockflags="" y="0" x="0" onionskin="0" visible="1" nodetype="paintlayer"/>
|
||||
<layer intimeline="0" name="Layer 3" locked="0" collapsed="0" channelflags="" uuid="{82aae6cb-88d2-43dd-b32b-8f697a58001f}" colorspacename="RGBA" filename="layer4" opacity="255" compositeop="normal" colorlabel="0" layerstyle="{117f8514-88e4-4553-bccb-44a87b6b9182}" channellockflags="" y="0" x="0" onionskin="0" visible="1" nodetype="paintlayer"/>
|
||||
<layer intimeline="0" name="Layer 2" locked="0" collapsed="0" channelflags="" uuid="{12fdb980-bba4-4d3f-94f0-9a88a529dc76}" colorspacename="RGBA" filename="layer5" opacity="255" compositeop="normal" colorlabel="0" layerstyle="{4bbe8f2c-7ad2-4e31-8af0-9ad6715cacf4}" channellockflags="" y="0" x="0" onionskin="0" visible="1" nodetype="paintlayer"/>
|
||||
<layer filename="layer6" y="0" intimeline="0" name="Background" visible="1" channellockflags="" opacity="255" nodetype="paintlayer" channelflags="" uuid="{c9e22005-6a14-42ff-bfc6-32ce0b70f20c}" x="0" onionskin="0" locked="0" compositeop="normal" collapsed="0" colorlabel="0" colorspacename="RGBA"/>
|
||||
</layers>
|
||||
<ProjectionBackgroundColor ColorData="AAAAAA=="/>
|
||||
<GlobalAssistantsColor SimpleColorData="176,176,176,255"/>
|
||||
<Palettes/>
|
||||
<resources/>
|
||||
<animation>
|
||||
<framerate type="value" value="24"/>
|
||||
<range to="100" type="timerange" from="0"/>
|
||||
<currentTime type="value" value="0"/>
|
||||
</animation>
|
||||
<Annotations>
|
||||
<Annotation description="0x0408 - Grid & guide info" type="PSD Resource Block: 1032"/>
|
||||
<Annotation description="0x040c - Thumbnail resource" type="PSD Resource Block: 1036"/>
|
||||
<Annotation description="0x040d - Global angle" type="PSD Resource Block: 1037"/>
|
||||
<Annotation description="0x0414 - Document specific IDs" type="PSD Resource Block: 1044"/>
|
||||
<Annotation description="0x0415 - Unicode alpha names" type="PSD Resource Block: 1045"/>
|
||||
<Annotation description="0x0419 - Global altitude" type="PSD Resource Block: 1049"/>
|
||||
<Annotation description="0x041a - Slices" type="PSD Resource Block: 1050"/>
|
||||
<Annotation description="0x0435 - (Photoshop CS3) DisplayInfo structure to support floating point colors. Also see ID 1007. See Appendix A in Photoshop API Guide.pdf ." type="PSD Resource Block: 1077"/>
|
||||
</Annotations>
|
||||
</IMAGE>
|
||||
</DOC>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1 @@
|
||||
application/x-krita
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
PROFILE="release"
|
||||
CARGO_FLAGS="--release"
|
||||
|
||||
if [ "$1" = "debug" ] || [ "$1" = "--debug" ]; then
|
||||
PROFILE="debug"
|
||||
CARGO_FLAGS=""
|
||||
echo "=== Compiling plugins in DEBUG mode ==="
|
||||
else
|
||||
echo "=== Compiling plugins in RELEASE mode ==="
|
||||
fi
|
||||
|
||||
PROJECT_DIR="/mnt/extra/00_PROJECTS/hcie-rust-v4"
|
||||
PLUGINS_DIR="$PROJECT_DIR/plugins"
|
||||
BUILD_LIB_DIR="$PROJECT_DIR/build/lib"
|
||||
mkdir -p "$PLUGINS_DIR"
|
||||
|
||||
echo "=== Building AI/Vision dynamic libraries ==="
|
||||
|
||||
echo "1. Building hcie-ai..."
|
||||
cargo build --manifest-path "$PROJECT_DIR/hcie-ai/Cargo.toml" $CARGO_FLAGS --features ffi
|
||||
cp "$PROJECT_DIR/target/$PROFILE/libhcie_ai.so" "$PLUGINS_DIR/"
|
||||
|
||||
echo "2. Building hcie-vision..."
|
||||
cargo build --manifest-path "$PROJECT_DIR/hcie-vision/Cargo.toml" $CARGO_FLAGS --features ffi
|
||||
cp "$PROJECT_DIR/target/$PROFILE/libhcie_vision.so" "$PLUGINS_DIR/"
|
||||
# Copy the C++ backend runtime dependencies next to the plugin so the dynamic
|
||||
# loader finds them without extra LD_LIBRARY_PATH setup.
|
||||
for lib in libvisioncpp.so libvisioncpp.so.0 libvisioncpp.so.0.3.1 \
|
||||
libggml.so libggml.so.0 libggml.so.0.9.9 \
|
||||
libggml-cpu.so libggml-cpu.so.0 libggml-cpu.so.0.9.9 \
|
||||
libggml-base.so libggml-base.so.0 libggml-base.so.0.9.9; do
|
||||
if [ -f "$PROJECT_DIR/target/$PROFILE/$lib" ]; then
|
||||
cp -L "$PROJECT_DIR/target/$PROFILE/$lib" "$PLUGINS_DIR/"
|
||||
elif [ -f "$BUILD_LIB_DIR/$lib" ]; then
|
||||
cp -L "$BUILD_LIB_DIR/$lib" "$PLUGINS_DIR/"
|
||||
fi
|
||||
done
|
||||
|
||||
# Ensure the backend libraries can still find each other when loaded from the
|
||||
# plugins directory by making their RUNPATH point to the canonical build/lib.
|
||||
if command -v patchelf >/dev/null 2>&1; then
|
||||
for lib in libvisioncpp.so.0 libggml.so.0 libggml-cpu.so.0 libggml-base.so.0; do
|
||||
if [ -f "$PLUGINS_DIR/$lib" ]; then
|
||||
patchelf --set-rpath "$BUILD_LIB_DIR" "$PLUGINS_DIR/$lib" || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "===================================================="
|
||||
echo "AI/Vision plugins built and copied to $PLUGINS_DIR successfully in $PROFILE mode!"
|
||||
echo "===================================================="
|
||||
Executable
+120
@@ -0,0 +1,120 @@
|
||||
#!/bin/bash
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# HCIE Qt6 Build Script
|
||||
# Builds the Rust engine as a static library, then builds the Qt6 GUI.
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " HCIE Qt6 Build"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
|
||||
# ── Step 1: Build Rust engine as static library ──────────────────────────────
|
||||
echo ""
|
||||
echo "▶ Step 1: Building Rust engine (hcie-engine-api)..."
|
||||
cargo build -p hcie-engine-api --release 2>&1
|
||||
|
||||
# Verify the static library exists
|
||||
ENGINE_LIB="target/release/libhcie_engine_api.a"
|
||||
if [ ! -f "$ENGINE_LIB" ]; then
|
||||
# Try debug build
|
||||
ENGINE_LIB="target/debug/libhcie_engine_api.a"
|
||||
cargo build -p hcie-engine-api 2>&1
|
||||
fi
|
||||
|
||||
if [ ! -f "$ENGINE_LIB" ]; then
|
||||
echo "ERROR: Could not find libhcie_engine_api.a"
|
||||
echo "Expected at: target/release/libhcie_engine_api.a"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Engine library: $ENGINE_LIB ($(du -h "$ENGINE_LIB" | cut -f1))"
|
||||
|
||||
# ── Step 2: Check Qt6 is available ───────────────────────────────────────────
|
||||
echo ""
|
||||
echo "▶ Step 2: Checking Qt6..."
|
||||
|
||||
if ! command -v cmake &> /dev/null; then
|
||||
echo "ERROR: cmake not found. Install it:"
|
||||
echo " Ubuntu/Debian: sudo apt install cmake"
|
||||
echo " Fedora: sudo dnf install cmake"
|
||||
echo " Arch: sudo pacman -S cmake"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for Qt6 cmake config
|
||||
QT6_FOUND=$(find /usr -name "Qt6Config.cmake" 2>/dev/null | head -1)
|
||||
if [ -z "$QT6_FOUND" ]; then
|
||||
echo "ERROR: Qt6 development packages not found."
|
||||
echo ""
|
||||
echo "Install Qt6 dev packages:"
|
||||
echo ""
|
||||
echo " Ubuntu/Debian (apt):"
|
||||
echo " sudo apt install qt6-base-dev libqt6opengl6-dev qt6-tools-dev"
|
||||
echo ""
|
||||
echo " Fedora (dnf):"
|
||||
echo " sudo dnf install qt6-qtbase-devel qt6-qtopengl-devel"
|
||||
echo ""
|
||||
echo " Arch (pacman):"
|
||||
echo " sudo pacman -S qt6-base qt6-tools qt6-declarative"
|
||||
echo ""
|
||||
echo " openSUSE (zypper):"
|
||||
echo " sudo zypper install qt6-base-devel libqt6-qtdeclarative-devel"
|
||||
echo ""
|
||||
echo " macOS (brew):"
|
||||
echo " brew install qt@6"
|
||||
echo " export CMAKE_PREFIX_PATH=$(brew --prefix qt@6)"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Qt6 found: $QT6_FOUND"
|
||||
|
||||
# ── Step 3: Copy engine lib to expected location ─────────────────────────────
|
||||
echo ""
|
||||
echo "▶ Step 3: Preparing build directory..."
|
||||
|
||||
# Ensure the engine lib is in the right place for CMake
|
||||
# CMakeLists.txt looks for ../target/release/libhcie_engine_api.a
|
||||
mkdir -p hcie-qt-app/build
|
||||
|
||||
# ── Step 4: Configure CMake ──────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "▶ Step 4: Configuring CMake..."
|
||||
|
||||
cd hcie-qt-app
|
||||
|
||||
# Auto-detect Qt6 prefix path
|
||||
QT6_PREFIX=""
|
||||
if [ -n "$CMAKE_PREFIX_PATH" ]; then
|
||||
QT6_PREFIX="-DCMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH"
|
||||
elif command -v qmake6 &> /dev/null; then
|
||||
QT6_PREFIX="-DCMAKE_PREFIX_PATH=$(dirname $(dirname $(readlink -f $(which qmake6))))"
|
||||
elif [ -d "/usr/lib/x86_64-linux-gnu/cmake/Qt6" ]; then
|
||||
QT6_PREFIX="-DCMAKE_PREFIX_PATH=/usr/lib/x86_64-linux-gnu/cmake"
|
||||
fi
|
||||
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release $QT6_PREFIX 2>&1
|
||||
|
||||
# ── Step 5: Build ────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "▶ Step 5: Building Qt application..."
|
||||
|
||||
cmake --build build --config Release -j$(nproc) 2>&1
|
||||
|
||||
# ── Step 6: Done ─────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " BUILD SUCCESSFUL"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo " Run the application:"
|
||||
echo " cd hcie-qt-app && ./build/hcie-qt"
|
||||
echo ""
|
||||
echo " Or with Qt Designer (for UI editing):"
|
||||
echo " designer-qt6 ui/mainwindow.ui"
|
||||
echo ""
|
||||
Executable
+444
@@ -0,0 +1,444 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================
|
||||
# countlines.sh — HCIE-Rust Projesi Satır Sayma Aracı
|
||||
# ============================================================
|
||||
# Bu script, projedeki tüm kod dosyalarının satır
|
||||
# sayılarını dosya tipine göre ayrı ayrı ve toplam
|
||||
# olarak hesaplar ve detaylı bir rapor dosyasına yazar.
|
||||
#
|
||||
# Desteklenen Dosya Tipleri:
|
||||
# - Rust (.rs)
|
||||
# - C++ (.cpp, .h)
|
||||
# - JavaScript (.js)
|
||||
# - Svelte (.svelte)
|
||||
# - TypeScript (.ts)
|
||||
# - Python (.py)
|
||||
# - HTML (.html)
|
||||
# - Shell Script (.sh)
|
||||
# - CSS (.css)
|
||||
# - JSON (.json)
|
||||
# - Markdown (.md)
|
||||
#
|
||||
# Kullanım: bash countlines.sh
|
||||
# Çıktı: line_count_report.txt
|
||||
# ============================================================
|
||||
|
||||
# ---- AYARLAR ----
|
||||
# Projenin ana dizini (bu scriptin bulunduğu yer)
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Raporun yazılacağı dosya
|
||||
OUTPUT_FILE="line_count_report.txt"
|
||||
|
||||
# ---- FONKSİYON: Belirli bir dizindeki, belirli uzantıdaki dosyaların
|
||||
# toplam satır sayısını hesapla ----
|
||||
# Kullanım: count_lines "dizin_yolu" "uzantı" ["exclude_pattern"]
|
||||
# Örnek: count_lines "src" "svelte"
|
||||
# → src/ altındaki tüm .svelte dosyalarının satır sayısını döndürür
|
||||
count_lines() {
|
||||
# $1 = aranacak dizin (parametre 1)
|
||||
local search_dir="$1"
|
||||
# $2 = dosya uzantısı (parametre 2), nokta olmadan (örneğin "svelte")
|
||||
local extension="$2"
|
||||
# $3 = isteğe bağlı hariç tutma deseni (örneğin "tests" veya "node_modules")
|
||||
local exclude_pattern="${3:-}"
|
||||
|
||||
# find komutu için filtreleri oluştur
|
||||
local find_cmd=(find "$search_dir" -type f -iname "*.$extension")
|
||||
|
||||
# Eğer hariç tutma deseni verilmişse, ekle
|
||||
if [ -n "$exclude_pattern" ]; then
|
||||
find_cmd+=(-not -path "*/$exclude_pattern/*")
|
||||
fi
|
||||
|
||||
# find ile dosyaları bul, wc -l ile satır sayısını hesapla
|
||||
local result
|
||||
result=$("${find_cmd[@]}" 2>/dev/null \
|
||||
| xargs wc -l 2>/dev/null \
|
||||
| tail -1 \
|
||||
| awk '{print $1}')
|
||||
|
||||
# Eğer sonuç boşsa (hiç dosya bulunamadıysa) 0 yaz
|
||||
if [ -z "$result" ]; then
|
||||
echo 0
|
||||
else
|
||||
echo "$result"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---- FONKSİYON: Birden fazla dizinde aynı uzantıdaki dosyaları say ----
|
||||
# Kullanım: count_lines_multi "dizin1" "dizin2" "uzantı" ["exclude_pattern"]
|
||||
count_lines_multi() {
|
||||
local total=0
|
||||
local extension="$3"
|
||||
local exclude_pattern="${4:-}"
|
||||
|
||||
# İlk iki parametre dizinler, geri kalanlar atlanır (extension ve exclude_pattern zaten alındı)
|
||||
shift 3
|
||||
|
||||
for dir in "$@"; do
|
||||
if [ -d "$dir" ]; then
|
||||
local lines
|
||||
lines=$(count_lines "$dir" "$extension" "$exclude_pattern")
|
||||
total=$((total + lines))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$total"
|
||||
}
|
||||
|
||||
# ---- RAPOR DOSYASINI SIFIRLA (üzerine yaz) ----
|
||||
echo "=========================================" > "$OUTPUT_FILE"
|
||||
echo "SATIR SAYISI RAPORU" >> "$OUTPUT_FILE"
|
||||
echo "Proje: HCIE-Rust v4" >> "$OUTPUT_FILE"
|
||||
echo "Konum: $PROJECT_DIR" >> "$OUTPUT_FILE"
|
||||
echo "Tarih: $(date '+%Y-%m-%d %H:%M:%S')" >> "$OUTPUT_FILE"
|
||||
echo "=========================================" >> "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
|
||||
# ============================================================
|
||||
# 1. RUST DOSYALARI (.rs)
|
||||
# ============================================================
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
echo " RUST (.rs) — hcie-* dizinleri (her paket)" | tee -a "$OUTPUT_FILE"
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
|
||||
# Proje kök dizinindeki hcie-* dizinleri (her bir Rust paketi) için döngü
|
||||
grand_total_rust=0
|
||||
for dir in "$PROJECT_DIR"/hcie-*/; do
|
||||
# Dizinin sadece adını al (örneğin "hcie-protocol")
|
||||
crate_name=$(basename "$dir")
|
||||
|
||||
# Bu crate içindeki tüm .rs dosyalarının satır sayısını hesapla
|
||||
# (test dosyalarını sayma: */tests/* ve test_* ile başlayanları atla)
|
||||
lines=$(find "$dir" -type f -name '*.rs' \
|
||||
-not -path '*/tests/*' \
|
||||
-not -name 'test_*' \
|
||||
-not -name '*.rs.bak' \
|
||||
2>/dev/null \
|
||||
| xargs wc -l 2>/dev/null \
|
||||
| tail -1 \
|
||||
| awk '{print $1}')
|
||||
|
||||
# Eğer satır sayısı boş değilse (yani dosya varsa) ekrana ve dosyaya yaz
|
||||
if [ -n "$lines" ] && [ "$lines" -gt 0 ] 2>/dev/null; then
|
||||
# printf: formatlı yazdırma
|
||||
# %-25s → sola yaslı 25 karakter (paket adı)
|
||||
# %6s → sağa yaslı 6 karakter (satır sayısı)
|
||||
printf " %-25s %6s satır\n" "$crate_name" "$lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_rust=$((grand_total_rust + lines))
|
||||
fi
|
||||
done
|
||||
|
||||
# Rust toplamını yaz
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "TOPLAM RUST:" "$grand_total_rust" | tee -a "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
|
||||
# ============================================================
|
||||
# 2. C++ VE HEADER DOSYALARI (.cpp, .h)
|
||||
# ============================================================
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
echo " C++ / HEADER (.cpp, .h)" | tee -a "$OUTPUT_FILE"
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
|
||||
grand_total_cpp=0
|
||||
|
||||
# C++ dosyaları (build dizini hariç)
|
||||
if [ -d "$PROJECT_DIR/hcie-qt-app/src" ]; then
|
||||
cpp_lines=$(count_lines "$PROJECT_DIR/hcie-qt-app/src" "cpp" "build")
|
||||
h_lines=$(count_lines "$PROJECT_DIR/hcie-qt-app/src" "h" "build")
|
||||
|
||||
printf " %-25s %6s satır\n" "hcie-qt-app/src/*.cpp" "$cpp_lines" | tee -a "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "hcie-qt-app/src/*.h" "$h_lines" | tee -a "$OUTPUT_FILE"
|
||||
|
||||
grand_total_cpp=$((cpp_lines + h_lines))
|
||||
fi
|
||||
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "TOPLAM C++ / HEADER:" "$grand_total_cpp" | tee -a "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
|
||||
# ============================================================
|
||||
# 3. JAVASCRIPT DOSYALARI (.js)
|
||||
# ============================================================
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
echo " JAVASCRIPT (.js)" | tee -a "$OUTPUT_FILE"
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
|
||||
grand_total_js=0
|
||||
|
||||
# Tauri frontend JavaScript dosyaları
|
||||
if [ -d "$PROJECT_DIR/hcie-tauri-app/src" ]; then
|
||||
js_lines=$(count_lines "$PROJECT_DIR/hcie-tauri-app/src" "js" "node_modules")
|
||||
printf " %-25s %6s satır\n" "hcie-tauri-app/src/" "$js_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_js=$((grand_total_js + js_lines))
|
||||
fi
|
||||
|
||||
# _tools klasöründeki JavaScript dosyaları
|
||||
if [ -d "$PROJECT_DIR/_tools" ]; then
|
||||
js_tools_lines=$(count_lines "$PROJECT_DIR/_tools" "js")
|
||||
if [ "$js_tools_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "_tools/" "$js_tools_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_js=$((grand_total_js + js_tools_lines))
|
||||
fi
|
||||
fi
|
||||
|
||||
# hcie-egui-app plugins klasöründeki JavaScript dosyaları
|
||||
if [ -d "$PROJECT_DIR/hcie-egui-app/plugins" ]; then
|
||||
js_plugins_lines=$(count_lines "$PROJECT_DIR/hcie-egui-app/plugins" "js")
|
||||
if [ "$js_plugins_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "hcie-egui-app/plugins/" "$js_plugins_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_js=$((grand_total_js + js_plugins_lines))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "TOPLAM JAVASCRIPT:" "$grand_total_js" | tee -a "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
|
||||
# ============================================================
|
||||
# 4. SVELTE DOSYALARI (.svelte)
|
||||
# ============================================================
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
echo " SVELTE (.svelte)" | tee -a "$OUTPUT_FILE"
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
|
||||
grand_total_svelte=0
|
||||
|
||||
# Tauri frontend Svelte dosyaları
|
||||
if [ -d "$PROJECT_DIR/hcie-tauri-app/src" ]; then
|
||||
svelte_lines=$(count_lines "$PROJECT_DIR/hcie-tauri-app/src" "svelte" "node_modules")
|
||||
if [ "$svelte_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "hcie-tauri-app/src/" "$svelte_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_svelte=$((grand_total_svelte + svelte_lines))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "TOPLAM SVELTE:" "$grand_total_svelte" | tee -a "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
|
||||
# ============================================================
|
||||
# 5. TYPESCRIPT DOSYALARI (.ts)
|
||||
# ============================================================
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
echo " TYPESCRIPT (.ts)" | tee -a "$OUTPUT_FILE"
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
|
||||
grand_total_ts=0
|
||||
|
||||
# Tauri frontend TypeScript dosyaları
|
||||
if [ -d "$PROJECT_DIR/hcie-tauri-app/src" ]; then
|
||||
ts_lines=$(count_lines "$PROJECT_DIR/hcie-tauri-app/src" "ts" "node_modules")
|
||||
if [ "$ts_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "hcie-tauri-app/src/" "$ts_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_ts=$((grand_total_ts + ts_lines))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "TOPLAM TYPESCRIPT:" "$grand_total_ts" | tee -a "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
|
||||
# ============================================================
|
||||
# 6. PYTHON DOSYALARI (.py)
|
||||
# ============================================================
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
echo " PYTHON (.py)" | tee -a "$OUTPUT_FILE"
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
|
||||
grand_total_python=0
|
||||
|
||||
# _tools klasöründeki Python dosyaları
|
||||
if [ -d "$PROJECT_DIR/_tools" ]; then
|
||||
py_tools_lines=$(count_lines "$PROJECT_DIR/_tools" "py")
|
||||
if [ "$py_tools_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "_tools/" "$py_tools_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_python=$((grand_total_python + py_tools_lines))
|
||||
fi
|
||||
fi
|
||||
|
||||
# logs klasöründeki Python dosyaları
|
||||
if [ -d "$PROJECT_DIR/logs" ]; then
|
||||
py_logs_lines=$(count_lines "$PROJECT_DIR/logs" "py")
|
||||
if [ "$py_logs_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "logs/" "$py_logs_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_python=$((grand_total_python + py_logs_lines))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "TOPLAM PYTHON:" "$grand_total_python" | tee -a "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
|
||||
# ============================================================
|
||||
# 7. HTML DOSYALARI (.html)
|
||||
# ============================================================
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
echo " HTML (.html)" | tee -a "$OUTPUT_FILE"
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
|
||||
grand_total_html=0
|
||||
|
||||
# Tauri frontend HTML dosyaları
|
||||
if [ -d "$PROJECT_DIR/hcie-tauri-app/src" ]; then
|
||||
html_lines=$(count_lines "$PROJECT_DIR/hcie-tauri-app/src" "html" "node_modules")
|
||||
if [ "$html_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "hcie-tauri-app/src/" "$html_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_html=$((grand_total_html + html_lines))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "TOPLAM HTML:" "$grand_total_html" | tee -a "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
|
||||
# ============================================================
|
||||
# 8. CSS DOSYALARI (.css)
|
||||
# ============================================================
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
echo " CSS (.css)" | tee -a "$OUTPUT_FILE"
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
|
||||
grand_total_css=0
|
||||
|
||||
# Tauri frontend CSS dosyaları
|
||||
if [ -d "$PROJECT_DIR/hcie-tauri-app/src" ]; then
|
||||
css_lines=$(count_lines "$PROJECT_DIR/hcie-tauri-app/src" "css" "node_modules")
|
||||
if [ "$css_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "hcie-tauri-app/src/" "$css_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_css=$((grand_total_css + css_lines))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "TOPLAM CSS:" "$grand_total_css" | tee -a "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
|
||||
# ============================================================
|
||||
# 9. SHELL SCRIPT DOSYALARI (.sh)
|
||||
# ============================================================
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
echo " SHELL SCRIPT (.sh)" | tee -a "$OUTPUT_FILE"
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
|
||||
grand_total_shell=0
|
||||
|
||||
# Kök dizindeki shell scriptler
|
||||
sh_root_lines=$(count_lines "$PROJECT_DIR" "sh" ".git")
|
||||
if [ "$sh_root_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "(kök dizin)" "$sh_root_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_shell=$((grand_total_shell + sh_root_lines))
|
||||
fi
|
||||
|
||||
# logs klasöründeki shell scriptler
|
||||
if [ -d "$PROJECT_DIR/logs" ]; then
|
||||
sh_logs_lines=$(count_lines "$PROJECT_DIR/logs" "sh")
|
||||
if [ "$sh_logs_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "logs/" "$sh_logs_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_shell=$((grand_total_shell + sh_logs_lines))
|
||||
fi
|
||||
fi
|
||||
|
||||
# tools klasöründeki shell scriptler
|
||||
if [ -d "$PROJECT_DIR/tools" ]; then
|
||||
sh_tools_lines=$(count_lines "$PROJECT_DIR/tools" "sh")
|
||||
if [ "$sh_tools_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "tools/" "$sh_tools_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_shell=$((grand_total_shell + sh_tools_lines))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "TOPLAM SHELL:" "$grand_total_shell" | tee -a "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
|
||||
# ============================================================
|
||||
# 10. DOKÜMANTASYON / VERİ DOSYALARI
|
||||
# ============================================================
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
echo " DOKÜMANTASYON / VERİ DOSYALARI" | tee -a "$OUTPUT_FILE"
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
|
||||
# JSON dosyaları
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
echo " JSON (.json)" | tee -a "$OUTPUT_FILE"
|
||||
echo " --------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
|
||||
grand_total_json=0
|
||||
|
||||
# Kök dizindeki JSON dosyaları (package.json, Cargo.toml, vs.)
|
||||
json_root_lines=$(count_lines "$PROJECT_DIR" "json" ".git")
|
||||
if [ "$json_root_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "(kök dizin)" "$json_root_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_json=$((grand_total_json + json_root_lines))
|
||||
fi
|
||||
|
||||
# hcie-tauri-app klasöründeki JSON dosyaları
|
||||
if [ -d "$PROJECT_DIR/hcie-tauri-app" ]; then
|
||||
json_tauri_lines=$(count_lines "$PROJECT_DIR/hcie-tauri-app" "json" "node_modules")
|
||||
if [ "$json_tauri_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "hcie-tauri-app/" "$json_tauri_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_json=$((grand_total_json + json_tauri_lines))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "TOPLAM JSON:" "$grand_total_json" | tee -a "$OUTPUT_FILE"
|
||||
|
||||
# Markdown dosyaları
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
echo " MARKDOWN (.md)" | tee -a "$OUTPUT_FILE"
|
||||
echo " --------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
|
||||
grand_total_md=0
|
||||
|
||||
# Kök dizindeki markdown dosyaları
|
||||
md_root_lines=$(count_lines "$PROJECT_DIR" "md" ".git")
|
||||
if [ "$md_root_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "(kök dizin)" "$md_root_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_md=$((grand_total_md + md_root_lines))
|
||||
fi
|
||||
|
||||
# crates klasöründeki markdown dosyaları
|
||||
if [ -d "$PROJECT_DIR/crates" ]; then
|
||||
md_crates_lines=$(count_lines "$PROJECT_DIR/crates" "md")
|
||||
if [ "$md_crates_lines" -gt 0 ] 2>/dev/null; then
|
||||
printf " %-25s %6s satır\n" "crates/" "$md_crates_lines" | tee -a "$OUTPUT_FILE"
|
||||
grand_total_md=$((grand_total_md + md_crates_lines))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "TOPLAM MARKDOWN:" "$grand_total_md" | tee -a "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
|
||||
# ============================================================
|
||||
# 11. GRAND TOTAL (KOD SATIRLARI TOPLAMI)
|
||||
# Sadece kod dosyalarının satır sayılarını topla
|
||||
# ============================================================
|
||||
grand_total=$((grand_total_rust + grand_total_cpp + grand_total_js + grand_total_svelte + grand_total_ts + grand_total_python + grand_total_html + grand_total_css + grand_total_shell))
|
||||
|
||||
echo "=========================================" | tee -a "$OUTPUT_FILE"
|
||||
echo " KOD SATIRLARI TOPLAMI (GRAND TOTAL)" | tee -a "$OUTPUT_FILE"
|
||||
echo "=========================================" | tee -a "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "Rust:" "$grand_total_rust" | tee -a "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "C++ / Header:" "$grand_total_cpp" | tee -a "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "JavaScript:" "$grand_total_js" | tee -a "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "Svelte:" "$grand_total_svelte" | tee -a "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "TypeScript:" "$grand_total_ts" | tee -a "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "Python:" "$grand_total_python" | tee -a "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "HTML:" "$grand_total_html" | tee -a "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "CSS:" "$grand_total_css" | tee -a "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "Shell Script:" "$grand_total_shell" | tee -a "$OUTPUT_FILE"
|
||||
echo "-----------------------------------------" | tee -a "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "KOD TOPLAMI:" "$grand_total" | tee -a "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
printf " %-25s %6s satır\n" "RUST + C++/H TOPLAMI:" "$((grand_total_rust + grand_total_cpp))" | tee -a "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
echo " (JSON ve Markdown dosyaları yukarıda ayrı olarak gösterilmiştir)" | tee -a "$OUTPUT_FILE"
|
||||
echo "=========================================" | tee -a "$OUTPUT_FILE"
|
||||
echo "" >> "$OUTPUT_FILE"
|
||||
echo "Rapor dosyaya kaydedildi: $(realpath "$OUTPUT_FILE")" | tee -a "$OUTPUT_FILE"
|
||||
Executable
+289
@@ -0,0 +1,289 @@
|
||||
# Graphite → HCIE 3: Taşınabilir Özellikler ve Rust'tan Rust'a Dönüşüm Analizi
|
||||
|
||||
**Tarih:** 2026-05-24
|
||||
**Kaynak:** `/run/media/hc/data/Downloads/Graphite-master` (Graphite, Rust)
|
||||
**Hedef:** `/home/hc/Documents/00_PROJECTS/hcie-rust-v3` (HCIE Rust v3)
|
||||
|
||||
---
|
||||
|
||||
## 1. Yürütme Özeti
|
||||
|
||||
Graphite, Rust ile yazılmış **node-graph tabanlı** bir 2D grafik düzenleyicidir. Krita'dan (C++) farklı olarak Graphite tamamen Rust ekosisteminde olduğu için HCIE'ye taşıma maliyeti çok daha düşüktür. Doğrudan kod alıntısı, crate referansı veya algoritma kopyası yapılabilir.
|
||||
|
||||
**Fayda seviyeleri:**
|
||||
- **Yüksek:** Doğrudan kullanılabilir algoritmalar (renk ayarları, gaussian blur, node-graph compiler)
|
||||
- **Orta:** Uyarlanması gereken sistemler (brush engine, message passing, tool sistemi)
|
||||
- **Düşük:** Mimari fark nedeniyle port edilmesi zor (frontend/WebAssembly, node-graph runtime)
|
||||
|
||||
---
|
||||
|
||||
## 2. Mevcut HCIE Mimarisi ile Kıyaslama
|
||||
|
||||
| Bileşen | Graphite | HCIE 3 | Taşınabilirlik |
|
||||
|---------|----------|--------|----------------|
|
||||
| **Pixel Storage** | `DynamicImage` / `ImageFrame<Color>` | KisTile (64×64 tile) | Düşük — farklı yaklaşım |
|
||||
| **Blend Modes** | `BlendMode` enum (nodes/blending) | ~27 blend mode (hcie-blend) | Orta — port edilebilir |
|
||||
| **Color** | RGB, HSL, LinearRGB, sRGB dönüşümleri | sRGB, HSL, Lab | Yüksek — color math doğrudan alınabilir |
|
||||
| **Filters** | Gaussian blur, dehaze | 7 temel filter | Yüksek |
|
||||
| **Node Graph** | Full compiler + executor | Yok | **Stratejik — yeni mimari** |
|
||||
| **Brush** | Stamp-based + stroke rendering | Temel brush engine | Orta |
|
||||
| **Vector** | Path boolean, SVG, fill/stroke | Temel vector shapes | Yüksek — port edilebilir |
|
||||
| **Text** | Font cache, text-to-path | fontdue-based | Orta |
|
||||
| **GUI** | WebAssembly + Svelte + WebGL | egui/eframe | Düşük — toolkit farkı |
|
||||
| **Message System** | Dispatcher + Message trait | EventBus | Orta |
|
||||
| **Tool System** | Message-based tool routing | ToolState + match | Orta |
|
||||
| **Animation** | Keyframe timeline | Yok | Yüksek — başlangıç için |
|
||||
| **File I/O** | image crate tabanlı | image, psd, zip | Düşük — benzer seviyede |
|
||||
| **Camera Raw** | `rawkit` (Sony ARW) | Yok | Yüksek — bağımsız crate |
|
||||
|
||||
---
|
||||
|
||||
## 3. Doğrudan Alınabilecek Kodlar (Kategori 1: Copy-Paste Uyumlu)
|
||||
|
||||
### 3.1 Renk Ayarları — `node-graph/nodes/raster/src/adjustments.rs`
|
||||
|
||||
Graphite'deki renk ayarlama algoritmaları saf Rust fonksiyonlarıdır, doğrudan `hcie-filter` crate'ine taşınabilir:
|
||||
|
||||
| Fonksiyon | Graphite Kaynağı | HCIE Hedefi | Zorluk |
|
||||
|-----------|-----------------|-------------|--------|
|
||||
| `brightness_contrast` | `raster/src/adjustments.rs` | `hcie-filter` | 1/10 — LUT tabanlı, bağımlılık yok |
|
||||
| `levels` | `raster/src/adjustments.rs` | `hcie-filter` | 1/10 |
|
||||
| `hue_saturation` | `raster/src/adjustments.rs` | `hcie-filter` | 1/10 |
|
||||
| `channel_mixer` | `raster/src/adjustments.rs` | `hcie-filter` | 1/10 |
|
||||
| `selective_color` | `raster/src/adjustments.rs` | `hcie-filter` | 2/10 |
|
||||
| `vibrance` | `raster/src/adjustments.rs` | `hcie-filter` | 1/10 |
|
||||
| `exposure` | `raster/src/adjustments.rs` | `hcie-filter` | 1/10 |
|
||||
| `black_and_white` | `raster/src/adjustments.rs` | `hcie-filter` | 1/10 |
|
||||
| `threshold` | `raster/src/adjustments.rs` | `hcie-filter` | 1/10 |
|
||||
| `posterize` | `raster/src/adjustments.rs` | `hcie-filter` | 1/10 |
|
||||
| `invert` | `raster/src/adjustments.rs` | `hcie-filter` | 1/10 |
|
||||
|
||||
```rust
|
||||
// Graphite: brightness_contrast (doğrudan kullanılabilir)
|
||||
pub fn brightness_contrast(image: &Image, brightness: f32, contrast: f32) -> Image {
|
||||
// LUT-based implementation
|
||||
let mut output = image.clone();
|
||||
// ...
|
||||
output
|
||||
}
|
||||
```
|
||||
|
||||
**Toplam:** 11 yeni filter, hiçbir external bağımlılık gerektirmez.
|
||||
|
||||
### 3.2 Gaussian Blur — `node-graph/nodes/raster/src/filter.rs`
|
||||
|
||||
Graphite'in Gaussian blur'u **two-pass separable** ve **gamma/sRGB-aware**. HCIE'nin mevcut blur'undan daha kaliteli.
|
||||
|
||||
```rust
|
||||
// İmza (doğrudan taşınabilir):
|
||||
fn gaussian_blur(image: &Image, radius: f64) -> Image
|
||||
```
|
||||
|
||||
**Zorluk:** 2/10
|
||||
|
||||
### 3.3 Dehaze (Dark Channel Prior) — `node-graph/nodes/raster/src/dehaze.rs`
|
||||
|
||||
Single-image dehazing algoritması, fotoğraf düzenlemede değerli bir özellik.
|
||||
|
||||
**Zorluk:** 3/10
|
||||
|
||||
### 3.4 Gradient Map — `node-graph/nodes/raster/src/gradient_map.rs`
|
||||
|
||||
Luminance değerlerini gradient color ramp üzerinden eşleyen algoritma.
|
||||
|
||||
**Zorluk:** 2/10
|
||||
|
||||
### 3.5 Procedural Pattern — `node-graph/nodes/raster/src/std_nodes.rs`
|
||||
|
||||
Perlin, OpenSimplex, Cellular, Value noise + Mandelbrot set renderer.
|
||||
|
||||
**Zorluk:** 3/10
|
||||
|
||||
### 3.6 Math Expression Parser — `libraries/math-parser/`
|
||||
|
||||
Birim destekli (px, cm, deg, rad, %) matematik ifade ayrıştırıcı. HCIE'de eklenti geliştirme veya brush boyutu/formül girişi için kullanılabilir.
|
||||
|
||||
**Zorluk:** 2/10 — PEG grammar + tree-walking executor
|
||||
**Bağımlılık:** `pest` parser (Cargo.toml'a eklenmeli)
|
||||
|
||||
---
|
||||
|
||||
## 4. Uyarlanarak Taşınabilecek Sistemler (Kategori 2: Adaptasyon Gerekli)
|
||||
|
||||
### 4.1 Node Graph Sistemi — `node-graph/` (Stratejik)
|
||||
|
||||
Graphite'in en büyük farkı node-graph tabanlı olmasıdır. HCIE'ye node-graph eklemek uzun vadede büyük bir mimari değişikliktir ancak Graphite'in compiler ve executor'ı referans alınabilir:
|
||||
|
||||
| Bileşen | Dosya | HCIE'ye Uyarlama |
|
||||
|---------|-------|------------------|
|
||||
| `NodeNetwork` | `graph-craft/src/document.rs` | Document modeline node graph eklenebilir |
|
||||
| `Compiler` | `graph-craft/src/graphene_compiler.rs` | Flat → optimized graph dönüşümü |
|
||||
| `ProtoNetwork` | `graph-craft/src/proto.rs` | Executable IR |
|
||||
| `DynamicExecutor` | `interpreted-executor/src/dynamic_executor.rs` | Runtime execution |
|
||||
| `NodeRegistry` | `interpreted-executor/src/node_registry.rs` | Node constructor registry |
|
||||
|
||||
**Ancak:** Graphite'in node-graph sistemi oldukça karmaşıktır (~20K satır). HCIE'ye tam node-graph eklemek büyük bir projedir. Daha gerçekçi bir yaklaşım:
|
||||
- **Filter chain pipeline**: `hcie-filter`'de filter'ların birleştirilebildiği basit bir DAG sistemi
|
||||
- **Adjustment layer**: Node-graph'in en hafifi — sadece filter zinciri
|
||||
|
||||
**Zorluk:** 7/10 (tam port), 4/10 (basitleştirilmiş version)
|
||||
|
||||
### 4.2 Brush Engine — `node-graph/nodes/brush/`
|
||||
|
||||
| Dosya | İçerik |
|
||||
|-------|--------|
|
||||
| `brush.rs` | `BrushDaub` (stamp generation), `BrushStyle` definition |
|
||||
| `brush_stroke.rs` | Stroke rendering, dab placement |
|
||||
| `brush_cache.rs` | Cached brush textures |
|
||||
|
||||
HCIE'nin mevcut brush engine'i daha basit. Graphite'in brush'ı:
|
||||
- Stamp-based (fırça ucunu tekrar tekrar basar)
|
||||
- Spacing, rotation, scatter
|
||||
- Stroke interpolation
|
||||
|
||||
**Uyarlama:** `hcie-brush-engine`'e `BrushStyle` ve `BrushDaub` yapıları eklenebilir.
|
||||
|
||||
**Zorluk:** 5/10
|
||||
|
||||
### 4.3 Vector Path Boolean — `node-graph/nodes/path-bool/`
|
||||
|
||||
Union, Subtract, Intersection, Difference işlemleri. `linesweeper` kütüphanesi kullanır.
|
||||
|
||||
HCIE'nin `hcie-vector` crate'i şu an path boolean desteklemiyor. Graphite'in implementasyonu referans alınabilir.
|
||||
|
||||
**Zorluk:** 6/10
|
||||
|
||||
### 4.4 Tool Sistemi — `editor/src/messages/tool/`
|
||||
|
||||
Graphite'in tool sistemi message-passing üzerine kurulu. HCIE'nin mevcut tool sistemi (`ToolState` + `match` + `CanvasWidget`) daha basit.
|
||||
|
||||
Ödünç alınabilecek konseptler:
|
||||
- Tool input preprocessing (`input_preprocessor/`)
|
||||
- Tool message routing (`tool_messages/`)
|
||||
- Common functionality (`common_functionality/`) — snapping, guides, bounds
|
||||
|
||||
**Zorluk:** 5/10
|
||||
|
||||
### 4.5 Repeat/Grid — `node-graph/nodes/repeat/`
|
||||
|
||||
Count-based repeat + grid layout + radial repeat. HCIE'de pattern oluşturma için kullanılabilir.
|
||||
|
||||
**Zorluk:** 3/10
|
||||
|
||||
---
|
||||
|
||||
## 5. Mimari Fark Nedeniyle Portu Zor (Kategori 3: Referans Alınabilir)
|
||||
|
||||
| Sistem | Graphite Yaklaşımı | HCIE Yaklaşımı | Port Zorluğu |
|
||||
|--------|-------------------|----------------|-------------|
|
||||
| **GUI** | WebAssembly + Svelte + WebGL | egui/eframe (native) | 9/10 — toolkit tamamen farklı |
|
||||
| **Message Bus** | `Dispatcher` + `MessageHandler` trait'leri | `EventBus<AppEvent>` push/pop | 7/10 — pattern farkı |
|
||||
| **Render** | WebGL compute shaders (WGSL) | egui painter | 9/10 — GPU pipeline farkı |
|
||||
| **Undo** | Node graph memoization + history | `KisHistory` closure-based | 6/10 — farklı yaklaşım |
|
||||
| **Layout** | Auto-layout widget tree (Svelte) | egui immediate mode | 8/10 — paradigma farkı |
|
||||
| **Font Loading** | Font loading + text-to-path | fontdue | 5/10 — mevcut sistem yeterli |
|
||||
|
||||
---
|
||||
|
||||
## 6. Bağımsız Crate Olarak Alınabilecek Kütüphaneler
|
||||
|
||||
### 6.1 `rawkit` — Camera Raw Decoder
|
||||
|
||||
**Dosya:** `libraries/rawkit/`
|
||||
|
||||
Sony ARW formatları için ham fotoğraf decoder'ı. TIFF/IFD parsing, demosaicing, white balance, gamma correction.
|
||||
|
||||
**Durum:** Tamamen bağımsız crate, hiçbir Graphite bağımlılığı yok. HCIE'ye `hcie-raw` olarak eklenebilir.
|
||||
|
||||
**Zorluk:** 1/10 — sadece Cargo.toml'a ekle + FFI yok
|
||||
|
||||
### 6.2 `dyn-any` — Dynamic Type Erasure
|
||||
|
||||
**Dosya:** `libraries/dyn-any/`
|
||||
|
||||
`DynAny` trait + derive macro — `'static` olmayan tiplerle çalışan type erasure kütüphanesi.
|
||||
|
||||
**Durum:** HCIE'nin mevcut `dyn Any` kullanımı için alternatif. Plugin sisteminde kullanılabilir.
|
||||
|
||||
**Zorluk:** 2/10 — mevcut projeye entegre edilebilir
|
||||
|
||||
### 6.3 `math-parser` — Matematik İfade Ayrıştırıcı
|
||||
|
||||
**Dosya:** `libraries/math-parser/`
|
||||
|
||||
Birim destekli matematik parser: `2cm + 3px`, `sin(45deg) * 100%`
|
||||
|
||||
**Durum:** Brush boyutu, filtre parametreleri, transform girdileri için kullanılabilir.
|
||||
|
||||
**Zorluk:** 1/10 — `pest` bağımlılığı eklenmeli
|
||||
|
||||
---
|
||||
|
||||
## 7. Öncelikli Yol Haritası
|
||||
|
||||
### Aşama 1: Quick Wins (1-2 hafta)
|
||||
```
|
||||
[ ] brightness_contrast, levels, hue_saturation → hcie-filter (11 yeni filter)
|
||||
[ ] Gaussian blur (gamma-aware, two-pass) → hcie-filter
|
||||
[ ] math-parser → hcie-gui-egui (opsiyonel bağımlılık)
|
||||
```
|
||||
|
||||
### Aşama 2: Orta Vadeli (2-4 hafta)
|
||||
```
|
||||
[ ] Gradient map → hcie-filter
|
||||
[ ] Dehaze → hcie-filter
|
||||
[ ] Procedural noise patterns → hcie-filter
|
||||
[ ] Brush stroke rendering (brush_stroke.rs) → hcie-brush-engine
|
||||
```
|
||||
|
||||
### Aşama 3: Stratejik (1-3 ay)
|
||||
```
|
||||
[ ] Node graph compiler (basitleştirilmiş DAG filter chain)
|
||||
[ ] rawkit → hcie-raw (bağımsız crate)
|
||||
[ ] Vector path boolean → hcie-vector
|
||||
```
|
||||
|
||||
### Aşama 4: Uzun Vade (3+ ay)
|
||||
```
|
||||
[ ] Full node graph executor
|
||||
[ ] Tool system redesign (message-passing)
|
||||
[ ] Animation timeline
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Graphite vs Krita: Hangisi Daha Faydalı?
|
||||
|
||||
| Kriter | Graphite (Rust) | Krita (C++) |
|
||||
|--------|----------------|-------------|
|
||||
| **Dil uyumu** | ✅ Aynı dil (Rust) | ❌ C++ → Rust çeviri |
|
||||
| **Algoritma kalitesi** | Orta — daha yeni, daha az test | ✅ Yüksek — 20+ yıl olgunlaşma |
|
||||
| **Filter çeşitliliği** | Orta (~15 filter) | ✅ Yüksek (30+ filter) |
|
||||
| **Brush engine** | Basit | ✅ Çok gelişmiş |
|
||||
| **Renk yönetimi** | Temel | ✅ ICC + CMS |
|
||||
| **Node graph** | ✅ Full implementation | Yok |
|
||||
| **PSD uyumu** | Yok | ✅ Kapsamlı |
|
||||
| **Camera Raw** | ✅ `rawkit` | Yok |
|
||||
| **Port maliyeti** | **Düşük** (doğrudan kod) | Yüksek (çeviri gerek) |
|
||||
|
||||
**Sonuç:** Kısa vadede en hızlı kazanç Graphite'den gelir çünkü Rust kodunu doğrudan kopyalayıp HCIE'ye entegre edebilirsiniz. Krita ise uzun vadede daha olgun algoritmalar ve daha fazla çeşitlilik sunar. İdeal yaklaşım: **önce Graphite'den hızlı kazanımlar, sonra Krita'dan seçilmiş portlar.**
|
||||
|
||||
---
|
||||
|
||||
## 9. Graphite Source Tree Reference (Önemli Yollar)
|
||||
|
||||
| Klasör | İçerik | HCIE Karşılığı |
|
||||
|--------|--------|----------------|
|
||||
| `node-graph/nodes/raster/src/` | Renk ayarları, blur, dehaze, gradient map, procedural patterns | `hcie-filter` (doğrudan port) |
|
||||
| `node-graph/nodes/brush/` | Brush stamp, stroke, cache | `hcie-brush-engine` |
|
||||
| `node-graph/nodes/vector/` | Path modifications, fill/stroke | `hcie-vector` |
|
||||
| `node-graph/nodes/path-bool/` | Boolean path ops | `hcie-vector` (yeni modül) |
|
||||
| `node-graph/nodes/blending/` | Opacity, blend mode manipulation | `hcie-blend` (referans) |
|
||||
| `node-graph/nodes/text/` | Font cache, text-to-path | `hcie-text` (referans) |
|
||||
| `node-graph/nodes/math/` | Math expression nodes | `math-parser` + hcie-gui |
|
||||
| `node-graph/graph-craft/src/` | Document node network, compiler | Yok (yeni mimari) |
|
||||
| `node-graph/interpreted-executor/src/` | Runtime executor | Yok (yeni mimari) |
|
||||
| `libraries/dyn-any/` | Type erasure | Plugin sistemi |
|
||||
| `libraries/math-parser/` | Expression parser | GUI widget'ları |
|
||||
| `libraries/rawkit/` | Camera raw decoder | `hcie-raw` (yeni crate) |
|
||||
| `editor/src/` | App layer: dispatcher, tools, messages | `hcie-gui-egui` (farklı toolkit) |
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
//! Runnable demo: `cargo run --example demo`
|
||||
//!
|
||||
//! Opens a window with a pre-built split layout. Tabs can be dragged between
|
||||
//! panes, panes can be resized by dragging the handle between them.
|
||||
|
||||
use eframe::egui;
|
||||
use grimdock::{
|
||||
AddTabEntry, ChildSide, DropPolicy, Node, PaneBuilder, PaneStyleOverride, PanelContext,
|
||||
PanelStyle, PanelTree, SplitDir, Tab, TabStyleOverride,
|
||||
};
|
||||
|
||||
fn main() -> eframe::Result<()> {
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_title("grimdock demo")
|
||||
.with_inner_size([1024.0, 680.0]),
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native("grimdock demo", options, Box::new(|_cc| Ok(Box::new(App::new()))))
|
||||
}
|
||||
|
||||
/// A tab identifier. Using a simple enum so each variant renders unique content.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
enum TabId {
|
||||
Editor(u32),
|
||||
FileTree,
|
||||
Terminal,
|
||||
Search,
|
||||
Problems,
|
||||
Output,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TabId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
TabId::Editor(n) => write!(f, "editor_{n}"),
|
||||
TabId::FileTree => write!(f, "file_tree"),
|
||||
TabId::Terminal => write!(f, "terminal"),
|
||||
TabId::Search => write!(f, "search"),
|
||||
TabId::Problems => write!(f, "problems"),
|
||||
TabId::Output => write!(f, "output"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct App {
|
||||
tree: PanelTree<TabId>,
|
||||
/// Counter that ticks up each frame in the terminal pane.
|
||||
tick: u64,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn new() -> Self {
|
||||
// Build an initial layout:
|
||||
//
|
||||
// ┌───────────┬──────────────────┐
|
||||
// │ File Tree │ Editor 1 │
|
||||
// │ ├──────────────────┤
|
||||
// │ │ Terminal │ Output│
|
||||
// └───────────┴──────────────────┘
|
||||
//
|
||||
// We do this by starting with a single pane and splitting.
|
||||
|
||||
let mut tree = PanelTree::new(vec![
|
||||
Tab::new("editor_1", TabId::Editor(1))
|
||||
.with_leading_visual(">")
|
||||
.with_style_override(TabStyleOverride {
|
||||
active_bg: Some(egui::Color32::from_rgb(62, 73, 50)),
|
||||
inactive_bg: Some(egui::Color32::from_rgb(44, 52, 38)),
|
||||
hovered_bg: Some(egui::Color32::from_rgb(55, 65, 45)),
|
||||
text_color: Some(egui::Color32::from_rgb(221, 231, 200)),
|
||||
accent_color: Some(egui::Color32::from_rgb(164, 196, 92)),
|
||||
icon_color: None,
|
||||
max_width: None,
|
||||
}),
|
||||
Tab::new("editor_2", TabId::Editor(2)).with_leading_visual("+"),
|
||||
]);
|
||||
|
||||
// Split root (editors) horizontally: file tree on the left (First side).
|
||||
let _file_tree_pane = tree.split_leaf_with(
|
||||
0,
|
||||
SplitDir::Horizontal,
|
||||
PaneBuilder::new(Tab::new("file_tree", TabId::FileTree).with_leading_visual("#")).with_options(
|
||||
grimdock::PaneOptions {
|
||||
style_override: Some(PaneStyleOverride {
|
||||
header_bg: Some(egui::Color32::from_rgb(27, 47, 44)),
|
||||
content_bg: Some(egui::Color32::from_rgb(21, 36, 34)),
|
||||
border_color: Some(egui::Color32::from_rgb(52, 90, 84)),
|
||||
accent_color: Some(egui::Color32::from_rgb(85, 188, 162)),
|
||||
content_inset: None,
|
||||
}),
|
||||
drop_policy: DropPolicy::merge_only(),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
ChildSide::First,
|
||||
);
|
||||
// Node 0 is now Horizontal split.
|
||||
// Node 1 (left/First): FileTree
|
||||
// Node 2 (right/Second): editors
|
||||
|
||||
// Split the right pane (editors, node 2) vertically: bottom gets terminal.
|
||||
let editors_pane = tree
|
||||
.find_pane_containing(&TabId::Editor(1))
|
||||
.expect("editor pane should exist after initial split");
|
||||
let bottom_pane = {
|
||||
let mut pane = tree.pane_mut(editors_pane).expect("editor pane should exist");
|
||||
pane.split_with(
|
||||
SplitDir::Vertical,
|
||||
PaneBuilder::new(Tab::new("terminal", TabId::Terminal).with_leading_visual("$")),
|
||||
ChildSide::Second,
|
||||
)
|
||||
};
|
||||
// Node 2 is now Vertical split.
|
||||
// Node 5 (top/First): editors
|
||||
// Node 6 (bottom/Second): terminal
|
||||
|
||||
// Add extra tabs to the bottom bar.
|
||||
// Nodes are: 5 = editors top pane, 6 = bottom pane (terminal).
|
||||
let mut bottom = tree.pane_mut(bottom_pane).expect("bottom pane should exist");
|
||||
bottom.push_tab(Tab::new("output", TabId::Output).with_leading_visual("!"));
|
||||
bottom.push_tab(Tab::new("problems", TabId::Problems).with_leading_visual("x"));
|
||||
bottom.push_tab(Tab::new("search", TabId::Search).with_leading_visual("?"));
|
||||
|
||||
// Narrow the left (file tree) column — set the split ratio.
|
||||
if let Node::Split { ratio, .. } = tree.node_mut(0) {
|
||||
*ratio = 0.22;
|
||||
}
|
||||
|
||||
// Make the bottom strip shorter.
|
||||
if let Node::Split { ratio, .. } = tree.node_mut(2) {
|
||||
*ratio = 0.65;
|
||||
}
|
||||
|
||||
Self {
|
||||
tree,
|
||||
tick: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for App {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
self.tick += 1;
|
||||
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame::NONE)
|
||||
.show(ctx, |ui| {
|
||||
let tree = &mut self.tree;
|
||||
let mut style = PanelStyle::from_egui_style(ui.style().as_ref());
|
||||
style.content_inset = 4.0;
|
||||
style.tabs.rounding = egui::CornerRadius::same(5);
|
||||
style.header.button.rounding = egui::CornerRadius::same(5);
|
||||
let tick = self.tick;
|
||||
let add_tab_entries = vec![
|
||||
AddTabEntry::new(
|
||||
"Editor 3",
|
||||
Tab::new("editor_3", TabId::Editor(3))
|
||||
.with_leading_visual("*")
|
||||
.with_closable(true),
|
||||
),
|
||||
AddTabEntry::new(
|
||||
"Terminal",
|
||||
Tab::new("terminal", TabId::Terminal)
|
||||
.with_leading_visual("$")
|
||||
.with_closable(true),
|
||||
),
|
||||
AddTabEntry::new(
|
||||
"Search",
|
||||
Tab::new("search", TabId::Search)
|
||||
.with_leading_visual("?")
|
||||
.with_closable(true),
|
||||
),
|
||||
AddTabEntry::new(
|
||||
"Problems",
|
||||
Tab::new("problems", TabId::Problems)
|
||||
.with_leading_visual("x")
|
||||
.with_closable(true),
|
||||
),
|
||||
];
|
||||
|
||||
PanelContext::new(ui, tree, &style)
|
||||
.with_add_tab_entries(&add_tab_entries)
|
||||
.show(|ui, tab_id| {
|
||||
render_tab(ui, tab_id, tick);
|
||||
});
|
||||
});
|
||||
|
||||
// Repaint continuously so the terminal counter updates.
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
fn render_tab(ui: &mut egui::Ui, tab_id: &TabId, tick: u64) {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
match tab_id {
|
||||
TabId::Editor(n) => {
|
||||
ui.add_space(8.0);
|
||||
ui.label(
|
||||
egui::RichText::new(format!("// Editor {n}\n\nfn main() {{\n println!(\"Hello, world!\");\n}}"))
|
||||
.monospace()
|
||||
.size(13.0),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
ui.label("Drag a tab to a different pane to rearrange.");
|
||||
ui.label("Drag the handle between panes to resize.");
|
||||
}
|
||||
TabId::FileTree => {
|
||||
ui.add_space(6.0);
|
||||
for item in &["src/", " lib.rs", " tree.rs", " layout.rs", " header.rs", " dnd.rs", "Cargo.toml"] {
|
||||
ui.label(egui::RichText::new(*item).monospace().size(12.0));
|
||||
}
|
||||
}
|
||||
TabId::Terminal => {
|
||||
ui.add_space(4.0);
|
||||
ui.label(egui::RichText::new(format!("$ frame {tick}")).monospace().size(12.0));
|
||||
ui.label(egui::RichText::new("grimdock running ✓").monospace().color(egui::Color32::from_rgb(100, 200, 120)).size(12.0));
|
||||
}
|
||||
TabId::Search => {
|
||||
ui.add_space(8.0);
|
||||
ui.label("Search");
|
||||
ui.text_edit_singleline(&mut String::new());
|
||||
}
|
||||
TabId::Problems => {
|
||||
ui.add_space(8.0);
|
||||
ui.label(egui::RichText::new("No problems detected.").color(egui::Color32::from_rgb(100, 200, 120)));
|
||||
}
|
||||
TabId::Output => {
|
||||
ui.add_space(4.0);
|
||||
ui.label(egui::RichText::new("[INFO] Build complete.").monospace().size(12.0));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"design_mode": true,
|
||||
"selected_element": "Toolbox.PrimarySwatch",
|
||||
"toolbox_slot_w": 36.0,
|
||||
"tool_btn_size": 28.0,
|
||||
"tool_btn_gap": 8.0,
|
||||
"left_col2_min_w": 60.0,
|
||||
"right_col1_min_w": 60.0,
|
||||
"right_col2_min_w": 60.0,
|
||||
"auto_hide_overlay_w": 220.0,
|
||||
"auto_hide_header_h": 32.0,
|
||||
"auto_hide_rounding": 8.0,
|
||||
"dialog_w": 540.0,
|
||||
"dialog_h": 440.0,
|
||||
"dialog_rounding": 10.0,
|
||||
"button_rounding": 6.0,
|
||||
"label_size": 12.5,
|
||||
"small_size": 11.0,
|
||||
"overrides": {
|
||||
"Toolbox.SecondarySwatch": {
|
||||
"width": null,
|
||||
"height": null,
|
||||
"margin_left": null,
|
||||
"margin_right": null,
|
||||
"margin_top": null,
|
||||
"margin_bottom": null,
|
||||
"font_size": null,
|
||||
"text_color": null,
|
||||
"bg_color": null,
|
||||
"border_color": null,
|
||||
"border_width": null,
|
||||
"rounding": null
|
||||
},
|
||||
"Toolbox.Slot7": {
|
||||
"width": null,
|
||||
"height": null,
|
||||
"margin_left": null,
|
||||
"margin_right": null,
|
||||
"margin_top": null,
|
||||
"margin_bottom": null,
|
||||
"font_size": null,
|
||||
"text_color": null,
|
||||
"bg_color": null,
|
||||
"border_color": null,
|
||||
"border_width": null,
|
||||
"rounding": null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "hcie-ai"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
ffi = []
|
||||
|
||||
[dependencies]
|
||||
hcie-engine-api = { path = "../hcie-engine-api" }
|
||||
hcie-protocol = { path = "../hcie-protocol" }
|
||||
reqwest = { version = "0.12", features = ["json", "multipart", "blocking", "stream"] }
|
||||
tokio = { version = "1.40", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
async-trait = "0.1"
|
||||
anyhow = "1.0"
|
||||
futures-util = "0.3"
|
||||
base64 = "0.22"
|
||||
log = "0.4"
|
||||
bincode = "1.3"
|
||||
once_cell = "1.20"
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib", "cdylib"]
|
||||
|
||||
[dev-dependencies]
|
||||
rstest = "0.23"
|
||||
proptest = "1.5"
|
||||
approx = "0.5"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user