Dalibor Votruba c9bdcad78f initial commit 4 mesiacov pred
..
android c9bdcad78f initial commit 4 mesiacov pred
assets c9bdcad78f initial commit 4 mesiacov pred
build c9bdcad78f initial commit 4 mesiacov pred
docs c9bdcad78f initial commit 4 mesiacov pred
src c9bdcad78f initial commit 4 mesiacov pred
test c9bdcad78f initial commit 4 mesiacov pred
tests c9bdcad78f initial commit 4 mesiacov pred
third_party c9bdcad78f initial commit 4 mesiacov pred
CMakeLists.txt c9bdcad78f initial commit 4 mesiacov pred
README.md c9bdcad78f initial commit 4 mesiacov pred
libglvnd-dev_1.7.0-1_all.deb c9bdcad78f initial commit 4 mesiacov pred

README.md

UIDefEdit

A native Android C++ application for visually designing UI layouts conforming to the ui-layout-def XML specification (v1.6). Built entirely on-device in Termux — no desktop host, no Android Studio.

What It Does

UIDefEdit provides a visual canvas for building UI layout definitions:

  • Draw wireframes — place controls from a categorized toolbox onto a pan/zoom canvas
  • Define windows — create windows from wireframe templates with slot overrides, or build standalone layouts
  • Edit properties — type-aware attribute editor (text, int, bool, enum, color) with validation
  • Manage documents — create new documents targeting different control sets (WinForms, Avalonia, Web Awesome)
  • Save/Load XML — full round-trip fidelity preserving unedited sections (views, validation-rules, state-management)
  • XSD validation — validate documents against 8 bundled XSD schemas with error/warning panel
  • File browser — custom ImGui file picker for /sdcard/ with open, save-as, and auto-save
  • Undo/redo — full command-pattern undo stack for all canvas operations + three-finger tap gesture

Controls render as color-coded rounded rectangles (blue=input, green=display, gray=container, orange=list) with nesting, resize handles, and grid snap.

Screenshots

Portrait layout: toolbar at top, canvas center, document tree bottom. Toolbox slides in from left, properties from right.

Implementation Status

All 8 milestones are complete.

Milestone Status
1. Project skeleton (NativeActivity + EGL + ImGui + canvas pan/zoom) Done
2. Control registry + toolbox (XML parsing, inheritance resolution) Done
3. Canvas drawing (place, move, resize, nesting, selection, undo/redo) Done
4. Property editor (type-specific widgets, validation highlights) Done
5. Wireframes & windows (slot marking, overrides, document tree) Done
6. XML I/O (save/load with round-trip, file browser, auto-save) Done
7. XSD validation (libxml2 via dlopen, error panel) Done
8. Polish & APK packaging (gestures, culling, responsive scaling) Done

Architecture

ANativeActivity_onCreate (main.cpp)
  │  Direct callbacks on main UI thread
  │  timerfd + ALooper for ~60fps rendering
  │  EGL/GLES3 context
  │  Multi-touch: pinch zoom, double-tap zoom, three-finger undo
  │
  ├── App (app.cpp — lifecycle, docking layout, frame orchestration)
  │
  ├── UI layer (Dear ImGui, docking branch)
  │   ├── toolbar          — top bar: New/Open/Save/Validate + undo/redo
  │   ├── toolbox          — left slide-in: control palette by category
  │   ├── properties       — right slide-in: attribute editor
  │   ├── canvas           — central: pan/zoom drawing surface with viewport culling
  │   ├── doc_tree         — bottom collapsible: wireframe/window hierarchy
  │   ├── validation_panel — bottom tab: XSD error/warning list
  │   └── file_dialog      — custom /sdcard/ browser (open/save-as)
  │
  ├── Model layer
  │   ├── Document       — root: version, platform, pattern_includes, wireframes, windows
  │   ├── ControlRegistry — parsed control definitions with resolved inheritance
  │   ├── ControlInstance — placed control: id, position, properties, parent/children tree
  │   ├── WireframeDef   — template layout with named SlotDefs
  │   └── WindowDef      — wireframe-based (slot overrides) or standalone (template)
  │
  ├── XML engine
  │   ├── xml_reader/writer — pugixml DOM (round-trips non-visual sections verbatim)
  │   └── xsd_validator    — libxml2 via dlopen (graceful degradation if unavailable)
  │
  └── Utilities
      ├── undo_redo      — command pattern stack (100 action cap, drag grouping)
      └── platform       — AAssetManager helpers, path resolution

Key Design Decisions

No android_native_app_glue. The standard NDK app_glue creates a background pthread for the app's main loop and communicates via pipes. This threading model fails when built with Termux's native Clang toolchain. UIDefEdit uses direct ANativeActivity callbacks on the main UI thread with a timerfd registered on the main ALooper for frame rendering. This is single-threaded and reliable.

Java wrapper required. Android 16 (API 36) requires android:hasCode="true" with a valid classes.dex. A minimal DebugActivity extends NativeActivity Java class serves as the entry point and provides soft keyboard support (showSoftInput/pollUnicodeChar).

RPATH stripping. Termux's Clang embeds RPATH=/data/data/com.termux/files/usr/lib into shared libraries. This path is inaccessible from an installed APK's process. The build script strips it with patchelf --remove-rpath.

GLES3 on the main thread. EGL context and all GL calls happen on the main UI thread (same thread as Activity lifecycle callbacks). This avoids the cross-thread context issues that plagued the app_glue approach.

libxml2 via dlopen. XSD validation uses runtime dlopen("libxml2.so") instead of link-time dependency. This avoids bundling Termux's libxml2 and its transitive dependencies (ICU, iconv, lzma) which conflict with Android's system libraries. Validation gracefully degrades if libxml2 is not available.

v1.6 XML namespace compliance. Documents use xmlns="http://quadarax.com/ui-layout-definition" and follow the XSD document order: validation-rulespatterns (with wireframes inside) → viewswindow-includeswindow.

Project Structure

├── src/                            # C++ sources (~4,500 lines)
│   ├── main.cpp                    # ANativeActivity_onCreate, EGL, ImGui loop, input
│   ├── app.h / app.cpp             # App state machine, docking layout
│   ├── canvas/
│   │   ├── canvas.h / canvas.cpp   # Pan/zoom surface, control rendering, viewport culling
│   │   ├── grid.h / grid.cpp       # Snap grid
│   │   └── selection.h / selection.cpp  # Hit testing, multi-select, resize handles
│   ├── model/
│   │   ├── control_registry.h/.cpp # Parse control XMLs, resolve inheritance
│   │   ├── control_instance.h/.cpp # Placed control with position + properties
│   │   ├── document.h              # Document root (PatternInclude, WireframeDef, WindowDef)
│   │   ├── wireframe_def.h         # Wireframe with slots
│   │   └── window_def.h            # Window (template or slot-overrides)
│   ├── xml/
│   │   ├── xml_reader.h / xml_reader.cpp  # Load XML → Document model
│   │   ├── xml_writer.h / xml_writer.cpp  # Document model → XML file
│   │   └── xsd_validator.h / xsd_validator.cpp  # XSD validation (dlopen libxml2)
│   ├── ui/
│   │   ├── toolbar.h / toolbar.cpp # Top menu bar + dialogs (New Doc, New Wireframe, New Window)
│   │   ├── toolbox.h / toolbox.cpp # Left slide-in: control palette
│   │   ├── properties.h / properties.cpp  # Right slide-in: attribute editor
│   │   ├── doc_tree.h / doc_tree.cpp      # Bottom panel: wireframe/window hierarchy
│   │   ├── file_dialog.h / file_dialog.cpp  # Custom file browser
│   │   └── validation_panel.h / validation_panel.cpp  # XSD error/warning list
│   └── util/
│       ├── undo_redo.h / undo_redo.cpp    # Undo stack
│       └── platform.h / platform.cpp      # AAssetManager helpers
├── android/
│   ├── AndroidManifest.xml         # hasCode=true, DebugActivity, portrait, GLES3
│   ├── build-apk.sh               # Full APK pipeline: compile, dex, aapt2, zipalign, sign
│   └── java/com/uidef/edit/
│       └── DebugActivity.java      # NativeActivity subclass with keyboard support
├── assets/
│   ├── controls/                   # 4 control set XMLs (abstract, winform, avalonia, web)
│   ├── schemas/                    # 8 XSD schema files (ui-layout-def v1.6)
│   └── fonts/                      # Roboto-Regular.ttf
├── third_party/
│   ├── imgui/                      # Dear ImGui (docking branch)
│   └── pugixml/                    # pugixml 1.14+ (2 files)
├── test/                           # Sample XML documents for testing
│   ├── test-basic.xml              # Abstract controls, 1 wireframe, 1 window
│   ├── test-avalonia.xml           # Avalonia controls, 2 wireframes, 2 windows
│   ├── test-webawesome.xml         # Web Awesome controls, form layout
│   ├── test-complex.xml            # WinForms, 3 wireframes, 3 windows, 50+ controls
│   └── test-invalid.xml            # Intentionally invalid for validation testing
├── docs/
│   ├── 0-proposal-of-impl.md      # Full architecture specification
│   └── 1-implementation-todo.md    # Milestone-ordered task checklist
├── CMakeLists.txt                  # C++17, shared library, links EGL/GLESv3/android/log
└── build/                          # Build output
    ├── libuidef-tool.so            # ~8 MB (debug, arm64-v8a)
    └── uidef-edit.apk             # ~4.6 MB (signed, debug)

Libraries

Library Version Purpose
Dear ImGui (docking branch) 1.91+ All UI rendering — immediate mode
pugixml 1.14+ XML DOM read/write for documents and control definitions
libxml2 2.12+ XSD schema validation (loaded via dlopen at runtime)

Building

Prerequisites

Termux on an ARM64 Android device:

pkg install clang cmake make git patchelf
pkg install ndk-sysroot android-ndk

Additional tools for APK packaging:

pkg install aapt2 apksigner zipalign d8 javac

An android.jar (API 28+) is needed at $HOME/android.jar for resource linking.

Build

# Configure (only needed once)
cmake -B build -DCMAKE_BUILD_TYPE=Debug

# Compile
cmake --build build -j$(nproc)

# Package APK
bash android/build-apk.sh

The CMake build uses Termux's native Clang (which already targets aarch64-unknown-linux-android24) and links against /system/lib64/ for EGL and GLES3. No separate NDK toolchain file is needed.

Install

cp build/uidef-edit.apk ~/storage/downloads/
termux-open ~/storage/downloads/uidef-edit.apk

Direct pm install from Termux is blocked by SELinux on most devices. Use termux-open to launch the system package installer.

Touch Gestures

Gesture Action
Single tap Select control / place control (in placement mode)
Long press (500ms) Context menu (Delete, Duplicate, Mark Slot, etc.)
Single-finger drag Pan canvas (empty area) / move control (selected)
Two-finger pinch Zoom in/out (0.25x – 4.0x)
Double tap Toggle between 100% zoom and zoom-to-fit
Three-finger tap Undo
Scroll wheel Zoom (external mouse)
Middle-click drag Pan (external mouse)

Control Sets

Four control definition files are bundled as APK assets:

File Controls Platform
abstract-controls.xml Label, TextBox, Button, Panel, SplitPanel, TabControl, ListBox, DataGrid, etc. basic (always loaded)
winform-controls.xml WinForms-specific controls inheriting from abstract winforms
avalonia-controls.xml Avalonia-specific controls inheriting from abstract avalonia
webawesome-controls.xml wa-button, wa-input, wa-card, wa-dialog, etc. web

The control registry resolves inherits chains (topological sort) so each control has a complete standalone attribute list.

XML Round-Trip

UIDefEdit preserves sections it doesn't edit (views, validation-rules, state-management, etc.) as raw pugixml DOM nodes, merging them back on save in the correct XSD document order. The pattern-include elements carry src and platform attributes matching the PatternIncludeType in the v1.6 schema.

Implementation Notes

Termux-Specific Challenges

Building a NativeActivity APK entirely within Termux presented several challenges:

  1. RPATH poisoning. Termux's Clang embeds its library path as RPATH/RUNPATH in all shared libraries. Android's bionic linker rejects these paths when running outside Termux. Solution: patchelf --remove-rpath in the APK build script.

  2. libc++_shared.so bundling. The NDK's libc++ is not available on the device's system libraries. It must be bundled in the APK at lib/arm64-v8a/libc++_shared.so.

  3. android_native_app_glue incompatibility. The standard NDK helper creates a background pthread for the app loop. This threading model causes ANR (Application Not Responding) when the .so is built with Termux's native Clang. The fix is bypassing app_glue entirely and using direct ANativeActivity callbacks with timerfd + ALooper for frame rendering.

  4. hasCode="false" crashes on Android 16. Modern Android requires hasCode="true" with a valid classes.dex in the APK, even for NativeActivity apps. A minimal Java wrapper class is needed.

  5. No logcat access. Termux cannot read logcat output from other apps (different UID). Debugging is done via file-based logging to the app's internal data path.

  6. No pm install from Termux. SELinux blocks direct package installation from Termux's shell. Use termux-open to launch the system installer.

  7. libxml2 transitive dependencies. Bundling Termux's libxml2.so brings in libicuuc, libicudata, libiconv, and liblzma — all with versioned sonames that conflict with Android's system libraries. Solution: load libxml2 via dlopen at runtime instead of linking at build time.

Rendering Architecture

The rendering loop is driven by a timerfd at ~60fps, registered with the main thread's ALooper:

Main thread ALooper dispatch cycle:
  ├── timerfd fires → timer_callback → MainLoopStep()
  │   ├── ImGui_ImplOpenGL3_NewFrame()
  │   ├── ImGui_ImplAndroid_NewFrame()
  │   ├── ImGui::NewFrame()
  │   ├── App::update() (all UI rendering)
  │   ├── ImGui::Render()
  │   ├── glClear + ImGui_ImplOpenGL3_RenderDrawData()
  │   └── eglSwapBuffers()
  └── input queue fires → input_looper_callback → ProcessInput()
      └── AInputQueue_getEvent → handleInputEvent → ImGui_ImplAndroid_HandleInputEvent

Input events are attached to the same ALooper with a separate callback, providing low-latency touch handling.

Performance

  • Viewport culling: Controls fully outside the visible canvas area skip all draw calls
  • Responsive scaling: UI scale factor derived from screen width (width / 540dp)
  • Target: 60 FPS on mid-range devices with 100+ controls

Memory Model

  • std::unique_ptr for parent-to-child control ownership
  • Raw pointers for child-to-parent back-references
  • Document owns all wireframes and windows
  • Undo actions store deltas, not snapshots (capped at 100 actions)

License

To be determined.