Hi guys,
here is an example of using the SerializableClass
annotation. In some cases I need to encapsulate the configuration parameters of a certain application in a dedicated class. These parameters I then need to pass them to a Builder
class in order to build objects or perform some calculations. Taking advantage of the functionality of the SDK and AbstractControls
, we could take a cue from Unity, using an annotation to be able to access fields in a class that does not extend the AbstractControl
class. This way I can keep Parameters and Builders separate from AbstractControls
that act as Editors.
- Main.java
package com.test.ui.model;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import com.jme3.app.SimpleApplication;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.control.Control;
import com.jme3.system.AppSettings;
import jme.capdevon.ui.annotations.ButtonProperty;
import jme.capdevon.ui.annotations.SerializableClass;
public class Test_SerializableClass extends SimpleApplication {
/**
* @param args
*/
public static void main(String[] args) {
AppSettings settings = new AppSettings(true);
settings.setResolution(640, 480);
Test_SerializableClass app = new Test_SerializableClass();
app.setShowSettings(false);
app.setPauseOnLostFocus(false);
app.setSettings(settings);
app.start();
}
@Override
public void simpleInitApp() {
viewPort.setBackgroundColor(new ColorRGBA(0.5f, 0.6f, 0.7f, 1.0f));
rootNode.addControl(new TreeEditorComponent());
for (int i = 0; i < rootNode.getNumControls(); i++) {
Control control = rootNode.getControl(i);
buildUIPanel(control);
}
}
private JPanel buildUIPanel(Control control) {
JPanel container = new JPanel();
System.out.println(control.getClass());
Field[] fields = FieldUtils.getAllFields(control.getClass());
for (Field field : fields) {
System.out.println("\t" + field);
addUIComponent(control, field, container);
}
List<Method> methods = MethodUtils.getMethodsListWithAnnotation(control.getClass(), ButtonProperty.class);
for (Method method : methods) {
ButtonProperty bp = method.getAnnotation(ButtonProperty.class);
JButton button = new JButton(bp.name());
button.setToolTipText(bp.tooltip());
button.addActionListener(e -> this.enqueue(() -> {
try {
method.invoke(control);
} catch (ReflectiveOperationException ex) {
ex.printStackTrace();
}
}));
container.add(new JLabel(""), "align righ");
container.add(button, "wrap, pushx, growx");
}
return container;
}
private void addUIComponent(Object bean, Field field, JPanel panel) {
String propertyName = field.getName();
Class<?> fieldType = field.getType();
if (fieldType.getAnnotation(SerializableClass.class) != null) {
System.out.println("\t--SerializableClass: " + fieldType);
Object value = getValueOf(propertyName, bean);
Field[] fields = FieldUtils.getAllFields(value.getClass());
for (Field fd : fields) {
System.out.println("\t\t--" + fd);
addUIComponent(value, fd, panel);
}
} else {
JComponent aComponent = ...;
panel.add(new JLabel(propertyName), "align righ");
panel.add(aComponent, "wrap, pushx, growx");
}
}
private static Object getValueOf(String propertyName, Object bean) {
try {
PropertyDescriptor pd = new PropertyDescriptor(propertyName, bean.getClass());
return pd.getReadMethod().invoke(bean);
} catch (ReflectiveOperationException | IntrospectionException e) {
throw new RuntimeException(e);
}
}
}
- TreeEditorComponent.java
public class TreeEditorComponent extends AbstractControl {
private TreeSettings buildSettings = new TreeSettings();
@ButtonProperty(name="Generate", tooltip="Procedural Vegetation Placement")
public void generateTrees() {
TreeBuilder builder = new TreeBuilder();
builder.buildTrees(buildSettings);
}
@Override
protected void controlUpdate(float tpf) {
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
public TreeSettings getBuildSettings() {
return buildSettings;
}
public void setBuildSettings(TreeSettings buildSettings) {
this.buildSettings = buildSettings;
}
}
- TreeBuilder.java
public class TreeBuilder {
public void buildTrees(TreeSettings settings) {
System.out.println("Generate trees with settings: " + settings);
}
}
- TreeSettings.java
@SerializableClass
public class TreeSettings {
private float cellSize = 1f;
private float cellHeight = 1.5f;
private float minTraversableHeight = 7.5f;
private float maxTraversableStep = 1f;
private float maxTraversableSlope = 48.0f;
// getters & setters
}
- SerializableClass.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SerializableClass {
}
Here is the output:
class com.test.ui.model.TreeEditorComponent
private com.test.ui.model.TreeSettings com.test.ui.model.TreeEditorComponent.buildSettings
--SerializableClass: class com.test.ui.model.TreeSettings
--private float com.test.ui.model.TreeSettings.cellSize
--private float com.test.ui.model.TreeSettings.cellHeight
--private float com.test.ui.model.TreeSettings.minTraversableHeight
--private float com.test.ui.model.TreeSettings.maxTraversableStep
--private float com.test.ui.model.TreeSettings.maxTraversableSlope
protected boolean com.jme3.scene.control.AbstractControl.enabled
protected com.jme3.scene.Spatial com.jme3.scene.control.AbstractControl.spatial
Here is the result with java-swing
I hope it is useful as an example. Please let me know what you think about it.
@rickard @tonihele
Thank you.