diff --git a/README.md b/README.md index ace3e0a..94458c6 100644 --- a/README.md +++ b/README.md @@ -5,5 +5,4 @@ Java Markdown Rendering library # TODO -- [ ] Tables -- [ ] \
and \ tags \ No newline at end of file +- Tables \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index d70a759..fc30f8a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } group = "dev.dediamondpro" -version = "1.0-SNAPSHOT96" +version = "1.0-SNAPSHOT100" repositories { mavenCentral() @@ -30,8 +30,7 @@ allprojects { publishing { publications { var name = project.name.lowercase() - if (name != "minemark") name = "minemark-$name" - else name = "minemark-core" + name = if (name != "minemark") "minemark-$name" else "minemark-core" register(name) { groupId = "dev.dediamondpro" artifactId = name diff --git a/elementa/src/main/kotlin/dev/dediamondpro/minemark/elementa/MineMarkComponent.kt b/elementa/src/main/kotlin/dev/dediamondpro/minemark/elementa/MineMarkComponent.kt index 67ad16f..4adddcb 100644 --- a/elementa/src/main/kotlin/dev/dediamondpro/minemark/elementa/MineMarkComponent.kt +++ b/elementa/src/main/kotlin/dev/dediamondpro/minemark/elementa/MineMarkComponent.kt @@ -82,7 +82,7 @@ class MineMarkComponent( } fun MineMarkCoreBuilder.addElementaExtensions(): MineMarkCoreBuilder { - return this.addElement(Elements.TEXT, ::MarkdownTextComponent) + return this.setTextElement(::MarkdownTextComponent) .addElement(Elements.HEADING, ::MarkdownHeadingComponent) .addElement(Elements.IMAGE, ::MarkdownImageComponent) .addElement(Elements.HORIZONTAL_RULE, ::MarkdownHorizontalRuleComponent) diff --git a/elementa/src/main/kotlin/dev/dediamondpro/minemark/elementa/elements/MarkdownTextComponent.kt b/elementa/src/main/kotlin/dev/dediamondpro/minemark/elementa/elements/MarkdownTextComponent.kt index ced6eb4..eaaf0e8 100644 --- a/elementa/src/main/kotlin/dev/dediamondpro/minemark/elementa/elements/MarkdownTextComponent.kt +++ b/elementa/src/main/kotlin/dev/dediamondpro/minemark/elementa/elements/MarkdownTextComponent.kt @@ -13,11 +13,12 @@ import java.awt.Color import kotlin.math.round class MarkdownTextComponent( + text: String, style: MarkdownStyle, layoutStyle: LayoutStyle, parent: Element?, qName: String, attributes: Attributes? -) : TextElement(style, layoutStyle, parent, qName, attributes) { +) : TextElement(text, style, layoutStyle, parent, qName, attributes) { private val font = style.textStyle.font private var scale = layoutStyle.fontSize private var prefix = buildString { diff --git a/src/main/java/dev/dediamondpro/minemark/LayoutData.java b/src/main/java/dev/dediamondpro/minemark/LayoutData.java index 55a3170..b2e0e09 100644 --- a/src/main/java/dev/dediamondpro/minemark/LayoutData.java +++ b/src/main/java/dev/dediamondpro/minemark/LayoutData.java @@ -6,6 +6,7 @@ public class LayoutData { private final ArrayList> elementListeners = new ArrayList<>(); private MarkDownLine currentLine = new MarkDownLine(0f); + private MarkDownLine previousLine = null; private final float maxWidth; private boolean topSpacingLocked = false; private boolean bottomSpacingLocked = false; @@ -23,6 +24,7 @@ public boolean isLineOccupied() { } public void nextLine() { + previousLine = currentLine; currentLine = new MarkDownLine(currentLine.getBottomY()); topSpacingLocked = false; bottomSpacingLocked = false; @@ -87,6 +89,10 @@ public MarkDownLine getCurrentLine() { return currentLine; } + public MarkDownLine getPreviousLine() { + return previousLine; + } + public void updateTopSpacing(float spacing) { if (topSpacingLocked) return; currentLine.topSpacing = Math.max(currentLine.topSpacing, spacing); diff --git a/src/main/java/dev/dediamondpro/minemark/MineMarkCore.java b/src/main/java/dev/dediamondpro/minemark/MineMarkCore.java index abe0203..4e506d7 100644 --- a/src/main/java/dev/dediamondpro/minemark/MineMarkCore.java +++ b/src/main/java/dev/dediamondpro/minemark/MineMarkCore.java @@ -1,7 +1,8 @@ package dev.dediamondpro.minemark; -import dev.dediamondpro.minemark.elements.ElementLoader; +import dev.dediamondpro.minemark.elements.loaders.ElementLoader; import dev.dediamondpro.minemark.elements.MineMarkElement; +import dev.dediamondpro.minemark.elements.loaders.TextElementLoader; import dev.dediamondpro.minemark.style.Style; import org.commonmark.Extension; import org.commonmark.node.Node; @@ -31,18 +32,19 @@ public class MineMarkCore { private final ReentrantLock parsingLock = new ReentrantLock(); /** + * @param textElement The text element for this core * @param elements Elements supported to create a layout and render * @param extensions Markdown extensions that should be used when parsing * @param urlSanitizer An optional urlSanitizer */ - protected MineMarkCore(Map, ElementLoader> elements, Iterable extensions, @Nullable UrlSanitizer urlSanitizer) { + protected MineMarkCore(TextElementLoader textElement, Map, ElementLoader> elements, Iterable extensions, @Nullable UrlSanitizer urlSanitizer) { this.markdownParser = Parser.builder().extensions(extensions).build(); HtmlRenderer.Builder htmlRendererBuilder = HtmlRenderer.builder().extensions(extensions); if (urlSanitizer != null) { htmlRendererBuilder.urlSanitizer(urlSanitizer).sanitizeUrls(true); } this.htmlRenderer = htmlRendererBuilder.build(); - this.htmlParser = new MineMarkHtmlParser<>(elements); + this.htmlParser = new MineMarkHtmlParser<>(textElement, elements); xmlParser = new org.ccil.cowan.tagsoup.Parser(); xmlParser.setContentHandler(htmlParser); } diff --git a/src/main/java/dev/dediamondpro/minemark/MineMarkCoreBuilder.java b/src/main/java/dev/dediamondpro/minemark/MineMarkCoreBuilder.java index ba10ab4..a302fa5 100644 --- a/src/main/java/dev/dediamondpro/minemark/MineMarkCoreBuilder.java +++ b/src/main/java/dev/dediamondpro/minemark/MineMarkCoreBuilder.java @@ -1,12 +1,13 @@ package dev.dediamondpro.minemark; -import dev.dediamondpro.minemark.elements.ElementLoader; +import dev.dediamondpro.minemark.elements.loaders.ElementLoader; import dev.dediamondpro.minemark.elements.Elements; import dev.dediamondpro.minemark.elements.impl.LinkElement; import dev.dediamondpro.minemark.elements.impl.ParagraphElement; import dev.dediamondpro.minemark.elements.impl.formatting.AlignmentElement; import dev.dediamondpro.minemark.elements.impl.formatting.FormattingElement; import dev.dediamondpro.minemark.elements.impl.list.ListHolderElement; +import dev.dediamondpro.minemark.elements.loaders.TextElementLoader; import dev.dediamondpro.minemark.style.Style; import org.commonmark.Extension; import org.commonmark.renderer.html.UrlSanitizer; @@ -23,14 +24,25 @@ protected MineMarkCoreBuilder() { private final HashMap, ElementLoader> elements = new HashMap<>(); private final ArrayList extensions = new ArrayList<>(); + private TextElementLoader textElement = null; private boolean withDefaultElements = true; private UrlSanitizer urlSanitizer = null; + /** + * Set the text element that should be used. + * + * @param textElement The {@link TextElementLoader} of that text element + */ + public MineMarkCoreBuilder setTextElement(@NotNull TextElementLoader textElement) { + this.textElement = textElement; + return this; + } + /** * Add a supported element to be used * * @param elementName Tags the element should use - * @param element An ElementLoader of that element + * @param element An {@link ElementLoader} of that element */ public MineMarkCoreBuilder addElement(@NotNull Elements elementName, @NotNull ElementLoader element) { this.elements.put(elementName.tags, element); @@ -41,7 +53,7 @@ public MineMarkCoreBuilder addElement(@NotNull Elements elementName, @NotN * Add a supported element to be used * * @param tags Tags the element should use - * @param element An ElementLoader of that element + * @param element An {@link ElementLoader} of that element */ public MineMarkCoreBuilder addElement(@NotNull List tags, @NotNull ElementLoader element) { this.elements.put(tags, element); @@ -81,7 +93,7 @@ public MineMarkCoreBuilder addExtension(Extension extension) { } /** - * Disable default extensions + * Disable default elements */ public MineMarkCoreBuilder withoutDefaultElements() { withDefaultElements = false; @@ -101,6 +113,9 @@ public void setUrlSanitizer(UrlSanitizer urlSanitizer) { * @return a MineMarkCore with the given settings */ public MineMarkCore build() { + if (textElement == null) { + throw new IllegalArgumentException("A text element has to be provided by using \"setTextElement(textElement\""); + } if (withDefaultElements) { addElement(Elements.PARAGRAPH, ParagraphElement::new); addElement(Elements.FORMATTING, FormattingElement::new); @@ -108,6 +123,6 @@ public MineMarkCore build() { addElement(Elements.LINK, LinkElement::new); addElement(Elements.LIST_PARENT, ListHolderElement::new); } - return new MineMarkCore<>(elements, extensions, urlSanitizer); + return new MineMarkCore<>(textElement, elements, extensions, urlSanitizer); } } diff --git a/src/main/java/dev/dediamondpro/minemark/MineMarkHtmlParser.java b/src/main/java/dev/dediamondpro/minemark/MineMarkHtmlParser.java index eb7da8d..6d31f03 100644 --- a/src/main/java/dev/dediamondpro/minemark/MineMarkHtmlParser.java +++ b/src/main/java/dev/dediamondpro/minemark/MineMarkHtmlParser.java @@ -1,8 +1,9 @@ package dev.dediamondpro.minemark; import dev.dediamondpro.minemark.elements.Element; -import dev.dediamondpro.minemark.elements.ElementLoader; +import dev.dediamondpro.minemark.elements.loaders.ElementLoader; import dev.dediamondpro.minemark.elements.MineMarkElement; +import dev.dediamondpro.minemark.elements.loaders.TextElementLoader; import dev.dediamondpro.minemark.style.Style; import org.xml.sax.Attributes; import org.xml.sax.helpers.DefaultHandler; @@ -12,6 +13,7 @@ public class MineMarkHtmlParser extends DefaultHandler { private final Map, ElementLoader> elements; + private final TextElementLoader textElement; private MineMarkElement markDown; private Element currentElement; private LayoutStyle layoutStyle; @@ -19,7 +21,8 @@ public class MineMarkHtmlParser extends DefaultHandler { private StringBuilder textBuilder = new StringBuilder(); private boolean isPreFormatted = false; - protected MineMarkHtmlParser(Map, ElementLoader> elements) { + protected MineMarkHtmlParser(TextElementLoader textElement, Map, ElementLoader> elements) { + this.textElement = textElement; this.elements = elements; } @@ -93,11 +96,7 @@ public void characters(char[] ch, int start, int length) { private void addText() { String text = textBuilder.toString(); if (text.isEmpty()) return; - ElementLoader textLoader = findElement("text"); - if (textLoader == null) { - throw new IllegalArgumentException("No text element provided!"); - } - textLoader.get(style, currentElement.getLayoutStyle(), currentElement, "text", null).setText(textBuilder.toString()); + textElement.get(textBuilder.toString(), style, currentElement.getLayoutStyle(), currentElement, "text", null); textBuilder = new StringBuilder(); } diff --git a/src/main/java/dev/dediamondpro/minemark/elements/BasicElement.java b/src/main/java/dev/dediamondpro/minemark/elements/BasicElement.java index 49dbbee..6294f8b 100644 --- a/src/main/java/dev/dediamondpro/minemark/elements/BasicElement.java +++ b/src/main/java/dev/dediamondpro/minemark/elements/BasicElement.java @@ -3,13 +3,12 @@ import dev.dediamondpro.minemark.LayoutData; import dev.dediamondpro.minemark.LayoutStyle; import dev.dediamondpro.minemark.style.Style; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.xml.sax.Attributes; public abstract class BasicElement extends Element { - private float width; - private float height; protected LayoutData.MarkDownElementPosition position; public BasicElement(@NotNull S style, @NotNull LayoutStyle layoutStyle, @Nullable Element parent, @NotNull String qName, @Nullable Attributes attributes) { @@ -17,17 +16,18 @@ public BasicElement(@NotNull S style, @NotNull LayoutStyle layoutStyle, @Nullabl } @Override - protected void draw(float xOffset, float yOffset, float mouseX, float mouseY, R renderData) { + public void drawInternal(float xOffset, float yOffset, float mouseX, float mouseY, R renderData) { drawElement( - position.getX() + xOffset, position.getBottomY() - height + yOffset, + position.getX() + xOffset, position.getY() + yOffset, position.getWidth(), position.getHeight(), renderData ); } @Override - protected void generateLayout(LayoutData layoutData, R renderData) { - width = getWidth(layoutData, renderData); - height = getHeight(layoutData, renderData); + @ApiStatus.Internal + public void generateLayout(LayoutData layoutData, R renderData) { + float width = getWidth(layoutData, renderData); + float height = getHeight(layoutData, renderData); float padding = getPadding(layoutData, renderData); if ((!(this instanceof Inline) && layoutData.isLineOccupied()) || layoutData.getX() + width > layoutData.getMaxWidth()) { layoutData.nextLine(); diff --git a/src/main/java/dev/dediamondpro/minemark/elements/ChildBasedElement.java b/src/main/java/dev/dediamondpro/minemark/elements/ChildBasedElement.java index d9a6134..2c989aa 100644 --- a/src/main/java/dev/dediamondpro/minemark/elements/ChildBasedElement.java +++ b/src/main/java/dev/dediamondpro/minemark/elements/ChildBasedElement.java @@ -3,6 +3,7 @@ import dev.dediamondpro.minemark.LayoutData; import dev.dediamondpro.minemark.LayoutStyle; import dev.dediamondpro.minemark.style.Style; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.xml.sax.Attributes; @@ -13,14 +14,8 @@ public ChildBasedElement(@NotNull S style, @NotNull LayoutStyle layoutStyle, @Nu } @Override - protected void draw(float xOffset, float yOffset, float mouseX, float mouseY, R renderData) { - for (Element child : children) { - child.draw(xOffset, yOffset, mouseX, mouseY, renderData); - } - } - - @Override - protected void generateLayout(LayoutData layoutData, R renderData) { + @ApiStatus.Internal + public void generateLayout(LayoutData layoutData, R renderData) { if (!(this instanceof Inline) && layoutData.isLineOccupied()) { layoutData.nextLine(); } diff --git a/src/main/java/dev/dediamondpro/minemark/elements/ChildMovingElement.java b/src/main/java/dev/dediamondpro/minemark/elements/ChildMovingElement.java index a3e34d1..b8bde90 100644 --- a/src/main/java/dev/dediamondpro/minemark/elements/ChildMovingElement.java +++ b/src/main/java/dev/dediamondpro/minemark/elements/ChildMovingElement.java @@ -4,6 +4,7 @@ import dev.dediamondpro.minemark.LayoutStyle; import dev.dediamondpro.minemark.style.Style; import dev.dediamondpro.minemark.utils.MouseButton; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.xml.sax.Attributes; @@ -23,7 +24,8 @@ public ChildMovingElement(@NotNull S style, @NotNull LayoutStyle layoutStyle, @N } @Override - protected void generateLayout(LayoutData layoutData, R renderData) { + @ApiStatus.Internal + public void generateLayout(LayoutData layoutData, R renderData) { if (layoutData.isLineOccupied()) { layoutData.nextLine(); } @@ -77,12 +79,13 @@ protected void generateNewLayout(LayoutData layoutData, R renderData) { } @Override - protected void draw(float xOffset, float yOffset, float mouseX, float mouseY, R renderData) { + @ApiStatus.Internal + public void drawInternal(float xOffset, float yOffset, float mouseX, float mouseY, R renderData) { if (marker != null) { drawMarker(marker.getX() + xOffset, marker.getY() + yOffset, marker.getWidth(), marker.getHeight(), renderData); } for (Element child : children) { - child.draw( + child.drawInternal( xOffset + extraXOffset, yOffset + extraYOffset, mouseX - extraXOffset, mouseY - extraYOffset, renderData ); @@ -90,16 +93,18 @@ protected void draw(float xOffset, float yOffset, float mouseX, float mouseY, R } @Override - protected void beforeDraw(float xOffset, float yOffset, float mouseX, float mouseY, R renderData) { - super.beforeDraw( + @ApiStatus.Internal + public void beforeDrawInternal(float xOffset, float yOffset, float mouseX, float mouseY, R renderData) { + super.beforeDrawInternal( xOffset + extraXOffset, yOffset + extraXOffset, mouseX - extraXOffset, mouseY - extraYOffset, renderData ); } @Override - protected void onMouseClicked(MouseButton button, float mouseX, float mouseY) { - super.onMouseClicked(button, mouseX - extraXOffset, mouseY - extraYOffset); + @ApiStatus.Internal + public void onMouseClickedInternal(MouseButton button, float mouseX, float mouseY) { + super.onMouseClickedInternal(button, mouseX - extraXOffset, mouseY - extraYOffset); } protected abstract void drawMarker(float x, float y, float markerWidth, float totalHeight, R renderData); diff --git a/src/main/java/dev/dediamondpro/minemark/elements/Element.java b/src/main/java/dev/dediamondpro/minemark/elements/Element.java index b59966b..82c852a 100644 --- a/src/main/java/dev/dediamondpro/minemark/elements/Element.java +++ b/src/main/java/dev/dediamondpro/minemark/elements/Element.java @@ -3,8 +3,10 @@ import dev.dediamondpro.minemark.LayoutData; import dev.dediamondpro.minemark.LayoutStyle; import dev.dediamondpro.minemark.elements.impl.TextElement; +import dev.dediamondpro.minemark.elements.loaders.ElementLoader; import dev.dediamondpro.minemark.style.Style; import dev.dediamondpro.minemark.utils.MouseButton; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.xml.sax.Attributes; @@ -18,15 +20,14 @@ public abstract class Element { protected final Attributes attributes; protected final S style; protected LayoutStyle layoutStyle; - protected String text; /** * Base Element Constructor used by {@link ElementLoader} * * @param layoutStyle The configuration used to generate the layout positioning - * @param parent Parent element, null in top level element {@link MineMarkElement} - * @param qName The name of the HTML tag - * @param attributes The attributes of the HTML tag, null for text {@link TextElement} + * @param parent Parent element, null in top level element {@link MineMarkElement} + * @param qName The name of the HTML tag + * @param attributes The attributes of the HTML tag, null for text {@link TextElement} */ public Element(@NotNull S style, @NotNull LayoutStyle layoutStyle, @Nullable Element parent, @NotNull String qName, @Nullable Attributes attributes) { this.style = style; @@ -37,27 +38,45 @@ public Element(@NotNull S style, @NotNull LayoutStyle layoutStyle, @Nullable Ele this.attributes = attributes; } - public void setText(String text) { - if (text == null) { - throw new IllegalStateException("Text is already set, it can not be set twice."); + /** + * Internal method for drawing an element, should never be used directly. + */ + @ApiStatus.Internal + public void drawInternal(float xOffset, float yOffset, float mouseX, float mouseY, R renderData){ + for (Element child : children) { + child.drawInternal(xOffset, yOffset, mouseX, mouseY, renderData); } - this.text = text; } - public @Nullable Element getParent() { - return parent; - } - - public ArrayList> getChildren() { - return children; + /** + * Internal method called before drawing an element, should never be used directly. + */ + @ApiStatus.Internal + public void beforeDrawInternal(float xOffset, float yOffset, float mouseX, float mouseY, R renderData) { + for (Element child : children) { + child.beforeDrawInternal(xOffset, yOffset, mouseX, mouseY, renderData); + } } - public LayoutStyle getLayoutStyle() { - return layoutStyle; + /** + * Internal method for called when the mouse is clicked, should never be used directly. + */ + @ApiStatus.Internal + public void onMouseClickedInternal(MouseButton button, float mouseX, float mouseY) { + for (Element child : children) { + child.onMouseClickedInternal(button, mouseX, mouseY); + } } - protected abstract void generateLayout(LayoutData layoutData, R renderData); + /** + * Internal method for generating the layout of this element, should never be used directly. + */ + @ApiStatus.Internal + public abstract void generateLayout(LayoutData layoutData, R renderData); + /** + * Call this method to regenerate the layout of all associated elements + */ public void regenerateLayout() { if (parent == null) { throw new IllegalStateException("No top level MineMarkElement found to regenerate layout with."); @@ -65,20 +84,6 @@ public void regenerateLayout() { parent.regenerateLayout(); } - protected abstract void draw(float xOffset, float yOffset, float mouseX, float mouseY, R renderData); - - protected void beforeDraw(float xOffset, float yOffset, float mouseX, float mouseY, R renderData) { - for (Element child : children) { - child.beforeDraw(xOffset, yOffset, mouseX, mouseY, renderData); - } - } - - protected void onMouseClicked(MouseButton button, float mouseX, float mouseY) { - for (Element child : children) { - child.onMouseClicked(button, mouseX, mouseY); - } - } - /** * Build a tree of elements for debugging purposes */ @@ -93,4 +98,16 @@ public String buildTree(int depth) { } return builder.toString(); } + + public @Nullable Element getParent() { + return parent; + } + + public ArrayList> getChildren() { + return children; + } + + public LayoutStyle getLayoutStyle() { + return layoutStyle; + } } diff --git a/src/main/java/dev/dediamondpro/minemark/elements/Elements.java b/src/main/java/dev/dediamondpro/minemark/elements/Elements.java index 7ab59b7..02980d9 100644 --- a/src/main/java/dev/dediamondpro/minemark/elements/Elements.java +++ b/src/main/java/dev/dediamondpro/minemark/elements/Elements.java @@ -5,7 +5,6 @@ public enum Elements { PARAGRAPH(listOf("p")), - TEXT(listOf("text")), FORMATTING(listOf("strong", "b", "em", "i", "ins", "u", "del", "s", "pre")), HEADING(listOf("h1", "h2", "h3", "h4", "h5", "h6")), ALIGNMENT(listOf("div", "center")), diff --git a/src/main/java/dev/dediamondpro/minemark/elements/MineMarkElement.java b/src/main/java/dev/dediamondpro/minemark/elements/MineMarkElement.java index 3841a94..4299074 100644 --- a/src/main/java/dev/dediamondpro/minemark/elements/MineMarkElement.java +++ b/src/main/java/dev/dediamondpro/minemark/elements/MineMarkElement.java @@ -4,12 +4,13 @@ import dev.dediamondpro.minemark.LayoutStyle; import dev.dediamondpro.minemark.style.Style; import dev.dediamondpro.minemark.utils.MouseButton; +import org.jetbrains.annotations.ApiStatus; import org.xml.sax.Attributes; import java.util.ArrayList; import java.util.function.Consumer; -public final class MineMarkElement extends ChildBasedElement { +public class MineMarkElement extends ChildBasedElement { private final ArrayList> layoutCallbacks = new ArrayList<>(); private float lastWidth = -1; private float height; @@ -18,20 +19,38 @@ public MineMarkElement(S style, LayoutStyle layoutStyle, Attributes attributes) super(style, layoutStyle, null, null, attributes); } - @Override - public void generateLayout(LayoutData layoutData, R renderData) { - super.generateLayout(layoutData, renderData); - height = layoutData.getY() + layoutData.getLineHeight(); - for (Consumer callback : layoutCallbacks) { - callback.accept(height); + /** + * Draw the markdown layout + * + * @param x X-Coordinate of the top left corner + * @param y Y-Coordinate of the top left corner + * @param width The maximum width of the markdown element + * @param mouseX The current X-Coordinate of the mouse + * @param mouseY The current Y-Coordinate of the mouse + * @param renderData Data class passed to all subclassed to aid in rendering + */ + public void draw(float x, float y, float width, float mouseX, float mouseY, R renderData) { + if (width <= 0) { + throw new IllegalArgumentException("Width cannot be zero or negative!"); + } + if (width != lastWidth) { + beforeDraw(x, y, width, mouseX, mouseY, renderData); } + this.drawInternal(x, y, mouseX - x, mouseY - y, renderData); } - @Override - public void regenerateLayout() { - lastWidth = -1; - } + /** + * Method to call before drawing the markdown element in case you want to do something in between + * layout and draw. + * + * @param x X-Coordinate of the top left corner + * @param y Y-Coordinate of the top left corner + * @param width The maximum width of the markdown element + * @param mouseX The current X-Coordinate of the mouse + * @param mouseY The current Y-Coordinate of the mouse + * @param renderData Data class passed to all subclassed to aid in rendering + */ public void beforeDraw(float x, float y, float width, float mouseX, float mouseY, R renderData) { if (width <= 0) { throw new IllegalArgumentException("Width cannot be zero or negative!"); @@ -40,28 +59,75 @@ public void beforeDraw(float x, float y, float width, float mouseX, float mouseY generateLayout(new LayoutData(width), renderData); lastWidth = width; } - this.beforeDraw(x, y, mouseX - x, mouseY - y, renderData); + this.beforeDrawInternal(x, y, mouseX - x, mouseY - y, renderData); } - public void draw(float x, float y, float width, float mouseX, float mouseY, R renderData) { - if (width <= 0) { - throw new IllegalArgumentException("Width cannot be zero or negative!"); + /** + * Method to call when a mouse button was clicked + * + * @param button The button that was clicked + * @param x X-Coordinate of the top left corner + * @param y Y-Coordinate of the top left corner + * @param mouseX The current X-Coordinate of the mouse + * @param mouseY The current Y-Coordinate of the mouse + */ + public void onMouseClicked(float x, float y, MouseButton button, float mouseX, float mouseY) { + this.onMouseClickedInternal(button, mouseX - x, mouseY - y); + } + + /** + * Method to call if you want the layout to regenerate the next time + * {@link MineMarkElement#beforeDraw(float, float, float, float, float, Object)} is called. + */ + @Override + public void regenerateLayout() { + lastWidth = -1; + } + + /** + * Internal method to generate a new layout. + * This method shouldn't be called manually, please use {@link MineMarkElement#regenerateLayout()} instead. + */ + @Override + @ApiStatus.Internal + public void generateLayout(LayoutData layoutData, R renderData) { + layoutData.lockTopSpacing(); + super.generateLayout(layoutData, renderData); + float bottomSpacing = layoutData.getCurrentLine().getBottomSpacing(); + if (bottomSpacing == 0f && layoutData.isLineEmpty()) { + bottomSpacing = layoutData.getPreviousLine().getBottomSpacing(); } - if (width != lastWidth) { - throw new IllegalArgumentException("Draw has been called without calling beforeDraw, this is not allowed!"); + height = layoutData.getY() + layoutData.getLineHeight() - bottomSpacing; + for (Consumer callback : layoutCallbacks) { + callback.accept(height); } - this.draw(x, y, mouseX - x, mouseY - y, renderData); } - - public void onMouseClicked(float x, float y, MouseButton button, float mouseX, float mouseY) { - this.onMouseClicked(button, mouseX - x, mouseY - y); + /** + * Add a callback that will be called when the layout is updated, + * this callback includes the new height of the element + * + * @param callback The callback + */ + public void addLayoutCallback(Consumer callback) { + layoutCallbacks.add(callback); } + /** + * Get the height of this markdown element, + * will return -1 if {@link MineMarkElement#beforeDraw(float, float, float, float, float, Object)} hasn't been called yet + * + * @return The height of this markdown element + */ public float getHeight() { return height; } + /** + * Get a tree of this markdown element in string form, useful for debugging + * + * @return The tree in String form + */ public String getTree() { StringBuilder builder = new StringBuilder(); for (int i = 0; i < children.size(); i++) { @@ -71,8 +137,4 @@ public String getTree() { } return builder.toString(); } - - public void addLayoutCallback(Consumer callback) { - layoutCallbacks.add(callback); - } } diff --git a/src/main/java/dev/dediamondpro/minemark/elements/impl/CodeBlockElement.java b/src/main/java/dev/dediamondpro/minemark/elements/impl/CodeBlockElement.java index 93464bd..f4474a2 100644 --- a/src/main/java/dev/dediamondpro/minemark/elements/impl/CodeBlockElement.java +++ b/src/main/java/dev/dediamondpro/minemark/elements/impl/CodeBlockElement.java @@ -5,6 +5,7 @@ import dev.dediamondpro.minemark.elements.ChildMovingElement; import dev.dediamondpro.minemark.elements.Element; import dev.dediamondpro.minemark.style.Style; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.xml.sax.Attributes; @@ -22,7 +23,8 @@ public CodeBlockElement(@NotNull S style, @NotNull LayoutStyle layoutStyle, @Nul } @Override - protected void generateLayout(LayoutData layoutData, R renderData) { + @ApiStatus.Internal + public void generateLayout(LayoutData layoutData, R renderData) { switch (codeBlockType) { case BLOCK: super.generateLayout(layoutData, renderData); diff --git a/src/main/java/dev/dediamondpro/minemark/elements/impl/ImageElement.java b/src/main/java/dev/dediamondpro/minemark/elements/impl/ImageElement.java index 130bd81..c58b4b9 100644 --- a/src/main/java/dev/dediamondpro/minemark/elements/impl/ImageElement.java +++ b/src/main/java/dev/dediamondpro/minemark/elements/impl/ImageElement.java @@ -7,11 +7,11 @@ import dev.dediamondpro.minemark.elements.Inline; import dev.dediamondpro.minemark.providers.ImageProvider; import dev.dediamondpro.minemark.style.Style; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.xml.sax.Attributes; -import java.awt.image.BufferedImage; import java.util.regex.Pattern; public abstract class ImageElement extends BasicElement implements Inline { @@ -42,7 +42,8 @@ protected void onImageReceived(I image) { } @Override - protected void generateLayout(LayoutData layoutData, R renderData) { + @ApiStatus.Internal + public void generateLayout(LayoutData layoutData, R renderData) { calculateDimensions(layoutData); super.generateLayout(layoutData, renderData); } diff --git a/src/main/java/dev/dediamondpro/minemark/elements/impl/LinkElement.java b/src/main/java/dev/dediamondpro/minemark/elements/impl/LinkElement.java index 356392a..1f77f76 100644 --- a/src/main/java/dev/dediamondpro/minemark/elements/impl/LinkElement.java +++ b/src/main/java/dev/dediamondpro/minemark/elements/impl/LinkElement.java @@ -7,6 +7,7 @@ import dev.dediamondpro.minemark.elements.Inline; import dev.dediamondpro.minemark.style.Style; import dev.dediamondpro.minemark.utils.MouseButton; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.xml.sax.Attributes; @@ -28,21 +29,17 @@ public LinkElement(@NotNull S style, @NotNull LayoutStyle layoutStyle, @Nullable } @Override - protected void onMouseClicked(MouseButton button, float mouseX, float mouseY) { + @ApiStatus.Internal + public void onMouseClickedInternal(MouseButton button, float mouseX, float mouseY) { if ((button == MouseButton.LEFT || button == MouseButton.MIDDLE) && isAnyInside(mouseX, mouseY) && link != null) { style.getLinkStyle().getBrowserProvider().browse(link); } else { - super.onMouseClicked(button, mouseX, mouseY); + super.onMouseClickedInternal(button, mouseX, mouseY); } } - - @Override - protected void beforeDraw(float xOffset, float yOffset, float mouseX, float mouseY, R renderData) { - super.beforeDraw(xOffset, yOffset, mouseX, mouseY, renderData); - } - @Override - protected void generateLayout(LayoutData layoutData, R renderData) { + @ApiStatus.Internal + public void generateLayout(LayoutData layoutData, R renderData) { positions.clear(); layoutData.addElementListener(this::onElementAdded); super.generateLayout(layoutData, renderData); diff --git a/src/main/java/dev/dediamondpro/minemark/elements/impl/TextElement.java b/src/main/java/dev/dediamondpro/minemark/elements/impl/TextElement.java index d5107e3..2df0d7f 100644 --- a/src/main/java/dev/dediamondpro/minemark/elements/impl/TextElement.java +++ b/src/main/java/dev/dediamondpro/minemark/elements/impl/TextElement.java @@ -17,12 +17,14 @@ public abstract class TextElement extends Element implements Inline { protected final HashMap lines = new HashMap<>(); + protected final String text; protected float baseLineHeight; protected float ascender; protected float descender; - public TextElement(@NotNull S style, @NotNull LayoutStyle layoutStyle, @Nullable Element parent, @NotNull String qName, @Nullable Attributes attributes) { + public TextElement(@NotNull String text, @NotNull S style, @NotNull LayoutStyle layoutStyle, @Nullable Element parent, @NotNull String qName, @Nullable Attributes attributes) { super(style, layoutStyle, parent, qName, attributes); + this.text = text; } @Override @@ -59,7 +61,7 @@ public void generateLayout(LayoutData layoutData, R renderData) { } @Override - public void draw(float xOffset, float yOffset, float mouseX, float mouseY, R renderData) { + public void drawInternal(float xOffset, float yOffset, float mouseX, float mouseY, R renderData) { boolean hovered = false; for (LayoutData.MarkDownElementPosition position : lines.keySet()) { if (position.isInside(mouseX, mouseY)) { diff --git a/src/main/java/dev/dediamondpro/minemark/elements/impl/formatting/HeadingElement.java b/src/main/java/dev/dediamondpro/minemark/elements/impl/formatting/HeadingElement.java index 3b480b7..37edd05 100644 --- a/src/main/java/dev/dediamondpro/minemark/elements/impl/formatting/HeadingElement.java +++ b/src/main/java/dev/dediamondpro/minemark/elements/impl/formatting/HeadingElement.java @@ -7,6 +7,7 @@ import dev.dediamondpro.minemark.elements.Inline; import dev.dediamondpro.minemark.style.HeadingLevelStyleConfig; import dev.dediamondpro.minemark.style.Style; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.xml.sax.Attributes; @@ -27,8 +28,8 @@ public HeadingElement(@NotNull S style, @NotNull LayoutStyle layoutStyle, @Nulla } @Override - protected void draw(float xOffset, float yOffset, float mouseX, float mouseY, R renderData) { - super.draw(xOffset, yOffset, mouseX, mouseY, renderData); + public void drawInternal(float xOffset, float yOffset, float mouseX, float mouseY, R renderData) { + super.drawInternal(xOffset, yOffset, mouseX, mouseY, renderData); if (headingStyle.hasDivider()) { drawDivider( xOffset + dividerPosition.getX(), yOffset + dividerPosition.getY(), @@ -38,7 +39,8 @@ protected void draw(float xOffset, float yOffset, float mouseX, float mouseY, R } @Override - protected void generateLayout(LayoutData layoutData, R renderData) { + @ApiStatus.Internal + public void generateLayout(LayoutData layoutData, R renderData) { if (layoutData.isLineOccupied()) { layoutData.nextLine(); } diff --git a/src/main/java/dev/dediamondpro/minemark/elements/ElementLoader.java b/src/main/java/dev/dediamondpro/minemark/elements/loaders/ElementLoader.java similarity index 79% rename from src/main/java/dev/dediamondpro/minemark/elements/ElementLoader.java rename to src/main/java/dev/dediamondpro/minemark/elements/loaders/ElementLoader.java index 2f00ca2..3f6a4f1 100644 --- a/src/main/java/dev/dediamondpro/minemark/elements/ElementLoader.java +++ b/src/main/java/dev/dediamondpro/minemark/elements/loaders/ElementLoader.java @@ -1,7 +1,8 @@ -package dev.dediamondpro.minemark.elements; +package dev.dediamondpro.minemark.elements.loaders; import dev.dediamondpro.minemark.LayoutStyle; +import dev.dediamondpro.minemark.elements.Element; import dev.dediamondpro.minemark.style.Style; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/dev/dediamondpro/minemark/elements/loaders/TextElementLoader.java b/src/main/java/dev/dediamondpro/minemark/elements/loaders/TextElementLoader.java new file mode 100644 index 0000000..8178693 --- /dev/null +++ b/src/main/java/dev/dediamondpro/minemark/elements/loaders/TextElementLoader.java @@ -0,0 +1,13 @@ +package dev.dediamondpro.minemark.elements.loaders; + + +import dev.dediamondpro.minemark.LayoutStyle; +import dev.dediamondpro.minemark.elements.Element; +import dev.dediamondpro.minemark.style.Style; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.xml.sax.Attributes; + +public interface TextElementLoader { + Element get(@NotNull String text, S style, LayoutStyle layoutStyle, @Nullable Element parent, @NotNull String qName, @Nullable Attributes attributes); +}