feat(uidesigner,xml-inflater): automate process of adding widgets in UI Designer with annotations

The IViewAdapter implementations can use @IncludeInDesigner(group) annotation to indicate that the adapter supports views that can be added in the UI Designer. The adapters which are annotated with @IncludeInDesigner must override the 'List<UiWidget> createWidgets()' method and provide a list of UiWidgets that will be displayed in the UI Designer.
This commit is contained in:
Akash Yadav
2022-12-20 11:19:20 +05:30
parent c2ffb4682c
commit aeb314624f
45 changed files with 865 additions and 455 deletions
@@ -1,287 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.annotations.inflater
import com.google.auto.service.AutoService
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.CodeBlock
import com.squareup.javapoet.FieldSpec
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
import java.io.File
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Processor
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind.CLASS
import javax.lang.model.element.ElementKind.PACKAGE
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.Modifier.FINAL
import javax.lang.model.element.Modifier.PRIVATE
import javax.lang.model.element.Modifier.PUBLIC
import javax.lang.model.element.Modifier.STATIC
import javax.lang.model.element.TypeElement
import javax.lang.model.type.MirroredTypeException
import javax.tools.Diagnostic.Kind.ERROR
@Suppress("unused")
@AutoService(Processor::class)
class ViewAdapterAnnotationProcessor : AbstractProcessor() {
private val adapterElement by lazy {
processingEnv.typeUtils.erasure(
processingEnv.elementUtils.getTypeElement(ADAPTER_BASE_CLASS).asType()
)
}
private val viewElement by lazy {
processingEnv.typeUtils.erasure(
processingEnv.elementUtils.getTypeElement("android.view.View").asType()
)
}
private val indexClassBuilder =
TypeSpec.classBuilder(INDEX_CLASS_NAME).addModifiers(PUBLIC, FINAL)
private val indexAddStatements = CodeBlock.builder()
private val listOfString: TypeName =
ParameterizedTypeName.get(java.util.ArrayList::class.java, String::class.java)
init {
addIndexClassMembers()
}
companion object {
const val ADAPTER_BASE_CLASS_PCK = "com.itsaky.androidide.inflater"
const val ADAPTER_BASE_CLASS_NAME = "IViewAdapter"
const val ADAPTER_BASE_CLASS = "$ADAPTER_BASE_CLASS_PCK.$ADAPTER_BASE_CLASS_NAME"
const val INDEX_PACKAGE_NAME = "com.itsaky.androidide.inflater.internal"
const val INDEX_CLASS_NAME = "ViewAdapterIndex"
const val INDEX_MAP_FIELD = "adapterMap"
const val METHOD_SET_SUPERCLASS_HIERARCHY = "setSuperclassHierarchy"
const val METHOD_SET_MODULE = "setModuleNamespace"
}
override fun getSupportedAnnotationTypes(): MutableSet<String> {
return mutableSetOf(ViewAdapter::class.java.name)
}
override fun getSupportedSourceVersion(): SourceVersion {
return SourceVersion.latestSupported()
}
override fun process(
annotations: MutableSet<out TypeElement>?,
roundEnv: RoundEnvironment
): Boolean {
if (roundEnv.processingOver()) {
return true
}
roundEnv.getElementsAnnotatedWith(ViewAdapter::class.java).forEach {
if (it.kind != CLASS) {
processingEnv.messager.printMessage(
ERROR,
"@ViewAdapter can only be used with classes. Used on $it"
)
return true
}
process(it)
}
indexClassBuilder.addStaticBlock(indexAddStatements.build())
val file = JavaFile.builder(INDEX_PACKAGE_NAME, indexClassBuilder.build())
val generatedDir =
File(
processingEnv.options[KAPT_KOTLIN_GENERATED]
?: throw IllegalStateException("Cannot find kapt output directory path")
)
file.build().writeTo(generatedDir)
return false
}
private fun process(element: Element) {
if (element.enclosingElement?.kind != PACKAGE || element !is TypeElement) {
processingEnv.messager.printMessage(
ERROR,
"@ViewAdapter can only be used on top-level classes. Used on $element"
)
return
}
val block = CodeBlock.builder()
val constructors =
processingEnv.elementUtils.getAllMembers(element).filter {
"<init>".contentEquals(it.simpleName)
}
if (constructors.size != 1) {
processingEnv.messager.printMessage(
ERROR,
"A view adapter must have only default constructor"
)
return
}
if ((constructors.first() as ExecutableElement).parameters.isNotEmpty()) {
processingEnv.messager.printMessage(
ERROR,
"View adapter constructor should not have any parameters"
)
return
}
if (
!processingEnv.typeUtils.isAssignable(
processingEnv.typeUtils.erasure(element.asType()),
adapterElement
)
) {
processingEnv.messager.printMessage(ERROR, "Class must be a subtype of $ADAPTER_BASE_CLASS $element $adapterElement")
return
}
if (element.typeParameters.size != 1) {
processingEnv.messager.printMessage(
ERROR,
"A view adapter must have exactly one type parameter"
)
}
val typeParam = element.typeParameters[0]
if (!processingEnv.typeUtils.isAssignable(typeParam.asType(), viewElement)) {
processingEnv.messager.printMessage(
ERROR,
"The type parameter of view adapter must be assignable from android.view.View"
)
}
val annotation = element.getAnnotation(ViewAdapter::class.java) ?: throw IllegalStateException()
val viewName = getViewName(annotation)
val superclasses = getViewSuperclasses(annotation)
block.addStatement("final var superclasses = new \$T()", listOfString)
for (superclass in superclasses) {
block.addStatement("superclasses.add(\$S)", superclass)
}
val adapterType = ClassName.get(element)
val paramType = getViewTypeName(annotation)
block.add("\n")
block.addStatement("final var adapter = new \$T()", ParameterizedTypeName.get(adapterType, paramType))
block.addStatement("adapter.\$L(superclasses)", METHOD_SET_SUPERCLASS_HIERARCHY)
block.addStatement("adapter.\$L(\$S)", METHOD_SET_MODULE, annotation.moduleNamespace)
block.addStatement("\$L.put(\$S, adapter)", INDEX_MAP_FIELD, viewName)
indexAddStatements.add("{\n")
indexAddStatements.indent()
indexAddStatements.add(block.build())
indexAddStatements.unindent()
indexAddStatements.add("}\n")
}
private fun addIndexClassMembers() {
val adpaterType = ClassName.get(ADAPTER_BASE_CLASS_PCK, ADAPTER_BASE_CLASS_NAME)
val type =
ParameterizedTypeName.get(
ClassName.get(Map::class.java),
ClassName.get(String::class.java),
adpaterType
)
indexClassBuilder.addField(
FieldSpec.builder(type, INDEX_MAP_FIELD, PRIVATE, STATIC, FINAL)
.initializer("new \$T<>()", HashMap::class.java)
.build()
)
addUninstantiableConstructor(indexClassBuilder)
indexClassBuilder.addMethod(
MethodSpec.methodBuilder("getAdapter")
.addAnnotation(ClassName.get("androidx.annotation", "Nullable"))
.addModifiers(PUBLIC, STATIC)
.addParameter(String::class.java, "view", FINAL)
.returns(adpaterType)
.addStatement("return \$L.get(\$L)", INDEX_MAP_FIELD, "view")
.build()
)
}
private fun addUninstantiableConstructor(builder: TypeSpec.Builder) {
builder.addMethod(
MethodSpec.constructorBuilder()
.addModifiers(PRIVATE)
.addStatement(
"throw new \$T(\$S)",
UnsupportedOperationException::class.java,
"This class cannot be instantiated."
)
.build()
)
}
private fun getViewTypeName(annotation: ViewAdapter) : TypeName {
return try {
ClassName.get(annotation.forView.java)
?: throw IllegalStateException("Cannot find type of $annotation")
} catch (err: MirroredTypeException) {
(processingEnv.typeUtils.asElement(err.typeMirror) as TypeElement).let { ClassName.get(it) }
}
}
private fun getViewName(annotation: ViewAdapter): String {
return try {
annotation.forView.qualifiedName
?: throw IllegalStateException("Cannot find type of $annotation")
} catch (err: MirroredTypeException) {
(processingEnv.typeUtils.asElement(err.typeMirror) as TypeElement).qualifiedName.toString()
}
}
private fun getViewSuperclasses(annotation: ViewAdapter): List<String> {
val result = mutableListOf<String>()
try {
val view = annotation.forView
var superclass = view.java.superclass
while (superclass != null) {
result.add(superclass.name)
superclass = superclass.superclass
}
} catch (err: MirroredTypeException) {
val typeElement = processingEnv.typeUtils.asElement(err.typeMirror) as TypeElement
result.add(typeElement.qualifiedName.toString())
var superclass = typeElement.superclass
while (superclass != null) {
val superType = processingEnv.typeUtils.asElement(superclass) as? TypeElement?
superType ?: break
result.add(superType.qualifiedName.toString())
superclass = superType.superclass
}
}
return result
}
}
@@ -0,0 +1,139 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.annotations.processor
import com.google.auto.service.AutoService
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Processor
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind.CLASS
import javax.lang.model.element.ElementKind.PACKAGE
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.TypeElement
import javax.tools.Diagnostic.Kind.ERROR
@Suppress("unused")
@AutoService(Processor::class)
class ViewAdapterAnnotationProcessor : AbstractProcessor() {
private val adapterElement by lazy { processingEnv.erasure(processingEnv.adapterElement()) }
private val viewElement by lazy { processingEnv.erasure(processingEnv.viewElement()) }
private val generator = ViewAdapterIndexGenerator()
override fun getSupportedAnnotationTypes(): MutableSet<String> {
return mutableSetOf(ViewAdapter::class.java.name, IncludeInDesigner::class.java.name)
}
override fun getSupportedSourceVersion(): SourceVersion {
return SourceVersion.latestSupported()
}
override fun process(
annotations: MutableSet<out TypeElement>?,
roundEnv: RoundEnvironment
): Boolean {
if (roundEnv.processingOver()) {
return true
}
generator.init(processingEnv)
roundEnv.getElementsAnnotatedWith(ViewAdapter::class.java).forEach {
if (it.kind != CLASS) {
processingEnv.messager.printMessage(
ERROR,
"@ViewAdapter can only be used with classes. Used on $it"
)
return true
}
process(it)
}
generator.generate(
processingEnv.options[KAPT_KOTLIN_GENERATED]
?: throw IllegalStateException("Cannot find kapt output directory path")
)
return false
}
private fun process(element: Element) {
if (element.enclosingElement?.kind != PACKAGE || element !is TypeElement) {
processingEnv.messager.printMessage(
ERROR,
"@ViewAdapter can only be used on top-level classes. Used on $element"
)
return
}
val constructors =
processingEnv.elementUtils.getAllMembers(element).filter {
"<init>".contentEquals(it.simpleName)
}
if (constructors.size != 1) {
processingEnv.messager.printMessage(
ERROR,
"A view adapter must have only default constructor"
)
return
}
if ((constructors.first() as ExecutableElement).parameters.isNotEmpty()) {
processingEnv.messager.printMessage(
ERROR,
"View adapter constructor should not have any parameters"
)
return
}
if (
!processingEnv.typeUtils.isAssignable(
processingEnv.typeUtils.erasure(element.asType()),
adapterElement
)
) {
processingEnv.messager.printMessage(
ERROR,
"Class must be a subtype of $ADAPTER_BASE_CLASS $element $adapterElement"
)
return
}
if (element.typeParameters.size != 1) {
processingEnv.messager.printMessage(
ERROR,
"A view adapter must have exactly one type parameter"
)
}
val typeParam = element.typeParameters[0]
if (!processingEnv.typeUtils.isAssignable(typeParam.asType(), viewElement)) {
processingEnv.messager.printMessage(
ERROR,
"The type parameter of view adapter must be assignable from android.view.View"
)
}
generator.addViewAdapter(processingEnv, element)
}
}
@@ -0,0 +1,274 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.annotations.processor
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.CodeBlock
import com.squareup.javapoet.FieldSpec
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.ParameterSpec
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
import com.squareup.javapoet.WildcardTypeName
import java.io.File
import java.util.Collections
import javax.annotation.processing.ProcessingEnvironment
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.Modifier.FINAL
import javax.lang.model.element.Modifier.PRIVATE
import javax.lang.model.element.Modifier.PUBLIC
import javax.lang.model.element.Modifier.STATIC
import javax.lang.model.element.TypeElement
import javax.lang.model.type.MirroredTypeException
import javax.tools.Diagnostic.Kind.ERROR
/**
* Generates the ViewAdapterIndex class.
*
* @author Akash Yadav
*/
class ViewAdapterIndexGenerator {
private val indexClassBuilder =
TypeSpec.classBuilder(INDEX_CLASS_NAME).addModifiers(PUBLIC, FINAL)
private val indexAddStatements = CodeBlock.builder()
fun init(env: ProcessingEnvironment) {
val adapterType =
ParameterizedTypeName.get(
ClassName.get(ADAPTER_BASE_CLASS_PCK, ADAPTER_BASE_CLASS_NAME),
WildcardTypeName.subtypeOf(ClassName.get(env.viewElement()))
)
val mapOfStringToViewAdapter =
ParameterizedTypeName.get(
ClassName.get(Map::class.java),
ClassName.get(String::class.java),
adapterType
)
indexClassBuilder.addField(
FieldSpec.builder(mapOfStringToViewAdapter, INDEX_MAP_FIELD, PRIVATE, STATIC, FINAL).build()
)
indexAddStatements.addStatement(
"final var adapters = new \$T()",
ParameterizedTypeName.get(
ClassName.get(HashMap::class.java),
ClassName.get(String::class.java),
adapterType
)
)
addUninstantiableConstructor(indexClassBuilder)
indexClassBuilder.addMethod(
MethodSpec.methodBuilder("getAdapter")
.addAnnotation(ClassName.get("androidx.annotation", "Nullable"))
.addModifiers(PUBLIC, STATIC)
.addParameter(String::class.java, "view", FINAL)
.returns(adapterType)
.addStatement("return \$L.get(\$L)", INDEX_MAP_FIELD, "view")
.build()
)
val listOfViewAdapters =
ParameterizedTypeName.get(ClassName.get(java.util.List::class.java), adapterType)
val mapOfGroupToListOfViewAdapter =
ParameterizedTypeName.get(
ClassName.get(Map::class.java),
ClassName.get(IncludeInDesigner.Group::class.java),
listOfViewAdapters
)
indexClassBuilder.addField(
FieldSpec.builder(
mapOfGroupToListOfViewAdapter,
INDEX_PROVIDER_MAP_FIELD,
PRIVATE,
STATIC,
FINAL
)
.build()
)
indexAddStatements.addStatement(
"final var providers = new \$T()",
ParameterizedTypeName.get(
ClassName.get(HashMap::class.java),
ClassName.get(IncludeInDesigner.Group::class.java),
listOfViewAdapters
)
)
val nonNull = ClassName.get("androidx.annotation", "NonNull")
val groupParam =
ParameterSpec.builder(IncludeInDesigner.Group::class.java, "group", FINAL).build()
indexClassBuilder.addMethod(
MethodSpec.methodBuilder("getWidgetProviders")
.addAnnotation(nonNull)
.addModifiers(PUBLIC, STATIC)
.returns(listOfViewAdapters)
.addParameter(groupParam)
.addStatement("return \$L.get(group)", INDEX_PROVIDER_MAP_FIELD)
.build()
)
}
private val listOfString: TypeName =
ParameterizedTypeName.get(java.util.ArrayList::class.java, String::class.java)
fun addViewAdapter(processingEnv: ProcessingEnvironment, element: TypeElement) {
val block = CodeBlock.builder()
val viewAdapter =
element.getAnnotation(ViewAdapter::class.java) ?: throw IllegalStateException()
val includeInDesigner = element.getAnnotation(IncludeInDesigner::class.java)
val viewName = getViewName(processingEnv, viewAdapter)
val superclasses = getViewSuperclasses(processingEnv, viewAdapter)
block.addStatement("final var superclasses = new \$T()", listOfString)
for (superclass in superclasses) {
block.addStatement("superclasses.add(\$S)", superclass)
}
val adapterType = ClassName.get(element)
val paramType = getViewTypeName(processingEnv, viewAdapter)
block.add("\n")
block.addStatement(
"final var adapter = new \$T()",
ParameterizedTypeName.get(adapterType, paramType)
)
block.addStatement("adapter.\$L(superclasses)", METHOD_SET_SUPERCLASS_HIERARCHY)
block.addStatement("adapter.\$L(\$S)", METHOD_SET_MODULE, viewAdapter.moduleNamespace)
block.addStatement("adapters.put(\$S, adapter)", viewName)
if (includeInDesigner != null) {
assertOverridesCreateWidget(processingEnv, element)
block.addStatement(
"providers.computeIfAbsent(\$T.\$L, g -> new ArrayList<>()).add(adapter)",
ClassName.get(IncludeInDesigner.Group::class.java),
includeInDesigner.group
)
}
indexAddStatements.add("{\n")
indexAddStatements.indent()
indexAddStatements.add(block.build())
indexAddStatements.unindent()
indexAddStatements.add("}\n")
}
private fun assertOverridesCreateWidget(env: ProcessingEnvironment, element: TypeElement) {
element.enclosedElements.filterIsInstance<ExecutableElement>().find {
ADAPTER_FUNC_CREATE_WIDGET == it.simpleName.toString()
}
?: env.messager.printMessage(
ERROR,
"${element.qualifiedName} annotated with ${IncludeInDesigner::class.java.simpleName} must override $ADAPTER_FUNC_CREATE_WIDGET"
)
}
fun generate(outDir: String) {
val javaCollections = ClassName.get(Collections::class.java)
indexAddStatements.addStatement(
"\$L = \$T.unmodifiableMap(adapters)",
INDEX_MAP_FIELD,
javaCollections
)
indexAddStatements.addStatement(
"\$L = \$T.unmodifiableMap(providers)",
INDEX_PROVIDER_MAP_FIELD,
javaCollections
)
indexClassBuilder.addStaticBlock(indexAddStatements.build())
val file = JavaFile.builder(INDEX_PACKAGE_NAME, indexClassBuilder.build())
val generatedDir = File(outDir)
file.build().writeTo(generatedDir)
}
private fun addUninstantiableConstructor(builder: TypeSpec.Builder) {
builder.addMethod(
MethodSpec.constructorBuilder()
.addModifiers(PRIVATE)
.addStatement(
"throw new \$T(\$S)",
UnsupportedOperationException::class.java,
"This class cannot be instantiated."
)
.build()
)
}
private fun getViewTypeName(
processingEnv: ProcessingEnvironment,
annotation: ViewAdapter
): TypeName {
return try {
ClassName.get(annotation.forView.java)
?: throw IllegalStateException("Cannot find type of $annotation")
} catch (err: MirroredTypeException) {
(processingEnv.typeUtils.asElement(err.typeMirror) as TypeElement).let { ClassName.get(it) }
}
}
private fun getViewName(processingEnv: ProcessingEnvironment, annotation: ViewAdapter): String {
return try {
annotation.forView.qualifiedName
?: throw IllegalStateException("Cannot find type of $annotation")
} catch (err: MirroredTypeException) {
(processingEnv.typeUtils.asElement(err.typeMirror) as TypeElement).qualifiedName.toString()
}
}
private fun getViewSuperclasses(
processingEnv: ProcessingEnvironment,
annotation: ViewAdapter
): List<String> {
val result = mutableListOf<String>()
try {
val view = annotation.forView
var superclass = view.java.superclass
while (superclass != null) {
result.add(superclass.name)
superclass = superclass.superclass
}
} catch (err: MirroredTypeException) {
val typeElement = processingEnv.typeUtils.asElement(err.typeMirror) as TypeElement
result.add(typeElement.qualifiedName.toString())
var superclass = typeElement.superclass
while (superclass != null) {
val superType = processingEnv.typeUtils.asElement(superclass) as? TypeElement?
superType ?: break
result.add(superType.qualifiedName.toString())
superclass = superType.superclass
}
}
return result
}
}
@@ -0,0 +1,55 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.annotations.processor
import javax.annotation.processing.ProcessingEnvironment
import javax.lang.model.element.TypeElement
import javax.lang.model.type.TypeMirror
const val KAPT_KOTLIN_GENERATED = "kapt.kotlin.generated"
const val VIEW_CLASS = "android.view.View"
const val ADAPTER_BASE_CLASS_PCK = "com.itsaky.androidide.inflater"
const val ADAPTER_BASE_CLASS_NAME = "IViewAdapter"
const val ADAPTER_BASE_CLASS = "$ADAPTER_BASE_CLASS_PCK.$ADAPTER_BASE_CLASS_NAME"
const val ADAPTER_FUNC_CREATE_WIDGET = "createUiWidgets"
const val INDEX_PACKAGE_NAME = "com.itsaky.androidide.inflater.internal"
const val INDEX_CLASS_NAME = "ViewAdapterIndex"
const val INDEX_MAP_FIELD = "adapterMap"
const val INDEX_PROVIDER_MAP_FIELD = "widgetProviders"
const val METHOD_SET_SUPERCLASS_HIERARCHY = "setSuperclassHierarchy"
const val METHOD_SET_MODULE = "setModuleNamespace"
private var adapterElement: TypeElement? = null
private var viewElement: TypeElement? = null
fun ProcessingEnvironment.adapterElement(): TypeElement {
return adapterElement
?: elementUtils.getTypeElement(ADAPTER_BASE_CLASS)!!.also { adapterElement = it }
}
fun ProcessingEnvironment.viewElement(): TypeElement {
return viewElement ?: elementUtils.getTypeElement(VIEW_CLASS)!!.also { viewElement = it }
}
fun ProcessingEnvironment.erasure(typeElement: TypeElement): TypeMirror? {
return typeUtils.erasure(typeElement.asType())
}
@@ -15,10 +15,23 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.annotations.inflater
package com.itsaky.androidide.annotations.uidesigner
/**
* Indicates that the view (whose view adapter is annotated with [ViewAdapter]
* [com.itsaky.androidide.annotations.inflater.ViewAdapter] and [IncludeInDesigner]) should be
* included in the UI Designer and made accessible to the user.
*
* @author Akash Yadav
*/
annotation class IncludeInDesigner(val group: Group) {
const val KAPT_KOTLIN_GENERATED = "kapt.kotlin.generated"
/** The widget groups that are available in the UI Designer. */
enum class Group {
/** Includes the Android platform widgets. */
WIDGETS,
/** Includes the Android platform layouts. */
LAYOUTS
}
}
@@ -1,4 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ This file is part of AndroidIDE.
~
~ AndroidIDE is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ AndroidIDE is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
Binary file not shown.
+1
View File
@@ -52,6 +52,7 @@ dependencies {
implementation google.material
implementation project(path: ':actions')
implementation project(path: ':annotations')
implementation project(path: ':common')
implementation project(path: ':editor')
implementation project(path: ':logger')
@@ -24,7 +24,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.itsaky.androidide.uidesigner.adapters.WidgetsItemAdapter.VH
import com.itsaky.androidide.uidesigner.databinding.LayoutUiWidgetsItemBinding
import com.itsaky.androidide.uidesigner.fragments.DesignerWorkspaceFragment
import com.itsaky.androidide.uidesigner.models.UiWidget
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.uidesigner.drag.WidgetDragShadowBuilder
import com.itsaky.androidide.uidesigner.viewmodel.WorkspaceViewModel
@@ -24,7 +24,7 @@ import com.itsaky.androidide.inflater.viewGroup
import com.itsaky.androidide.uidesigner.fragments.DesignerWorkspaceFragment.Companion.DRAGGING_WIDGET_MIME
import com.itsaky.androidide.uidesigner.models.UiView
import com.itsaky.androidide.uidesigner.models.UiViewGroup
import com.itsaky.androidide.uidesigner.models.UiWidget
import com.itsaky.androidide.inflater.models.UiWidget
import kotlin.math.absoluteValue
import kotlin.math.max
import kotlin.math.min
@@ -18,5 +18,6 @@
package com.itsaky.androidide.uidesigner.models
import androidx.annotation.StringRes
import com.itsaky.androidide.inflater.models.UiWidget
data class UiWidgetCategory(@StringRes val label: Int, val widgets: MutableList<UiWidget> = mutableListOf(), var isExpanded: Boolean = false)
data class UiWidgetCategory(@StringRes val label: Int, var widgets: List<UiWidget> = emptyList(), var isExpanded: Boolean = false)
@@ -17,36 +17,11 @@
package com.itsaky.androidide.uidesigner.utils
import android.content.Context
import android.view.SurfaceView
import android.view.TextureView
import android.view.ViewGroup
import android.webkit.WebView
import android.widget.AutoCompleteTextView
import android.widget.Button
import android.widget.CheckBox
import android.widget.CheckedTextView
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.GridLayout
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ListView
import android.widget.ProgressBar
import android.widget.RadioButton
import android.widget.RelativeLayout
import android.widget.SeekBar
import android.widget.Switch
import android.widget.ToggleButton
import com.itsaky.androidide.inflater.INamespace
import com.itsaky.androidide.inflater.IView
import com.itsaky.androidide.inflater.internal.AttributeImpl
import com.itsaky.androidide.inflater.internal.LayoutFile
import com.itsaky.androidide.uidesigner.R.drawable
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.LAYOUTS
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.internal.ViewAdapterIndex
import com.itsaky.androidide.inflater.internal.utils.simpleName
import com.itsaky.androidide.uidesigner.R.string
import com.itsaky.androidide.uidesigner.models.UiAttribute
import com.itsaky.androidide.uidesigner.models.UiWidget
import com.itsaky.androidide.uidesigner.models.UiWidgetCategory
internal object Widgets {
@@ -69,72 +44,10 @@ internal object Widgets {
if (widgets.isNotEmpty()) {
return@apply
}
widgets.apply {
add(
UiWidget(
AutoCompleteTextView::class.java,
string.widget_auto_complete_textview,
drawable.ic_widget_auto_complete_textview
)
)
add(UiWidget(Button::class.java, string.widget_button, drawable.ic_widget_button))
add(UiWidget(CheckBox::class.java, string.widget_checkbox, drawable.ic_widget_checkbox))
add(
UiWidget(
CheckedTextView::class.java,
string.widget_checked_textview,
drawable.ic_widget_checked_textview
)
)
add(UiWidget(EditText::class.java, string.widget_edittext, drawable.ic_widget_edittext))
add(
UiWidget(
ImageButton::class.java,
string.widget_image_button,
drawable.ic_widget_imagebutton
)
)
add(UiWidget(ImageView::class.java, string.widget_image_view, drawable.ic_widget_imageview))
add(UiWidget(ListView::class.java, string.widget_listview, drawable.ic_widget_list_view))
add(
UiWidget(
ProgressBar::class.java,
string.widget_progressbar,
drawable.ic_widget_progress_bar
)
)
add(
UiWidget(
RadioButton::class.java,
string.widget_radio_button,
drawable.ic_widget_radio_button
)
)
add(UiWidget(SeekBar::class.java, string.widget_seekbar, drawable.ic_widget_seek_bar))
add(
UiWidget(
SurfaceView::class.java,
string.widget_surfaceview,
drawable.ic_widget_surface_view
)
)
add(UiWidget(Switch::class.java, string.widget_switch, drawable.ic_widget_switch))
add(
UiWidget(
TextureView::class.java,
string.widget_textureview,
drawable.ic_widget_textureview
)
)
add(
UiWidget(
ToggleButton::class.java,
string.widget_togglebutton,
drawable.ic_widget_toggle_button
)
)
add(UiWidget(WebView::class.java, string.widget_webview, drawable.ic_widget_webview))
}
this.widgets =
ViewAdapterIndex.getWidgetProviders(WIDGETS)
.flatMap { it.getUiWidgets() }
.sortedBy { it.name.simpleName() }
}
}
@@ -144,57 +57,10 @@ internal object Widgets {
return@apply
}
widgets.apply {
add(
UiWidget(
FrameLayout::class.java,
string.widget_frame_layout,
drawable.ic_widget_frame_layout
)
)
add(
UiWidget(
GridLayout::class.java,
string.widget_grid_layout,
drawable.ic_widget_grid_layout
)
)
add(
object :
UiWidget(
LinearLayout::class.java,
string.widget_linear_layout_horz,
drawable.ic_widget_linear_layout_horz
) {
override fun createView(context: Context, parent: ViewGroup, layoutFile: LayoutFile): IView {
return super.createView(context, parent, layoutFile).apply {
addAttribute(UiAttribute(name = "orientation", value = "horizontal"))
}
}
}
)
add(
object :
UiWidget(
LinearLayout::class.java,
string.widget_linear_layout_vert,
drawable.ic_widget_linear_layout_vert
) {
override fun createView(context: Context, parent: ViewGroup, layoutFile: LayoutFile): IView {
return super.createView(context, parent, layoutFile).apply {
addAttribute(UiAttribute(name = "orientation", value = "vertical"))
}
}
}
)
add(
UiWidget(
RelativeLayout::class.java,
string.widget_relative_layout,
drawable.ic_widget_relative_layout
)
)
}
this.widgets =
ViewAdapterIndex.getWidgetProviders(LAYOUTS)
.flatMap { it.getUiWidgets() }
.sortedBy { it.name.simpleName() }
}
}
}
@@ -21,6 +21,7 @@ import android.view.View
import com.android.SdkConstants
import com.itsaky.androidide.inflater.internal.AttributeImpl
import com.itsaky.androidide.inflater.internal.ViewImpl
import com.itsaky.androidide.inflater.models.UiWidget
/**
* Handles logic for applying attributes to a view.
@@ -67,6 +68,23 @@ abstract class IViewAdapter<T : View> : AbstractParser() {
return@lazy handlers
}
private val widget by lazy { createUiWidgets() }
/**
* Get the [UiWidget] model that can be used to list this adapter's view in the UI designer.
*
* @throws UnsupportedOperationException If this view adapter does not adapt a UI designer widget.
*/
fun getUiWidgets(): List<UiWidget> {
return widget
}
protected open fun createUiWidgets(): List<UiWidget> {
throw UnsupportedOperationException(
"${javaClass.simpleName} is not a UI Designer widget adapter"
)
}
/**
* Apply the given attribute to the given view.
*
@@ -17,6 +17,8 @@
package com.itsaky.androidide.inflater.internal.adapters
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.resources.R.drawable
import android.widget.AbsListView
import com.itsaky.androidide.inflater.AttributeHandlerScope
@@ -17,6 +17,8 @@
package com.itsaky.androidide.inflater.internal.adapters
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.resources.R.drawable
import android.widget.AbsSeekBar
import com.itsaky.androidide.inflater.AttributeHandlerScope
@@ -17,6 +17,8 @@
package com.itsaky.androidide.inflater.internal.adapters
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.resources.R.drawable
import android.R.layout
import android.widget.AbsSpinner
import com.itsaky.androidide.inflater.AttributeHandlerScope
@@ -17,6 +17,8 @@
package com.itsaky.androidide.inflater.internal.adapters
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.resources.R.drawable
import android.widget.AbsoluteLayout
import com.itsaky.androidide.annotations.inflater.ViewAdapter
@@ -17,6 +17,8 @@
package com.itsaky.androidide.inflater.internal.adapters
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.resources.R.drawable
import android.R.layout
import android.content.Context
import android.widget.AdapterView
@@ -17,9 +17,14 @@
package com.itsaky.androidide.inflater.internal.adapters
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.resources.R.drawable
import android.widget.AutoCompleteTextView
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.AttributeHandlerScope
import com.itsaky.androidide.inflater.models.UiWidget
/**
* Attribute adapter for [AutoCompleteTextView].
@@ -27,6 +32,7 @@ import com.itsaky.androidide.inflater.AttributeHandlerScope
* @author Akash Yadav
*/
@ViewAdapter(AutoCompleteTextView::class)
@IncludeInDesigner(group = WIDGETS)
open class AutoCompleteTextViewAdapter<T : AutoCompleteTextView> : EditTextAdapter<T>() {
override fun createAttrHandlers(create: (String, AttributeHandlerScope<T>.() -> Unit) -> Unit) {
@@ -37,4 +43,12 @@ open class AutoCompleteTextViewAdapter<T : AutoCompleteTextView> : EditTextAdapt
create("dropDownWidth") { view.dropDownWidth = parseDimension(context, value) }
create("dropDownHeight") { view.dropDownHeight = parseDimension(context, value) }
}
override fun createUiWidgets(): List<UiWidget> {
return listOf(UiWidget(
AutoCompleteTextView::class.java,
string.widget_auto_complete_textview,
drawable.ic_widget_auto_complete_textview
))
}
}
@@ -19,10 +19,21 @@ package com.itsaky.androidide.inflater.internal.adapters
import android.widget.Button
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [Button].
*
* @author Akash Yadav
*/
@ViewAdapter(Button::class) open class ButtonAdapter<T : Button> : TextViewAdapter<T>()
@ViewAdapter(Button::class)
@IncludeInDesigner(group = WIDGETS)
open class ButtonAdapter<T : Button> : TextViewAdapter<T>() {
override fun createUiWidgets(): List<UiWidget> {
return listOf(UiWidget(Button::class.java, string.widget_button, drawable.ic_widget_button))
}
}
@@ -19,10 +19,23 @@ package com.itsaky.androidide.inflater.internal.adapters
import android.widget.CheckBox
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [CheckBox].
*
* @author Akash Yadav
*/
@ViewAdapter(CheckBox::class) open class CheckBoxAdapter<T : CheckBox> : CompoundButtonAdapter<T>()
@ViewAdapter(CheckBox::class)
@IncludeInDesigner(group = WIDGETS)
open class CheckBoxAdapter<T : CheckBox> : CompoundButtonAdapter<T>() {
override fun createUiWidgets(): List<UiWidget> {
return listOf(
UiWidget(CheckBox::class.java, string.widget_checkbox, drawable.ic_widget_checkbox)
)
}
}
@@ -19,7 +19,12 @@ package com.itsaky.androidide.inflater.internal.adapters
import android.widget.CheckedTextView
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.AttributeHandlerScope
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [CheckedTextView].
@@ -27,6 +32,7 @@ import com.itsaky.androidide.inflater.AttributeHandlerScope
* @author Akash Yadav
*/
@ViewAdapter(CheckedTextView::class)
@IncludeInDesigner(group = WIDGETS)
open class CheckedTextViewAdapter<T : CheckedTextView> : TextViewAdapter<T>() {
override fun createAttrHandlers(create: (String, AttributeHandlerScope<T>.() -> Unit) -> Unit) {
@@ -34,4 +40,14 @@ open class CheckedTextViewAdapter<T : CheckedTextView> : TextViewAdapter<T>() {
create("checkMarkTintMode") { view.checkMarkTintMode = parsePorterDuffMode(value) }
create("checkMarkTint") { view.checkMarkTintList = parseColorStateList(context, value) }
}
override fun createUiWidgets(): List<UiWidget> {
return listOf(
UiWidget(
CheckedTextView::class.java,
string.widget_checked_textview,
drawable.ic_widget_checked_textview
)
)
}
}
@@ -17,6 +17,8 @@
package com.itsaky.androidide.inflater.internal.adapters
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.resources.R.drawable
import android.widget.CompoundButton
import com.itsaky.androidide.inflater.AttributeHandlerScope
import com.itsaky.androidide.inflater.IView
@@ -19,6 +19,11 @@ package com.itsaky.androidide.inflater.internal.adapters
import android.widget.EditText
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [EditText].
@@ -26,4 +31,11 @@ import com.itsaky.androidide.annotations.inflater.ViewAdapter
* @author Akash Yadav
*/
@ViewAdapter(EditText::class)
open class EditTextAdapter<T : EditText> : TextViewAdapter<T>()
@IncludeInDesigner(group = WIDGETS)
open class EditTextAdapter<T : EditText> : TextViewAdapter<T>() {
override fun createUiWidgets(): List<UiWidget> {
return listOf(
UiWidget(EditText::class.java, string.widget_edittext, drawable.ic_widget_edittext)
)
}
}
@@ -19,7 +19,12 @@ package com.itsaky.androidide.inflater.internal.adapters
import android.widget.FrameLayout
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.LAYOUTS
import com.itsaky.androidide.inflater.AttributeHandlerScope
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [FrameLayout].
@@ -27,11 +32,18 @@ import com.itsaky.androidide.inflater.AttributeHandlerScope
* @author Akash Yadav
*/
@ViewAdapter(FrameLayout::class)
@IncludeInDesigner(group = LAYOUTS)
open class FrameLayoutAdapter<T : FrameLayout> : ViewGroupAdapter<T>() {
override fun createAttrHandlers(create: (String, AttributeHandlerScope<T>.() -> Unit) -> Unit) {
super.createAttrHandlers(create)
create("foregroundGravity") { view.foregroundGravity = parseGravity(value) }
create("measureAllChildren") { view.measureAllChildren = parseBoolean(value) }
}
override fun createUiWidgets(): List<UiWidget> {
return listOf(
UiWidget(FrameLayout::class.java, string.widget_frame_layout, drawable.ic_widget_frame_layout)
)
}
}
@@ -17,6 +17,8 @@
package com.itsaky.androidide.inflater.internal.adapters
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.resources.R.drawable
import android.gesture.GestureOverlayView
import com.blankj.utilcode.util.ReflectUtils
import com.itsaky.androidide.annotations.inflater.ViewAdapter
@@ -19,7 +19,12 @@ package com.itsaky.androidide.inflater.internal.adapters
import android.widget.GridLayout
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.LAYOUTS
import com.itsaky.androidide.inflater.AttributeHandlerScope
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [GridLayout].
@@ -27,6 +32,7 @@ import com.itsaky.androidide.inflater.AttributeHandlerScope
* @author Akash Yadav
*/
@ViewAdapter(GridLayout::class)
@IncludeInDesigner(group = LAYOUTS)
open class GridLayoutAdapter<T : GridLayout> : ViewGroupAdapter<T>() {
override fun createAttrHandlers(create: (String, AttributeHandlerScope<T>.() -> Unit) -> Unit) {
super.createAttrHandlers(create)
@@ -39,6 +45,12 @@ open class GridLayoutAdapter<T : GridLayout> : ViewGroupAdapter<T>() {
create("useDefaultMargins") { view.useDefaultMargins = parseBoolean(value) }
}
override fun createUiWidgets(): List<UiWidget> {
return listOf(
UiWidget(GridLayout::class.java, string.widget_grid_layout, drawable.ic_widget_grid_layout)
)
}
protected open fun parseOrientation(value: String): Int {
return if ("vertical" == value) {
GridLayout.VERTICAL
@@ -19,6 +19,11 @@ package com.itsaky.androidide.inflater.internal.adapters
import android.widget.ImageButton
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [ImageButton].
@@ -26,4 +31,11 @@ import com.itsaky.androidide.annotations.inflater.ViewAdapter
* @author Akash Yadav
*/
@ViewAdapter(ImageButton::class)
open class ImageButtonAdapter<T : ImageButton> : ImageViewAdapter<T>()
@IncludeInDesigner(group = WIDGETS)
open class ImageButtonAdapter<T : ImageButton> : ImageViewAdapter<T>() {
override fun createUiWidgets(): List<UiWidget> {
return listOf(
UiWidget(ImageButton::class.java, string.widget_image_button, drawable.ic_widget_imagebutton)
)
}
}
@@ -27,9 +27,14 @@ import android.widget.ImageView.ScaleType.FIT_END
import android.widget.ImageView.ScaleType.FIT_START
import android.widget.ImageView.ScaleType.FIT_XY
import android.widget.ImageView.ScaleType.MATRIX
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.AttributeHandlerScope
import com.itsaky.androidide.inflater.IView
import com.itsaky.androidide.inflater.R
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adpater [ImageView]s.
@@ -37,6 +42,7 @@ import com.itsaky.androidide.inflater.R
* @author Akash Yadav
*/
@com.itsaky.androidide.annotations.inflater.ViewAdapter(ImageView::class)
@IncludeInDesigner(group = WIDGETS)
open class ImageViewAdapter<T : ImageView> : ViewAdapter<T>() {
override fun createAttrHandlers(create: (String, AttributeHandlerScope<T>.() -> Unit) -> Unit) {
@@ -53,6 +59,12 @@ open class ImageViewAdapter<T : ImageView> : ViewAdapter<T>() {
create("tintMode") { view.imageTintMode = parsePorterDuffMode(value) }
}
override fun createUiWidgets(): List<UiWidget> {
return listOf(
UiWidget(ImageView::class.java, string.widget_image_view, drawable.ic_widget_imageview)
)
}
override fun applyBasic(view: IView) {
super.applyBasic(view)
(view.view as ImageView).setImageResource(R.drawable.ic_android)
@@ -17,14 +17,26 @@
package com.itsaky.androidide.inflater.internal.adapters
import android.content.Context
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.LAYOUTS
import com.itsaky.androidide.inflater.AttributeHandlerScope
import com.itsaky.androidide.inflater.INamespace
import com.itsaky.androidide.inflater.IView
import com.itsaky.androidide.inflater.IViewGroup
import com.itsaky.androidide.inflater.LayoutBehavior
import com.itsaky.androidide.inflater.LayoutBehavior.HORIZONTAL
import com.itsaky.androidide.inflater.LayoutBehavior.VERTICAL
import com.itsaky.androidide.inflater.internal.LayoutFile
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.inflater.utils.newAttribute
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [LinearLayout].
@@ -32,6 +44,7 @@ import com.itsaky.androidide.inflater.LayoutBehavior.VERTICAL
* @author Akash Yadav
*/
@ViewAdapter(LinearLayout::class)
@IncludeInDesigner(group = LAYOUTS)
open class LinearLayoutAdapter<T : LinearLayout> : ViewGroupAdapter<T>() {
override fun createAttrHandlers(create: (String, AttributeHandlerScope<T>.() -> Unit) -> Unit) {
@@ -49,6 +62,22 @@ open class LinearLayoutAdapter<T : LinearLayout> : ViewGroupAdapter<T>() {
create("weightSum") { view.weightSum = parseFloat(value) }
}
override fun createUiWidgets(): List<UiWidget> {
val horizontal =
LinearLayoutWidget(
title = string.widget_linear_layout_horz,
icon = drawable.ic_widget_linear_layout_horz,
isVertical = false
)
val vertical =
LinearLayoutWidget(
title = string.widget_linear_layout_vert,
icon = drawable.ic_widget_linear_layout_vert,
isVertical = true
)
return listOf(horizontal, vertical)
}
override fun getLayoutBehavior(group: IViewGroup): LayoutBehavior {
val orientation = group.findAttribute(INamespace.ANDROID.uri, "orientation")
return if (orientation?.value == "vertical") VERTICAL else HORIZONTAL
@@ -61,4 +90,24 @@ open class LinearLayoutAdapter<T : LinearLayout> : ViewGroupAdapter<T>() {
else -> LinearLayout.HORIZONTAL
}
}
private class LinearLayoutWidget(
@StringRes title: Int,
@DrawableRes icon: Int,
private val isVertical: Boolean
) : UiWidget(LinearLayout::class.java, title, icon) {
companion object {
private const val HORIZONTAL = "horizontal"
private const val VERTICAL = "vertical"
}
override fun createView(context: Context, parent: ViewGroup, layoutFile: LayoutFile): IView {
return super.createView(context, parent, layoutFile).apply {
addAttribute(
newAttribute(this, name = "orientation", value = if (isVertical) VERTICAL else HORIZONTAL)
)
}
}
}
}
@@ -22,7 +22,12 @@ import android.widget.ArrayAdapter
import android.widget.ListView
import com.blankj.utilcode.util.SizeUtils
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.AttributeHandlerScope
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [ListView].
@@ -30,6 +35,7 @@ import com.itsaky.androidide.inflater.AttributeHandlerScope
* @author Akash Yadav
*/
@ViewAdapter(ListView::class)
@IncludeInDesigner(group = WIDGETS)
open class ListViewAdapter<T : ListView> : AbsListViewAdapter<T>() {
override fun createAttrHandlers(create: (String, AttributeHandlerScope<T>.() -> Unit) -> Unit) {
@@ -45,4 +51,10 @@ open class ListViewAdapter<T : ListView> : AbsListViewAdapter<T>() {
create("footerDividersEnabled") { view.setFooterDividersEnabled(parseBoolean(value)) }
create("headerDividersEnabled") { view.setHeaderDividersEnabled(parseBoolean(value)) }
}
override fun createUiWidgets(): List<UiWidget> {
return listOf(
UiWidget(ListView::class.java, string.widget_listview, drawable.ic_widget_list_view)
)
}
}
@@ -19,6 +19,9 @@ package com.itsaky.androidide.inflater.internal.adapters
import android.widget.ProgressBar
import com.itsaky.androidide.inflater.AttributeHandlerScope
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [ProgressBar].
@@ -74,4 +77,10 @@ open class ProgressBarAdapter<T : ProgressBar> : ViewAdapter<T>() {
view.secondaryProgressTintMode = parsePorterDuffMode(value)
}
}
override fun createUiWidgets(): List<UiWidget> {
return listOf(
UiWidget(ProgressBar::class.java, string.widget_progressbar, drawable.ic_widget_progress_bar)
)
}
}
@@ -19,6 +19,11 @@ package com.itsaky.androidide.inflater.internal.adapters
import android.widget.RadioButton
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [RadioButton].
@@ -26,4 +31,12 @@ import com.itsaky.androidide.annotations.inflater.ViewAdapter
* @author Akash Yadav
*/
@ViewAdapter(RadioButton::class)
open class RadioButtonAdapter<T : RadioButton> : CompoundButtonAdapter<T>()
@IncludeInDesigner(group = WIDGETS)
open class RadioButtonAdapter<T : RadioButton> : CompoundButtonAdapter<T>() {
override fun createUiWidgets(): List<UiWidget> {
return listOf(
UiWidget(RadioButton::class.java, string.widget_radio_button, drawable.ic_widget_radio_button)
)
}
}
@@ -19,7 +19,12 @@ package com.itsaky.androidide.inflater.internal.adapters
import android.widget.RelativeLayout
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.LAYOUTS
import com.itsaky.androidide.inflater.AttributeHandlerScope
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [RelativeLayout].
@@ -27,6 +32,7 @@ import com.itsaky.androidide.inflater.AttributeHandlerScope
* @author Akash Yadav
*/
@ViewAdapter(RelativeLayout::class)
@IncludeInDesigner(group = LAYOUTS)
open class RelativeLayoutAdapter<T : RelativeLayout> : ViewGroupAdapter<T>() {
override fun createAttrHandlers(create: (String, AttributeHandlerScope<T>.() -> Unit) -> Unit) {
@@ -34,4 +40,14 @@ open class RelativeLayoutAdapter<T : RelativeLayout> : ViewGroupAdapter<T>() {
create("gravity") { view.gravity = parseGravity(value) }
create("ignoreGravity") { view.ignoreGravity = parseId(file.resName, value) }
}
override fun createUiWidgets(): List<UiWidget> {
return listOf(
UiWidget(
RelativeLayout::class.java,
string.widget_relative_layout,
drawable.ic_widget_relative_layout
)
)
}
}
@@ -19,7 +19,12 @@ package com.itsaky.androidide.inflater.internal.adapters
import android.widget.SeekBar
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.AttributeHandlerScope
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [SeekBar].
@@ -27,6 +32,7 @@ import com.itsaky.androidide.inflater.AttributeHandlerScope
* @author Akash Yadav
*/
@ViewAdapter(SeekBar::class)
@IncludeInDesigner(group = WIDGETS)
open class SeekBarAdapter<T : SeekBar> : AbsSeekBarAdapter<T>() {
override fun createAttrHandlers(create: (String, AttributeHandlerScope<T>.() -> Unit) -> Unit) {
@@ -34,4 +40,8 @@ open class SeekBarAdapter<T : SeekBar> : AbsSeekBarAdapter<T>() {
create("thumb") { view.thumb = parseDrawable(context, value) }
}
override fun createUiWidgets(): List<UiWidget> {
return listOf(UiWidget(SeekBar::class.java, string.widget_seekbar, drawable.ic_widget_seek_bar))
}
}
@@ -17,6 +17,8 @@
package com.itsaky.androidide.inflater.internal.adapters
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.resources.R.drawable
import android.widget.Spinner
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.inflater.AttributeHandlerScope
@@ -18,10 +18,22 @@
package com.itsaky.androidide.inflater.internal.adapters
import android.view.SurfaceView
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [SurfaceView].
* @author Akash Yadav
*/
@com.itsaky.androidide.annotations.inflater.ViewAdapter(SurfaceView::class)
open class SurfaceViewAdapter<T : SurfaceView> : ViewAdapter<T>()
@IncludeInDesigner(group = WIDGETS)
open class SurfaceViewAdapter<T : SurfaceView> : ViewAdapter<T>() {
override fun createUiWidgets(): List<UiWidget> {
return listOf(
UiWidget(SurfaceView::class.java, string.widget_surfaceview, drawable.ic_widget_surface_view)
)
}
}
@@ -20,7 +20,12 @@ package com.itsaky.androidide.inflater.internal.adapters
import android.graphics.Typeface.DEFAULT
import android.widget.Switch
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.AttributeHandlerScope
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [Switch].
@@ -28,6 +33,7 @@ import com.itsaky.androidide.inflater.AttributeHandlerScope
* @author Akash Yadav
*/
@ViewAdapter(Switch::class)
@IncludeInDesigner(group = WIDGETS)
open class SwitchAdapter<T : Switch> : CompoundButtonAdapter<T>() {
override fun createAttrHandlers(create: (String, AttributeHandlerScope<T>.() -> Unit) -> Unit) {
super.createAttrHandlers(create)
@@ -47,4 +53,8 @@ open class SwitchAdapter<T : Switch> : CompoundButtonAdapter<T>() {
create("trackTintMode") { view.trackTintMode = parsePorterDuffMode(value) }
create("typeface") { view.setSwitchTypeface(parseTypeface(value)) }
}
override fun createUiWidgets(): List<UiWidget> {
return listOf(UiWidget(Switch::class.java, string.widget_switch, drawable.ic_widget_switch))
}
}
@@ -17,6 +17,8 @@
package com.itsaky.androidide.inflater.internal.adapters
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.resources.R.drawable
import android.graphics.Typeface
import android.text.TextUtils
import android.text.util.Linkify
@@ -18,6 +18,11 @@
package com.itsaky.androidide.inflater.internal.adapters
import android.view.TextureView
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [TextureView].
@@ -25,4 +30,11 @@ import android.view.TextureView
* @author Akash Yadav
*/
@com.itsaky.androidide.annotations.inflater.ViewAdapter(TextureView::class)
open class TextureViewAdapter<T : TextureView> : ViewAdapter<T>()
@IncludeInDesigner(group = WIDGETS)
open class TextureViewAdapter<T : TextureView> : ViewAdapter<T>() {
override fun createUiWidgets(): List<UiWidget> {
return listOf(
UiWidget(TextureView::class.java, string.widget_textureview, drawable.ic_widget_textureview)
)
}
}
@@ -21,6 +21,9 @@ import android.widget.ToggleButton
import com.blankj.utilcode.util.ReflectUtils.reflect
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.inflater.AttributeHandlerScope
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [ToggleButton].
@@ -36,4 +39,14 @@ open class ToggleButtonAdapter<T : ToggleButton> : CompoundButtonAdapter<T>() {
create("textOff") { view.textOff = parseString(value) }
create("textOn") { view.textOn = parseString(value) }
}
override fun createUiWidgets(): List<UiWidget> {
return listOf(
UiWidget(
ToggleButton::class.java,
string.widget_togglebutton,
drawable.ic_widget_toggle_button
)
)
}
}
@@ -17,6 +17,8 @@
package com.itsaky.androidide.inflater.internal.adapters
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.resources.R.drawable
import android.content.Context
import android.graphics.PorterDuff
import android.os.Build
@@ -17,6 +17,8 @@
package com.itsaky.androidide.inflater.internal.adapters
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.resources.R.drawable
import android.animation.LayoutTransition
import android.view.ViewGroup
import com.itsaky.androidide.inflater.AttributeHandlerScope
@@ -19,6 +19,11 @@ package com.itsaky.androidide.inflater.internal.adapters
import android.webkit.WebView
import com.itsaky.androidide.annotations.inflater.ViewAdapter
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner
import com.itsaky.androidide.annotations.uidesigner.IncludeInDesigner.Group.WIDGETS
import com.itsaky.androidide.inflater.models.UiWidget
import com.itsaky.androidide.resources.R.drawable
import com.itsaky.androidide.resources.R.string
/**
* Attribute adapter for [WebView].
@@ -26,4 +31,9 @@ import com.itsaky.androidide.annotations.inflater.ViewAdapter
* @author Akash Yadav
*/
@ViewAdapter(WebView::class)
open class WebViewAdapter<T : WebView> : AbsoluteLayoutAdapter<T>()
@IncludeInDesigner(group = WIDGETS)
open class WebViewAdapter<T : WebView> : AbsoluteLayoutAdapter<T>() {
override fun createUiWidgets(): List<UiWidget> {
return listOf(UiWidget(WebView::class.java, string.widget_webview, drawable.ic_widget_webview))
}
}
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.uidesigner.models
package com.itsaky.androidide.inflater.models
import android.content.Context
import android.view.View
@@ -25,10 +25,14 @@ import androidx.annotation.StringRes
import androidx.core.view.updatePaddingRelative
import com.blankj.utilcode.util.SizeUtils
import com.itsaky.androidide.inflater.IView
import com.itsaky.androidide.inflater.IViewGroup
import com.itsaky.androidide.inflater.internal.LayoutFile
import com.itsaky.androidide.inflater.internal.ViewAdapterIndex.getAdapter
import com.itsaky.androidide.inflater.internal.ViewGroupImpl
import com.itsaky.androidide.inflater.internal.ViewImpl
import com.itsaky.androidide.inflater.internal.utils.ViewFactory
import com.itsaky.androidide.inflater.internal.utils.ViewFactory.generateLayoutParams
import com.itsaky.androidide.inflater.utils.lookupComponentFactory
open class UiWidget(val name: String, @StringRes val label: Int, @DrawableRes val icon: Int) {
@@ -46,13 +50,14 @@ open class UiWidget(val name: String, @StringRes val label: Int, @DrawableRes va
*/
open fun createView(context: Context, parent: ViewGroup, layoutFile: LayoutFile): IView {
val v = ViewFactory.createViewInstance(name, context)
val view: IView =
if (v is ViewGroup) {
UiViewGroup(layoutFile, name, v).apply {
val dp8 = SizeUtils.dp2px(8f)
view.updatePaddingRelative(start = dp8, top = dp8, end = dp8, bottom = dp8)
}
} else UiView(layoutFile, name, v)
val view: IView = createView(layoutFile, v)
(view as? IViewGroup)?.apply {
val dp8 = SizeUtils.dp2px(8f)
this.view.updatePaddingRelative(start = dp8, top = dp8, end = dp8, bottom = dp8)
}
view.view.layoutParams = generateLayoutParams(parent)
val adapter =
getAdapter(view.name)
@@ -61,6 +66,12 @@ open class UiWidget(val name: String, @StringRes val label: Int, @DrawableRes va
return view
}
private fun createView(layoutFile: LayoutFile, v: View) =
(lookupComponentFactory()?.createView(layoutFile, name, v)
?: if (v is ViewGroup) {
ViewGroupImpl(layoutFile, name, v)
} else ViewImpl(layoutFile, name, v))
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is UiWidget) return false