|
|
4 luni în urmă | |
|---|---|---|
| .. | ||
| android | 4 luni în urmă | |
| assets | 4 luni în urmă | |
| build | 4 luni în urmă | |
| docs | 4 luni în urmă | |
| src | 4 luni în urmă | |
| test | 4 luni în urmă | |
| tests | 4 luni în urmă | |
| third_party | 4 luni în urmă | |
| CMakeLists.txt | 4 luni în urmă | |
| README.md | 4 luni în urmă | |
| libglvnd-dev_1.7.0-1_all.deb | 4 luni în urmă | |
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.
UIDefEdit provides a visual canvas for building UI layout definitions:
/sdcard/ with open, save-as, and auto-saveControls render as color-coded rounded rectangles (blue=input, green=display, gray=container, orange=list) with nesting, resize handles, and grid snap.
Portrait layout: toolbar at top, canvas center, document tree bottom. Toolbox slides in from left, properties from right.
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 |
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
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-rules → patterns (with wireframes inside) → views → window-includes → window.
├── 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)
| 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) |
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.
# 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.
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.
| 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) |
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.
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.
Building a NativeActivity APK entirely within Termux presented several challenges:
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.
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.
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.
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.
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.
No pm install from Termux. SELinux blocks direct package installation from Termux's shell. Use termux-open to launch the system installer.
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.
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.
width / 540dp)std::unique_ptr for parent-to-child control ownershipDocument owns all wireframes and windowsTo be determined.