Extending the functionality of Kanzi Engine with Java¶
With a Kanzi Engine plugin you can extend the functionality of Kanzi Engine and set how Kanzi Studio shows the custom content you create:
Create your own nodes and property types. See Creating custom nodes and property types.
Create your own message types that users of Kanzi Studio can use either as triggers or actions. See Creating custom message types.
Set how Kanzi Studio shows the property types you create in your Kanzi Engine plugin. See Setting custom property types.
Set the default value of any property type that is defined in the node on which you base your node. See Setting the default value of a property type.
Create your own Actions. See Creating custom actions.
Creating custom nodes and property types¶
In a Java Kanzi Engine plugin you can create custom nodes with custom property types. You can set how Kanzi Studio shows and lets users interact with these nodes.
To create a custom node with custom property types:
Create the node class and give it a metaclass to pass to Kanzi Studio information about this class:
public static class MyNode extends Node2D { @Metadata @EditorInfo( // Set the name that you want to use for your node in Kanzi Studio. displayName = "My Node", // Set the tooltip that you want to use in the Kanzi Studio Create menu. tooltip = "This example custom node uses custom properties.", customAttributes = { // Add the Horizontal Alignment and Vertical Alignment properties to the node. @EditorInfoAttribute(key = "AutomaticallyAddedProperties", value = "Node.HorizontalAlignment, Node.VerticalAlignment"), // Add the State Manager property as a frequently used property, // which users can add by clicking the + next to the property name. @EditorInfoAttribute( key = "FrequentlyAddedProperties", value = "Node.StateManager"), // Let the user add the Focusable property using the Add Property // context menu or the context tab of the Add Properties window. @EditorInfoAttribute(key = "ContextProperties", value = "Node.Focusable"), }) public static Metaclass metaclass = new Metaclass("MyPlugin.MyNode"); static public ObjectRef<MyNode> create(Domain domain, String name) { return Node2D.createDerived(domain, name, metaclass); } private MyNode(Domain domain, long nativeNode, Metaclass metaclass) { super(domain, nativeNode, metaclass); }
class MyNode private constructor( domain: Domain, nativeNode: Long, metaclass: Metaclass ) : Node2D(domain, nativeNode, metaclass) { companion object { @Metadata @EditorInfo( displayName = "My Node", tooltip = "This example custom node uses custom properties.", customAttributes = [EditorInfoAttribute( key = "AutomaticallyAddedProperties", value = "Node.HorizontalAlignment, Node.VerticalAlignment" ), EditorInfoAttribute( key = "FrequentlyAddedProperties", value = "Node.StateManager" ), EditorInfoAttribute(key = "ContextProperties", value = "Node.Focusable")] ) var metaclass = Metaclass("MyPlugin.MyNode") @JvmStatic fun create(domain: Domain?, name: String?): ObjectRef<MyNode> { return createDerived(domain, name, metaclass) }
Create the custom property types and their metadata.
For example:
String property with a one-line text editor:
// Creates a string property. // This example does not set the editor. If you do not set the editor, Kanzi Studio uses // the default editor for this property type. @Metadata @EditorInfo(displayName = "String") public static final PropertyType<String> StringProperty = new PropertyType<>("MyPlugin.String", String.class);
// Creates a string property. // This example does not set the editor. If you do not set the editor, Kanzi Studio uses // the default editor for this property type. @Metadata @EditorInfo(displayName = "String") val StringProperty = PropertyType( "MyPlugin.String", String::class.java )
String reference to a 3D node the user can select from a dropdown menu:
// Creates a string reference to a node. @Metadata @EditorInfo(displayName = "Node Reference by String", // When you set valueProvider to ProjectObject:Node3D, // Kanzi Studio automatically uses the Node 3D selector editor. valueProvider = "ProjectObject:Node3D") public static final PropertyType<String> NodeRefByStringProperty = new PropertyType<>("MyPlugin.NodeRefByString", String.class);
// Creates a string reference to a node. @Metadata @EditorInfo( displayName = "Node Reference by String", valueProvider = "ProjectObject:Node3D" ) val NodeRefByStringProperty = PropertyType( "MyPlugin.NodeRefByString", String::class.java )
String reference to a prefab the user can select from a dropdown menu:
// Creates a string reference to a prefab resource. @Metadata @EditorInfo(displayName = "Prefab Reference by String", // When you set valueProvider to ProjectObject:PrefabTemplate, // Kanzi Studio automatically uses the Prefab template selector editor. valueProvider = "ProjectObject:PrefabTemplate") public static final PropertyType<String> PrefabRefByStringProperty = new PropertyType<>("MyPlugin.PrefabRefByString", String.class);
// Creates a string reference to a prefab resource. @Metadata @EditorInfo( displayName = "Prefab Reference by String", valueProvider = "ProjectObject:PrefabTemplate" ) val PrefabRefByStringProperty = PropertyType( "MyPlugin.PrefabRefByString", String::class.java )
Resource reference to a prefab resource the user can select from a dropdown menu:
// Creates a resource reference to a prefab resource. @Metadata @EditorInfo(displayName = "Prefab Reference by Resource", // When you set valueProvider to ProjectObject:PrefabTemplate, // Kanzi Studio automatically uses the Prefab template selector editor. valueProvider = "ProjectObject:PrefabTemplate") public static final PropertyType<Resource> PrefabRefByResourceProperty = new PropertyType<>("MyPlugin.MaterialRefByResource", Resource.class);
// Creates a resource reference to a prefab resource. @Metadata @EditorInfo( displayName = "Prefab Reference by Resource", valueProvider = "ProjectObject:PrefabTemplate" ) val PrefabRefByResourceProperty = PropertyType( "MyPlugin.MaterialRefByResource", Resource::class.java )
Resource reference to a material resource the user can select from a dropdown menu:
// Creates a resource reference to a material resource. @Metadata @EditorInfo(displayName = "Material Reference by Resource", // When you set valueProvider to ProjectObject:Material, // Kanzi Studio automatically uses the Material dropdown editor. valueProvider = "ProjectObject:Material") public static final PropertyType<Resource> MaterialRefByResourceProperty = new PropertyType<>("MyPlugin.MaterialRefByResource", Resource.class);
// Creates a resource reference to a material resource. @Metadata @EditorInfo( displayName = "Material Reference by Resource", valueProvider = "ProjectObject:Material" ) val MaterialRefByResourceProperty = PropertyType( "MyPlugin.MaterialRefByResource", Resource::class.java )
Register the class with the plugin by adding to your plugin class
registerClasses
method:getDomain().getMetaclassRegistry().registerClass(MyNode.class);
domain.metaclassRegistry.registerClass(MyNode::class.java)
Build and install your plugin. See Adding a Kanzi Engine plugin to a Kanzi Studio project.
In Kanzi Studio in the Library > Kanzi Engine Plugins select your plugin. In the Properties you can see the custom property types.
In the Node Tree press Alt and right-click the node where you want to create your node and select your node.
(Optional) By default Kanzi Studio adds custom properties as frequently used properties to the nodes of the type for which you created the property type. To set the node types for which Kanzi Studio suggests the property, and whether Kanzi Studio adds the property automatically or lets the user add it, set the
@EditorInfo
host
attribute. See host.
Creating custom message types¶
You can create and configure custom message types that you use to add functionality to your application. For example, use a message type to make your node type react to an event.
For every custom message type that you define, Kanzi Studio creates a message action and a message trigger type, which let you dispatch and react to the message type, respectively.
When you are using Kanzi Connect you can use custom message types to get information about a Kanzi Connect server or service that your application uses.
Creating custom message triggers¶
In a Kanzi Engine Java plugin, you can create a custom message type that Kanzi Studio shows as a message trigger. A message type enables you to subscribe to notifications when a certain event occurs.
For example, to allow the Kanzi Studio user to add a message trigger called On Fuel Level Changed that is set off when the fuel level of a custom node called Fuel Indicator changes:
Inside your node class, create a class to hold arguments of your message type:
// Represents message arguments of FuelIndicator.OnFuelLevelChanged message type. @Metadata public static class OnFuelLevelChangedMessageArguments extends MessageArguments { // Define the metaclass. @Metadata public static Metaclass metaclass = new Metaclass("FuelIndicator.OnFuelLevelChangedMessageArguments"); // Define the property type to hold the fuel level. @Metadata public static PropertyType<Integer> FuelLevelProperty = new PropertyType<>(Integer.class); // Define constructors. // A constructor with a long argument, which Kanzi uses to wrap native arguments. public OnFuelLevelChangedMessageArguments(long nativeObject) { super(nativeObject); } // Any additional constructors for you to use when dispatching messages. public OnFuelLevelChangedMessageArguments() { super(); } }
// Represents message arguments of FuelIndicator.OnFuelLevelChanged message type. @Metadata class OnFuelLevelChangedMessageArguments : MessageArguments { // Define constructors. // A constructor with a long argument, which Kanzi uses to wrap native arguments. constructor(nativeObject: Long) : super(nativeObject) // Any additional constructors for you to use when dispatching messages. constructor() : super() companion object { // Define the metaclass. @Metadata var metaclass = Metaclass("FuelIndicator.OnFuelLevelChangedMessageArguments") // Define the property type to hold the fuel level. @Metadata var FuelLevelProperty = PropertyType(Int::class.java) } }
Define your message type:
// The message type of the FuelIndicator.OnFuelLevelChanged message. @Metadata @EditorInfo( // Set the name of the message the way you want to show it in Kanzi Studio. displayName = "On Fuel Level Changed", // Set the tooltip for the message. tooltip = "Fuel Indicator sets off this trigger when its level changes.", // Do not show the message in the trigger actions dropdown menu. // You show the message as a trigger. sendable = "False") public static final MessageType<OnFuelLevelChangedMessageArguments> OnFuelLevelChangedMessage = new MessageType<>( "Message.FuelIndicator.OnFuelLevelChanged", MessageRouting.MessageRoutingBubbling, OnFuelLevelChangedMessageArguments.class);
// The message type of the FuelIndicator.OnFuelLevelChanged message. @Metadata @EditorInfo( displayName = "On Fuel Level Changed", tooltip = "Fuel Indicator sets off this trigger when its level changes.", sendable = "False" ) val OnFuelLevelChangedMessage = MessageType( "Message.FuelIndicator.OnFuelLevelChanged", MessageRouting.MessageRoutingBubbling, OnFuelLevelChangedMessageArguments::class.java )
Create a function that dispatches the message when the fuel level changes:
// Sends a FuelIndicator.OnFuelLevelChanged message. void onFuelLevelChanged(int fuelLevel) { OnFuelLevelChangedMessageArguments messageArguments = new OnFuelLevelChangedMessageArguments(); messageArguments.setArgument( OnFuelLevelChangedMessageArguments.FuelLevelProperty, fuelLevel); dispatchMessage(OnFuelLevelChangedMessage, messageArguments); }
// Sends a FuelIndicator.OnFuelLevelChanged message. fun onFuelLevelChanged(fuelLevel: Int) { val messageArguments = OnFuelLevelChangedMessageArguments() messageArguments.setArgument( OnFuelLevelChangedMessageArguments.FuelLevelProperty, fuelLevel ) dispatchMessage(OnFuelLevelChangedMessage, messageArguments) }
Creating custom message actions¶
In a Kanzi Engine Java plugin, you can create a custom message type that Kanzi Studio shows as an action.
For example, to allow the Kanzi Studio user to add to a trigger an action called Fuel Indicator: Update Level which shows the level of a custom node called Fuel Indicator in a Text Block 3D node:
Inside your node class, create a class to hold arguments of your message type:
// Represents the message arguments of the FuelIndicator.UpdateLevel message. @Metadata public static class FuelIndicatorUpdateLevelMessageArguments extends MessageArguments { // Define metaclass. @Metadata public static Metaclass metaclass = new Metaclass("FuelIndicator.UpdateLevelMessageArguments"); // The property type which represents the value of the Fuel Indicator node. @Metadata public static PropertyType<Integer> FuelIndicatorLevelProperty = new PropertyType<>(Integer.class); // Returns the value of the Fuel Indicator message argument. public int getFuelIndicatorLevel() { return getArgument(FuelIndicatorLevelProperty); } // Define constructors. // A constructor with a long argument, which Kanzi uses to wrap native arguments. public FuelIndicatorUpdateLevelMessageArguments(long nativeObject) { super(nativeObject); } // Any additional constructors for you to use when dispatching messages. public FuelIndicatorUpdateLevelMessageArguments() { super(); } }
// Represents the message arguments of the FuelIndicator.UpdateLevel message. @Metadata class FuelIndicatorUpdateLevelMessageArguments : MessageArguments { // Returns the value of the Fuel Indicator message argument. val fuelIndicatorLevel: Int get() = getArgument(FuelIndicatorLevelProperty) // Define constructors. // A constructor with a long argument, which Kanzi uses to wrap native arguments. constructor(nativeObject: Long) : super(nativeObject) // Any additional constructors for you to use when dispatching messages. constructor() : super() companion object { // Define metaclass. @Metadata var metaclass = Metaclass("FuelIndicator.UpdateLevelMessageArguments") // The property type which represents the value of the Fuel Indicator node. @Metadata var FuelIndicatorLevelProperty = PropertyType(Int::class.java) } }
Define your message type:
// The message type of the FuelIndicator.UpdateLevel message. @Metadata @EditorInfo( // Set the name of the message the way you want to show it in Kanzi Studio. displayName = "Fuel Indicator: Update Level", // Do not show the message as a trigger. // You show the message in the trigger actions dropdown menu. listenable = "False") public static final MessageType<FuelIndicatorUpdateLevelMessageArguments> FuelIndicatorUpdateLevelMessage = new MessageType<>("Message.FuelIndicator.UpdateLevel", MessageRouting.MessageRoutingBubbling, FuelIndicatorUpdateLevelMessageArguments.class);
// The message type of the FuelIndicator.UpdateLevel message. @Metadata @EditorInfo(displayName = "Fuel Indicator: Update Level", listenable = "False") val FuelIndicatorUpdateLevelMessage = MessageType( "Message.FuelIndicator.UpdateLevel", MessageRouting.MessageRoutingBubbling, FuelIndicatorUpdateLevelMessageArguments::class.java )
In the
initialize()
function of your node class, register the message handler for theFuelIndicator.UpdateLevel
message:// The message handler for the FuelIndicator.UpdateLevel message. // Shows the level of the Fuel Indicator in a Text Block 3D node. @Override protected void initialize() { super.initialize(); addMessageHandler(FuelIndicatorUpdateLevelMessage, messageArguments -> { // Get the level of the Fuel Indicator node. mCurrentLevel += messageArguments.getFuelIndicatorLevel(); // Show the level of the Fuel Indicator node in a Text Block 3D node. mTextBlock.get().setText(Integer.toString(mCurrentLevel)); }); }
// The message handler for the FuelIndicator.UpdateLevel message. // Shows the level of the Fuel Indicator in a Text Block 3D node. override fun initialize() { super.initialize() addMessageHandler( FuelIndicatorUpdateLevelMessage ) { messageArguments: FuelIndicatorUpdateLevelMessageArguments -> // Get the level of the Fuel Indicator node. mCurrentLevel += messageArguments.fuelIndicatorLevel // Show the level of the Fuel Indicator node in a Text Block 3D node. mTextBlock.get().text = Integer.toString(mCurrentLevel) } }
Creating custom node components¶
A custom node component is an isolated piece of logic, which you implement in a Kanzi Engine plugin and you can attach to any node, in order to add custom functionality to that node. For example, you can print the value of a property to the Kanzi Log every time the value of that property changes or define animation playback with variable speed.
For example, to create a custom node component called Log Property Value that prints the value of a float property type to the Kanzi Log every time the value of that property changes:
Create the class that represents the custom node component and define its metadata:
// Represents the Log Property Value custom node component. public static class LogPropertyValueComponent extends NodeComponent { // Create the metadata for the Log Property Value custom node component. @Metadata @EditorInfo(displayName = "Log Property Value Component", tooltip = "Writes the value of a specified property to Kanzi Log every time that property changes.", category = "Custom Node Components") public static Metaclass metaclass = new Metaclass("MyPlugin.LogPropertyValueComponent"); static public ObjectRef<LogPropertyValueComponent> create(Domain domain, String name) { return NodeComponent.createDerived(domain, name, metaclass); } protected LogPropertyValueComponent(Domain domain, long handle, Metaclass metaclass) { super(domain, handle, metaclass); }
// Represents the Log Property Value custom node component. class LogPropertyValueComponent private constructor( domain: Domain?, handle: Long, metaclass: Metaclass? ) : NodeComponent(domain, handle, metaclass) { companion object { // Create the metadata for the Log Property Value custom node component. @Metadata @EditorInfo( displayName = "Log Property Value Component", tooltip = "Writes the value of a specified property to Kanzi Log every time that property changes.", category = "Custom Node Components" ) var metaclass = Metaclass("MyPlugin.LogPropertyValueComponent") // Declare the metadata for the Logged Property Type property type that you use to set which // property the Log Property Value custom node component logs. @Metadata @EditorInfo( displayName = "Logged Property Type", tooltip = "The property type whose value you want the node component to log.", valueProvider = "PropertyType" ) val LoggedPropertyTypeProperty = PropertyType( "LogPropertyValueComponent.LoggedPropertyType", String::class.java ) @JvmStatic fun create(domain: Domain?, name: String?): ObjectRef<LogPropertyValueComponent> { return createDerived(domain, name, metaclass) }
Implement a binding to track changes in property and log its value:
@Override protected void attachOverride() { super.attachOverride(); // Get the name of the property type that you want to log. String loggedPropertyTypeName = getProperty(LoggedPropertyTypeProperty); if (loggedPropertyTypeName.isEmpty()) { Log.info( "Use the Logged Property Type property to set which property type you want to log."); return; } // Get the property type that you want to log and check that it is valid. DynamicPropertyType<Float> loggedPropertyType = new DynamicPropertyType<>(loggedPropertyTypeName, Float.class); if (!loggedPropertyType.isValid()) { Log.warning( "LogPropertyValueComponent cannot find the specified property type with float data type."); return; } // Add the notification handler for the property type that you want to log. AbstractBinding binding = BindingBuilder.createOneWay(".", loggedPropertyType); binding.addProcessor(CallbackBindingProcessor.create(getDomain(), value -> { logPropertyValue(loggedPropertyTypeName, (Float) value); return false; })); getNode().setBinding(binding); // Print the initial value of the property type that you want to log. Float initialPropertyValue = getNode().getOptionalProperty(loggedPropertyType); if (initialPropertyValue != null) { logPropertyValue(loggedPropertyTypeName, initialPropertyValue); } } private void logPropertyValue(String loggedPropertyTypeName, Float value) { Log.info(getNode().getName() + ":" + loggedPropertyTypeName + " = " + value); }
override fun attachOverride() { super.attachOverride() // Get the name of the property type that you want to log. val loggedPropertyTypeName = getProperty(LoggedPropertyTypeProperty) if (loggedPropertyTypeName.isEmpty()) { Log.info( "Use the Logged Property Type property to set which property type you want to log." ) return } // Get the property type that you want to log and check that it is valid. val loggedPropertyType = DynamicPropertyType( loggedPropertyTypeName, Float::class.java ) if (!loggedPropertyType.isValid) { Log.warning( "LogPropertyValueComponent cannot find the specified property type with float data type." ) return } // Add the notification handler for the property type that you want to log. val binding = BindingBuilder.createOneWay(".", loggedPropertyType) binding.addProcessor(CallbackBindingProcessor.create( domain ) { value: Any -> logPropertyValue(loggedPropertyTypeName, value as Float) false }) node.setBinding(binding) // Print the initial value of the property type that you want to log. val initialPropertyValue = node.getOptionalProperty(loggedPropertyType) initialPropertyValue?.let { logPropertyValue(loggedPropertyTypeName, it) } } private fun logPropertyValue(loggedPropertyTypeName: String, value: Float) { Log.info(node.name + ":" + loggedPropertyTypeName + " = " + value) }
Register the class with the plugin.
Build and install your Kanzi Engine plugin. See Adding a Kanzi Engine plugin to a Kanzi Studio project.
In Kanzi Studio:
In the Node Tree select the node whose property you want to log.
In the Node Components press Alt and right-click Custom Node Components and select the Log Property Value Component custom node component that you created.
In the Log Property Value Component set the Logged Property Type property to the property type whose value you want to print to the Kanzi Log.
In Kanzi Studio, when you change the value of that property, you can see the log messages in the Kanzi Studio Log window.
Setting custom property types¶
To pass to Kanzi Studio information about the custom property types that you create in a Java Kanzi Engine plugin, declare metadata that describes the property types.
The metadata enable Kanzi Engine plugin users to interact with the plugin content in Kanzi Studio.
See Reference for showing Kanzi Engine plugin custom types in Kanzi Studio.
For example, to create the metadata for the custom property type VideoFileNameProperty to allow Kanzi Studio user to select a video file:
@Metadata
@EditorInfo(displayName = "Video Filename",
tooltip = "Name of the video file to be played.", category = "Video",
editor = "BrowseFileTextEditor", defaultValue = "video.mp4")
public static final PropertyType<String> VideoFileNameProperty =
new PropertyType<>("MyPlugin.VideoFileName", String.class);
@Metadata
@EditorInfo(
displayName = "Video Filename",
tooltip = "Name of the video file to be played.",
category = "Video",
editor = "BrowseFileTextEditor",
defaultValue = "video.mp4"
)
val VideoFileNameProperty: PropertyType<String> = PropertyType(
"MyPlugin.VideoFileName",
String::class.java
)
When the user creates a VideoView2D node, Kanzi Studio adds to the properties of the node in the category Video the Video Filename property, which has a text editor with a Browse button, a tooltip, and the default value set to video.mp4. See Ordering property categories in the Properties window.
Property type names cannot contain:
A period (.) in the beginning or at the end of a property name
Spaces
Tabs
Slashes (/)
Backslashes ()
Hashes (#)
Opening or closing braces ({})
At signs (@)
Setting the default value of a property type¶
In a custom node you can set the default value of any property type that is defined in the node on which you base your node.
For example, if your node inherits from the Node2D
class, to set the default value of the Node.Opacity
property type to 0.44
, in the makeEditorInfo
of your node:
@Metadata
@EditorInfo(displayName = "My Node",
customAttributes =
{
// Set the default value of the Node.Opacity property to 0.44.
@EditorInfoAttribute(key = "override:Node.Opacity:defaultValue", value = "0.44"),
// (Optional) Set how Kanzi Studio suggests the use of this property type.
// If the "host" value of the property in the base node is:
// - "auto", you can use "auto" or "fixed"
// - "context", you can use "auto", "context", "fixed", or "freq"
// - "fixed", you can use "fixed"
// - "freq", you can use "auto", "fixed", or "freq"
// For example, to automatically add the Node.Opacity property
// when a user adds your node.
@EditorInfoAttribute(key = "override:Node.Opacity:host", value = "auto"),
})
public static Metaclass metaclass = new Metaclass("MyPlugin.MyNode2");
@Metadata
@EditorInfo(
displayName = "My Node",
customAttributes = [EditorInfoAttribute(
key = "override:Node.Opacity:defaultValue",
value = "0.44"
), EditorInfoAttribute(key = "override:Node.Opacity:host", value = "auto")]
)
var metaclass = Metaclass("MyPlugin.MyNode2")
See host.
Creating custom actions¶
In a Java Kanzi Engine plugin you can create custom actions with custom property types.
Kanzi Studio groups message types in the create context menu based on the value of the displayName
property of the parent class metadata where you declare the message type. You can override this information by setting the category
property.
To create a custom action:
In the plugin project create a class.
For example, create a class named LogAction.
public static class LogAction extends Action { @Metadata @EditorInfo(displayName = "Log Action", category = "Plugin Actions") public static final Metaclass metaclass = new Metaclass("MyPlugin.LogAction"); public static ObjectRef<LogAction> create(Domain domain, String name) { return Action.createDerived(domain, name, metaclass); } protected LogAction(Domain domain, long handle, Metaclass metaclass) { super(domain, handle, metaclass); } @Override public void onInvoke() { Log.info("Hello from Java!"); } }
class LogAction private constructor(domain: Domain?, handle: Long, metaclass: Metaclass?) : Action(domain, handle, metaclass) { public override fun onInvoke() { Log.info("Hello from Java!") } companion object { @Metadata @EditorInfo(displayName = "Log Action", category = "Plugin Actions") val metaclass = Metaclass("MyPlugin.LogAction") @JvmStatic fun create(domain: Domain?, name: String?): ObjectRef<LogAction> { return createDerived(domain, name, metaclass) } } }
Register the class with the plugin.