# UI Layout Definition Draw Tool — Implementation TODO
**Version:** 0.1.0
**Derived from:** `0-proposal-of-impl.md`
**Platform:** Android APK — native C++ / Clang in Termux
**Schema Target:** ui-layout-def v1.6
---
## How to Use This Document
Each section corresponds to a milestone from the proposal. Tasks are ordered by dependency — complete them top-to-bottom within each milestone. Tasks marked `[CRITICAL]` are blockers for downstream work. Tasks marked `[RISK]` have an identified risk from the proposal and should be validated early.
---
## Milestone 1 — Project Skeleton (Weeks 1–2)
### 1.1 Termux Environment
- [x] Install base packages:
```bash
pkg install clang cmake make git patchelf
pkg install aapt2 apksigner zipalign d8 javac
```
Note: libxml2 is built from source (in `third_party/libxml2/`), no system package needed.
- [ ] Verify Clang targets arm64: `clang++ --print-target-triple` → expect `aarch64-linux-android*`
- [ ] Locate NDK path: `echo $NDK` or find under `$PREFIX/lib/android-ndk/`
- [ ] Create project root: `mkdir -p ~/uidef-tool && cd ~/uidef-tool`
### 1.2 Fetch Third-Party Sources
- [ ] Download **pugixml** 1.14+ — copy `pugixml.hpp` + `pugixml.cpp` into `third_party/pugixml/`
- [ ] Clone **Dear ImGui** docking branch:
```bash
git clone --branch docking https://github.com/ocornut/imgui.git third_party/imgui
```
- [ ] Copy ImGui Android+OpenGL3 backends from `third_party/imgui/backends/`:
- `imgui_impl_opengl3.h / .cpp`
- `imgui_impl_android.h / .cpp`
- [ ] Download **Roboto-Regular.ttf** into `assets/fonts/`
### 1.3 Create Directory Layout
```
uidef-tool/
├── CMakeLists.txt
├── src/
│ ├── main.cpp # NativeActivity entry, EGL init, main loop
│ ├── app.h / app.cpp # Application lifecycle + state machine
│ ├── canvas/
│ │ ├── canvas.h / .cpp # Drawing surface + view transform
│ │ ├── grid.h / .cpp # Snap grid rendering
│ │ └── selection.h / .cpp # Click-select, multi-select, resize handles
│ ├── model/
│ │ ├── document.h / .cpp # Document root (version, platform, includes)
│ │ ├── control_instance.h/.cpp # Placed control with position + props
│ │ ├── wireframe_def.h / .cpp # Wireframe with slots
│ │ ├── window_def.h / .cpp # Window (template or slot-overrides)
│ │ └── control_registry.h/.cpp # Parsed control definitions
│ ├── xml/
│ │ ├── xml_reader.h / .cpp # Load XML → Document model (pugixml)
│ │ ├── xml_writer.h / .cpp # Document model → XML file (pugixml)
│ │ └── xsd_validator.h / .cpp # XSD validation wrapper (libxml2)
│ ├── ui/
│ │ ├── toolbox.h / .cpp # Left slide-in: control palette
│ │ ├── properties.h / .cpp # Right slide-in: property editor
│ │ ├── doc_tree.h / .cpp # Bottom panel: document tree
│ │ ├── toolbar.h / .cpp # Top bar: New/Open/Save/Validate
│ │ ├── file_dialog.h / .cpp # ImGui file browser (/sdcard/)
│ │ └── validation_panel.h/.cpp # Validation results list
│ └── util/
│ ├── undo_redo.h / .cpp # Undo/redo command stack
│ └── platform.h / .cpp # Android helpers (asset loading, paths)
├── assets/
│ ├── schemas/ # All 8 .xsd files
│ ├── controls/ # abstract/avalonia/winform/web/webawesome .xml
│ └── fonts/ # Roboto-Regular.ttf
├── third_party/
│ ├── pugixml/
│ └── imgui/ # docking branch + backends
└── android/
└── AndroidManifest.xml
```
- [ ] Create all directories and placeholder `.h/.cpp` files with header guards
- [ ] Copy all 8 XSD schema files from project into `assets/schemas/`
- [ ] Copy all 5 control definition XMLs into `assets/controls/`
### 1.4 CMakeLists.txt `[CRITICAL]`
- [ ] Write root `CMakeLists.txt`:
- `cmake_minimum_required(VERSION 3.18)`
- `set(CMAKE_CXX_STANDARD 17)` / `set(CMAKE_CXX_STANDARD_REQUIRED ON)`
- Toolchain: `-DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake`
- ABI: `set(ANDROID_ABI arm64-v8a)`
- Platform: `set(ANDROID_PLATFORM android-26)`
- Target: `add_library(uidef-tool SHARED ...)` (NativeActivity requires shared lib)
- Sources: all `src/**/*.cpp` + pugixml + ImGui + backends
- Include: `third_party/pugixml/`, `third_party/imgui/`, `third_party/imgui/backends/`
- Link libraries: `android`, `log`, `EGL`, `GLESv2`, `xml2`, `z`
- Find libxml2: `find_package(LibXml2 REQUIRED)` or manual `-lxml2` + include path
- [ ] Write `android/AndroidManifest.xml`:
- `android:hasCode="false"` (pure NativeActivity)
- ``
- Permissions: `WRITE_EXTERNAL_STORAGE`, `READ_EXTERNAL_STORAGE` (for `/sdcard/` access)
- `android:screenOrientation="portrait"`
### 1.5 NativeActivity Entry Point `[CRITICAL]`
Implement `src/main.cpp`:
- [ ] `#include ` — use NDK glue
- [ ] `android_main(struct android_app* app)` entry function
- [ ] EGL initialization: create display → choose config (GLES2, RGBA8) → create surface → create context
- [ ] Initialize ImGui: `ImGui::CreateContext()`, load Roboto font from APK assets via `AAssetManager`
- [ ] Initialize ImGui backends: `ImGui_ImplAndroid_Init(app->window)`, `ImGui_ImplOpenGL3_Init("#version 100")`
- [ ] Main loop: poll events → `ImGui_ImplAndroid_NewFrame()` → `ImGui_ImplOpenGL3_NewFrame()` → `ImGui::NewFrame()` → draw UI → `ImGui::Render()` → `ImGui_ImplOpenGL3_RenderDrawData()` → `eglSwapBuffers()`
- [ ] Handle `APP_CMD_INIT_WINDOW` / `APP_CMD_TERM_WINDOW` for EGL surface lifecycle
- [ ] Handle `APP_CMD_GAINED_FOCUS` / `APP_CMD_LOST_FOCUS` for render pause
### 1.6 Basic ImGui Docking Layout
- [ ] Enable docking: `ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable`
- [ ] Create fullscreen dockspace in main loop: `ImGui::DockSpaceOverViewport()`
- [ ] Stub windows (empty, will be filled in later milestones):
- `ImGui::Begin("Toolbar")` — top bar placeholder
- `ImGui::Begin("Canvas")` — central area
- `ImGui::Begin("Document Tree")` — bottom panel
- [ ] **Do not** create persistent side panels yet — toolbox and properties are slide-in drawers (portrait layout per proposal §5)
### 1.7 Canvas: Pan & Zoom
- [ ] In `canvas.h/cpp`:
- `struct ViewTransform { float offset_x, offset_y, zoom; }` — default: offset(0,0), zoom(1.0)
- Two-finger drag → update offset (pan)
- Pinch gesture → update zoom (clamp to 0.25 – 4.0)
- [ ] Apply transform to ImGui draw list via `ImDrawList::PushClipRect()` + coordinate mapping
- [ ] Draw a faint grid background (respecting current snap size)
- [ ] `[RISK]` Test touch input early — ImGui Android backend must correctly translate `AInputEvent` to ImGui IO. If not, implement manual `AMotionEvent` → ImGui mapping in `main.cpp` event handler.
### 1.8 Build & Smoke Test
- [ ] Configure and build:
```bash
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=arm64-v8a \
-DANDROID_PLATFORM=android-26 \
-DCMAKE_BUILD_TYPE=Debug
cmake --build build -j$(nproc)
```
- [ ] Package APK manually or via `android/build-apk.sh` (using `aapt2` + `apksigner`)
- [ ] `[RISK]` Install and verify: ImGui renders, canvas pans/zooms, toolbar shows
- [ ] If APK signing fails in Termux: install `apksigner` from Android SDK build-tools, document the path
### Milestone 1 — Done Criteria
> App launches, shows ImGui docked layout with toolbar, empty canvas with grid, and bottom panel. Canvas supports touch pan and pinch zoom. No functionality yet.
---
## Milestone 2 — Control Registry + Toolbox (Weeks 3–4)
### 2.1 Control Definition Parser `[CRITICAL]`
Implement `model/control_registry.h/.cpp`:
- [ ] Define core data structures (per proposal §3.1):
```cpp
struct AttributeDef {
std::string name, type, default_val, values; // values = comma-separated for enum
bool required = false;
int min = 0, max = 0;
};
struct EventDef { std::string name; bool required = false; };
struct ControlDef {
std::string name, type, category, inherits, platform, description;
bool is_container = false;
std::vector allowed_parents, allowed_children;
std::vector attributes;
std::vector events;
};
```
- [ ] `ControlRegistry::load_control_set(const std::string& xml_path)`:
- Open XML with pugixml: `pugi::xml_document doc; doc.load_buffer(data, size)`
- Iterate `` elements under `` root
- Parse `name`, `type`, `category`, `inherits`, `platform` attributes
- Parse `` text
- Parse ``: extract ``, ``, `` → split comma-separated values
- Parse `` → vector of `AttributeDef` (name, type, default, required, min, max, values)
- Parse `` → vector of `EventDef`
- Also parse `` elements (same shape but include ``)
- Store in `std::unordered_map controls_`
- Build category index: `std::map> by_category_`
- [ ] `ControlRegistry::resolve_inheritance()`:
- For each control with non-empty `inherits`: find parent → merge parent's attributes (parent first, child overrides) → merge events → merge allowed_parents/children
- Handle chains: resolve root ancestor first (topological sort)
- After resolution, every `ControlDef` has complete standalone attribute list
### 2.2 Asset Loading Helper
Implement `util/platform.h/.cpp`:
- [ ] `std::string load_asset_text(AAssetManager* mgr, const std::string& path)`:
- Use `AAssetManager_open()` → `AAsset_getLength()` → `AAsset_read()` → return string
- APK assets are not on the filesystem — pugixml needs `doc.load_buffer(data, size)` instead of `load_file()`
- [ ] `bool extract_asset_to_file(AAssetManager* mgr, const std::string& asset_path, const std::string& dest_path)`:
- For libxml2 validation later (needs filesystem paths for XSD ``)
- Extract all schemas to cache dir on first run
### 2.3 Control Set Selection Dialog (New Document)
Implement in `ui/toolbar.h/.cpp` (the "New" action):
- [ ] ImGui modal popup `"New UI Definition"`:
- **Platform selection** — radio buttons:
- `basic` — Abstract only
- `winforms` — Abstract + WinForms
- `avalonia` — Abstract + Avalonia
- `web` — Abstract + Web Awesome
- `mixed` — Custom combination (enable checkboxes below)
- **Custom checkboxes** (only when `mixed` selected):
- `[x] Abstract controls` (always on, greyed out)
- `[ ] WinForms controls`
- `[ ] Avalonia controls`
- `[ ] Web Awesome controls`
- **Document name** — text input, default `"untitled"`
- **OK / Cancel** buttons
- [ ] On OK:
- Create new `Document` instance with selected `target_platform`
- Set `pattern_includes` list based on selection (e.g. `["abstract-controls.xml", "avalonia-controls.xml"]`)
- Clear & reload `ControlRegistry` with selected control set XMLs
- Clear canvas
- Set `document.modified = false`
### 2.4 Toolbox Panel (Left Slide-In Drawer)
Implement `ui/toolbox.h/.cpp`:
- [ ] Triggered by hamburger menu (☰) on toolbar or left-edge swipe
- [ ] Render as ImGui window with fixed width (~220px) anchored to left edge
- [ ] Content: accordion/collapsible headers by **category**:
- Map category strings to display names: `"basic"` → "Basic Controls", `"avalonia-input"` → "Avalonia Input", `"web-specific"` → "Web Awesome", etc.
- Under each header: list control names from `ControlRegistry::by_category_`
- Each entry: `ImGui::Selectable(control_name)` — on tap → enter placement mode
- [ ] **Placement mode state** (stored in `App`):
- `std::optional pending_placement` — name of control to place
- When active: canvas cursor changes (draw a ghost rectangle following touch)
- Tap on canvas → place control at snapped position → exit placement mode
- Tap toolbar or another toolbox item → cancel or switch
- [ ] Color-code entries by type: blue (input), green (display), gray (container), orange (list)
### 2.5 Verify: Registry Loads All Control Sets
- [ ] Load `abstract-controls.xml` — expect ~30+ controls (Label, TextBox, Button, Panel, etc.)
- [ ] Load `avalonia-controls.xml` — expect Avalonia-specific controls + inherited attributes resolved
- [ ] Load `winform-controls.xml` — expect WinForms-specific controls
- [ ] Load `webawesome-controls.xml` — expect `wa-button`, `wa-input`, etc.
- [ ] Log control count + sample attributes for sanity check
### Milestone 2 — Done Criteria
> "New" dialog works: user selects platform → control registry loads → toolbox shows controls grouped by category. Tapping a control enters placement mode (ghost preview). No canvas placement yet.
---
## Milestone 3 — Canvas Drawing (Weeks 5–7)
### 3.1 Control Instance Model
Implement `model/control_instance.h/.cpp`:
- [ ] Define `ControlInstance` (per proposal §3.2):
```cpp
struct ControlInstance {
std::string id; // auto-generated unique ID
std::string control_name; // reference into ControlRegistry
float x, y, w, h; // canvas-space position and size
std::map properties; // attribute overrides
std::vector> children;
ControlInstance* parent = nullptr; // non-owning
};
```
- [ ] `generate_id()` — unique within document (e.g. `"ctrl_001"`, incrementing counter)
- [ ] Default dimensions: look up ControlDef, use `width`/`height` defaults if defined, else 120×30
### 3.2 Place Controls on Canvas `[CRITICAL]`
In `canvas/canvas.h/.cpp`:
- [ ] When placement mode is active and user taps canvas:
- Convert touch coords to canvas-space (apply inverse ViewTransform)
- Snap to grid (configurable: 4, 8, 16, 32px — default 8px)
- Create `ControlInstance` with `control_name = pending_placement`, position = snapped (x,y)
- Add to current working context (wireframe root or window template)
- Clear `pending_placement`
- Set `document.modified = true`
- [ ] Render placed controls:
- For each `ControlInstance` in the active tree:
- Apply `ViewTransform` to get screen coords
- Draw filled rounded rectangle — color by control type:
- Input → `#4A90D9` (blue)
- Display → `#5CB85C` (green)
- Container → `#999999` (gray)
- List/Data → `#F0AD4E` (orange)
- Navigation/Menu → `#9B59B6` (purple)
- Draw border: solid for normal, dotted for containers
- Render control type name centered inside (use ImGui `AddText()`)
- If selected: draw selection handles (8 squares at corners + edge midpoints)
### 3.3 Drag to Move
- [ ] Hit test on touch-down: find topmost `ControlInstance` under touch point
- [ ] On touch-move (single finger, after hit): update instance `x, y` by delta
- [ ] Snap movement to grid
- [ ] On touch-up: commit position
- [ ] Push undo action: `MoveAction { instance_id, old_x, old_y, new_x, new_y }`
### 3.4 Resize Handles
- [ ] When a control is selected, draw 8 resize handles (small squares)
- [ ] Hit test handles on touch-down (handles take priority over move)
- [ ] On drag: adjust `x, y, w, h` depending on which handle is dragged:
- Top-left: adjust x, y, w, h (shrink/grow from corner)
- Right-edge: adjust w only
- Bottom-edge: adjust h only
- etc.
- [ ] Minimum size: 20×20
- [ ] Snap resize to grid
- [ ] Push undo action: `ResizeAction { instance_id, old_rect, new_rect }`
### 3.5 Container Nesting `[CRITICAL]`
- [ ] When placing or dropping a control, check if the drop point is inside a container:
- Walk the tree from root, find deepest container whose bounds contain the drop point
- Validate: check `ControlDef::allowed_children` of the container and `allowed_parents` of the child
- If valid → add as child, adjust (x,y) to be relative to container
- If invalid → place at document root level or show warning toast
- [ ] Render children clipped to parent bounds
- [ ] Moving a container moves all children with it
### 3.6 Selection
In `canvas/selection.h/.cpp`:
- [ ] Single tap on control → select it (deselect previous)
- [ ] Tap on empty canvas → deselect all
- [ ] Long-press on control → context menu:
- Delete
- Duplicate
- Mark as Slot (if in wireframe editing mode)
- Bring to Front / Send to Back
- [x] Marquee/rectangle selection: toggle via View > Marquee Select. In select mode, drag on empty canvas draws blue selection rectangle. Controls intersecting the rectangle are added to selection.
- [x] Multi-select: marquee or Shift+tap adds to selection. Group move and group delete supported. Group move uses `begin_group()`/`end_group()` for single undo step.
- [x] Selected controls stored in `Selection` class: `std::vector selected_ids_` with `add_to_selection()`, `deselect_all()`, `is_selected()`
### 3.7 Grid Snap Configuration
- [ ] Toolbar or canvas context menu: grid size selector (4, 8, 16, 32)
- [ ] Toggle: snap on/off
- [ ] Grid rendering: draw faint lines at snap intervals within visible canvas area
### 3.8 Undo / Redo `[CRITICAL]`
Implement `util/undo_redo.h/.cpp`:
- [ ] Abstract base: `struct UndoAction { virtual void undo() = 0; virtual void redo() = 0; virtual ~UndoAction() = default; }`
- [ ] Concrete actions:
- `PlaceAction` — undo: remove instance; redo: re-add
- `MoveAction` — undo: restore old pos; redo: apply new pos
- `ResizeAction` — undo: restore old rect; redo: apply new rect
- `DeleteAction` — undo: re-add instance + children; redo: remove
- `PropertyChangeAction` — undo: restore old value; redo: apply new value
- [ ] `UndoStack`: two stacks (`std::vector>`). Push action → clear redo stack.
- [ ] Grouping for drag sequences: `begin_group()` / `end_group()` — group = single undo step (per proposal §11, decision #3)
- [ ] Wire to toolbar buttons or Ctrl+Z / Ctrl+Y (external keyboard)
- [ ] Cap undo stack at 100 actions
### Milestone 3 — Done Criteria
> Controls can be placed from toolbox onto canvas. Controls can be moved, resized, selected, deleted. Container nesting works with validation. Undo/redo functions for all canvas operations. Grid snap works.
---
## Milestone 4 — Property Editor (Week 8)
### 4.1 Property Panel (Bottom Docked Tab)
Implement `ui/properties.h/.cpp`:
- [x] ~~Originally right slide-in drawer, moved to bottom docked tab (alongside Document Tree and Validation)~~
- [x] Content driven by selected `ControlInstance` + its `ControlDef` from registry
- [x] Labels rendered above fields (`ImGui::TextDisabled`) for narrow layout — widget labels use `##hidden_id`
- [x] Smaller content font (Fonts[1], 70% of main) for compact display
### 4.2 Property Sections (per proposal §4.4)
For the selected control, render these collapsible sections:
- [ ] **Identity:**
- `name` — editable text input (updates `ControlInstance::id`)
- `type` — read-only label (from `ControlDef::type`)
- `category` — read-only label
- [ ] **Layout:**
- `x`, `y` — int inputs (update instance position, snap to grid)
- `width`, `height` — int inputs (update instance size, enforce min 20)
- `dock` — enum dropdown: none, left, right, top, bottom
- `anchor` — enum dropdown: none, left, right, top, bottom, all
- `margin` — string input (format: `"t,r,b,l"`)
- `padding` — string input
- [ ] **Appearance:**
- `background-color` — ImGui `ColorEdit4()` picker
- `foreground-color` — ImGui `ColorEdit4()` picker
- `font-family` — text input
- `font-size` — int slider (6–72)
- `font-style` — enum dropdown: normal, italic, bold, bold-italic
- `visible` — checkbox
- `enabled` — checkbox
- [ ] **Type-Specific Attributes:**
- Iterate `ControlDef::attributes` that aren't covered above
- Render editor widget based on `AttributeDef::type`:
- `string` → `ImGui::InputText()`
- `int` → `ImGui::InputInt()` with min/max clamp; also show `ImGui::SliderInt()` if range defined
- `bool` → `ImGui::Checkbox()`
- `enum` → `ImGui::Combo()` populated from `AttributeDef::values` (split by comma)
- `color` → `ImGui::ColorEdit4()`
- `double`/`float` → `ImGui::InputFloat()` or `ImGui::SliderFloat()`
- Show default value hint when property is unset
- [ ] **Data Binding:**
- `data-source` — text input
- Show bindable properties if control has `` in its definition
- [ ] **Raw XML** (advanced toggle):
- `ImGui::InputTextMultiline()` showing all current properties as `key="value"` pairs
- Editable — parse changes back into `properties` map on focus-loss
### 4.3 Validation Feedback
- [ ] Red border/highlight on inputs that fail constraints:
- Required attribute is empty
- Int value outside min/max
- String violates pattern (if `validation-pattern` defined)
- [ ] Tooltip on hover: show constraint violation message
### 4.4 Property Changes → Undo
- [ ] Every property edit pushes `PropertyChangeAction` onto undo stack
- [ ] Debounce: for text inputs, only push undo action after 500ms of no typing or on focus-loss (avoid one undo per keystroke)
### Milestone 4 — Done Criteria
> Selecting a control on canvas opens the properties panel. All attribute types render with correct editor widgets. Editing updates the control instance. Validation constraints show red highlights. Property changes are undoable.
---
## Milestone 5 — Wireframes & Windows (Weeks 9–11)
### 5.1 Document Model Extensions
Implement `model/wireframe_def.h/.cpp` and `model/window_def.h/.cpp`:
- [ ] `SlotDef`:
```cpp
struct SlotDef {
std::string name;
std::string allowed_controls; // comma-separated or "any"
bool required = false;
ControlInstance* region; // the container marked as slot on canvas
};
```
- [ ] `WireframeDef` — per proposal §3.2: name, type, slots vector, root layout tree
- [ ] `WindowDef` — per proposal §3.2: name, title, wireframe_ref, template_root or slot_overrides map
- [ ] `Document` gains: `std::vector wireframes`, `std::vector windows`
### 5.2 Wireframe Mode
- [ ] Toolbar → "New Wireframe" button:
- Modal: enter wireframe name + type (e.g. `"base-layout"`, `"form-layout"`, `"dialog-layout"`)
- Creates `WireframeDef`, switches canvas to wireframe editing mode
- [ ] Canvas in wireframe mode:
- Working tree = `WireframeDef::root_layout`
- All placement, move, resize, nesting operates on this tree
- Visual indicator: canvas border changes color (e.g. blue tint)
### 5.3 Slot Marking `[CRITICAL]`
- [x] Long-press context menu on a container control in wireframe mode → "Mark as Slot"
- [x] Slot properties editable in Properties panel: name, allowed controls, required checkbox, region ID (read-only)
- [x] Creates `SlotDef` linked to that `ControlInstance`
- [x] Rendering: slot regions draw with dashed border + slot name label above
- [x] Slot container color: distinct from regular containers (yellow-tinted)
- [x] "Unmark Slot" option in context menu to remove slot definition
### 5.4 Window Creation — Wireframe-Based `[CRITICAL]`
- [ ] Toolbar → "New Window" button:
- Modal: enter window name, title
- **Wireframe selection dropdown**: lists all `Document::wireframes` by name, plus `"(none)"` option
- If wireframe selected → creates `WindowDef` with `wireframe_ref` set
- OK → switches canvas to window editing mode
- [ ] Canvas in window-with-wireframe mode:
- Render the referenced wireframe layout as **read-only background** (greyed out, non-interactive)
- Slot regions are **highlighted and interactive** — user can place controls inside them
- Placed controls go into `WindowDef::slot_overrides[slot_name]`
- Non-slot areas: clicks do nothing (toast: "This area is defined by wireframe, edit slots only")
### 5.5 Window Creation — Standalone
- [ ] If user selects `"(none)"` for wireframe:
- Creates `WindowDef` with empty `template_root`
- Canvas enters free-form template mode (same as wireframe editing, but stored in `WindowDef::template_root`)
- Optional: user can pick a `` pattern from registry to use as starting template
### 5.6 Document Tree Panel
Implement `ui/doc_tree.h/.cpp`:
- [ ] Bottom collapsible panel (drag handle to resize)
- [ ] Tree structure using `ImGui::TreeNode()`:
```
▸ Wireframes
▸ MainLayout (3 slots: menu-bar, main-content, status-bar)
▸ DialogLayout (2 slots: dialog-content, dialog-buttons)
▸ Windows
▸ ProductListWindow [wireframe: MainLayout]
▸ SettingsDialog [wireframe: DialogLayout]
▸ AboutWindow [standalone]
```
- [x] Double-click a wireframe or window → switch canvas to edit that item
- [x] Touch-friendly inline action buttons: `[Edit] [Props] [Rename] [Del]` below each item (replaces right-click context menu)
- [x] Visual indicator: active wireframe highlighted blue, active window highlighted green
- [x] Property dialogs for wireframes (name/type/platform) and windows (name/title/wireframe-ref/platform) as modal popups
### 5.7 Multi-Tab Canvas (Optional for v0.1)
- [ ] If time permits: tab bar above canvas area showing open wireframes/windows
- [ ] Switch between them via tabs
- [ ] Minimum viable: single active item, switch via document tree double-click
### Milestone 5 — Done Criteria
> User can create wireframes with named slots. User can create windows referencing a wireframe (controls placed in slots) or standalone windows. Document tree shows the hierarchy. Switching between wireframes/windows works via tree double-click.
---
## Milestone 6 — XML I/O (Weeks 12–13)
### 6.1 XML Writer (Save) `[CRITICAL]`
Implement `xml/xml_writer.h/.cpp`:
- [ ] `void save_document(const Document& doc, const std::string& path)`:
- Create `pugi::xml_document`
- Add XML declaration: ``
- Build `` root node:
- Attributes: `version="1.6"`, `schema-version="1.6"`, `target-platform="..."`, `xmlns:xsi`, `xsi:schemaLocation`
- Build `` section:
- For each `pattern_includes` entry → ``
- Build wireframes within patterns (or as schema dictates):
- `name`, `type`, `platform` attributes
- `` child: recursively serialize `root_layout` tree
- Slot containers get `` elements with `name`, `allowed-controls`, `required`
- Build `` elements:
- `name`, `title`, `wireframe` (if ref set), `target-platform`
- If wireframe-based: `` → for each slot, serialize control tree
- If standalone: `` → serialize control tree
- **Round-trip**: if document was loaded from file, merge raw DOM nodes for unedited sections (views, validation-rules, etc.) — per proposal §3.3
- `doc.save_file(path, " ")` — indented output
- [ ] Recursive control serializer:
```cpp
void serialize_control(pugi::xml_node parent, const ControlInstance* ctrl) {
auto node = parent.append_child(ctrl->control_name.c_str());
node.append_attribute("name") = ctrl->id.c_str();
for (auto& [key, val] : ctrl->properties)
node.append_attribute(key.c_str()) = val.c_str();
for (auto& child : ctrl->children)
serialize_control(node, child.get());
}
```
### 6.2 XML Reader (Open) `[CRITICAL]`
Implement `xml/xml_reader.h/.cpp`:
- [ ] `Document load_document(const std::string& path)`:
- Parse with pugixml: `doc.load_file(path)`
- Handle parse errors → return error + show dialog
- Read root `` attributes → `version`, `target_platform`, `schema-version`
- Version check: warn if `schema-version != "1.6"` but continue
- Read `` → extract `` → populate `pattern_includes`
- Resolve control registry from includes (load matching bundled XMLs)
- Read `` elements → parse into `WireframeDef`:
- Parse `` children recursively into `ControlInstance` tree
- Identify `` elements → create `SlotDef` entries
- Read `` elements → parse into `WindowDef`:
- If `wireframe` attribute present → set `wireframe_ref`
- Parse `` or `` into control trees
- Store remaining sections (views, validation-rules, etc.) as raw `pugi::xml_node` copies for round-trip
- [ ] Recursive control parser:
```cpp
std::unique_ptr parse_control(pugi::xml_node node) {
auto ctrl = std::make_unique();
ctrl->control_name = node.name();
ctrl->id = node.attribute("name").as_string();
for (auto attr : node.attributes())
ctrl->properties[attr.name()] = attr.value();
for (auto child : node.children()) {
if (child.type() == pugi::node_element) {
auto c = parse_control(child);
c->parent = ctrl.get();
ctrl->children.push_back(std::move(c));
}
}
return ctrl;
}
```
### 6.3 File Browser Dialog
Implement `ui/file_dialog.h/.cpp`:
- [x] Custom ImGui file browser (no native picker in NativeActivity):
- Browse starting from `/sdcard/` (or configurable root)
- Show directories and `.xml` files
- Directory navigation: tap folder to enter, ".." to go up
- Display file name and size
- Mode: `Open` (single-tap to select + Open button, or double-tap shortcut) or `Save As` (enter filename + confirm overwrite)
- [x] File extension enforcement: auto-append `.xml` if not present
### 6.4 Auto-Save
- [ ] Timer: every 60 seconds, if `document.modified == true`:
- Save to `{cache_dir}/autosave/{document_name}_autosave.xml`
- Keep at most 3 autosave versions (rotate)
- [ ] On startup: check for autosave files, offer recovery dialog
### 6.5 Toolbar Integration
- [ ] **Save** button: if file_path set → overwrite; else → file dialog (Save As)
- [ ] **Open** button: if document modified → "Save changes?" confirmation → file dialog (Open)
- [ ] **New** button: if document modified → "Save changes?" confirmation → new document dialog
### Milestone 6 — Done Criteria
> Documents can be saved to `.xml` conforming to ui-layout-def schema structure. Existing `.xml` files can be opened and rendered on canvas. Round-trip: open → save → open produces identical result. Auto-save works silently.
---
## Milestone 7 — XSD Validation (Week 14)
### 7.1 Schema Extraction [DONE]
- [x] On first run, extract all 8 XSD files from APK assets to cache directory
- `{data_path}/schemas/ui-layout-def.xsd` (etc.)
- libxml2 resolves `` via filesystem paths
- [x] Relative `schemaLocation` paths resolve correctly (all in same directory)
### 7.2 XSD Validator Wrapper `[CRITICAL]` [DONE]
Implemented in `xml/xsd_validator.h/.cpp`:
- [x] libxml2 2.12.9 built from source as static library in `third_party/libxml2/` — no ICU/iconv/lzma/zlib dependencies (saves 32MB+ vs Termux system libxml2)
- [x] Direct linking (not dlopen) — `#include ` with `xmlInitParser()` initialization
- [x] Error callbacks on both schema parser context (`xmlSchemaSetParserErrors`) and validation context (`xmlSchemaSetValidErrors`) for detailed error reporting
- [x] Schema parse errors shown to user (not just "failed to parse")
- [x] CMake integration: `add_subdirectory(third_party/libxml2)` with `LIBXML2_WITH_SCHEMAS=ON`
### 7.3 Validation Workflow [DONE]
- [x] **Validate button** on toolbar (Tools > Validate):
1. Serializes current document to temp file (reuses xml_writer)
2. Calls `validate_document()` with temp file + schema dir
3. Shows results in validation panel
- [x] Shows "No document loaded" if no document open
### 7.4 Validation Results Panel [DONE]
Implemented in `ui/validation_panel.h/.cpp`:
- [x] Renders in bottom tab area alongside Document Tree and Properties
- [x] List of errors/warnings with `[E]`/`[W]` color-coded icons
- [x] Line numbers from XML shown when available
- [x] Summary header: "Valid" (green) or "N error(s), M warning(s)" (red)
- [x] Smaller content font, 80px bottom padding for Android nav bar
- [ ] Tap on error → navigate to relevant control on canvas (not yet implemented)
### Milestone 7 — Done Criteria
> User taps "Validate" → document is validated against all XSD schemas → results panel shows errors/warnings with navigation to relevant controls on canvas.
---
## Milestone 8 — Polish & Package (Weeks 15–16)
### 8.1 Touch Input Refinement
- [ ] `[RISK]` Verify ImGui Android backend handles soft keyboard (IME) for text inputs:
- If not working: implement manual IME show/hide via JNI calls from NativeActivity
- [ ] Long-press timing: 500ms threshold for context menu
- [ ] Double-tap: zoom to fit selection or zoom to 100%
- [ ] Three-finger tap: undo (common Android gesture)
- [ ] Swipe gestures: left edge → toolbox, right edge → properties
- [ ] Implement "precision mode" toggle: when active, drag movement is dampened (1/4 speed) for fine positioning
### 8.2 Responsive Layout
- [x] Detect screen density and size via `ANativeWindow_getWidth/Height()`
- [x] Scale ImGui font size: 21sp base × density factor (width / 540dp)
- [x] Dual font: main (21sp) + content (15sp) for bottom panel text
- [x] Scrollbars: 24dp wide with 20dp minimum grab size for touch
- [x] Bottom panel padding: 80px to clear Android navigation bar
- [x] Resize handles: 12px visual / 28px touch hit area
- [x] Doc tree: inline visible action buttons instead of hidden popup menus
### 8.3 Performance Optimization
- [ ] `[RISK]` Large documents (100+ controls):
- ImGui viewport culling: only render controls within visible canvas rect
- Spatial hash or quadtree for hit testing (avoid O(n) scan)
- Lazy rendering: off-screen controls skip draw calls
- [ ] Profile with sample documents: 10, 50, 100, 200 controls — target 60 FPS on mid-range device
### 8.4 APK Build Script
`android/build-apk.sh` (implemented):
- [x] 7-step pipeline: copy .so → copy assets → javac+d8 → aapt2 compile+link resources → zip → zipalign → apksigner
- [x] Custom app icon: `android/gen_icon.py` generates PNG icons at 5 densities, referenced in AndroidManifest as `@mipmap/ic_launcher`
- [x] aapt2 compiles `android/res/mipmap-*/ic_launcher.png` resource directories
- [x] Auto-generates debug keystore on first build
- [x] Strips Termux RPATH with `patchelf --remove-rpath`
### 8.5 Sample Documents for Testing
- [ ] Create test files:
- `test-basic.xml` — Abstract controls only, 1 wireframe, 1 window
- `test-avalonia.xml` — Avalonia controls, AvaloniaMainWindow wireframe, 2 windows with slot overrides
- `test-webawesome.xml` — Web Awesome controls, wa-application-layout, form window
- `test-complex.xml` — Mixed platform, multiple wireframes, 50+ controls, views + validation sections (for round-trip testing)
- `test-invalid.xml` — Intentionally invalid for testing validation error display
- [ ] Verify for each: open → render → edit → save → reopen → diff = clean
### 8.6 Final Checks
- [ ] All proposal open questions resolved (§11):
- Gallery: renders as block, no visual box-template editing ✓
- Web Awesome slots: shown as named regions ✓
- Undo: per-action with drag grouping ✓
- Portrait orientation with slide-in drawers ✓
- [ ] Memory: no leaks on document open/close cycles (use `std::unique_ptr` ownership throughout)
- [ ] File permissions: test on Android 11+ (scoped storage) — may need `MANAGE_EXTERNAL_STORAGE` or MediaStore API fallback
- [ ] Crash recovery: autosave enables recovery after force-close
### Milestone 8 — Done Criteria
> APK installs and runs on a real Android device. All 7 features work end-to-end. Touch interactions are smooth. Sample documents open, edit, save, and validate correctly. APK size ≤ 12 MB.
---
## Cross-Cutting Concerns (Apply Throughout All Milestones)
### Logging
- [ ] Use `__android_log_print(ANDROID_LOG_INFO, "uidef", ...)` throughout
- [ ] Log levels: ERROR, WARN, INFO, DEBUG
- [ ] Key events to log: document load/save, registry load, validation start/end, unhandled exceptions
### Error Handling
- [ ] Never crash silently — show ImGui modal dialog on critical errors
- [ ] File I/O errors: show path + error message
- [ ] XML parse errors: show line number + message
- [ ] Schema validation errors: collect all, show in panel
### Memory Management
- [ ] `ControlInstance` trees: use `std::unique_ptr` for child ownership, raw pointers for parent back-references
- [ ] `Document` owns all wireframes and windows
- [ ] Undo stack: actions store delta data (not full snapshots)
- [ ] Cap undo stack at 100 actions (configurable)
---
## Dependency Checklist
| Dependency | Version | License | Where to Get |
|-----------|---------|---------|--------------|
| Dear ImGui | 1.91+ (docking) | MIT | `git clone --branch docking github.com/ocornut/imgui` |
| pugixml | 1.14+ | MIT | `github.com/zeux/pugixml` — copy 2 files |
| libxml2 | 2.12.9 | MIT | Built from source in `third_party/libxml2/` (no system pkg needed) |
| Roboto Font | — | Apache 2.0 | `fonts.google.com` |
**Target APK size:** 8–12 MB (arm64-v8a, Release build).
---
## Post-0.1 Roadmap (For Reference)
| Version | Scope |
|---------|-------|
| v0.2 | View definitions editor; Gallery box-template visual editing |
| v0.3 | Live preview — render platform-like controls instead of rectangles |
| v0.4 | External includes resolution (`` from files) |
| v0.5 | Multi-file project support |
| v0.6 | Code generation (XAML, HTML, WinForms designer output) |
| v1.0 | Collaboration; shared control libraries; theming preview |