style support for html-defined menus
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2025-05-05 19:32:36 -04:00
parent f1b27c631b
commit 72673ba08c
6 changed files with 619 additions and 7 deletions

View File

@ -1 +1,7 @@
<p>Hello!</p>
<style>
testClass {
margin-top: "50px";
margin-left: "50px";
}
</style>
<p class="testClass">Hello!</p>

View File

@ -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

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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<ElementStyling> styles = new LinkedList<ElementStyling>();
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<ElementStyling> 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());
}
}
}
}

View File

@ -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<String,ElementStyling> classStyleMap = new HashMap<String,ElementStyling>();
/**
* 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);
}
}
}