1-implementation-todo.md 38 KB

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:

    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:

    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)
    • <meta-data android:name="android.app.lib_name" android:value="uidef-tool"/>
    • Permissions: WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE (for /sdcard/ access)
    • android:screenOrientation="portrait"

1.5 NativeActivity Entry Point [CRITICAL]

Implement src/main.cpp:

  • #include <android_native_app_glue.h> — 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:

    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):

    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<std::string> allowed_parents, allowed_children;
      std::vector<AttributeDef> attributes;
      std::vector<EventDef> events;
    };
    
  • [ ] ControlRegistry::load_control_set(const std::string& xml_path):

    • Open XML with pugixml: pugi::xml_document doc; doc.load_buffer(data, size)
    • Iterate <control> elements under <patterns> root
    • Parse name, type, category, inherits, platform attributes
    • Parse <description> text
    • Parse <constraints>: extract <allowed-parents>, <allowed-children>, <container> → split comma-separated values
    • Parse <attributes> → vector of AttributeDef (name, type, default, required, min, max, values)
    • Parse <behavior> → vector of EventDef
    • Also parse <structure> elements (same shape but include <template>)
    • Store in std::unordered_map<std::string, ControlDef> controls_
    • Build category index: std::map<std::string, std::vector<std::string>> 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 <xs:import schemaLocation>)
    • 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<std::string> 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):

    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<std::string, std::string> properties;  // attribute overrides
      std::vector<std::unique_ptr<ControlInstance>> 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
  • 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.
  • 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.
  • Selected controls stored in Selection class: std::vector<std::string> 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<std::unique_ptr<UndoAction>>). 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:

  • Originally right slide-in drawer, moved to bottom docked tab (alongside Document Tree and Validation)
  • Content driven by selected ControlInstance + its ControlDef from registry
  • Labels rendered above fields (ImGui::TextDisabled) for narrow layout — widget labels use ##hidden_id
  • 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:
    • stringImGui::InputText()
    • intImGui::InputInt() with min/max clamp; also show ImGui::SliderInt() if range defined
    • boolImGui::Checkbox()
    • enumImGui::Combo() populated from AttributeDef::values (split by comma)
    • colorImGui::ColorEdit4()
    • double/floatImGui::InputFloat() or ImGui::SliderFloat()
    • Show default value hint when property is unset
  • [ ] Data Binding:

    • data-source — text input
    • Show bindable properties if control has <data-binding> 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:

    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<WireframeDef> wireframes, std::vector<WindowDef> 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]

  • Long-press context menu on a container control in wireframe mode → "Mark as Slot"
  • Slot properties editable in Properties panel: name, allowed controls, required checkbox, region ID (read-only)
  • Creates SlotDef linked to that ControlInstance
  • Rendering: slot regions draw with dashed border + slot name label above
  • Slot container color: distinct from regular containers (yellow-tinted)
  • "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 <structure> 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: <?xml version="1.0" encoding="UTF-8"?>
    • Build <ui-layout-def> root node:
    • Attributes: version="1.6", schema-version="1.6", target-platform="...", xmlns:xsi, xsi:schemaLocation
    • Build <patterns> section:
    • For each pattern_includes entry → <pattern-include src="..." important="true"/>
    • Build wireframes within patterns (or as schema dictates):
    • name, type, platform attributes
    • <template> child: recursively serialize root_layout tree
    • Slot containers get <slot> elements with name, allowed-controls, required
    • Build <window> elements:
    • name, title, wireframe (if ref set), target-platform
    • If wireframe-based: <slot-overrides> → for each slot, serialize control tree
    • If standalone: <template> → 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:

    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 <ui-layout-def> attributes → version, target_platform, schema-version
    • Version check: warn if schema-version != "1.6" but continue
    • Read <patterns> → extract <pattern-include src="..."> → populate pattern_includes
    • Resolve control registry from includes (load matching bundled XMLs)
    • Read <wireframe> elements → parse into WireframeDef:
    • Parse <template> children recursively into ControlInstance tree
    • Identify <slot> elements → create SlotDef entries
    • Read <window> elements → parse into WindowDef:
    • If wireframe attribute present → set wireframe_ref
    • Parse <slot-overrides> or <template> into control trees
    • Store remaining sections (views, validation-rules, etc.) as raw pugi::xml_node copies for round-trip
  • [ ] Recursive control parser:

    std::unique_ptr<ControlInstance> parse_control(pugi::xml_node node) {
      auto ctrl = std::make_unique<ControlInstance>();
      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:

  • 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)
  • 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]

  • On first run, extract all 8 XSD files from APK assets to cache directory
    • {data_path}/schemas/ui-layout-def.xsd (etc.)
    • libxml2 resolves <xs:import schemaLocation="..."> via filesystem paths
  • Relative schemaLocation paths resolve correctly (all in same directory)

7.2 XSD Validator Wrapper [CRITICAL] [DONE]

Implemented in xml/xsd_validator.h/.cpp:

  • 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)
  • Direct linking (not dlopen) — #include <libxml/xmlschemas.h> with xmlInitParser() initialization
  • Error callbacks on both schema parser context (xmlSchemaSetParserErrors) and validation context (xmlSchemaSetValidErrors) for detailed error reporting
  • Schema parse errors shown to user (not just "failed to parse")
  • CMake integration: add_subdirectory(third_party/libxml2) with LIBXML2_WITH_SCHEMAS=ON

7.3 Validation Workflow [DONE]

  • 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
  • Shows "No document loaded" if no document open

7.4 Validation Results Panel [DONE]

Implemented in ui/validation_panel.h/.cpp:

  • Renders in bottom tab area alongside Document Tree and Properties
  • List of errors/warnings with [E]/[W] color-coded icons
  • Line numbers from XML shown when available
  • Summary header: "Valid" (green) or "N error(s), M warning(s)" (red)
  • 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

  • Detect screen density and size via ANativeWindow_getWidth/Height()
  • Scale ImGui font size: 21sp base × density factor (width / 540dp)
  • Dual font: main (21sp) + content (15sp) for bottom panel text
  • Scrollbars: 24dp wide with 20dp minimum grab size for touch
  • Bottom panel padding: 80px to clear Android navigation bar
  • Resize handles: 12px visual / 28px touch hit area
  • 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):

  • 7-step pipeline: copy .so → copy assets → javac+d8 → aapt2 compile+link resources → zip → zipalign → apksigner
  • Custom app icon: android/gen_icon.py generates PNG icons at 5 densities, referenced in AndroidManifest as @mipmap/ic_launcher
  • aapt2 compiles android/res/mipmap-*/ic_launcher.png resource directories
  • Auto-generates debug keystore on first build
  • 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 (<pattern-include src="..."> 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