aboutsummaryrefslogtreecommitdiffstats
path: root/src/graphui/classes/com
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2023-12-19 17:40:17 +0100
committerSven Gothel <[email protected]>2023-12-19 17:40:17 +0100
commitfeb3d34be097bcbef5ebc40342b405a832ac581f (patch)
tree09eb2f8dbb2173d9d90438397819739c3fc3b994 /src/graphui/classes/com
parenteb99bfc27f9f49387cbb08471debcd4d61e4f745 (diff)
Bug 805: GraphUI: Add Widget 'marker' (a Group), derived by {MediaUI01 -> MediaPlayer} and new RangeSlider
- A widget specifies specific UI semantics including individual controls. - Being a {@link Group}, implementations provide shape(s) and its instance can be added to the user's scene. - Due to the specific nature of widgets, individual controls/listener may be provided with semantic values. +++ MediaPlayer exposes a RangeSlider for current position (view and control).
Diffstat (limited to 'src/graphui/classes/com')
-rw-r--r--src/graphui/classes/com/jogamp/graph/ui/widgets/MediaPlayer.java (renamed from src/graphui/classes/com/jogamp/graph/ui/widgets/MediaUI01.java)272
-rw-r--r--src/graphui/classes/com/jogamp/graph/ui/widgets/RangeSlider.java252
-rw-r--r--src/graphui/classes/com/jogamp/graph/ui/widgets/Widget.java65
3 files changed, 476 insertions, 113 deletions
diff --git a/src/graphui/classes/com/jogamp/graph/ui/widgets/MediaUI01.java b/src/graphui/classes/com/jogamp/graph/ui/widgets/MediaPlayer.java
index a37d73539..4cef454ea 100644
--- a/src/graphui/classes/com/jogamp/graph/ui/widgets/MediaUI01.java
+++ b/src/graphui/classes/com/jogamp/graph/ui/widgets/MediaPlayer.java
@@ -50,6 +50,7 @@ import com.jogamp.graph.ui.shapes.Button;
import com.jogamp.graph.ui.shapes.Label;
import com.jogamp.graph.ui.shapes.MediaButton;
import com.jogamp.graph.ui.shapes.Rectangle;
+import com.jogamp.graph.ui.widgets.RangeSlider.SliderListener;
import com.jogamp.math.Vec2f;
import com.jogamp.math.Vec3f;
import com.jogamp.math.Vec4f;
@@ -68,14 +69,14 @@ import com.jogamp.opengl.util.texture.TextureSequence;
import com.jogamp.opengl.util.texture.TextureSequence.TextureFrame;
/**
- * UI Media player factory, embedding a {@link MediaButton} and its controls within a {@link Group}.
- * @see MediaUI01#create(Scene, GLMediaPlayer, int, Uri, int, float, boolean, float, List)
+ * Media player {@link Widget}, embedding a {@link MediaButton} and its controls.
+ * @see #MediaPlayer(int, Scene, GLMediaPlayer, Uri, int, float, boolean, float, List)
*/
-public class MediaUI01 {
- public static final int MediaTexUnitMediaPlayer = 1;
+public class MediaPlayer extends Widget {
+ public static final int TexUnit = 1;
/** Default texture count, value {@value}, same as {@link GLMediaPlayer#TEXTURE_COUNT_DEFAULT}. */
- public static final int MediaTexCount = GLMediaPlayer.TEXTURE_COUNT_DEFAULT;
+ public static final int TexCount = GLMediaPlayer.TEXTURE_COUNT_DEFAULT;
public static final Vec2f FixedSymSize = new Vec2f(0.0f, 1.0f);
public static final Vec2f SymSpacing = new Vec2f(0f, 0.2f);
@@ -83,30 +84,11 @@ public class MediaUI01 {
public static final float CtrlButtonHeight = 1f;
public static final Vec4f CtrlCellCol = new Vec4f(0, 0, 0, 0);
- /** Returns the used info font or null if n/a */
- public static Font getInfoFont() {
- try {
- return FontFactory.get(FontFactory.UBUNTU).getDefault();
- } catch(final IOException ioe) {
- ioe.printStackTrace();
- return null;
- }
- }
- /** Returns the used symbols font or null if n/a */
- public static Font getSymbolsFont() {
- try {
- return FontFactory.get(FontFactory.SYMBOLS).getDefault();
- } catch(final IOException ioe) {
- ioe.printStackTrace();
- return null;
- }
- }
-
/**
- * Returns a {@link Group} containing a {@link MediaButton} and its controls.
+ * Constructs a {@link MediaPlayer}, i.e. its shapes and controls.
+ * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}.
* @param scene the used {@link Scene} to query parameter and access rendering loop
* @param mPlayer fresh {@link GLMediaPlayer} instance, maybe customized via e.g. {@link GLMediaPlayer#setTextureMinMagFilter(int[])}.
- * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}.
* @param medium {@link Uri} stream source, either a file or network source
* @param aid audio-id to start playing, may use {@link GLMediaPlayer#STREAM_ID_AUTO}
* @param aratio aspect ratio of the resulting {@link Shape}, usually 16.0f/9.0f or 4.0f/3.0f, which also denotes the width of this shape while using height 1.0.
@@ -114,14 +96,16 @@ public class MediaUI01 {
* @param zoomSize zoom-size (0..1] for zoom-out control
* @param customCtrls optional custom controls, maybe an empty list
*/
- public static Shape create(final Scene scene, final GLMediaPlayer mPlayer, final int renderModes,
- final Uri medium, final int aid,
- final float aratio, final boolean letterBox, final float zoomSize,
- final List<Shape> customCtrls)
+ public MediaPlayer(final int renderModes, final Scene scene, final GLMediaPlayer mPlayer,
+ final Uri medium, final int aid,
+ final float aratio, final boolean letterBox, final float zoomSize,
+ final List<Shape> customCtrls)
{
+ super( new BoxLayout(aratio, 1, Alignment.None) );
+
final Font fontInfo = getInfoFont(), fontSymbols = getSymbolsFont();
if( null == fontInfo || null == fontSymbols ) {
- return new Rectangle(renderModes, aratio, 1, 0.10f);
+ return;
}
final float borderSz = 0.01f;
final float borderSzS = 0.03f;
@@ -137,23 +121,35 @@ public class MediaUI01 {
final float ctrlCellWidth = (aratio-2*borderSzS)/ctrlCells;
final float ctrlCellHeight = ctrlCellWidth;
+ final float ctrlSliderHeight = ctrlCellHeight/15f;
final Shape[] zoomReplacement = { null };
final Vec3f[] zoomOrigScale = { null };
final Vec3f[] zoomOrigPos = { null };
- final Group container = new Group(new BoxLayout(aratio, 1, Alignment.None));
- container.setName("container");
- container.setBorderColor(borderColor).setBorder(borderSz);
- container.setInteractive(true).setFixedARatioResize(true);
+ this.setName("mp.container");
+ this.setBorderColor(borderColor).setBorder(borderSz);
+ this.setInteractive(true).setFixedARatioResize(true);
final MediaButton mButton = new MediaButton(renderModes, aratio, 1, mPlayer);
- mButton.setName("mButton").setInteractive(false);
+ mButton.setName("mp.mButton").setInteractive(false);
mButton.setPerp().setPressedColorMod(1f, 1f, 1f, 0.85f);
+ final RangeSlider ctrlSlider = new RangeSlider(renderModes, aratio, ctrlSliderHeight, 4.0f, 0, 100, 0);
+ {
+ final float dy = ( ctrlSlider.getKnobSize() - ctrlSliderHeight ) * 0.5f;
+ ctrlSlider.setPaddding(new Padding(0, 0, ctrlCellHeight-dy, 0));
+ }
+ ctrlSlider.setName("mp.slider");
+
+ final Button playButton = new Button(renderModes, fontSymbols,
+ fontSymbols.getUTF16String("play_arrow"), fontSymbols.getUTF16String("pause"), CtrlButtonWidth, CtrlButtonHeight, zEpsilon);
+ playButton.setName("mp.play");
+ playButton.setSpacing(SymSpacing, FixedSymSize).setPerp().setColor(CtrlCellCol);
+
// mButton.setBorderColor(borderNormal).setBorder(borderSz);
{
- mPlayer.setTextureUnit(MediaTexUnitMediaPlayer);
+ mPlayer.setTextureUnit(TexUnit);
mButton.setVerbose(false).addDefaultEventListener().setTextureLetterbox(letterBox);
mPlayer.setAudioVolume( 0f );
mPlayer.addEventListener( new GLMediaEventListener() {
@@ -166,6 +162,11 @@ public class MediaUI01 {
// System.err.println("MediaButton State: "+mp);
if( eventMask.isSet(GLMediaPlayer.EventMask.Bit.Init) ) {
System.err.println(mp.toString());
+ ctrlSlider.setMinMax(0, mp.getDuration(), 0);
+ } else if( eventMask.isSet(GLMediaPlayer.EventMask.Bit.Play) ) {
+ playButton.setToggle(true);
+ } else if( eventMask.isSet(GLMediaPlayer.EventMask.Bit.Pause) ) {
+ playButton.setToggle(false);
}
if( eventMask.isSet(GLMediaPlayer.EventMask.Bit.EOS) ) {
final StreamException err = mp.getStreamException();
@@ -184,8 +185,8 @@ public class MediaUI01 {
}
}
});
- mPlayer.playStream(medium, GLMediaPlayer.STREAM_ID_AUTO, aid, MediaTexCount);
- container.addShape(mButton);
+ mPlayer.playStream(medium, GLMediaPlayer.STREAM_ID_AUTO, aid, TexCount);
+ this.addShape(mButton);
}
Group ctrlGroup, infoGroup;
@@ -194,7 +195,7 @@ public class MediaUI01 {
final Button timeLabel;
{
muteLabel = new Label(renderModes, fontSymbols, aratio/6f, fontSymbols.getUTF16String("music_off")); // volume_mute, headset_off
- muteLabel.setName("muteLabel");
+ muteLabel.setName("mp.mute");
{
final float sz = aratio/6f;
muteLabel.setColor(1, 0, 0, 1);
@@ -202,25 +203,25 @@ public class MediaUI01 {
muteLabel.setInteractive(false);
muteLabel.setVisible( mPlayer.isAudioMuted() );
- container.addShape(muteLabel);
+ this.addShape(muteLabel);
}
infoGroup = new Group(new BoxLayout());
- infoGroup.setName("infoGroup").setInteractive(false);
- container.addShape( infoGroup.setVisible(false) );
+ infoGroup.setName("mp.info").setInteractive(false);
+ this.addShape( infoGroup.setVisible(false) );
{
final float sz = 1/7f;
- final Rectangle rect = new Rectangle(renderModes, aratio, sz, sz/2f);
- rect.setName("info.blend").setInteractive(false);
+ final Rectangle rect = new Rectangle(renderModes, aratio, sz, 0);
+ rect.setName("mp.info.blend").setInteractive(false);
rect.setColor(0, 0, 0, alphaBlend);
rect.setPaddding(new Padding(0, 0, 1f-sz, 0));
infoGroup.addShape(rect);
}
{
final int lines = 3;
- final String text = getInfo(mPlayer, false);
+ final String text = getInfo(Clock.currentMillis(), mPlayer, false);
infoLabel = new Label(renderModes, fontInfo, aratio/40f, text);
- infoLabel.setName("infoLabel");
+ infoLabel.setName("mp.info.label");
final float szw = aratio/40f;
infoLabel.setPaddding(new Padding(0, 0, 1f-szw*lines, szw));
infoLabel.setInteractive(false);
@@ -229,8 +230,8 @@ public class MediaUI01 {
}
{
timeLabel = new Button(renderModes, fontInfo,
- getMultilineTime(mPlayer), CtrlButtonWidth, CtrlButtonHeight, zEpsilon);
- timeLabel.setName("timeLabel");
+ getMultilineTime(Clock.currentMillis(), mPlayer), CtrlButtonWidth, CtrlButtonHeight, zEpsilon);
+ timeLabel.setName("mp.time");
timeLabel.setPerp().setColor(CtrlCellCol);
timeLabel.setLabelColor(1, 1, 1);
}
@@ -240,43 +241,70 @@ public class MediaUI01 {
public void display(final GLAutoDrawable drawable) {
final GLAnimatorControl anim = drawable.getAnimator();
if( ( timeLabel.isVisible() || infoLabel.isVisible() ) &&
- mPlayer.getState() != GLMediaPlayer.State.Uninitialized && null != anim )
+ mPlayer.getState() == GLMediaPlayer.State.Playing && null != anim )
{
final long t1 = anim.getTotalFPSDuration();
- if( t1 - t0 >= 300) {
+ if( t1 - t0 >= 333) {
t0 = t1;
- infoLabel.setText(getInfo(mPlayer, false));
- timeLabel.setText(getMultilineTime(mPlayer));
+ final int ptsMS = mPlayer.getPTS().get(Clock.currentMillis());
+ final int durationMS = mPlayer.getDuration();
+ infoLabel.setText(getInfo(ptsMS, durationMS, mPlayer, false));
+ timeLabel.setText(getMultilineTime(ptsMS, durationMS));
+ ctrlSlider.setValue(ptsMS);
}
}
}
} );
+ ctrlSlider.onSlider(new SliderListener() {
+ private void seekPlayer(final int ptsMS) {
+ final int durationMS = mPlayer.getDuration();
+ timeLabel.setText(getMultilineTime(ptsMS, durationMS));
+ mPlayer.seek(ptsMS);
+ }
+ @Override
+ public void clicked(final RangeSlider w, final MouseEvent e) {
+ System.err.println("Clicked "+w.getName()+": "+millisToTimeStr(Math.round(w.getValue()), true)+"ms, "+(w.getValuePct()*100f)+"%");
+ seekPlayer( Math.round( w.getValue() ) );
+ }
+ @Override
+ public void pressed(final RangeSlider w, final MouseEvent e) {
+ // mPlayer.pause(false);
+ // seekPlayer( Math.round( w.getValue() ) );
+ }
+ @Override
+ public void released(final RangeSlider w, final MouseEvent e) {
+ // seekPlayer( Math.round( w.getValue() ) );
+ // mPlayer.resume();
+ }
+
+ @Override
+ public void dragged(final RangeSlider w, final float old_val, final float val, final float old_val_pct, final float val_pct) {
+ System.err.println("Dragged "+w.getName()+": "+millisToTimeStr(Math.round(val), true)+"ms, "+(val_pct*100f)+"%");
+ seekPlayer( Math.round( val ) );
+ }
+ });
+ this.addShape( ctrlSlider.setVisible(false) );
- ctrlBlend = new Rectangle(renderModes, aratio, ctrlCellHeight, ctrlCellHeight/2f);
+ ctrlBlend = new Rectangle(renderModes, aratio, ctrlCellHeight, 0);
ctrlBlend.setName("ctrl.blend").setInteractive(false);
ctrlBlend.setColor(0, 0, 0, alphaBlend);
- ctrlBlend.setPaddding(new Padding(0, 0, 0, 0));
- container.addShape( ctrlBlend.setVisible(false) );
+ this.addShape( ctrlBlend.setVisible(false) );
ctrlGroup = new Group(new GridLayout(ctrlCellWidth, ctrlCellHeight, Alignment.FillCenter, Gap.None, 1));
ctrlGroup.setName("ctrlGroup").setInteractive(false);
ctrlGroup.setPaddding(new Padding(0, borderSzS, 0, borderSzS));
- container.addShape( ctrlGroup.move(0, 0, ctrlZOffset).setVisible(false) );
+ this.addShape( ctrlGroup.move(0, 0, ctrlZOffset).setVisible(false) );
{ // 1
- final Button button = new Button(renderModes, fontSymbols,
- fontSymbols.getUTF16String("play_arrow"), fontSymbols.getUTF16String("pause"), CtrlButtonWidth, CtrlButtonHeight, zEpsilon);
- button.setName("play");
- button.setSpacing(SymSpacing, FixedSymSize).setPerp().setColor(CtrlCellCol);
- button.onToggle((final Shape s) -> {
+ playButton.onToggle((final Shape s) -> {
if( s.isToggleOn() ) {
mPlayer.resume();
} else {
mPlayer.pause(false);
}
});
- button.setToggle(true); // on == play
- ctrlGroup.addShape(button);
+ playButton.setToggle(true); // on == play
+ ctrlGroup.addShape(playButton);
}
{ // 2
final Button button = new Button(renderModes, fontSymbols,
@@ -384,53 +412,48 @@ public class MediaUI01 {
button.onToggle( (final Shape s) -> {
if( s.isToggleOn() ) {
final AABBox sbox = scene.getBounds();
- final Group parent = container.getParent();
+ final Group parent = this.getParent();
if( null != parent ) {
zoomReplacement[0] = new Label(renderModes, fontInfo, aratio/40f, "zoomed");
- final boolean r = parent.replaceShape(container, zoomReplacement[0]);
+ final boolean r = parent.replaceShape(this, zoomReplacement[0]);
if( r ) {
- System.err.println("Zoom1: "+parent);
- final float sxy = sbox.getWidth() * zoomSize / container.getScaledWidth();
- scene.addShape(container);
- System.err.println("cont1 "+container);
- System.err.println("cbox1 "+container.getBounds());
- System.err.println("sbox1 "+sbox);
- System.err.println("sxy1 "+sxy);
- container.scale(sxy, sxy, 1f);
- container.moveTo(sbox.getLow()).move(sbox.getWidth() * ( 1f - zoomSize )/2.0f, sbox.getHeight() * ( 1f - zoomSize )/2.0f, ctrlZOffset);
+ // System.err.println("Zoom1: p "+parent);
+ // System.err.println("Zoom1: t "+this);
+ final float sxy = sbox.getWidth() * zoomSize / this.getScaledWidth();
+ scene.addShape(this);
+ this.scale(sxy, sxy, 1f);
+ this.moveTo(sbox.getLow()).move(sbox.getWidth() * ( 1f - zoomSize )/2.0f, sbox.getHeight() * ( 1f - zoomSize )/2.0f, ctrlZOffset);
} else {
- System.err.println("Zoom1: failed");
+ System.err.println("Zoom1: failed "+this);
}
} else {
- zoomOrigScale[0] = container.getScale().copy();
- zoomOrigPos[0] = container.getPosition().copy();
- System.err.println("Zoom2: top");
- final float sxy = sbox.getWidth() * zoomSize / container.getScaledWidth();
- System.err.println("cont2 "+container);
- System.err.println("cbox2 "+container.getBounds());
- System.err.println("sbox2 "+sbox);
- System.err.println("sxy2 "+sxy);
- container.scale(sxy, sxy, 1f);
- container.moveTo(sbox.getLow()).move(sbox.getWidth() * ( 1f - zoomSize )/2.0f, sbox.getHeight() * ( 1f - zoomSize )/2.0f, ctrlZOffset);
+ zoomOrigScale[0] = this.getScale().copy();
+ zoomOrigPos[0] = this.getPosition().copy();
+ // System.err.println("Zoom2: top");
+ // System.err.println("Zoom2: t "+this);
+ final float sxy = sbox.getWidth() * zoomSize / this.getScaledWidth();
+ this.scale(sxy, sxy, 1f);
+ this.moveTo(sbox.getLow()).move(sbox.getWidth() * ( 1f - zoomSize )/2.0f, sbox.getHeight() * ( 1f - zoomSize )/2.0f, ctrlZOffset);
}
} else {
if( null != zoomReplacement[0] ) {
final Group parent = zoomReplacement[0].getParent();
- container.moveTo(0, 0, 0);
- parent.replaceShape(zoomReplacement[0], container);
+ scene.removeShape(this);
+ this.moveTo(0, 0, 0);
+ parent.replaceShape(zoomReplacement[0], this);
scene.invoke(true, (drawable) -> {
final GL2ES2 gl = drawable.getGL().getGL2ES2();
zoomReplacement[0].destroy(gl, scene.getRenderer());
return true;
});
zoomReplacement[0] = null;
- System.err.println("Reset1: "+parent);
+ // System.err.println("Reset1: "+parent);
} else if( null != zoomOrigScale[0] && null != zoomOrigPos[0] ){
- container.scale(zoomOrigScale[0]);
- container.moveTo(zoomOrigPos[0]);
+ this.scale(zoomOrigScale[0]);
+ this.moveTo(zoomOrigPos[0]);
zoomOrigScale[0] = null;
zoomOrigPos[0] = null;
- System.err.println("Reset2: top");
+ // System.err.println("Reset2: top");
}
}
});
@@ -441,25 +464,27 @@ public class MediaUI01 {
ctrlGroup.addShape(cs);
}
}
- container.setWidgetMode(true);
+ this.setWidgetMode(true);
- container.onActivation( (final Shape s) -> {
- if( container.isActive() ) {
- container.setBorderColor(borderColorA);
+ this.onActivation( (final Shape s) -> {
+ if( this.isActive() ) {
+ this.setBorderColor(borderColorA);
} else {
- container.setBorderColor(borderColor);
+ this.setBorderColor(borderColor);
}
- if( ctrlGroup.isActive() ) {
+ if( ctrlGroup.isActive() || ctrlSlider.isActive() ) {
+ ctrlSlider.setVisible(true);
ctrlBlend.setVisible(true);
ctrlGroup.setVisible(true);
infoGroup.setVisible(true);
} else {
+ ctrlSlider.setVisible(false);
ctrlBlend.setVisible(false);
ctrlGroup.setVisible(false);
infoGroup.setVisible(false);
}
});
- container.addMouseListener(new Shape.MouseGestureAdapter() {
+ this.addMouseListener(new Shape.MouseGestureAdapter() {
@Override
public void mouseReleased(final MouseEvent e) {
mButton.setPressedColorMod(1f, 1f, 1f, 1f);
@@ -469,8 +494,28 @@ public class MediaUI01 {
mButton.setPressedColorMod(1f, 1f, 1f, 0.85f);
}
} );
- container.forAll((final Shape s) -> { s.setDraggable(false).setResizable(false); return false; });
- return container;
+ this.forAll((final Shape s) -> { s.setDraggable(false).setResizable(false); return false; });
+ ctrlSlider.getKnob().setDraggable(true);
+ }
+
+ /** Returns the used info font or null if n/a */
+ public static Font getInfoFont() {
+ try {
+ return FontFactory.get(FontFactory.UBUNTU).getDefault();
+ } catch(final IOException ioe) {
+ ioe.printStackTrace();
+ return null;
+ }
+ }
+
+ /** Returns the used symbols font or null if n/a */
+ public static Font getSymbolsFont() {
+ try {
+ return FontFactory.get(FontFactory.SYMBOLS).getDefault();
+ } catch(final IOException ioe) {
+ ioe.printStackTrace();
+ return null;
+ }
}
public static String millisToTimeStr(final long millis, final boolean addFractions) {
@@ -502,7 +547,10 @@ public class MediaUI01 {
}
}
}
- public static String getInfo(final GLMediaPlayer mPlayer, final boolean full) {
+ public static String getInfo(final long currentMillis, final GLMediaPlayer mPlayer, final boolean full) {
+ return getInfo(mPlayer.getPTS().get(currentMillis), mPlayer.getDuration(), mPlayer, full);
+ }
+ public static String getInfo(final int ptsMS, final int durationMS, final GLMediaPlayer mPlayer, final boolean full) {
final String name;
{
final String basename;
@@ -521,12 +569,10 @@ public class MediaUI01 {
}
}
final float aspect = (float)mPlayer.getWidth() / (float)mPlayer.getHeight();
- final int pts = mPlayer.getPTS().get(Clock.currentMillis());
- final long duration = mPlayer.getDuration();
- final float pct = (float)pts / (float)duration;
+ final float pct = (float)ptsMS / (float)durationMS;
if( full ) {
final String text1 = String.format("%s / %s (%.0f %%), %s (%01.1fx, vol %1.2f), A/R %0.2f, fps %02.1f",
- millisToTimeStr(pts, false), millisToTimeStr(duration, false), pct*100,
+ millisToTimeStr(ptsMS, false), millisToTimeStr(durationMS, false), pct*100,
mPlayer.getState().toString().toLowerCase(), mPlayer.getPlaySpeed(), mPlayer.getAudioVolume(), aspect, mPlayer.getFramerate());
final String text2 = String.format("audio: id %d, kbps %d, codec %s",
mPlayer.getAID(), mPlayer.getAudioBitrate()/1000, mPlayer.getAudioCodec());
@@ -535,17 +581,17 @@ public class MediaUI01 {
return text1+"\n"+text2+"\n"+text3+"\n"+name;
} else {
final String text1 = String.format("%s / %s (%.0f %%), %s (%01.1fx, vol %1.2f), A/R %.2f",
- millisToTimeStr(pts, false), millisToTimeStr(duration, false), pct*100,
+ millisToTimeStr(ptsMS, false), millisToTimeStr(durationMS, false), pct*100,
mPlayer.getState().toString().toLowerCase(), mPlayer.getPlaySpeed(), mPlayer.getAudioVolume(), aspect);
return text1+"\n"+name;
}
}
- public static String getMultilineTime(final GLMediaPlayer mPlayer) {
- final int pts = mPlayer.getPTS().get(Clock.currentMillis());
- final long duration = mPlayer.getDuration();
- final float pct = (float)pts / (float)duration;
+ public static String getMultilineTime(final long currentMillis, final GLMediaPlayer mPlayer) {
+ return getMultilineTime(mPlayer.getPTS().get(currentMillis), mPlayer.getDuration());
+ }
+ public static String getMultilineTime(final int ptsMS, final int durationMS) {
+ final float pct = (float)ptsMS / (float)durationMS;
return String.format("%.0f %%%n%s%n%s",
- pct*100, millisToTimeStr(pts, false), millisToTimeStr(duration, false));
+ pct*100, millisToTimeStr(ptsMS, false), millisToTimeStr(durationMS, false));
}
-
}
diff --git a/src/graphui/classes/com/jogamp/graph/ui/widgets/RangeSlider.java b/src/graphui/classes/com/jogamp/graph/ui/widgets/RangeSlider.java
new file mode 100644
index 000000000..495d81149
--- /dev/null
+++ b/src/graphui/classes/com/jogamp/graph/ui/widgets/RangeSlider.java
@@ -0,0 +1,252 @@
+/**
+ * Copyright 2010-2023 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``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 JogAmp Community 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.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package com.jogamp.graph.ui.widgets;
+
+import com.jogamp.graph.curve.Region;
+import com.jogamp.graph.curve.opengl.GLRegion;
+import com.jogamp.graph.curve.opengl.RegionRenderer;
+import com.jogamp.graph.ui.Group;
+import com.jogamp.graph.ui.Shape;
+import com.jogamp.graph.ui.shapes.BaseButton;
+import com.jogamp.graph.ui.shapes.Button;
+import com.jogamp.graph.ui.shapes.Rectangle;
+import com.jogamp.math.Vec2f;
+import com.jogamp.math.Vec3f;
+import com.jogamp.math.Vec4f;
+import com.jogamp.newt.event.MouseEvent;
+import com.jogamp.opengl.GL2ES2;
+import com.jogamp.opengl.GLProfile;
+import com.jogamp.opengl.util.texture.TextureSequence;
+
+/**
+ * RangeSlider {@link Widget}
+ * @see #RangeSlider(int, float, float, float, float, float, float)
+ */
+public final class RangeSlider extends Widget {
+ /**
+ * {@link RangeSlider} slider listener
+ */
+ public static interface SliderListener {
+ /** Slider clicked by user (after completing pressed + released). */
+ void clicked(RangeSlider w, final MouseEvent e);
+ /** Slider pressed down by user. */
+ void pressed(RangeSlider w, final MouseEvent e);
+ /** Slider released down by user. */
+ void released(RangeSlider w, final MouseEvent e);
+ /**
+ * Slide dragged by user
+ * @param w the {@link RangeSlider} widget owning the slider
+ * @param old_val previous absolute value position of the slider
+ * @param val the absolute value position of the slider
+ * @param old_val_pct previous percentage value position of the slider
+ * @param val_pct the percentage value position of the slider
+ */
+ void dragged(RangeSlider w, float old_val, float val, float old_val_pct, float val_pct);
+ }
+
+ private final boolean horizontal;
+ private final float knobSz;
+ private final float width, height;
+ private final Group barAndKnob;
+ private final Rectangle bar;
+ private final BaseButton knob;
+ private final Vec4f colBar = new Vec4f(0, 0, 1, 1);
+ private final Vec4f colKnob = new Vec4f(1, 0, 0, 1);
+ private SliderListener sliderListener = null;
+ private float min, max;
+ private float val, val_pct;
+
+ /**
+ * Constructs a {@link RangeSlider}, i.e. its shapes and controls.
+ * @param renderModes Graph's {@link Region} render modes, see {@link GLRegion#create(GLProfile, int, TextureSequence) create(..)}.
+ * @param width width of this slider box. A horizontal slider has width >= height.
+ * @param height height of this slider box. A vertical slider has width < height.
+ * @param knobScale multiple of slider-bar height for {@link #getKnobSize()}
+ * @param min minimum value of slider
+ * @param max maximum value of slider
+ * @param value current value of slider
+ */
+ public RangeSlider(final int renderModes, final float width, final float height, final float knobScale,
+ final float min, final float max, final float value) {
+ this.horizontal = width >= height;
+ if( horizontal ) {
+ knobSz = height*knobScale;
+ this.width = width - knobSz; // half knobSz left and right
+ this.height = height;
+ } else {
+ knobSz = width*knobScale;
+ this.width = width;
+ this.height = height - knobSz; // half knobSz bottom and top
+ }
+ barAndKnob = new Group();
+ barAndKnob.setInteractive(true).setDraggable(false).setToggleable(false);
+ bar = new Rectangle(renderModes & ~Region.AA_RENDERING_MASK, this.width, this.height, 0);
+ bar.setToggleable(false);
+ bar.setColor(colBar);
+ knob = new BaseButton(renderModes & ~Region.AA_RENDERING_MASK, knobSz*1.01f, knobSz);
+ knob.setToggleable(false);
+ knob.setColor(colKnob);
+ setName(getName());
+ barAndKnob.addShape( bar );
+ barAndKnob.addShape( knob );
+ addShape(barAndKnob);
+
+ setMinMax(min, max, value);
+
+ knob.onMove((final Shape s, final Vec3f origin, final Vec3f dest) -> {
+ final float old_val = val;
+ final float old_val_pct = val_pct;
+ setValuePct( getKnobValuePct( dest.x(), dest.y(), knobSz*0.5f ) );
+ // System.err.println("KnobMove "+getName()+": "+origin+" -> "+dest+": "+old_val+" -> "+val+", "+(old_val_pct*100f)+"% -> "+(val_pct*100f)+"%");
+ if( null != sliderListener ) {
+ sliderListener.dragged(this, old_val, val, old_val_pct, val_pct);
+ }
+ });
+ barAndKnob.addMouseListener(new Shape.MouseGestureAdapter() {
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ final Shape.EventInfo shapeEvent = (Shape.EventInfo) e.getAttachment();
+ setValuePct( getKnobValuePct( shapeEvent.objPos.x(), shapeEvent.objPos.y(), 0 ) );
+ if( null != sliderListener ) {
+ sliderListener.clicked(RangeSlider.this, e);
+ }
+ }
+ });
+ knob.addMouseListener(new Shape.MouseGestureAdapter() {
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ if( null != sliderListener ) {
+ sliderListener.clicked(RangeSlider.this, e);
+ }
+ }
+ @Override
+ public void mousePressed(final MouseEvent e) {
+ if( null != sliderListener ) {
+ sliderListener.pressed(RangeSlider.this, e);
+ }
+ }
+ @Override
+ public void mouseReleased(final MouseEvent e) {
+ if( null != sliderListener ) {
+ sliderListener.released(RangeSlider.this, e);
+ }
+ }
+ @Override
+ public void mouseWheelMoved(final MouseEvent e) {
+ // Support ?
+ }
+ });
+ }
+
+ @Override
+ protected void clearImpl0(final GL2ES2 gl, final RegionRenderer renderer) {
+ super.clearImpl0(gl, renderer);
+ sliderListener = null;
+ }
+ @Override
+ protected void destroyImpl0(final GL2ES2 gl, final RegionRenderer renderer) {
+ super.destroyImpl0(gl, renderer);
+ sliderListener = null;
+ }
+
+ public RangeSlider onSlider(final SliderListener l) {
+ sliderListener = l;
+ return this;
+ }
+
+ public Rectangle getBar() { return bar; }
+ public BaseButton getKnob() { return knob; }
+
+ public final float getWidth() { return width; }
+ public final float getHeight() { return height; }
+ public final float getKnobSize() { return knobSz; }
+
+ public float getMin() { return min; }
+ public float getMax() { return max; }
+ public float getRange() { return max - min; }
+ public float getValue() { return val; }
+ public float getValuePct() { return val_pct; }
+
+ /**
+ * Sets slider value range and current value
+ * @param min minimum value of slider
+ * @param max maximum value of slider
+ * @param value current value of slider
+ * @return this instance of chaining
+ */
+ public RangeSlider setMinMax(final float min, final float max, final float value) {
+ this.min = min;
+ this.max = max;
+ this.val = Math.max(min, Math.min(max, value));
+ this.val_pct = ( value - min ) / getRange();
+ setKnob();
+ return this;
+ }
+
+ public RangeSlider setValuePct(final float v) {
+ val_pct = v;
+ val = min + ( val_pct * getRange() );
+ setKnob();
+ return this;
+ }
+
+ public RangeSlider setValue(final float v) {
+ val = v;
+ val_pct = ( val - min ) / getRange();
+ setKnob();
+ return this;
+ }
+
+ /**
+ * Knob position reflects value on its center and ranges from zero to max.
+ */
+ private Vec2f getKnobPos(final Vec2f posRes, final float val_pct) {
+ if( horizontal ) {
+ posRes.setX( val_pct*width - knobSz*0.5f );
+ posRes.setY( -( knobSz - height ) * 0.5f );
+ } else {
+ posRes.setX( -( knobSz - width ) * 0.5f );
+ posRes.setY( val_pct*height - knobSz*0.5f );
+ }
+ return posRes;
+ }
+ private float getKnobValuePct(final float pos_x, final float pos_y, final float adjustment) {
+ final float v;
+ if( horizontal ) {
+ v = ( pos_x + adjustment ) / width;
+ } else {
+ v = ( pos_y + adjustment ) / height;
+ }
+ return Math.max(0.0f, Math.min(1.0f, v));
+ }
+
+ private void setKnob() {
+ final Vec2f pos = getKnobPos(new Vec2f(), val_pct);
+ knob.moveTo(pos.x(), pos.y(), Button.DEFAULT_LABEL_ZOFFSET);
+ }
+}
diff --git a/src/graphui/classes/com/jogamp/graph/ui/widgets/Widget.java b/src/graphui/classes/com/jogamp/graph/ui/widgets/Widget.java
new file mode 100644
index 000000000..fb547c6c0
--- /dev/null
+++ b/src/graphui/classes/com/jogamp/graph/ui/widgets/Widget.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright 2010-2023 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``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 JogAmp Community 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.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package com.jogamp.graph.ui.widgets;
+
+import com.jogamp.graph.ui.Group;
+import com.jogamp.graph.ui.Shape;
+import com.jogamp.graph.ui.Group.Layout;
+
+/**
+ * A widget specifies specific UI semantics including individual controls.
+ * <p>
+ * Being a {@link Group}, implementations provide shape(s) and its instance can be added to the user's scene.
+ * </p>
+ * <p>
+ * Due to the specific nature of widgets,
+ * individual controls/listener may be provided with semantic values.
+ * </p>
+ */
+public class Widget extends Group {
+ /**
+ * Create a Widget group of {@link Shape}s w/o {@link Group.Layout}.
+ * <p>
+ * Default is non-interactive, see {@link #setInteractive(boolean)}.
+ * </p>
+ */
+ public Widget() {
+ super(null);
+ }
+
+ /**
+ * Create a Widget group of {@link Shape}s w/ given {@link Group.Layout}.
+ * <p>
+ * Default is non-interactive, see {@link #setInteractive(boolean)}.
+ * </p>
+ * @param l optional {@link Layout}, maybe {@code null}
+ */
+ public Widget(final Layout l) {
+ super(l);
+ }
+}