diff --git a/app/src/main/java/com/itsaky/androidide/DesignerActivity.java b/app/src/main/java/com/itsaky/androidide/DesignerActivity.java index a3c8fe829..45423ab5c 100755 --- a/app/src/main/java/com/itsaky/androidide/DesignerActivity.java +++ b/app/src/main/java/com/itsaky/androidide/DesignerActivity.java @@ -145,6 +145,8 @@ public class DesignerActivity extends StudioActivity implements WidgetItemAdapte layoutContainerGroup.addView(view); mBinding.layoutContainer.setOnDragListener(getOnDragListener(layoutContainerGroup)); + + LOG.debug("Generated code: ", view.generateCode()); } catch (Throwable th) { mBinding.layoutContainer.removeAllViews(); mBinding.layoutContainer.addView(createErrorText(th)); diff --git a/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java b/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java index 65f58f017..ee310ee0c 100755 --- a/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/lexer/impls/java/JavaLexerImpl.java @@ -58,6 +58,7 @@ import io.github.rosemoe.editor.text.TextUtils; import io.github.rosemoe.editor.widget.EditorColorScheme; // TODO Request Language server for syntax highlighting instead of waiting for it to send highlight ranges +// FIXME Current implementation does not work. public class JavaLexerImpl extends io.github.rosemoe.editor.langs.AbstractCodeAnalyzer { private final EditorLanguage language; diff --git a/layout-inflater/src/main/java/com/itsaky/layoutinflater/IView.java b/layout-inflater/src/main/java/com/itsaky/layoutinflater/IView.java index 81b386937..1f975d59d 100755 --- a/layout-inflater/src/main/java/com/itsaky/layoutinflater/IView.java +++ b/layout-inflater/src/main/java/com/itsaky/layoutinflater/IView.java @@ -29,6 +29,13 @@ import com.itsaky.layoutinflater.IResourceFinder; * @author Akash Yadav */ public interface IView { + + /** + * The indentation length that will be used while generating layout code. + * You can modify this to change the number of spaces used for + * indentation. + */ + public static int DEFAULT_INDENTATION_LENGTH = 4; /** * Get this view as android {@link View}. This will be used to display this view in the designer @@ -94,4 +101,28 @@ public interface IView { */ @Nullable Object getExtraData (); + + /** + * Get the layout resource code for this file. + * + * @param indentationLength The number of spaces to indent. + */ + String generateCode (int indentationLength); + + /** + * @see #generateCode(int) + */ + default String generateCode () { + return generateCode(0); + } + + /** + * Get the tag name which will be used for generating XML Code + * for this view. + * + * Default implementation simply returns the simple name of this view's class. + * + * @return The tag name of this view. + */ + String getXmlTag (); } diff --git a/layout-inflater/src/main/java/com/itsaky/layoutinflater/impl/BaseView.java b/layout-inflater/src/main/java/com/itsaky/layoutinflater/impl/BaseView.java index 4d2a8e27f..017cebdf4 100755 --- a/layout-inflater/src/main/java/com/itsaky/layoutinflater/impl/BaseView.java +++ b/layout-inflater/src/main/java/com/itsaky/layoutinflater/impl/BaseView.java @@ -18,7 +18,9 @@ package com.itsaky.layoutinflater.impl; import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.itsaky.layoutinflater.IResourceFinder; @@ -129,24 +131,105 @@ public abstract class BaseView implements IView { } @Override - public boolean equals(Object obj) { - - if (obj == null) { - return false; + public String getXmlTag() { + return Objects.requireNonNull(asView()).getClass().getSimpleName(); + } + + /** + * Generate the XML layout code for this view. + *
+ * NOTE: To avoid writing same logic in multiple files, + * this implementation tries to handle logic for both + * {@link IView} and {@link IViewGroup}. + * + * For example, if this view is an {@link IViewGroup}, then + * the layout code for its children will also be printed. + *
+ * + * @param indentCount The number of tabs to indent. To define custom tab size, + * see {@link IView#DEFAULT_INDENTATION_LENGTH}. + * @return The generated XML code. Never null. + */ + @NonNull + @Override + public String generateCode(int indentCount) { + final var sb = new StringBuilder(); + sb.append("<"); + sb.append(getXmlTag()); + + if (!attributes.isEmpty()) { + for (var attr : attributes) { + newLine(sb, indentCount + 1); // Attributes must be indented by one tab (4 spaces by default) + attr.getNamespace(); + if (attr.getNamespace().trim().length() > 0) { + sb.append(attr.getNamespace()); + sb.append(":"); + } + sb.append(attr.getAttributeName()); + sb.append("="); + sb.append("\""); + sb.append(attr.getValue()); + sb.append("\""); + } } - - return this.hashCode() == obj.hashCode(); + + var hasChildren = false; + if (this instanceof IViewGroup) { + final var group = (IViewGroup) this; + hasChildren = group.getChildCount() > 0; + + if (hasChildren) { + sb.append(">"); + newLine(sb, indentCount + 1); + for (var child : group.getChildren()) { + newLine(sb, indentCount + 1); + sb.append(child.generateCode(indentCount + 1)); + } + } + } + + if (hasChildren) { + // Closing '>' is already printed, no need to print it here. + // Leave one line and append the closing tag. + newLine(sb, indentCount); + newLine(sb, indentCount - 1); + sb.append(""); + sb.append(getXmlTag()); + sb.append(">"); + } else { + sb.append("/>"); + } + + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BaseView baseView = (BaseView) o; + return isPlaceholder() == baseView.isPlaceholder() + && Objects.equals(attributes, baseView.attributes) + && Objects.equals(attrAdapters, baseView.attrAdapters) + && Objects.equals(qualifiedName, baseView.qualifiedName) + && Objects.equals(view, baseView.view) + && Objects.equals(getParent(), baseView.getParent()) + && Objects.equals(stored, baseView.stored); } @Override public int hashCode() { - return Objects.hash( - attributes, - attrAdapters, - qualifiedName, - view, - parent, - isPlaceholder - ); + return Objects.hash(attributes, attrAdapters, qualifiedName, view, getParent(), stored, isPlaceholder()); + } + + private void newLine (@NonNull StringBuilder stringBuilder, int indentCount) { + stringBuilder.append("\n"); + indent(stringBuilder, indentCount); + } + + private void indent (StringBuilder sb, int count) { + for (var i = 0; i < DEFAULT_INDENTATION_LENGTH * count; i++) { + sb.append(" "); + } } }