Commit 9d89d29b authored by Nico Mack's avatar Nico Mack

Improved InfoBox Corona. Fixes to Zoom mechanism.

parent 2547363a
......@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>lu.list.itis.dkd.tui</groupId>
<artifactId>tulip</artifactId>
<version>2.4.0</version>
<version>2.5.0</version>
<name>TULIP</name>
<description>A framework for tabletop tangible user interface applications.</description>
......@@ -82,12 +82,12 @@
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.8</version>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8</version>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
......
......@@ -213,12 +213,14 @@ public class TangibleInterfaceManager extends JComponent {
frame.getContentPane().remove(this);
frame.getContentPane().setLayout(new FlowLayout());
frame.getContentPane().add(splashScreen);
splashScreen.setVisible(true);
frame.setVisible(true);
frame.repaint();
splashShown = true;
}
protected void hideSplashScreen() {
splashScreen.setVisible(false);
frame.getContentPane().remove(splashScreen);
frame.getContentPane().add(this);
frame.setLayout(null);
......
......@@ -22,8 +22,6 @@ package lu.list.itis.dkd.tui.utility;
import lu.list.itis.dkd.dbc.annotation.NonNull;
import com.google.common.base.Preconditions;
/**
* Class implementing a {@link CoordinateState} for camera coordinates. The class features methods
* to transition to all other states.
......@@ -89,8 +87,8 @@ public class CameraCoordinates extends CoordinateState {
/** {@inheritDoc} */
@Override
public Point transform(Point normalized) {
Preconditions.checkArgument((normalized.x >= 0) && (normalized.x <= 1));
Preconditions.checkArgument((normalized.y >= 0) && (normalized.y <= 1));
// Preconditions.checkArgument((normalized.x >= 0) && (normalized.x <= 1));
// Preconditions.checkArgument((normalized.y >= 0) && (normalized.y <= 1));
double camx = Calibration.getCameraLeft() + (normalized.x * Calibration.getCameraWidth());
double camy = Calibration.getCameraTop() + (normalized.y * Calibration.getCameraHeight());
......
......@@ -549,7 +549,7 @@ public class Point extends Float implements KdComparator<Point> {
@Override
public double getDistance(Point other) {
Point delta = this.subtract(other, false);
return ((delta.x * delta.x) + (delta.y * delta.y));
return (Math.sqrt(delta.x * delta.x) + (delta.y * delta.y));
}
/** {@inheritDoc} */
......
......@@ -16,6 +16,7 @@
*/
package lu.list.itis.dkd.tui.utility;
import lu.list.itis.dkd.tui.TangibleApplication;
import lu.list.itis.dkd.tui.logging.LogListener;
import lu.list.itis.dkd.tui.logging.OnScreenLogAppender;
......@@ -65,7 +66,7 @@ public class SplashScreen extends JPanel implements LogListener {
private static final Logger LOGGER = LoggerFactory.getLogger(SplashScreen.class.getSimpleName());
private static final String LOGO_PATH = "../resources/TULIP.png"; //$NON-NLS-1$
private static final String LOGO_PATH = "resources/TULIP.png"; //$NON-NLS-1$
private static final String COLUMN_SPECS = "left:pref, 5dlu, 60dlu:grow"; //$NON-NLS-1$
private static final String ROW_SPECS = "center:300dlu:grow, 5dlu, bottom:300dlu:grow"; //$NON-NLS-1$
......@@ -76,7 +77,7 @@ public class SplashScreen extends JPanel implements LogListener {
// ---------------------------------------------------------------------------
public SplashScreen(int screenWidth, int screenHeight) {
logo = this.getImage(this.getClass(), LOGO_PATH);
logo = this.getImage(TangibleApplication.class, LOGO_PATH);
this.setLayout(new FormLayout(COLUMN_SPECS, ROW_SPECS));
this.setBackground(Color.BLACK);
......@@ -136,11 +137,15 @@ public class SplashScreen extends JPanel implements LogListener {
return image;
}
// ---------------------------------------------------------------------------
/** {@inheritDoc} */
@Override
public void messageLogged(String message) {
log.append(message);
log.setRows(appender.getMessageCount());
if (this.isVisible()) {
log.setRows(appender.getMessageCount());
}
}
// ---------------------------------------------------------------------------
......
......@@ -22,8 +22,6 @@ package lu.list.itis.dkd.tui.utility;
import lu.list.itis.dkd.dbc.annotation.NonNull;
import com.google.common.base.Preconditions;
/**
* Class implementing a {@link CoordinateState} for table coordinates. The class features methods to
* transition to all other states.
......@@ -89,8 +87,8 @@ public class TableCoordinates extends CoordinateState {
/** {@inheritDoc} */
@Override
public Point transform(Point normalized) {
Preconditions.checkArgument((normalized.x >= 0) && (normalized.x <= 1));
Preconditions.checkArgument((normalized.y >= 0) && (normalized.y <= 1));
// Preconditions.checkArgument((normalized.x >= 0) && (normalized.x <= 1));
// Preconditions.checkArgument((normalized.y >= 0) && (normalized.y <= 1));
double tblx = (normalized.x * Calibration.getTableWidth());
double tbly = (normalized.y * Calibration.getTableHeight());
......
......@@ -25,6 +25,7 @@ import lu.list.itis.dkd.tui.adapter.TangibleObject;
import lu.list.itis.dkd.tui.content.Zoomable;
import lu.list.itis.dkd.tui.event.TimerEvent;
import lu.list.itis.dkd.tui.event.TimerEventListener;
import lu.list.itis.dkd.tui.utility.CameraCoordinates;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.widget.builder.BaseZoomBuilder;
import lu.list.itis.dkd.tui.widget.state.StateManager;
......@@ -56,6 +57,8 @@ public class ZoomWidget extends StatefulWidget implements TimerEventListener {
/** A {@link Logger} to log all messages during execution. */
private static final Logger logger = LoggerFactory.getLogger(ZoomWidget.class.getSimpleName());
private static final double ONE_POINT_FIVE_PI = 1.5 * Math.PI;
private static final double TWO_PI = 2 * Math.PI;
/**
* Constructor making use of the super constructor to initialise all fields.
......@@ -103,23 +106,42 @@ public class ZoomWidget extends StatefulWidget implements TimerEventListener {
*/
@Override
public void actionMove(TangibleObject tangibleObject) {
super.actionMove(tangibleObject);
StateManager manager = states.get(tangibleObject.getObjectId());
Point position = positions.get(tangibleObject.getObjectId());
double lastRotation;
if (position != null) {
lastRotation = tangibleObject.getAngle() - position.getAngle();
} else {
lastRotation = tangibleObject.getAngle();
}
super.actionMove(tangibleObject);
if (position == null) {
position = positions.get(tangibleObject.getObjectId());
}
if (synchronousZoom) {
if (manager.isRotating() && manager.getLastRotation() != null) {
position.toCameraCoordinates();
if (lastRotation > ONE_POINT_FIVE_PI)
lastRotation -= TWO_PI;
else if (lastRotation < -ONE_POINT_FIVE_PI)
lastRotation += TWO_PI;
Point zoom = new Point(position.x, position.y, (float) lastRotation, CameraCoordinates.class);
if (manager.isRotating()) {
for (Zoomable resource : resources.values()) {
resource.zoom(manager.getLastRotation().toCameraCoordinates());
resource.zoom(zoom);
if (recentre)
resource.centre(position.toCameraCoordinates());
resource.centre(position);
}
}
if (manager.isMoving()) {
for (Zoomable resource : resources.values()) {
resource.centre(position.toCameraCoordinates());
resource.centre(position);
}
}
}
......
......@@ -22,14 +22,16 @@ package lu.list.itis.dkd.tui.widget.corona;
import lu.list.itis.dkd.dbc.annotation.NonNullByDefault;
import lu.list.itis.dkd.tui.content.InformationReceiver;
import lu.list.itis.dkd.tui.utility.Calibration;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.utility.TableCoordinates;
import lu.list.itis.dkd.tui.widget.corona.builder.BaseInfoBoxBuilder;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.RoundRectangle2D;
import java.util.ArrayList;
import java.util.List;
......@@ -44,9 +46,16 @@ import java.util.List;
@NonNullByDefault
public class InfoBox extends Corona implements InformationReceiver<List<String>> {
private List<String> information;
private int fontSize;
private Color fillColour;
private Color strokeColour;
private Color textColour;
private Font textFont;
private FontMetrics fontMetrics;
private float lineHeightRatio;
private Stroke borderStroke;
/**
* Constructor building all fields and calling the implicit super constructor using a
* {@link BaseInfoBoxBuilder} instance holding all values.
......@@ -57,8 +66,13 @@ public class InfoBox extends Corona implements InformationReceiver<List<String>>
public InfoBox(BaseInfoBoxBuilder<?> builder) {
super(builder.withShape(ShapeFactory.buildRoundedSquare(0)));
information = new ArrayList<>();
fontSize = builder.fontSize;
fillColour = builder.fillColour;
strokeColour = builder.strokeColour;
textColour = builder.textColour;
textFont = builder.textFont;
lineHeightRatio = builder.lineHeightRatio;
borderStroke = (builder.strokeWidth > 0) ? new BasicStroke(builder.strokeWidth) : null;
}
......@@ -68,15 +82,26 @@ public class InfoBox extends Corona implements InformationReceiver<List<String>>
if (!active)
return;
Graphics2D localCanvas = (Graphics2D) canvas.create();
localCanvas.setFont(textFont);
centre.toTableCoordinates();
Point localOffset = initialTranslation == null ? new Point() : initialTranslation;
Point summit = new Point((float) (centre.getX() + localOffset.getX() * Math.cos(centre.getAngle())), (float) (centre.getY() + localOffset.getY() * Math.sin(centre.getAngle())), centre.getAngle(), TableCoordinates.class);
int lineWidth = 0;
if (information != null) {
for (String line : information) {
lineWidth = Math.max(lineWidth, localCanvas.getFontMetrics().stringWidth(line));
}
}
float padding = 10;
float xPosition = 0;
float yPosition = 0;
float boxHeight = lineHeight() + information.size() * lineHeight();
float boxWidth = Calibration.getSquareSize() * 2f;
float boxHeight = lineHeight() + information.size() * lineHeight() + padding;
float boxWidth = lineWidth + (2 * padding);
summit.toScreenCoordinates();
if (initialTranslation == null) {
......@@ -109,15 +134,23 @@ public class InfoBox extends Corona implements InformationReceiver<List<String>>
shape = new RoundRectangle2D.Float(xPosition, yPosition, boxWidth, boxHeight, 20, 20);
canvas.setPaint(new Color(230, 230, 230));
canvas.fill(shape);
canvas.setColor(Color.black);
localCanvas.setPaint(fillColour);
localCanvas.fill(shape);
if (strokeColour != null) {
localCanvas.setPaint(strokeColour);
if (borderStroke != null)
canvas.setStroke(borderStroke);
localCanvas.draw(shape);
}
canvas.setFont(new Font("Arial", Font.PLAIN, fontSize)); //$NON-NLS-1$
localCanvas.setColor(textColour);
for (int i = 0; i < information.size(); i++) {
canvas.drawString(information.get(i), xPosition + 10, yPosition + lineHeight() + i * lineHeight());
localCanvas.drawString(information.get(i), xPosition + padding, padding + yPosition + lineHeight() + i * lineHeight());
}
localCanvas.dispose();
}
/**
......@@ -170,12 +203,14 @@ public class InfoBox extends Corona implements InformationReceiver<List<String>>
* The value to set fontSize to.
*/
public void setFontSize(int fontSize) {
this.fontSize = fontSize;
if (textFont != null) {
textFont = textFont.deriveFont(fontSize);
}
}
/** Method for returning the line height as given by font size and line-height ratio. */
private float lineHeight() {
return fontSize * lineHeightRatio;
return (textFont != null) ? textFont.getSize() * lineHeightRatio : 0;
}
/**
......
......@@ -32,6 +32,9 @@ import lu.list.itis.dkd.tui.widget.corona.InfoBox;
import org.jdom2.Element;
import java.awt.Color;
import java.awt.Font;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.List;
......@@ -50,7 +53,14 @@ public abstract class BaseInfoBoxBuilder<B extends BaseInfoBoxBuilder<B>> extend
@Nullable
public List<String> information;
/** Field holding the font size to display text in. */
public int fontSize;
public Color fillColour;
public Color strokeColour;
public Color textColour;
public int strokeWidth;
public Shape labelShape;
public Font textFont;
/** Field holding the ratio of the line height to the font size. */
public float lineHeightRatio;
......@@ -107,14 +117,20 @@ public abstract class BaseInfoBoxBuilder<B extends BaseInfoBoxBuilder<B>> extend
information.add(BootstrappingUtils.getContentAsString(lineNode, Externalization.EMPTY_STRING, BootstrappingUtils.OPTIONAL, Externalization.EMPTY_STRING, context));
}
}
fontSize = BootstrappingUtils.getContentAsInteger(rootElement, Externalization.FONT_SIZE_NODE, BootstrappingUtils.OPTIONAL, 14, context);
lineHeightRatio = (float) BootstrappingUtils.getContentAsDouble(rootElement, Externalization.LINE_HEIGHT_RATIO_NODE, BootstrappingUtils.OPTIONAL, 1.0, context);
}
fillColour = BootstrappingUtils.getContentAsColour(rootElement, Externalization.FILL_COLOUR_ELEMENT, BootstrappingUtils.OPTIONAL, Color.WHITE, context);
strokeColour = BootstrappingUtils.getContentAsColour(rootElement, Externalization.STROKE_COLOUR_ELEMENT, BootstrappingUtils.OPTIONAL, null, context);
textColour = BootstrappingUtils.getContentAsColour(rootElement, Externalization.TEXT_COLOUR_ELEMENT, BootstrappingUtils.OPTIONAL, Color.BLACK, context);
strokeWidth = BootstrappingUtils.getContentAsInteger(rootElement, Externalization.STROKE_WIDTH_NODE, BootstrappingUtils.OPTIONAL, 1, context);
String fontName = BootstrappingUtils.getContentAsString(rootElement, Externalization.FONT_NODE, BootstrappingUtils.OPTIONAL, "Monospaced"); //$NON-NLS-1$
Integer fontSize = BootstrappingUtils.getContentAsInteger(rootElement, Externalization.FONT_SIZE_NODE, BootstrappingUtils.OPTIONAL, 14);
/** {@inheritDoc} */
@Override
public abstract InfoBox build();
if ((fontName != null) && (fontName.length() > 0)) {
textFont = new Font(fontName, Font.BOLD, fontSize);
}
lineHeightRatio = (float) BootstrappingUtils.getContentAsDouble(rootElement, Externalization.LINE_HEIGHT_RATIO_NODE, BootstrappingUtils.OPTIONAL, 1.0, context);
}
/**
* Method for setting the initial information to display.
......@@ -129,19 +145,66 @@ public abstract class BaseInfoBoxBuilder<B extends BaseInfoBoxBuilder<B>> extend
return (B) this;
}
// ---------------------------------------------------------------------------
/**
* The font size the text should be displayed in.
*
* @param size
* the size of the font.
* @return An instance of this builder for chain calling.
* @param colour
* @return
*/
@SuppressWarnings("unchecked")
public B withFillColour(Color colour) {
fillColour = colour;
return (B) this;
}
// ---------------------------------------------------------------------------
/**
* @param colour
* @return
*/
@SuppressWarnings("unchecked")
public B withStrokeColour(Color colour) {
strokeColour = colour;
return (B) this;
}
// ---------------------------------------------------------------------------
/**
* @param width
* @return
*/
@SuppressWarnings("unchecked")
public B withStrokeWidth(int width) {
this.strokeWidth = width;
return (B) this;
}
// ---------------------------------------------------------------------------
/**
* @param colour
* @return
*/
@SuppressWarnings("unchecked")
public B withFontSize(int size) {
fontSize = size;
public B withTextColour(Color colour) {
textColour = colour;
return (B) this;
}
// ---------------------------------------------------------------------------
/**
* @param font
* @return
*/
@SuppressWarnings("unchecked")
public B withFont(Font font) {
this.textFont = font;
return (B) this;
}
// ---------------------------------------------------------------------------
/**
* The ratio of the line spacing to text size.
*
......@@ -154,4 +217,9 @@ public abstract class BaseInfoBoxBuilder<B extends BaseInfoBoxBuilder<B>> extend
lineHeightRatio = ratio;
return (B) this;
}
/** {@inheritDoc} */
@Override
public abstract InfoBox build();
}
\ No newline at end of file
......@@ -35,8 +35,8 @@ import lu.list.itis.dkd.tui.utility.Point;
*/
@NonNullByDefault
public class Attenuator {
private static final float ROTATION_THRESHOLD = 0.1f;
private static final float MOVEMENT_THRESHOLD = 0.01f;
private static final float ROTATION_THRESHOLD = 0.2f; // 0.1f
private static final float MOVEMENT_THRESHOLD = 0.02f; // 0.01f;
private static final int MOVEMENT_COOLDOWN = 500;
private static final int ROTATION_COOLDOWN = 500;
......@@ -92,11 +92,14 @@ public class Attenuator {
* least equal to the threshold.
*/
public static boolean allowRotation(@Nullable Point base, Point current) {
boolean allowRotation = true;
if (base == null)
return true;
return allowRotation;
float delta = (float) (Math.abs(base.getAngle() - current.getAngle()) % (2 * Math.PI));
return Float.compare((float) (delta > Math.PI ? 2 * Math.PI - delta : delta), ROTATION_THRESHOLD) >= 0;
allowRotation = Float.compare((float) (delta > Math.PI ? 2 * Math.PI - delta : delta), ROTATION_THRESHOLD) >= 0;
return allowRotation;
}
/**
......@@ -112,11 +115,16 @@ public class Attenuator {
* -1, if the distance moved does not suffice to be at least equal to the threshold.
*/
public static boolean allowMovement(@Nullable Point base, Point current) {
boolean allowMovement = true;
if (base == null)
return true;
double distance = base.toCameraCoordinates().distance(current.toCameraCoordinates());
return allowMovement;
Point _base = base.clone();
Point _current = current.clone();
double distance = _base.toCameraCoordinates().distance(_current.toCameraCoordinates());
allowMovement = Double.compare(distance, MOVEMENT_THRESHOLD) >= 0;
return Double.compare(distance, MOVEMENT_THRESHOLD) >= 0;
return allowMovement;
// return Double.compare(base.getDistance(current), MOVEMENT_THRESHOLD) >= 0;
}
}
\ No newline at end of file
......@@ -29,6 +29,9 @@ import lu.list.itis.dkd.tui.utility.Point;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
......@@ -90,6 +93,8 @@ public class StateManager {
@Nullable
private Point lastRotationStart, lastMovementStart;
private static final Logger logger = LoggerFactory.getLogger(StateManager.class.getSimpleName());
/**
* Constructor initialising the internal map of states. The map will contain one entry, a
* {@link LimboState}.
......@@ -191,11 +196,15 @@ public class StateManager {
if (Attenuator.allowRotation(lastRotationStart, transition.getTo().base)) {
if (!states.containsKey(RotationState.class.getSimpleName())) {
lastRotationStart = transition.getTo().getBase();
if (logger.isTraceEnabled()) {
logger.trace("Rotation initiated..."); //$NON-NLS-1$
}
}
if (transition.getFrom() != null) {
states.removeAll(transition.getFrom().getClass().getSimpleName());
}
states.put(RotationState.class.getSimpleName(), transition.getTo());
resetRotationTimer();
}
......@@ -232,6 +241,9 @@ public class StateManager {
if (Attenuator.allowMovement(lastMovementStart, transition.getTo().base)) {
if (!states.containsKey(MovementState.class.getSimpleName())) {
lastMovementStart = transition.getTo().getBase();
if (logger.isTraceEnabled()) {
logger.trace("Movement initiated..."); //$NON-NLS-1$
}
}
if (transition.getFrom() != null) {
states.removeAll(transition.getFrom().getClass().getSimpleName());
......@@ -518,6 +530,10 @@ public class StateManager {
wasRotating = true;
rotationTimer.stop();
if (logger.isTraceEnabled()) {
logger.trace("Rotation stopped!"); //$NON-NLS-1$
}
for (TimerEventListener listener : listeners) {
listener.stoppedRotating(new TimerEvent(StateManager.this));
}
......@@ -538,6 +554,10 @@ public class StateManager {
wasMoving = true;
movementTimer.stop();
if (logger.isTraceEnabled()) {
logger.trace("Movement stopped!"); //$NON-NLS-1$
}
for (TimerEventListener listener : listeners) {
listener.stoppedMoving(new TimerEvent(StateManager.this));
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment