diff --git a/assets/Data/menu/npcintro.html b/assets/Data/menu/npcintro.html index e5342827..d74924c0 100644 --- a/assets/Data/menu/npcintro.html +++ b/assets/Data/menu/npcintro.html @@ -1 +1,7 @@ -

Hello!

\ No newline at end of file + +

Hello!

\ No newline at end of file diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 8a42833b..42865f87 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1682,6 +1682,7 @@ Block pathing work Scaffolding for structure scanning service Add JSoup dependency Proof of concept of loading html to define ui +Styling support for html-defined menus diff --git a/src/main/java/electrosphere/client/ui/menu/dialog/DialogMenuGenerator.java b/src/main/java/electrosphere/client/ui/menu/dialog/DialogMenuGenerator.java index 3906e6a5..6d44a952 100644 --- a/src/main/java/electrosphere/client/ui/menu/dialog/DialogMenuGenerator.java +++ b/src/main/java/electrosphere/client/ui/menu/dialog/DialogMenuGenerator.java @@ -60,9 +60,7 @@ public class DialogMenuGenerator { String content = FileUtils.getAssetFileAsString(path); Document doc = Jsoup.parseBodyFragment(content); Node bodyNode = doc.getElementsByTag("body").first(); - for(Node node : bodyNode.childNodes()){ - container.addChild(HtmlParser.recursivelyParseChildren(node)); - } + container.addChild(HtmlParser.parseJSoup(bodyNode)); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); diff --git a/src/main/java/electrosphere/client/ui/parsing/ElementStyling.java b/src/main/java/electrosphere/client/ui/parsing/ElementStyling.java new file mode 100644 index 00000000..fdd2c781 --- /dev/null +++ b/src/main/java/electrosphere/client/ui/parsing/ElementStyling.java @@ -0,0 +1,232 @@ +package electrosphere.client.ui.parsing; + +import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaAlignment; +import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaFlexDirection; +import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaJustification; +import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaOverflow; +import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaPositionType; +import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaWrap; + +/** + * Contains the styling data for an element + */ +public class ElementStyling { + + /** + * Width in pixels + */ + Integer width; + + /** + * Height in pixels + */ + Integer height; + + /** + * Margin + */ + Integer marginTop; + Integer marginBottom; + Integer marginLeft; + Integer marginRight; + + + /** + * Padding + */ + Integer paddingTop; + Integer paddingRight; + Integer paddingBottom; + Integer paddingLeft; + + /** + * Position type (static, relative, absolute) + */ + YogaPositionType position; + + + /** + * Alignment self type + */ + YogaAlignment alignSelf; + + + /** + * Flex direction + */ + YogaFlexDirection flexDirection; + + /** + * Justification + */ + YogaJustification justification; + + /** + * Item alignment + */ + YogaAlignment alignItems; + + /** + * Content alignment + */ + YogaAlignment alignContent; + + /** + * Wrap behavior + */ + YogaWrap wrap; + + /** + * Overflow behavior + */ + YogaOverflow overflow; + + public Integer getWidth() { + return width; + } + + public void setWidth(Integer width) { + this.width = width; + } + + public Integer getHeight() { + return height; + } + + public void setHeight(Integer height) { + this.height = height; + } + + public Integer getMarginTop() { + return marginTop; + } + + public void setMarginTop(Integer marginTop) { + this.marginTop = marginTop; + } + + public Integer getMarginBottom() { + return marginBottom; + } + + public void setMarginBottom(Integer marginBottom) { + this.marginBottom = marginBottom; + } + + public Integer getMarginLeft() { + return marginLeft; + } + + public void setMarginLeft(Integer marginLeft) { + this.marginLeft = marginLeft; + } + + public Integer getMarginRight() { + return marginRight; + } + + public void setMarginRight(Integer marginRight) { + this.marginRight = marginRight; + } + + public Integer getPaddingTop() { + return paddingTop; + } + + public void setPaddingTop(Integer paddingTop) { + this.paddingTop = paddingTop; + } + + public Integer getPaddingRight() { + return paddingRight; + } + + public void setPaddingRight(Integer paddingRight) { + this.paddingRight = paddingRight; + } + + public Integer getPaddingBottom() { + return paddingBottom; + } + + public void setPaddingBottom(Integer paddingBottom) { + this.paddingBottom = paddingBottom; + } + + public Integer getPaddingLeft() { + return paddingLeft; + } + + public void setPaddingLeft(Integer paddingLeft) { + this.paddingLeft = paddingLeft; + } + + public YogaPositionType getPosition() { + return position; + } + + public void setPosition(YogaPositionType position) { + this.position = position; + } + + public YogaFlexDirection getFlexDirection() { + return flexDirection; + } + + public void setFlexDirection(YogaFlexDirection flexDirection) { + this.flexDirection = flexDirection; + } + + public YogaJustification getJustification() { + return justification; + } + + public void setJustification(YogaJustification justification) { + this.justification = justification; + } + + public YogaAlignment getAlignItems() { + return alignItems; + } + + public void setAlignItems(YogaAlignment alignItems) { + this.alignItems = alignItems; + } + + public YogaWrap getWrap() { + return wrap; + } + + public void setWrap(YogaWrap wrap) { + this.wrap = wrap; + } + + public YogaOverflow getOverflow() { + return overflow; + } + + public void setOverflow(YogaOverflow overflow) { + this.overflow = overflow; + } + + public YogaAlignment getAlignSelf() { + return alignSelf; + } + + public void setAlignSelf(YogaAlignment alignSelf) { + this.alignSelf = alignSelf; + } + + public YogaAlignment getAlignContent() { + return alignContent; + } + + public void setAlignContent(YogaAlignment alignContent) { + this.alignContent = alignContent; + } + + + + + +} diff --git a/src/main/java/electrosphere/client/ui/parsing/HtmlParser.java b/src/main/java/electrosphere/client/ui/parsing/HtmlParser.java index daa38047..f86ad21a 100644 --- a/src/main/java/electrosphere/client/ui/parsing/HtmlParser.java +++ b/src/main/java/electrosphere/client/ui/parsing/HtmlParser.java @@ -1,29 +1,50 @@ package electrosphere.client.ui.parsing; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + import org.jsoup.nodes.Node; import electrosphere.renderer.ui.elements.Div; import electrosphere.renderer.ui.elements.Label; +import electrosphere.renderer.ui.elementtypes.ContainerElement; import electrosphere.renderer.ui.elementtypes.Element; /** * Converts HTML to engine elements */ public class HtmlParser { + + /** + * Parses a jsoup node into in-engine ui elements + * @param jsoupNode The jsoup node + * @return The in-engine ui stack + */ + public static Element parseJSoup(Node jsoupNode){ + StyleAccumulator styleAccumulator = HtmlParser.parseStyles(jsoupNode); + Element rVal = HtmlParser.recursivelyParseChildren(jsoupNode, styleAccumulator); + return rVal; + } /** * Recursively parses a jsoup node into engine ui elements * @param jsoupNode The jsoup node * @return The engine ui elements */ - public static Element recursivelyParseChildren(Node jsoupNode){ + private static Element recursivelyParseChildren(Node jsoupNode, StyleAccumulator styleAccumulator){ String tag = jsoupNode.nodeName(); + Element rVal = null; switch(tag){ case "p": { rVal = Div.createDiv(); for(Node child : jsoupNode.childNodes()){ - ((Div)rVal).addChild(HtmlParser.recursivelyParseChildren(child)); + Element childEl = HtmlParser.recursivelyParseChildren(child, styleAccumulator); + if(childEl != null){ + ((Div)rVal).addChild(childEl); + } } } break; case "#text": { @@ -33,14 +54,153 @@ public class HtmlParser { case "div": { rVal = Div.createDiv(); for(Node child : jsoupNode.childNodes()){ - ((Div)rVal).addChild(HtmlParser.recursivelyParseChildren(child)); + Element childEl = HtmlParser.recursivelyParseChildren(child, styleAccumulator); + if(childEl != null){ + ((Div)rVal).addChild(childEl); + } } } break; + case "body": { + rVal = Div.createDiv(); + for(Node child : jsoupNode.childNodes()){ + Element childEl = HtmlParser.recursivelyParseChildren(child, styleAccumulator); + if(childEl != null){ + ((Div)rVal).addChild(childEl); + } + } + } break; + case "style": + //silently ignore + break; default: { throw new Error("Unsupported element type " + tag); } } + + //figure out the styling applied to this element + String classData = jsoupNode.attr("class"); + List styles = new LinkedList(); + if(classData.length() > 0){ + String[] cssClasses = classData.split(" "); + styles = Arrays.asList(cssClasses).stream().map((String cssClassName) -> {return styleAccumulator.getStyle(cssClassName);}).filter((ElementStyling elStyling) -> {return elStyling != null;}).collect(Collectors.toList()); + } + HtmlParser.applyStyling(rVal, styles); + return rVal; } + /** + * Determines all css styles to apply + * @param root The root node + * @return The style accumulator + */ + private static StyleAccumulator parseStyles(Node root){ + StyleAccumulator rVal = new StyleAccumulator(); + HtmlParser.recursivelyParseStyles(root, rVal); + return rVal; + } + + /** + * Recurses through the html tree to calculate styles to apply + * @param root The root node + * @param styleAccumulator The style accumulator + */ + private static void recursivelyParseStyles(Node root, StyleAccumulator styleAccumulator){ + String tag = root.nodeName(); + switch(tag){ + case "style": { + if(root.childNodes() == null || root.childNodes().size() < 1){ + throw new Error("Style tag has no content! " + root.outerHtml()); + } + styleAccumulator.parseCssString(root.childNodes().get(0).outerHtml()); + } break; + } + for(Node child : root.childNodes()){ + HtmlParser.recursivelyParseStyles(child, styleAccumulator); + } + } + + /** + * Applies a set of styles to an element + * @param el The element + * @param styles The styles + */ + private static void applyStyling(Element el, List styles){ + for(ElementStyling styleCurr : styles){ + + //width + height + if(styleCurr.getWidth() != null){ + el.setWidth(styleCurr.getWidth()); + } + if(styleCurr.getHeight() != null){ + el.setHeight(styleCurr.getHeight()); + } + + //margin + if(styleCurr.getMarginTop() != null){ + el.setMarginTop(styleCurr.getMarginTop()); + } + if(styleCurr.getMarginRight() != null){ + el.setMarginRight(styleCurr.getMarginRight()); + } + if(styleCurr.getMarginBottom() != null){ + el.setMarginBottom(styleCurr.getMarginBottom()); + } + if(styleCurr.getMarginLeft() != null){ + el.setMarginLeft(styleCurr.getMarginLeft()); + } + + //padding + if(styleCurr.getPaddingTop() != null){ + el.setPaddingTop(styleCurr.getPaddingTop()); + } + if(styleCurr.getPaddingRight() != null){ + el.setPaddingRight(styleCurr.getPaddingRight()); + } + if(styleCurr.getPaddingBottom() != null){ + el.setPaddingBottom(styleCurr.getPaddingBottom()); + } + if(styleCurr.getPaddingLeft() != null){ + el.setPaddingLeft(styleCurr.getPaddingLeft()); + } + + //align self + if(styleCurr.getAlignSelf() != null){ + el.setAlignSelf(styleCurr.getAlignSelf()); + } + + //flex direction + if(styleCurr.getFlexDirection() != null && el instanceof ContainerElement){ + ((ContainerElement)el).setFlexDirection(styleCurr.getFlexDirection()); + } + + //positioning + if(styleCurr.getPosition() != null){ + el.setPositionType(styleCurr.getPosition()); + } + + //justification + if(styleCurr.getJustification() != null && el instanceof ContainerElement){ + ((ContainerElement)el).setJustifyContent(styleCurr.getJustification()); + } + + //align items + if(styleCurr.getAlignItems() != null && el instanceof ContainerElement){ + ((ContainerElement)el).setAlignItems(styleCurr.getAlignItems()); + } + + //wrap + if(styleCurr.getWrap() != null && el instanceof ContainerElement){ + ((ContainerElement)el).setWrap(styleCurr.getWrap()); + } + + //overflow + if(styleCurr.getOverflow() != null && el instanceof ContainerElement){ + ((ContainerElement)el).setOverflow(styleCurr.getOverflow()); + } + + + } + } + } diff --git a/src/main/java/electrosphere/client/ui/parsing/StyleAccumulator.java b/src/main/java/electrosphere/client/ui/parsing/StyleAccumulator.java new file mode 100644 index 00000000..0c3e0d7a --- /dev/null +++ b/src/main/java/electrosphere/client/ui/parsing/StyleAccumulator.java @@ -0,0 +1,215 @@ +package electrosphere.client.ui.parsing; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import electrosphere.logger.LoggerInterface; +import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaAlignment; +import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaFlexDirection; +import electrosphere.renderer.ui.elementtypes.ContainerElement.YogaJustification; + +/** + * Accumulates css + */ +public class StyleAccumulator { + + /** + * The css capturing pattern + * Group 1 is the class name + * Group 2 is the styling on the class + */ + static Pattern cssPattern = Pattern.compile("([a-zA-Z0-9]+)[ ]*\\{([^{}]*)\\}"); + + /** + * Maps a css class to its corresponding style + */ + Map classStyleMap = new HashMap(); + + /** + * The name of the css class + * @param className The name of the css class + * @return The element styling for that class if it exists + */ + public ElementStyling getStyle(String className){ + return classStyleMap.get(className); + } + + /** + * Parses a css string + * @param content The string + */ + public void parseCssString(String content){ + if(content == null || content.equals("")){ + throw new Error("String is empty"); + } + Matcher matcher = cssPattern.matcher(content); + while(matcher.find()){ + String className = matcher.group(1); + String styleContent = matcher.group(2); + ElementStyling elementStyling = new ElementStyling(); + + //parse individual lines of the class + String[] styleElements = styleContent.split(";"); + for(String styleString : styleElements){ + String[] pair = styleString.split(":"); + + // + //error checking + if(pair.length == 1 && pair[0].trim().length() == 0){ + continue; + } + if(pair.length != 2){ + System.out.println("Skipping style pair that was parsed as " + pair.length); + if(pair.length > 0){ + int i = 0; + for(String pairVal : pair){ + System.out.println(i + ": " + pairVal); + i++; + } + } + System.out.println("Root line: " + styleString); + continue; + } + + // + //parse css data + String styleType = pair[0].trim(); + String styleValue = pair[1].trim(); + switch(styleType){ + case "height": { + String shortened = styleValue.replace("px", "").replace("\"", ""); + int intVal = Integer.parseInt(shortened); + elementStyling.setHeight(intVal); + } break; + case "width": { + String shortened = styleValue.replace("px", "").replace("\"", ""); + int intVal = Integer.parseInt(shortened); + elementStyling.setWidth(intVal); + } break; + case "margin-top": { + String shortened = styleValue.replace("px", "").replace("\"", ""); + int intVal = Integer.parseInt(shortened); + elementStyling.setMarginTop(intVal); + } break; + case "margin-right": { + String shortened = styleValue.replace("px", "").replace("\"", ""); + int intVal = Integer.parseInt(shortened); + elementStyling.setMarginRight(intVal); + } break; + case "margin-bottom": { + String shortened = styleValue.replace("px", "").replace("\"", ""); + int intVal = Integer.parseInt(shortened); + elementStyling.setMarginBottom(intVal); + } break; + case "margin-left": { + String shortened = styleValue.replace("px", "").replace("\"", ""); + int intVal = Integer.parseInt(shortened); + elementStyling.setMarginLeft(intVal); + } break; + case "padding-top": { + String shortened = styleValue.replace("px", "").replace("\"", ""); + int intVal = Integer.parseInt(shortened); + elementStyling.setPaddingTop(intVal); + } break; + case "padding-right": { + String shortened = styleValue.replace("px", "").replace("\"", ""); + int intVal = Integer.parseInt(shortened); + elementStyling.setPaddingRight(intVal); + } break; + case "padding-bottom": { + String shortened = styleValue.replace("px", "").replace("\"", ""); + int intVal = Integer.parseInt(shortened); + elementStyling.setPaddingBottom(intVal); + } break; + case "padding-left": { + String shortened = styleValue.replace("px", "").replace("\"", ""); + int intVal = Integer.parseInt(shortened); + elementStyling.setPaddingLeft(intVal); + } break; + case "align-items": { + switch(styleValue){ + case "auto": { + elementStyling.setAlignItems(YogaAlignment.Auto); + } break; + case "baseline": { + elementStyling.setAlignItems(YogaAlignment.Baseline); + } break; + case "center": { + elementStyling.setAlignItems(YogaAlignment.Center); + } break; + case "flex-start": { + elementStyling.setAlignItems(YogaAlignment.Start); + } break; + case "flex-end": { + elementStyling.setAlignItems(YogaAlignment.End); + } break; + case "around": { + elementStyling.setAlignItems(YogaAlignment.Around); + } break; + case "between": { + elementStyling.setAlignItems(YogaAlignment.Between); + } break; + case "stretch": { + elementStyling.setAlignItems(YogaAlignment.Stretch); + } break; + default: { + LoggerInterface.loggerUI.WARNING("Unsupported align-items type " + styleValue); + } break; + } + } break; + case "flex-direction": { + switch(styleValue){ + case "column": { + elementStyling.setFlexDirection(YogaFlexDirection.Column); + } break; + case "column-reverse": { + elementStyling.setFlexDirection(YogaFlexDirection.Column_Reverse); + } break; + case "row": { + elementStyling.setFlexDirection(YogaFlexDirection.Row); + } break; + case "row-reverse": { + elementStyling.setFlexDirection(YogaFlexDirection.Row_Reverse); + } break; + default: { + LoggerInterface.loggerUI.WARNING("Unsupported flex-direction type " + styleValue); + } break; + } + } break; + case "justify-content": { + switch(styleValue){ + case "center": { + elementStyling.setJustification(YogaJustification.Center); + } break; + case "flex-start": { + elementStyling.setJustification(YogaJustification.Start); + } break; + case "flex-end": { + elementStyling.setJustification(YogaJustification.End); + } break; + case "space-around": { + elementStyling.setJustification(YogaJustification.Around); + } break; + case "space-between": { + elementStyling.setJustification(YogaJustification.Between); + } break; + case "space-evenly": { + elementStyling.setJustification(YogaJustification.Evenly); + } break; + default: { + LoggerInterface.loggerUI.WARNING("Unsupported justify-content type " + styleValue); + } break; + } + } break; + default: { + LoggerInterface.loggerUI.WARNING("Unsupported style type " + styleType); + } break; + } + } + this.classStyleMap.put(className,elementStyling); + } + } + +}