Version: 0.1.0 Platform: Android (APK), native C++ compiled with Clang in Termux Target Spec: UI Layout Definition Format v1.6
A lightweight Android application for visually designing UI layouts conforming to the ui-layout-def XML specification (v1.6). The tool enables selecting a control set, drawing wireframes by placing controls from a toolbox, creating windows with slot overrides, editing control properties, and saving/loading/validating XML files against the XSD schemas.
The app is built entirely in C++ using the Android NDK toolchain available in Termux, with no Java/Kotlin layer — pure NativeActivity + OpenGL ES or Skia for rendering.
┌─────────────────────────────────────────────────────┐
│ Android APK │
│ ┌───────────────────────────────────────────────┐ │
│ │ NativeActivity (C++) │ │
│ │ ┌─────────┐ ┌──────────┐ ┌───────────────┐ │ │
│ │ │ UI Core │ │ Renderer │ │ Input Handler │ │ │
│ │ │ (ImGui) │ │(GLES 2.0)│ │ (touch/keys) │ │ │
│ │ └────┬────┘ └─────┬────┘ └───────┬───────┘ │ │
│ │ │ │ │ │ │
│ │ ┌────▼────────────▼──────────────▼───────┐ │ │
│ │ │ Application Core │ │ │
│ │ │ ┌────────────┐ ┌──────────────────┐ │ │ │
│ │ │ │ Document │ │ Control │ │ │ │
│ │ │ │ Model │ │ Registry │ │ │ │
│ │ │ │ (DOM tree) │ │ (parsed XMLs) │ │ │ │
│ │ │ └──────┬─────┘ └────────┬─────────┘ │ │ │
│ │ │ │ │ │ │ │
│ │ │ ┌──────▼─────────────────▼──────────┐ │ │ │
│ │ │ │ XML Engine │ │ │ │
│ │ │ │ ┌──────────┐ ┌──────────────┐ │ │ │ │
│ │ │ │ │ pugixml │ │ libxml2 XSD │ │ │ │ │
│ │ │ │ │ (R/W) │ │ (validation) │ │ │ │ │
│ │ │ │ └──────────┘ └──────────────┘ │ │ │ │
│ │ │ └───────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────┘ │
│ ┌───────────────┐ │
│ │ File System │ │
│ │ (storage) │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────┘
| Component | Library | Rationale |
|---|---|---|
| UI Framework | Dear ImGui (docking branch) | Immediate-mode GUI, trivial to embed in NativeActivity, excellent for toolbox/property panels, zero Java dependency |
| Rendering | OpenGL ES 2.0 via EGL | Universal Android support, ImGui has a backend for it |
| XML Parsing/Writing | pugixml | Header-only C++, lightweight, DOM-based — perfect fit for ui-layout-def tree manipulation |
| XSD Validation | libxml2 | The only practical C library with XSD schema validation; available in Termux (pkg install libxml2) |
| Touch/Input | android_native_app_glue | Standard NDK input handling for NativeActivity |
| Build System | CMake + NDK via Termux | pkg install android-ndk cmake in Termux |
| File Dialogs | Custom ImGui panel | Browsing /sdcard/ for open/save since there's no native file picker in NativeActivity |
SDL2 is an option but adds an unnecessary abstraction layer. NativeActivity + android_native_app_glue + ImGui is more direct, smaller APK, and gives full control over the GL context lifecycle. If future versions need audio or gamepad input, SDL2 can be introduced later.
On startup, the app parses the control definition XML files (abstract-controls.xml, avalonia-controls.xml, winform-controls.xml, webawesome-controls.xml) using pugixml. Each parsed control becomes an entry in a ControlRegistry:
struct ControlDef {
std::string name; // "TextBox", "Panel", "wa-button"
std::string type; // "input", "container", "display"
std::string category; // "basic", "avalonia-input", etc.
std::string inherits; // "CommonControl"
std::string platform; // "basic", "avalonia", "web"
std::string description;
bool is_container;
std::vector<std::string> allowed_parents;
std::vector<std::string> allowed_children;
std::vector<AttributeDef> attributes;
std::vector<EventDef> events;
};
struct AttributeDef {
std::string name;
std::string type; // "string", "int", "bool", "enum"
std::string default_val;
bool required;
int min, max;
std::string values; // comma-separated enum values
};
class ControlRegistry {
std::unordered_map<std::string, ControlDef> controls_;
// Grouped by control set for toolbox display
std::map<std::string, std::vector<std::string>> by_category_;
public:
void load_control_set(const std::string& xml_path);
const ControlDef* find(const std::string& name) const;
void resolve_inheritance(); // flatten inherited attributes
};
The registry resolves inherits chains so each ControlDef has the full merged attribute list from CommonControl down to the specific control.
The working document is an in-memory tree mirroring the ui-layout-def XML structure:
// Represents a placed control instance on the canvas
struct ControlInstance {
std::string id; // unique within document
std::string control_name; // reference to ControlDef
float x, y, w, h; // canvas position/size
std::map<std::string, std::string> properties; // attribute overrides
std::vector<ControlInstance*> children;
ControlInstance* parent;
};
// A wireframe definition
struct WireframeDef {
std::string name;
std::string type;
std::vector<SlotDef> slots; // named regions
ControlInstance* root_layout; // the template tree
};
// A window definition
struct WindowDef {
std::string name;
std::string title;
std::string wireframe_ref; // optional wireframe name
std::string view_ref; // optional view name
std::string target_platform;
// Either template (free-form) or slot-overrides
ControlInstance* template_root;
std::map<std::string, ControlInstance*> slot_overrides;
};
// Root document
struct Document {
std::string version = "1.6";
std::string target_platform = "basic";
std::vector<std::string> pattern_includes;
std::vector<WireframeDef> wireframes;
std::vector<WindowDef> windows;
// views, validation-rules stored as raw pugixml nodes
// (v0.1 focuses on patterns+windows)
pugi::xml_document raw_doc; // for round-tripping unsupported sections
std::string file_path;
bool modified = false;
};
Sections the tool doesn't visually edit (views, validation-rules, state-management, etc.) are preserved as raw XML nodes via pugixml's DOM. When saving, the tool:
This guarantees no data loss when editing files created by hand or other tools.
Flow:
basic, winforms, avalonia, web, mixed)Document with appropriate <pattern-include> entriesControl set files bundled in APK assets:
| File | Control Set |
|---|---|
abstract-controls.xml |
Always loaded — base controls |
winform-controls.xml |
WinForms-specific controls |
avalonia-controls.xml |
Avalonia-specific controls |
webawesome-controls.xml |
Web Awesome components |
The XSD schemas are also bundled for validation.
Canvas:
Toolbox panel (left sidebar, collapsible):
Wireframe creation:
Rendering style for v0.1:
Two modes:
A) Window with wireframe reference:
<slot-overrides>B) Standalone window (no wireframe):
<template> mode<structure> pattern or build free-formWindow list panel (right sidebar):
When a control is selected on the canvas, the Properties panel shows:
ControlDef (e.g., max-length for TextBox, paging-enabled for Gallery)Property editors by attribute type:
string → text inputint → numeric input with min/max clamp and sliderbool → checkboxenum → dropdown (populated from values attribute)color → color picker widget (ImGui has one built-in)Validation feedback: red highlight on properties that fail constraints (min/max, required).
Serialization pipeline:
Document model → pugixml DOM tree → formatted XML string → file
<ui-layout-def> root with version, schema-version, target-platform attributes<patterns> with <pattern-include> elements for selected control sets<wireframe> with <template> containing the layout tree<window wireframe="..."> with <slot-overrides><window> with <template> content/sdcard/Documents/ui-layout/)File naming: {document-name}.xml with .xml extension enforced.
Auto-save: optional, every 60 seconds to ~/.uidef-autosave/.
Loading pipeline:
File → pugixml parse → Document model population → Canvas render
<pattern-include> entries → resolve control registry<wireframe> elements → build wireframe canvas objects<window> elements → build window canvas objects with control instancesVersion compatibility: warn if schema-version != "1.6" but attempt to load anyway.
Using libxml2 for schema validation:
#include <libxml/xmlschemastypes.h>
bool validate_against_schema(const std::string& xml_path,
const std::string& xsd_path) {
xmlSchemaParserCtxtPtr parser_ctx =
xmlSchemaNewParserCtxt(xsd_path.c_str());
xmlSchemaPtr schema = xmlSchemaParse(parser_ctx);
xmlSchemaValidCtxtPtr valid_ctx =
xmlSchemaNewValidCtxt(schema);
// Collect errors via callback
xmlSchemaSetValidErrors(valid_ctx, error_cb, warning_cb, &errors);
int ret = xmlSchemaValidateFile(valid_ctx, xml_path.c_str(), 0);
// cleanup...
return ret == 0;
}
Validation workflow:
ui-layout-def.xsd (which imports all sub-schemas)Bundled XSD files:
| Schema | Purpose |
|---|---|
ui-layout-def.xsd |
Root document structure |
ui-layout-patterns.xsd |
Controls, structures, wireframes |
ui-layout-views.xsd |
View definitions |
ui-layout-validation.xsd |
Validation rules, profiles |
ui-layout-basic-controls.xsd |
Basic control types |
ui-layout-winforms-controls.xsd |
WinForms controls |
ui-layout-avalonia-controls.xsd |
Avalonia controls |
ui-layout-webawesome-controls.xsd |
Web Awesome controls |
┌──────────────────────────────────────┐
│ Toolbar: [☰] [New][Open][Save][✓Val] │
├──────────────────────────────────────┤
│ │
│ Canvas │
│ (wireframe/window │
│ drawing area) │
│ │
│ ┌──────────────────┐ │
│ │ Panel │ │
│ │ ┌──────────────┐ │ │
│ │ │ TextBox │ │ │
│ │ └──────────────┘ │ │
│ │ ┌──────────────┐ │ │
│ │ │ Button │ │ │
│ │ └──────────────┘ │ │
│ └──────────────────┘ │
│ │
├──────────────────────────────────────┤
│ Doc Tree / Validation (collapsible) │
│ ▸ Wireframes │
│ ▸ MainLayout (3 slots) │
│ ▸ Windows │
│ ▸ ProductListWindow │
└──────────────────────────────────────┘
◄ Swipe from left Swipe from right ►
┌────────────┐ ┌────────────────┐
│ Toolbox │ │ Properties │
│ │ │ │
│ [Label] │ │ Name: ________ │
│ [TextBox] │ │ X: ___ Y: ___ │
│ [Button] │ │ W: ___ H: ___ │
│ [Panel] │ │ dock: [none ▾] │
│ [SplitPnl] │ │ enabled: [☑] │
│ [TabCtrl] │ │ visible: [☑] │
│ [ListBox] │ │ │
│ [DataGrid] │ │ --- Type --- │
│ [Tree] │ │ maxLen: [255] │
│ ... │ │ placeholder: _ │
└────────────┘ └────────────────┘
Portrait layout behavior:
# Install required packages
pkg install clang cmake make ndk-sysroot android-ndk
pkg install libxml2 git
# Clone project
mkdir -p ~/uidef-tool && cd ~/uidef-tool
# External dependencies (header-only or source-included)
# pugixml: single header+source, copy into project
# imgui: clone docking branch
# imgui backends: imgui_impl_opengl3 + imgui_impl_android
uidef-tool/
├── CMakeLists.txt
├── src/
│ ├── main.cpp # NativeActivity entry point
│ ├── app.h / app.cpp # Application lifecycle
│ ├── canvas/
│ │ ├── canvas.h / .cpp # Drawing surface + transform
│ │ ├── grid.h / .cpp # Snap grid rendering
│ │ └── selection.h / .cpp # Selection + multi-select
│ ├── model/
│ │ ├── document.h / .cpp # Document root
│ │ ├── control_instance.h # Placed control
│ │ ├── wireframe_def.h # Wireframe
│ │ ├── window_def.h # Window
│ │ └── control_registry.h # Registry
│ ├── xml/
│ │ ├── xml_reader.h / .cpp # Load from XML (pugixml)
│ │ ├── xml_writer.h / .cpp # Save to XML (pugixml)
│ │ └── xsd_validator.h / .cpp# Validate (libxml2)
│ ├── ui/
│ │ ├── toolbox.h / .cpp # Toolbox panel
│ │ ├── properties.h / .cpp # Property editor
│ │ ├── doc_tree.h / .cpp # Document tree panel
│ │ ├── toolbar.h / .cpp # Top toolbar
│ │ ├── file_dialog.h / .cpp # Open/save file browser
│ │ └── validation_panel.h # Validation results
│ └── util/
│ ├── undo_redo.h / .cpp # Undo/redo stack
│ └── platform.h / .cpp # Android-specific helpers
├── assets/ # Bundled into APK
│ ├── schemas/ # All .xsd files
│ ├── controls/ # Control definition .xml files
│ └── fonts/ # ImGui font (e.g. Roboto)
├── third_party/
│ ├── pugixml/
│ ├── imgui/ # docking branch
│ └── imgui_backends/
└── android/
└── AndroidManifest.xml
# Configure
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=arm64-v8a \
-DANDROID_PLATFORM=android-26 \
-DCMAKE_BUILD_TYPE=Release
# Build
cmake --build build -j$(nproc)
# Package APK (using aapt2 + apksigner from build-tools)
# Script provided in android/build-apk.sh
abstract-controls.xml with pugixml.xml (serialization with pugixml).xml files (deserialization)| Risk | Impact | Mitigation |
|---|---|---|
| libxml2 XSD validation may not handle all schema features (imports, complex types) | Validation incomplete | Test early with all bundled XSDs; fallback to partial validation reporting |
| Touch precision for small controls | Poor UX on phones | Configurable zoom + snap grid; "precision mode" toggle; portrait orientation maximizes canvas width |
| ImGui text input on Android soft keyboard | Keyboard may not work well | Use ImGui's Android backend which handles IME; test early |
| Large documents (100+ controls) | Performance | Use ImGui viewport culling; lazy render off-screen controls |
| APK signing in Termux | Can't install | Use apksigner from Android SDK build-tools; document setup |
| NDK version compatibility | Build failures | Pin NDK version in CMakeLists.txt; document tested versions |
<pattern-include src="..."> from external files| Dependency | Version | License | Size | Purpose |
|---|---|---|---|---|
| Dear ImGui | 1.91+ (docking) | MIT | ~400KB src | UI framework |
| pugixml | 1.14+ | MIT | ~300KB src | XML DOM read/write |
| libxml2 | 2.12+ | MIT | ~5MB .so | XSD validation |
| Android NDK | r26+ | Apache 2.0 | (system) | Toolchain |
| Roboto Font | — | Apache 2.0 | ~150KB | UI font |
Total estimated APK size: ~8–12 MB (Release, arm64-v8a only).
Gallery control editor — The Gallery box-template with visual element placement is complex. Should v0.1 support visual editing of <box-template> or defer to raw XML editing?
Decision: Deferred to v0.2.0. In v0.1.0 the Gallery renders as a compact block on canvas; box-template content is not visually editable.
Web Awesome controls — These have HTML-specific semantics (slots via <div slot="..."> pattern). How should the canvas represent HTML slot semantics?
Decision: Show as named regions similar to wireframe slots; HTML-specific attributes editable in property editor.
Undo granularity — Should undo be per-action (move, resize, property change) or group related actions? Decision: Per-action with optional grouping for drag sequences.
Portrait orientation Decision: Portrait orientation. Toolbox and properties become slide-in drawers from left/right edges; canvas gets maximum vertical space. Bottom panel remains collapsible.