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 Scaffolding for structure scanning service
Add JSoup dependency Add JSoup dependency
Proof of concept of loading html to define ui 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); String content = FileUtils.getAssetFileAsString(path);
Document doc = Jsoup.parseBodyFragment(content); Document doc = Jsoup.parseBodyFragment(content);
Node bodyNode = doc.getElementsByTag("body").first(); Node bodyNode = doc.getElementsByTag("body").first();
for(Node node : bodyNode.childNodes()){ container.addChild(HtmlParser.parseJSoup(bodyNode));
container.addChild(HtmlParser.recursivelyParseChildren(node));
}
} catch (IOException e) { } catch (IOException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); 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,9 +1,15 @@
package electrosphere.client.ui.parsing; 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 org.jsoup.nodes.Node;
import electrosphere.renderer.ui.elements.Div; import electrosphere.renderer.ui.elements.Div;
import electrosphere.renderer.ui.elements.Label; import electrosphere.renderer.ui.elements.Label;
import electrosphere.renderer.ui.elementtypes.ContainerElement;
import electrosphere.renderer.ui.elementtypes.Element; import electrosphere.renderer.ui.elementtypes.Element;
/** /**
@ -11,19 +17,34 @@ import electrosphere.renderer.ui.elementtypes.Element;
*/ */
public class HtmlParser { 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 * Recursively parses a jsoup node into engine ui elements
* @param jsoupNode The jsoup node * @param jsoupNode The jsoup node
* @return The engine ui elements * @return The engine ui elements
*/ */
public static Element recursivelyParseChildren(Node jsoupNode){ private static Element recursivelyParseChildren(Node jsoupNode, StyleAccumulator styleAccumulator){
String tag = jsoupNode.nodeName(); String tag = jsoupNode.nodeName();
Element rVal = null; Element rVal = null;
switch(tag){ switch(tag){
case "p": { case "p": {
rVal = Div.createDiv(); rVal = Div.createDiv();
for(Node child : jsoupNode.childNodes()){ 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; } break;
case "#text": { case "#text": {
@ -33,14 +54,153 @@ public class HtmlParser {
case "div": { case "div": {
rVal = Div.createDiv(); rVal = Div.createDiv();
for(Node child : jsoupNode.childNodes()){ 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; } 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: { default: {
throw new Error("Unsupported element type " + tag); 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; 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);
}
}
}