Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ See the official [plugin documentation](https://www.appdevforall.org/codeonthego
| [`rainbow-on-the-go/`](rainbow-on-the-go/) | Colors matching parentheses, brackets, and braces by nesting depth, with light/dark palettes. |
| [`compose-preview/`](compose-preview/) | Renders Jetpack Compose `@Preview` functions on-device — no full app build or run. |
| [`ai-literacy-course/`](ai-literacy-course/) | Bundles Learn AI Anywhere's offline "Introduction to AI" course (26 videos + interactive activities) and plays it full-screen, fully offline. |
| [`layout-editor/`](layout-editor/) | Visual drag-and-drop editor for Android XML layouts. |

## Building a plugin

Expand Down
108 changes: 108 additions & 0 deletions layout-editor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Layout Editor Plugin for Code on the Go

A visual, drag-and-drop editor for Android XML layouts, running on-device. Open a
layout XML file, tap the Layout Editor action in the editor toolbar, and build the
view hierarchy on a canvas, set attributes through typed dialogs, and preview the
result at different device sizes.

The editor was extracted from the host IDE into this standalone plugin so the host
APK stays smaller and its build stays shorter. The plugin carries its own resources
and libraries and links only against the stable `plugin-api` contract.

## Features

- **Design and Blueprint views** - render the layout as it looks at runtime, or as a wireframe of each view's bounds.
- **Palette** - drag widgets onto the canvas, grouped as Common, Text, Buttons, Widgets, Layouts, and Containers.
- **Component tree** - the view hierarchy as a tree; tap to select, long-press for information.
- **Attribute editing** - add and remove attributes on the selected view and edit values through typed dialogs (color, dimension, size, number, string, boolean, enum, flag, id, reference).
- **Resource Manager** - browse and edit the project's colors, strings, drawables, and fonts.
- **Device size preview** - check the layout at different device dimensions and orientations.
- **XML view** - read the generated XML for the current layout.
- **Long-press documentation** - a tooltip on every control, palette item, and attribute.

## Plugin interfaces implemented

- `IPlugin` - core plugin lifecycle.
- `UIExtension` - the toolbar action and its enable rule.
- `DocumentationExtension` - tooltips and documentation.
- `BuildStatusListener` - disables the action while a project has a failed sync.

## Architecture

```
layout-editor/
├── build.gradle.kts, settings.gradle.kts, proguard-rules.pro
├── layout-editor-documentation.html # full reference for this plugin
├── src/main/
│ ├── AndroidManifest.xml # plugin id, main class, icons, permissions
│ ├── assets/ # icons, tooltips.json, widgetclasses.json, palette/, attributes/, editor/
│ ├── kotlin|java/.../layouteditor/
│ │ ├── LayoutEditorPlugin.kt # entry point: toolbar action, enable rule, tooltips, build-status
│ │ ├── LayoutEditorFragment.kt # full-screen editor: canvas, palette, component tree, drawer
│ │ ├── ResourceManagerFragment.kt # colors, strings, drawables, fonts
│ │ ├── editor/DesignEditor.kt # drag-and-drop canvas over the live view hierarchy
│ │ ├── editor/dialogs/ # one typed dialog per attribute format
│ │ ├── editor/callers|initializer # apply values to real View instances; track attributes
│ │ ├── LayoutEditorDocs.kt # long-press tooltip helper
│ │ └── PluginDialogContext.kt # dialog context with a valid window token
│ └── res/ # layout/, drawable/, values/, menu/
└── README.md
```

The editor opens full-screen through `IdeUIService.openPluginScreen()`, which carries
no `Bundle`, so screens hand data through process-level state holders
(`LayoutEditorState`, `EditorSubScreenState`). Because a plugin has its own resource
namespace, plugin XML is inflated with the plugin's own inflater
(`PluginFragmentHelper.getPluginInflater`) so `?attr/` and `app:` attributes resolve
against the plugin theme, and dialogs are shown through `PluginDialogContext`, which
pairs the host activity (for the window token) with the plugin context (for resources
and theme).

## Host services used

- `IdeEditorService` - read the current file for the enable rule and the target layout.
- `IdeProjectService` - resolve the project and its resource directories.
- `IdeBuildService` - subscribe to build status so a failed sync disables the action.
- `IdeUIService` - `openPluginScreen()` to present the editor and its sub-screens.
- `IdeTooltipService` - show the long-press documentation tooltips.

## Building

```bash
cd layout-editor
./gradlew clean assemblePluginDebug # or assemblePlugin for release
```

Output: `build/plugin/layout-editor-debug.cgp`. Run `clean` first, since the plugin
builder copies the built APK into the `.cgp` and then deletes the source APK.

## Installation

1. Open Preferences, then Plugin Manager, then the add button.
2. Select the `layout-editor-debug.cgp` file.
3. The IDE discovers `LayoutEditorPlugin` from the manifest metadata and activates it.

## Usage

1. Open a layout XML file (under a `layout` resource directory).
2. Tap the Layout Editor action in the editor toolbar.
3. Drag widgets from the palette, select views on the canvas or in the component tree, and edit their attributes.
4. Open the Resource Manager to edit colors, strings, drawables, and fonts.
5. Save to write the layout back. Long-press any control for its tooltip.

## Requirements

- Android API 26 or newer.
- Minimum IDE version 1.0.0.
- Permissions: `filesystem.read`, `filesystem.write`, `project.structure`.
- No network access.

## Documentation

`layout-editor-documentation.html` is the full reference for this plugin. In the IDE,
long-press any control, palette item, or attribute for its tooltip.

## License

Layout Editor is an open-source example plugin for Code on the Go, licensed per the
surrounding `plugin-examples` repository. See `LICENSE` at the repo root.
101 changes: 101 additions & 0 deletions layout-editor/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.itsaky.androidide.plugins.build")
}

pluginBuilder {
pluginName = "layout-editor"
}

android {
namespace = "org.appdevforall.codeonthego.layouteditor"
compileSdk = 34

defaultConfig {
applicationId = "org.appdevforall.codeonthego.layouteditor"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0.0"
}

buildTypes {
release {
isMinifyEnabled = false
isShrinkResources = false
signingConfig = signingConfigs.getByName("debug")
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

buildFeatures {
viewBinding = true
buildConfig = true
}

packaging {
resources {
excludes += setOf(
"META-INF/DEPENDENCIES",
"META-INF/LICENSE",
"META-INF/LICENSE.txt",
"META-INF/NOTICE",
"META-INF/NOTICE.txt",
"META-INF/INDEX.LIST",
"META-INF/*.kotlin_module"
)
}
}
}

kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
}

dependencies {
// Host-provided at runtime (parent classloader) — link only, never bundle.
compileOnly(files("../libs/plugin-api.jar"))
compileOnly(files("../libs/eventbus-events.jar"))
compileOnly(files("../libs/common.jar"))
compileOnly(files("../libs/idetooltips.jar"))
compileOnly("org.greenrobot:eventbus:3.3.1")
compileOnly("org.slf4j:slf4j-api:2.0.9")

// Bundled into the .cgp (vectormaster is sourced directly; the rest are libraries).
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.activity:activity-ktx:1.8.2")
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.preference:preference-ktx:1.2.1")
implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation("androidx.viewpager2:viewpager2:1.1.0-beta02")
implementation("androidx.palette:palette-ktx:1.0.0")
implementation("androidx.fragment:fragment-ktx:1.8.8")
implementation("com.google.android.material:material:1.12.0")
implementation("com.google.code.gson:gson:2.10.1")
implementation("com.github.bumptech.glide:glide:4.16.0")
implementation("com.jsibbold:zoomage:1.3.1")
implementation("com.blankj:utilcodex:1.31.1")
implementation("com.github.skydoves:colorpickerview:2.3.0")
implementation(platform("io.github.Rosemoe.sora-editor:bom:0.23.6"))
implementation("io.github.Rosemoe.sora-editor:editor")
implementation("io.github.Rosemoe.sora-editor:language-textmate")
implementation("org.apache.commons:commons-text:1.11.0")
implementation("commons-io:commons-io:2.15.1")
implementation("org.jetbrains.kotlin:kotlin-stdlib:2.3.0")
}

// application-as-library packaging trips AAR metadata checks; disabling is intentional
// (documented convention in plugin-examples/CLAUDE.md).
tasks.matching {
it.name.contains("checkDebugAarMetadata") || it.name.contains("checkReleaseAarMetadata")
}.configureEach { enabled = false }
6 changes: 6 additions & 0 deletions layout-editor/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
org.gradle.parallel=true
org.gradle.caching=true
android.useAndroidX=true
android.nonTransitiveRClass=true
kotlin.code.style=official
Binary file added layout-editor/gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
7 changes: 7 additions & 0 deletions layout-editor/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading