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
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.
[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
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 / .cppimgui_impl_android.h / .cpp[ ] Download Roboto-Regular.ttf into assets/fonts/
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
.h/.cpp files with header guardsassets/schemas/assets/controls/[CRITICAL]CMakeLists.txt:
cmake_minimum_required(VERSION 3.18)set(CMAKE_CXX_STANDARD 17) / set(CMAKE_CXX_STANDARD_REQUIRED ON)-DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmakeset(ANDROID_ABI arm64-v8a)set(ANDROID_PLATFORM android-26)add_library(uidef-tool SHARED ...) (NativeActivity requires shared lib)src/**/*.cpp + pugixml + ImGui + backendsthird_party/pugixml/, third_party/imgui/, third_party/imgui/backends/android, log, EGL, GLESv2, xml2, zfind_package(LibXml2 REQUIRED) or manual -lxml2 + include pathandroid/AndroidManifest.xml:
android:hasCode="false" (pure NativeActivity)<meta-data android:name="android.app.lib_name" android:value="uidef-tool"/>WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE (for /sdcard/ access)android:screenOrientation="portrait"[CRITICAL]Implement src/main.cpp:
#include <android_native_app_glue.h> — use NDK glueandroid_main(struct android_app* app) entry functionImGui::CreateContext(), load Roboto font from APK assets via AAssetManagerImGui_ImplAndroid_Init(app->window), ImGui_ImplOpenGL3_Init("#version 100")ImGui_ImplAndroid_NewFrame() → ImGui_ImplOpenGL3_NewFrame() → ImGui::NewFrame() → draw UI → ImGui::Render() → ImGui_ImplOpenGL3_RenderDrawData() → eglSwapBuffers()APP_CMD_INIT_WINDOW / APP_CMD_TERM_WINDOW for EGL surface lifecycleAPP_CMD_GAINED_FOCUS / APP_CMD_LOST_FOCUS for render pauseImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnableImGui::DockSpaceOverViewport()ImGui::Begin("Toolbar") — top bar placeholderImGui::Begin("Canvas") — central areaImGui::Begin("Document Tree") — bottom panelcanvas.h/cpp:
struct ViewTransform { float offset_x, offset_y, zoom; } — default: offset(0,0), zoom(1.0)ImDrawList::PushClipRect() + coordinate mapping[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.[ ] 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
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.
[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):
pugi::xml_document doc; doc.load_buffer(data, size)<control> elements under <patterns> rootname, type, category, inherits, platform attributes<description> text<constraints>: extract <allowed-parents>, <allowed-children>, <container> → split comma-separated values<attributes> → vector of AttributeDef (name, type, default, required, min, max, values)<behavior> → vector of EventDef<structure> elements (same shape but include <template>)std::unordered_map<std::string, ControlDef> controls_std::map<std::string, std::vector<std::string>> by_category_[ ] ControlRegistry::resolve_inheritance():
inherits: find parent → merge parent's attributes (parent first, child overrides) → merge events → merge allowed_parents/childrenControlDef has complete standalone attribute listImplement util/platform.h/.cpp:
std::string load_asset_text(AAssetManager* mgr, const std::string& path):
AAssetManager_open() → AAsset_getLength() → AAsset_read() → return stringdoc.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):
<xs:import schemaLocation>)Implement in ui/toolbar.h/.cpp (the "New" action):
"New UI Definition":
basic — Abstract onlywinforms — Abstract + WinFormsavalonia — Abstract + Avaloniaweb — Abstract + Web Awesomemixed — Custom combination (enable checkboxes below)mixed selected):[x] Abstract controls (always on, greyed out)[ ] WinForms controls[ ] Avalonia controls[ ] Web Awesome controls"untitled"Document instance with selected target_platformpattern_includes list based on selection (e.g. ["abstract-controls.xml", "avalonia-controls.xml"])ControlRegistry with selected control set XMLsdocument.modified = falseImplement ui/toolbox.h/.cpp:
"basic" → "Basic Controls", "avalonia-input" → "Avalonia Input", "web-specific" → "Web Awesome", etc.ControlRegistry::by_category_ImGui::Selectable(control_name) — on tap → enter placement modeApp):
std::optional<std::string> pending_placement — name of control to placeabstract-controls.xml — expect ~30+ controls (Label, TextBox, Button, Panel, etc.)avalonia-controls.xml — expect Avalonia-specific controls + inherited attributes resolvedwinform-controls.xml — expect WinForms-specific controlswebawesome-controls.xml — expect wa-button, wa-input, etc."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.
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
[CRITICAL]In canvas/canvas.h/.cpp:
[ ] When placement mode is active and user taps canvas:
ControlInstance with control_name = pending_placement, position = snapped (x,y)pending_placementdocument.modified = true[ ] Render placed controls:
ControlInstance in the active tree:ViewTransform to get screen coords#4A90D9 (blue)#5CB85C (green)#999999 (gray)#F0AD4E (orange)#9B59B6 (purple)AddText())ControlInstance under touch pointx, y by deltaMoveAction { instance_id, old_x, old_y, new_x, new_y }x, y, w, h depending on which handle is dragged:
ResizeAction { instance_id, old_rect, new_rect }[CRITICAL]ControlDef::allowed_children of the container and allowed_parents of the childIn canvas/selection.h/.cpp:
begin_group()/end_group() for single undo step.Selection class: std::vector<std::string> selected_ids_ with add_to_selection(), deselect_all(), is_selected()[CRITICAL]Implement util/undo_redo.h/.cpp:
struct UndoAction { virtual void undo() = 0; virtual void redo() = 0; virtual ~UndoAction() = default; }PlaceAction — undo: remove instance; redo: re-addMoveAction — undo: restore old pos; redo: apply new posResizeAction — undo: restore old rect; redo: apply new rectDeleteAction — undo: re-add instance + children; redo: removePropertyChangeAction — undo: restore old value; redo: apply new valueUndoStack: two stacks (std::vector<std::unique_ptr<UndoAction>>). Push action → clear redo stack.begin_group() / end_group() — group = single undo step (per proposal §11, decision #3)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.
Implement ui/properties.h/.cpp:
ControlInstance + its ControlDef from registryImGui::TextDisabled) for narrow layout — widget labels use ##hidden_idFor 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, bottomanchor — enum dropdown: none, left, right, top, bottom, allmargin — string input (format: "t,r,b,l")padding — string input[ ] Appearance:
background-color — ImGui ColorEdit4() pickerforeground-color — ImGui ColorEdit4() pickerfont-family — text inputfont-size — int slider (6–72)font-style — enum dropdown: normal, italic, bold, bold-italicvisible — checkboxenabled — checkbox[ ] Type-Specific Attributes:
ControlDef::attributes that aren't covered aboveAttributeDef::type:string → ImGui::InputText()int → ImGui::InputInt() with min/max clamp; also show ImGui::SliderInt() if range definedbool → ImGui::Checkbox()enum → ImGui::Combo() populated from AttributeDef::values (split by comma)color → ImGui::ColorEdit4()double/float → ImGui::InputFloat() or ImGui::SliderFloat()[ ] Data Binding:
data-source — text input<data-binding> in its definition[ ] Raw XML (advanced toggle):
ImGui::InputTextMultiline() showing all current properties as key="value" pairsproperties map on focus-lossvalidation-pattern defined)PropertyChangeAction onto undo stackSelecting 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.
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
"base-layout", "form-layout", "dialog-layout")WireframeDef, switches canvas to wireframe editing modeWireframeDef::root_layout[CRITICAL]SlotDef linked to that ControlInstance[CRITICAL][ ] Toolbar → "New Window" button:
Document::wireframes by name, plus "(none)" optionWindowDef with wireframe_ref set[ ] Canvas in window-with-wireframe mode:
WindowDef::slot_overrides[slot_name]"(none)" for wireframe:
WindowDef with empty template_rootWindowDef::template_root)<structure> pattern from registry to use as starting templateImplement ui/doc_tree.h/.cpp:
[ ] 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
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.
[CRITICAL]Implement xml/xml_writer.h/.cpp:
[ ] void save_document(const Document& doc, const std::string& path):
pugi::xml_document<?xml version="1.0" encoding="UTF-8"?><ui-layout-def> root node:version="1.6", schema-version="1.6", target-platform="...", xmlns:xsi, xsi:schemaLocation<patterns> section:pattern_includes entry → <pattern-include src="..." important="true"/>name, type, platform attributes<template> child: recursively serialize root_layout tree<slot> elements with name, allowed-controls, required<window> elements:name, title, wireframe (if ref set), target-platform<slot-overrides> → for each slot, serialize control tree<template> → serialize control treedoc.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());
}
[CRITICAL]Implement xml/xml_reader.h/.cpp:
[ ] Document load_document(const std::string& path):
doc.load_file(path)<ui-layout-def> attributes → version, target_platform, schema-versionschema-version != "1.6" but continue<patterns> → extract <pattern-include src="..."> → populate pattern_includes<wireframe> elements → parse into WireframeDef:<template> children recursively into ControlInstance tree<slot> elements → create SlotDef entries<window> elements → parse into WindowDef:wireframe attribute present → set wireframe_ref<slot-overrides> or <template> into control treespugi::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;
}
Implement ui/file_dialog.h/.cpp:
/sdcard/ (or configurable root).xml filesOpen (single-tap to select + Open button, or double-tap shortcut) or Save As (enter filename + confirm overwrite).xml if not presentdocument.modified == true:
{cache_dir}/autosave/{document_name}_autosave.xmlDocuments can be saved to
.xmlconforming to ui-layout-def schema structure. Existing.xmlfiles can be opened and rendered on canvas. Round-trip: open → save → open produces identical result. Auto-save works silently.
{data_path}/schemas/ui-layout-def.xsd (etc.)<xs:import schemaLocation="..."> via filesystem pathsschemaLocation paths resolve correctly (all in same directory)[CRITICAL] [DONE]Implemented in xml/xsd_validator.h/.cpp:
third_party/libxml2/ — no ICU/iconv/lzma/zlib dependencies (saves 32MB+ vs Termux system libxml2)#include <libxml/xmlschemas.h> with xmlInitParser() initializationxmlSchemaSetParserErrors) and validation context (xmlSchemaSetValidErrors) for detailed error reportingadd_subdirectory(third_party/libxml2) with LIBXML2_WITH_SCHEMAS=ONvalidate_document() with temp file + schema dirImplemented in ui/validation_panel.h/.cpp:
[E]/[W] color-coded iconsUser taps "Validate" → document is validated against all XSD schemas → results panel shows errors/warnings with navigation to relevant controls on canvas.
[RISK] Verify ImGui Android backend handles soft keyboard (IME) for text inputs:
ANativeWindow_getWidth/Height()[RISK] Large documents (100+ controls):
android/build-apk.sh (implemented):
android/gen_icon.py generates PNG icons at 5 densities, referenced in AndroidManifest as @mipmap/ic_launcherandroid/res/mipmap-*/ic_launcher.png resource directoriespatchelf --remove-rpathtest-basic.xml — Abstract controls only, 1 wireframe, 1 windowtest-avalonia.xml — Avalonia controls, AvaloniaMainWindow wireframe, 2 windows with slot overridestest-webawesome.xml — Web Awesome controls, wa-application-layout, form windowtest-complex.xml — Mixed platform, multiple wireframes, 50+ controls, views + validation sections (for round-trip testing)test-invalid.xml — Intentionally invalid for testing validation error displaystd::unique_ptr ownership throughout)MANAGE_EXTERNAL_STORAGE or MediaStore API fallbackAPK 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.
__android_log_print(ANDROID_LOG_INFO, "uidef", ...) throughoutControlInstance trees: use std::unique_ptr for child ownership, raw pointers for parent back-referencesDocument owns all wireframes and windows| 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).
| 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 |