Forráskód Böngészése

New Animation System: Added (incomplete) Nodes for AnimClip and AnimComposer

MeFisto94 5 éve
szülő
commit
1337cd6bd4

+ 320 - 0
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/animation/JmeAnimClip.java

@@ -0,0 +1,320 @@
+/*
+ *  Copyright (c) 2009-2020 jMonkeyEngine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.core.sceneexplorer.nodes.animation;
+
+import com.jme3.anim.AnimClip;
+import com.jme3.anim.AnimComposer;
+import com.jme3.animation.AnimChannel;
+import com.jme3.gde.core.icons.IconList;
+import com.jme3.gde.core.properties.AnimationProperty;
+import com.jme3.gde.core.scene.SceneApplication;
+import com.jme3.gde.core.sceneexplorer.nodes.AbstractSceneExplorerNode;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeTrackChildren;
+import com.jme3.gde.core.sceneexplorer.nodes.SceneExplorerNode;
+import com.jme3.gde.core.sceneexplorer.nodes.animation.JmeAnimation.PlayBackParamsAction;
+import java.awt.Image;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import javax.swing.Action;
+import org.openide.actions.DeleteAction;
+import org.openide.actions.RenameAction;
+import org.openide.awt.Actions;
+import org.openide.loaders.DataObject;
+import org.openide.nodes.Node;
+import org.openide.nodes.NodeAdapter;
+import org.openide.nodes.Sheet;
+import org.openide.util.Exceptions;
+import org.openide.util.actions.SystemAction;
+
+/**
+ * Visual representation of the AnimClip Class in the Scene Explorer
+ * @author MeFisto94
+ */
[email protected](service = SceneExplorerNode.class)
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class JmeAnimClip extends AbstractSceneExplorerNode {
+
+    private AnimClip animClip;
+    private Image icon;
+    private JmeAnimComposer jmeControl;
+    private boolean playing = false;
+    private float animSpeed = 1.0f;
+    private AnimChannel channel = null;
+
+    public JmeAnimClip() {
+    }
+
+    public JmeAnimClip(JmeAnimComposer control, AnimClip animation, DataObject dataObject) {
+        super();
+        this.dataObject = dataObject;
+        this.animClip = animation;
+        this.jmeControl = control;
+        lookupContents.add(this);
+        lookupContents.add(animation);
+        setName(animation.getName());
+        icon = IconList.animation.getImage();
+
+        addNodeListener(new NodeAdapter(){
+            @Override
+            public void propertyChange(PropertyChangeEvent evt) {
+                if (evt.getPropertyName().equalsIgnoreCase("name")){
+                    doRenameAnimation((String) evt.getOldValue(),(String)evt.getNewValue());
+                }
+            }        
+        });
+    }
+    
+    @Override
+    public Image getIcon(int type) {
+        return icon;
+    }
+
+    @Override
+    public Image getOpenedIcon(int type) {
+        return icon;
+    }
+
+    public void toggleIcon(boolean enabled) {
+        if (!playing) {
+            icon = IconList.animation.getImage();
+
+        } else {
+            icon = IconList.animationPlay.getImage();
+
+        }
+        fireIconChange();
+    }
+
+    @Override
+    public Action getPreferredAction() {
+        return Actions.alwaysEnabled(new PlayAction(), "Play", "", false);
+    }
+
+    private void play() {
+        playing = !playing;
+        toggleIcon(playing);
+        jmeControl.setAnimClip(this);
+    }
+
+    @Override
+    protected Sheet createSheet() {
+        Sheet sheet = Sheet.createDefault();
+        Sheet.Set set = Sheet.createPropertiesSet();
+        set.setDisplayName("AnimClip");
+        set.setName(AnimClip.class.getName());
+        if (animClip != null) {
+            //set.put(new AnimationProperty(jmeControl.getLookup().lookup(AnimComposer.class)));
+            sheet.put(set);
+        } // else: Empty Sheet
+        
+        return sheet;
+    }
+
+    public void setChanged() {
+        fireSave(true);
+    }
+
+    @Override
+    public Action[] getActions(boolean context) {
+        return new Action[]{Actions.alwaysEnabled(new PlayAction(), playing ? "Stop" : "Play", "", false),
+                    //Actions.alwaysEnabled(new PlayBackParamsAction(), "Playback parameters", "", false),
+                    SystemAction.get(RenameAction.class),
+                    SystemAction.get(DeleteAction.class),
+                    //Actions.alwaysEnabled(new EffectTrackWizardAction(jmeControl.getLookup().lookup(AnimComposer.class).getSpatial(), this), "Add Effect Track", "", false),
+                    //Actions.alwaysEnabled(new AudioTrackWizardAction(jmeControl.getLookup().lookup(AnimComposer.class).getSpatial(), this), "Add Audio Track", "", false),
+                    // @TODO: not working yet, Actions.alwaysEnabled(new ExtractAnimationAction(), "Extract sub-animation", "", true)
+                };
+    }
+
+    @Override
+    public boolean canDestroy() {
+         return !jmeControl.isReadOnly();
+    }
+
+    public void stop() {
+        playing = false;
+        toggleIcon(playing);
+    }
+
+    @Override
+    public void destroy() throws IOException {
+        super.destroy();     
+        final AnimComposer control = jmeControl.getLookup().lookup(AnimComposer.class);
+        try {
+            lookupContents.remove(this.animClip);
+            lookupContents.remove(this);
+            SceneApplication.getApplication().enqueue(() -> {
+                control.removeAnimClip(this.animClip);
+                return null;
+            }).get();            
+            jmeControl.refreshChildren();
+            setChanged();
+        } catch (InterruptedException | ExecutionException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+
+    @Override
+    public void refresh(boolean immediate) {
+        super.refresh(immediate);
+        ((JmeTrackChildren) getChildren()).refreshChildren(false);
+    }
+
+    @Override
+    public Class getExplorerObjectClass() {
+        return AnimClip.class;
+    }
+
+    @Override
+    public Class getExplorerNodeClass() {
+        return JmeAnimClip.class;
+    }
+
+    @Override
+    public Node[] createNodes(Object key, DataObject key2, boolean cookie) {
+        JmeAnimClip jsc = new JmeAnimClip(jmeControl, (AnimClip)key, key2);
+        return new Node[]{jsc};
+    }
+
+    class PlayAction implements ActionListener {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            final AnimComposer control = jmeControl.getLookup().lookup(AnimComposer.class);
+            if (control == null) {
+                return;
+            }
+
+            try {
+                SceneApplication.getApplication().enqueue(() -> {
+                    if (playing) { // Stop Playing
+                        control.setCurrentAction(null);
+                        jmeControl.setAnimClip(null);
+                        return null;
+                    } else {
+                        control.setCurrentAction(animClip.getName());
+                        control.setGlobalSpeed(animSpeed);
+                        java.awt.EventQueue.invokeLater(() -> {
+                            play();
+                        });
+                        return null;
+                    }
+                }).get();
+            } catch (InterruptedException | ExecutionException ex) {
+                Exceptions.printStackTrace(ex);
+            }
+        }
+    }
+
+    /*class PlayBackParamsAction implements ActionListener {
+        ChannelDialog dialog = new ChannelDialog(null, false, JmeAnimClip.this);
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            dialog.setLocationRelativeTo(null);
+            dialog.setVisible(true);
+
+        }
+    }*/
+
+    public float getAnimSpeed() {
+        return animSpeed;
+    }
+
+    public void setAnimSpeed(float speed) {
+        this.animSpeed = speed;
+        try {
+            SceneApplication.getApplication().enqueue(() -> {
+                final AnimComposer composer = jmeControl.getLookup().lookup(AnimComposer.class);
+                if (composer != null) {
+                    composer.setGlobalSpeed(animSpeed);
+                }
+                return null;
+            }).get();
+        } catch (InterruptedException | ExecutionException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+    
+    @Override
+    public boolean canRename() {
+        return !jmeControl.isReadOnly();        
+    }
+    
+    /*class ExtractAnimationAction implements ActionListener {
+        ExtractSubAnimationDialog dialog = new ExtractSubAnimationDialog();
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            try {
+                dialog.setAnimControl(JmeAnimClip.this.jmeControl);
+                dialog.setAnimation(JmeAnimClip.this.animClip);
+                //animation
+                dialog.setLocationRelativeTo(null);
+                dialog.setVisible(true);
+
+                JmeAnimClip.this.jmeControl.fireSave(true);
+                JmeAnimClip.this.jmeControl.refresh(true);
+                JmeAnimClip.this.jmeControl.refreshChildren();
+            } catch (Exception ex) {
+                JOptionPane.showMessageDialog(null, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
+            }
+        }
+    }*/
+    
+    /**
+     * Renames the animation in the OpenGL thread.
+     * Note that renaming an animation mean to delete the old one and create a
+     * new anim with the new name and the old data
+     * @param evt 
+     */
+    protected void doRenameAnimation(final String oldName,final String newName) {
+        final AnimComposer control = jmeControl.getLookup().lookup(AnimComposer.class);
+        try {
+            lookupContents.remove(JmeAnimClip.this.animClip);
+            JmeAnimClip.this.animClip = SceneApplication.getApplication().enqueue(() -> {
+                AnimClip anim = control.getAnimClip(oldName);
+                AnimClip newAnim = new AnimClip(newName);
+                newAnim.setTracks(anim.getTracks());
+                control.removeAnimClip(anim);
+                control.addAnimClip(newAnim);
+                return newAnim;
+            }).get();
+            lookupContents.add(JmeAnimClip.this.animClip);
+            setChanged();
+        } catch (InterruptedException | ExecutionException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+    }
+}

+ 115 - 0
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/animation/JmeAnimClipChildren.java

@@ -0,0 +1,115 @@
+/*
+ *  Copyright (c) 2009-2020 jMonkeyEngine
+ *  All rights reserved.
+ * 
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ * 
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * 
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.core.sceneexplorer.nodes.animation;
+
+import com.jme3.anim.AnimClip;
+import com.jme3.anim.AnimComposer;
+import com.jme3.gde.core.scene.SceneApplication;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.openide.loaders.DataObject;
+import org.openide.nodes.Children;
+import org.openide.nodes.Node;
+import org.openide.util.Exceptions;
+
+/**
+ * Representation of multiple Animations in the Scene Explorer
+ * @author MeFisto94
+ */
+public class JmeAnimClipChildren extends Children.Keys<Object> {
+    protected JmeAnimComposer jmeAnimComposer;
+    protected boolean readOnly = true;
+    protected HashMap<Object, Node> map = new HashMap<>();
+    private DataObject dataObject;
+
+    public JmeAnimClipChildren() {
+    }
+
+    public JmeAnimClipChildren(JmeAnimComposer jmeAnimComposer) {
+        this.jmeAnimComposer = jmeAnimComposer;
+    }
+
+    public void refreshChildren(boolean immediate) {
+        setKeys(createKeys());
+        refresh();
+    }
+
+    public void setReadOnly(boolean cookie) {
+        this.readOnly = cookie;
+    }
+
+    @Override
+    protected void addNotify() {
+        super.addNotify();
+        setKeys(createKeys());
+    }
+
+    protected List<Object> createKeys() {
+        try {
+            return SceneApplication.getApplication().enqueue(() -> {
+                List<Object> keys = new LinkedList<>();
+                AnimComposer composer = jmeAnimComposer.getLookup().lookup(AnimComposer.class);
+                if (composer != null) {
+                    keys.addAll(composer.getAnimClips());
+                }
+                
+                return keys;
+            }).get();
+        } catch (InterruptedException | ExecutionException ex) {
+            Exceptions.printStackTrace(ex);
+            return null;
+        }
+    }
+
+    @Override
+    protected Node[] createNodes(Object key) {
+        if (key instanceof AnimClip) {
+            return new Node[]{ new JmeAnimClip(jmeAnimComposer, (AnimClip)key, dataObject).setReadOnly(readOnly)};
+        } else {
+            return new Node[]{ Node.EMPTY };
+        }
+    }
+
+    public void setAnimComposer(JmeAnimComposer jmeAnimComposer) {
+        this.jmeAnimComposer = jmeAnimComposer;
+    }
+
+    public DataObject getDataObject() {
+        return dataObject;
+    }
+
+    public void setDataObject(DataObject dataObject) {
+        this.dataObject = dataObject;
+    }
+}

+ 258 - 0
jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/animation/JmeAnimComposer.java

@@ -0,0 +1,258 @@
+/*
+ *  Copyright (c) 2009-2020 jMonkeyEngine
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are
+ *  met:
+ *
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ *  * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ *  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.core.sceneexplorer.nodes.animation;
+
+import com.jme3.anim.AnimComposer;
+import com.jme3.gde.core.icons.IconList;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeControl;
+import com.jme3.gde.core.sceneexplorer.nodes.JmeTrackChildren;
+import com.jme3.gde.core.sceneexplorer.nodes.SceneExplorerNode;
+import com.jme3.gde.core.sceneexplorer.nodes.actions.ControlsPopup;
+import java.awt.Image;
+import javax.swing.Action;
+import org.openide.actions.DeleteAction;
+import org.openide.loaders.DataObject;
+import org.openide.nodes.Node;
+import org.openide.nodes.Sheet;
+import org.openide.util.actions.SystemAction;
+
+/**
+ * Visual representation of the AnimComposer Class in the Scene Explorer
+ * @author MeFisto94
+ */
[email protected](service = SceneExplorerNode.class)
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class JmeAnimComposer extends JmeControl {
+    private AnimComposer animComposer;
+    private JmeAnimClip playingAnimation = null;
+    private boolean displayBoneTracks = false;
+    private boolean displayEffectTracks = true;
+    private boolean displayAudioTracks = true;
+    private static Image smallImage = IconList.animControl.getImage();
+
+    public JmeAnimComposer() {
+    }
+
+    public JmeAnimComposer(AnimComposer animComposer, JmeAnimClipChildren children, DataObject obj) {
+        super(children);
+        dataObject = obj;
+        children.setDataObject(dataObject);
+        this.animComposer = animComposer;
+        lookupContents.add(this);
+        lookupContents.add(animComposer);
+        setName("AnimComposer");
+        children.setAnimComposer(this);
+        control = animComposer;
+    }
+
+    @Override
+    public Image getIcon(int type) {
+        return smallImage;
+    }
+
+    @Override
+    public Image getOpenedIcon(int type) {
+        return smallImage;
+    }
+
+    @Override
+    protected Sheet createSheet() {
+        Sheet sheet = Sheet.createDefault();
+        Sheet.Set set = Sheet.createPropertiesSet();
+        set.setDisplayName("AnimComposer");
+        set.setName(AnimComposer.class.getName());
+
+        if (animComposer != null) {
+            //set.put(new AnimationProperty(animComposer));
+            sheet.put(set);
+        } // else: Empty Sheet
+        
+        return sheet;
+    }
+
+    public boolean isPlaying() {
+        return playingAnimation != null;
+    }
+
+    public void setAnimClip(JmeAnimClip anim) {
+        if (playingAnimation != null) {
+            playingAnimation.stop();
+        }
+        playingAnimation = anim;
+    }
+
+    @Override
+    public Action[] getActions(boolean context) {
+        return new Action[]{
+                    //new TrackVisibilityPopup(this),
+                    new ControlsPopup(this),
+                    SystemAction.get(DeleteAction.class)
+                };
+    }
+
+    @Override
+    public Class getExplorerObjectClass() {
+        return AnimComposer.class;
+    }
+
+    @Override
+    public Class getExplorerNodeClass() {
+        return JmeAnimComposer.class;
+    }
+
+    @Override
+    public Node[] createNodes(Object key, DataObject key2, boolean cookie) {
+        JmeAnimClipChildren children = new JmeAnimClipChildren(this);
+        return new Node[]{ new JmeAnimComposer((AnimComposer)key, children, key2)};
+    }
+
+    public boolean isDisplayAudioTracks() {
+        return displayAudioTracks;
+    }
+
+    public boolean isDisplayBoneTracks() {
+        return displayBoneTracks;
+    }
+
+    public boolean isDisplayEffectTracks() {
+        return displayEffectTracks;
+    }
+
+    public void setDisplayAudioTracks(boolean displayAudioTracks) {
+        this.displayAudioTracks = displayAudioTracks;
+        refreshChildren();
+    }
+
+    public void setDisplayBoneTracks(boolean displayBoneTracks) {
+        this.displayBoneTracks = displayBoneTracks;
+        refreshChildren();
+    }
+
+    public void setDisplayEffectTracks(boolean displayEffectTracks) {
+        this.displayEffectTracks = displayEffectTracks;
+        refreshChildren();
+    }
+
+    public void refreshChildren() {
+        ((JmeAnimChildren)this.jmeChildren).refreshChildren(true);
+        for (Object node : getChildren().getNodes()) {
+            JmeAnimation anim = (JmeAnimation) node;
+            ((JmeTrackChildren) anim.getChildren()).refreshChildren(true);
+        }
+    }
+
+    /*
+    class ToggleBoneTrackAction extends BooleanStateAction {
+
+        @Override
+        public String getName() {
+            return "Display bone tracks";
+        }
+
+        @Override
+        public void setBooleanState(boolean value) {
+            super.setBooleanState(value);
+            displayBoneTracks = value;
+            for (Object node : getChildren().getNodes()) {
+                JmeAnimation anim = (JmeAnimation) node;
+                ((JmeTrackChildren) anim.getChildren()).refreshChildren(true);
+            }
+        }
+
+        @Override
+        public boolean getBooleanState() {
+            return displayBoneTracks;
+        }
+
+        @Override
+        public HelpCtx getHelpCtx() {
+            return JmeAnimComposer.this.getHelpCtx();
+        }
+    };
+
+    class ToggleEffectTrackAction extends BooleanStateAction {
+
+        @Override
+        public String getName() {
+            return "Display effect tracks";
+        }
+
+        @Override
+        public void setBooleanState(boolean value) {
+            super.setBooleanState(value);
+            displayEffectTracks = value;
+            for (Object node : getChildren().getNodes()) {
+                JmeAnimation anim = (JmeAnimation) node;
+                ((JmeTrackChildren) anim.getChildren()).refreshChildren(true);
+            }
+        }
+
+        @Override
+        public boolean getBooleanState() {
+            return displayEffectTracks;
+        }
+
+        @Override
+        public HelpCtx getHelpCtx() {
+            return JmeAnimComposer.this.getHelpCtx();
+        }
+    };
+
+    class ToggleAudioTrackAction extends BooleanStateAction {
+
+        @Override
+        public String getName() {
+            return "Display audio tracks";
+        }
+
+        @Override
+        public void setBooleanState(boolean value) {
+            super.setBooleanState(value);
+            displayAudioTracks = value;
+            for (Object node : getChildren().getNodes()) {
+                JmeAnimation anim = (JmeAnimation) node;
+                ((JmeTrackChildren) anim.getChildren()).refreshChildren(true);
+            }
+        }
+
+        @Override
+        public boolean getBooleanState() {
+            return displayAudioTracks;
+        }
+
+        @Override
+        public HelpCtx getHelpCtx() {
+            return JmeAnimComposer.this.getHelpCtx();
+        }
+    };
+    */
+}