/*
* Copyright (C) 2011 Alasdair C. Hamilton
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package ketUI.panel;
import java.awt.*;
import javax.swing.*;
import geom.Offset;
import java.awt.image.*;
import java.util.Vector;
import ket.*;
import ket.math.*;
import ket.display.*;
import geom.Position;
import ket.treeDiff.Step;
import ket.display.box.Box;
import ket.treeDiff.TreeDiff;
import ket.display.Transition;
import ket.display.box.BoxText;
/**
* Animate the transitions during argument editing.
*/
public class AnimatedDisplay implements Display {
static final double ANIMATION_TIME = 250.0; // ms
static final int TOP = 0x1;
static final int CENTRE = 0x2;
static final int BOTTOM = 0x3;
final MathCollection mathCollection;
/**
* When the equation is drawn, record its box from which the arguments
* associated with a mouse click may be determined.
*/
Box afterRootBox;
Box equationBox;
Box labelBox;
Position equationTopLeft;
Position topLeft;
Vector<Transition> transitions;
boolean repaint;
long initialTime;
int view;
public AnimatedDisplay(MathCollection mathCollection) {
this.mathCollection = mathCollection;
initialTime = 0L;
transitions = null;
repaint = true;
view = TOP;
}
public void fromTop() {
view = TOP;
}
public void fromMiddle() {
view = CENTRE;
}
public void fromBottom() {
view = BOTTOM;
}
@Override
public Box findDeepestBox(Position p) {
return equationBox!=null ? equationBox.findDeepestBox(p) : null;
}
@Override
public Vector<Integer> findVisibleEquationIndices() {
Vector<Integer> indices = new Vector<Integer>();
if (equationBox==null) return indices;
Argument a = equationBox.getArgument();
if (a==null) return indices;
Integer index = a.getEquationIndex();
if (index==null) return indices;
indices.add(index);
return indices;
}
@Override
public Argument findDeepestArgument(Position p) {
return equationBox!=null ? equationBox.findDeepestArgument(p) : null;
}
@Override
public Equation pointToEquation(Position p) {
Argument argument = equationBox.getArgument();
return argument!=null ? argument.getEquation() : null;
}
public void noteChange(Graphics2D g2D, ColourScheme colourScheme, Argument before, Equation afterEquation, int fontSize, Offset panelRectangle) {
Argument after = afterEquation.getRoot();
if (before.subBranchEquals(after) || afterEquation.isText()) { //+ Did the equation change?
transitions = null;
afterRootBox = null;
return;
}
afterRootBox = after.toBox(0L, colourScheme);
labelSetup(afterEquation, colourScheme);
Offset windowWithoutLabel = afterBoxSetup(fontSize, panelRectangle);
Box beforeRootBox = beforeBoxSetup(g2D, colourScheme, before, fontSize, windowWithoutLabel);
initialTime = System.currentTimeMillis();
TreeDiff treeDiff = new TreeDiff(before, after);
transitions = treeDiff.getTransitions(beforeRootBox, afterRootBox);
}
private void labelSetup(Equation afterEquation, ColourScheme colourScheme) {
labelBox = afterEquation.getLabelBox(colourScheme);
labelBox.setProperty(Box.RIGHT_ALIGN);
labelBox.setProperty(Box.Y_CENTRE_ALIGN);
}
/**
* Setup the box that represents the previous state.
*/
private Box beforeBoxSetup(Graphics2D g2D, ColourScheme colourScheme, Argument before, int fontSize, Offset windowWithoutLabel) {
Box beforeRootBox = before.toBox(0L, colourScheme);
beforeRootBox.setupInnerRectangle(fontSize);
beforeRootBox.setupOuterRectangle(windowWithoutLabel);
beforeRootBox.calcRootOffset();
return beforeRootBox;
}
/**
* Animate the box that represents the new state.
*/
private Offset afterBoxSetup(int fontSize, Offset panelRectangle) {
double shapeWidth = panelRectangle.width - KetPanel.BORDER_OFFSET.width;
afterRootBox.setupInnerRectangle(fontSize);
double minimumHeight = afterRootBox.getInnerRectangle().height;
Offset equationRectangle = new Offset(shapeWidth, minimumHeight);
labelBox.setup(fontSize, equationRectangle);
Offset windowWithoutLabel = calcWindowWithoutLabel(equationRectangle);
afterRootBox.setupOuterRectangle(windowWithoutLabel);
afterRootBox.calcRootOffset();
topLeft = new Position(KetPanel.LEFT_BORDER_SIZE, getBoxHeight(afterRootBox, panelRectangle));
return windowWithoutLabel;
}
/**
* This is called every time the user interacts with the box model, either changing or selecting.
*/
@Override
public boolean generateBoxes(Graphics2D g2D, ColourScheme colourScheme, int fontSize, Offset panelRectangle) {
Equation equation = mathCollection.getCursor().getEquation();
equationBox = equation.toBox(colourScheme);
labelBox = equation.getLabelBox(colourScheme);
labelBox.setProperty(Box.RIGHT_ALIGN);
equationBox.setupInnerRectangle(fontSize);
Offset equationRectangle = calcEquationRectangle(panelRectangle);
labelBox.setup(fontSize, equationRectangle);
Offset windowWithoutLabel = calcWindowWithoutLabel(equationRectangle);
equationBox.setupOuterRectangle(windowWithoutLabel);
equationBox.calcRootOffset();
double boxHeight = getBoxHeight(equationBox, panelRectangle);
equationTopLeft = new Position(KetPanel.LEFT_BORDER_SIZE, boxHeight);
return true;
}
private Offset calcEquationRectangle(Offset panelRectangle) {
double shapeWidth = panelRectangle.width - KetPanel.BORDER_OFFSET.width;
double minimumHeight = equationBox.getInnerRectangle().height;
return new Offset(shapeWidth, minimumHeight);
}
private Offset calcWindowWithoutLabel(Offset equationRectangle) {
return new Offset(equationRectangle.width-labelBox.getInnerRectangle().width, equationRectangle.height);
}
@Override
public boolean requiresRepaint() {
return repaint;
}
/**
* Draw a single equation in the centre of the screen or text to the
* top left. In either case a label is also displayed.
*/
@Override
public void paint(Graphics2D g2D, ColourScheme colourScheme, int fontSize, Offset panelRectangle) {
if (equationBox==null) {
// Nothing to paint yet: this is called before an equation was specified.
return;
}
double fractionalTime = (System.currentTimeMillis()-initialTime) / ANIMATION_TIME;
if (animate(fractionalTime)) {
repaint = true;
labelBox.paint(g2D, topLeft, colourScheme);
// Draw the next frame.
for (Transition t : transitions) {
t.animate(g2D, colourScheme, fractionalTime, topLeft);
}
} else {
repaint = false;
initialTime = 0L;
labelBox.paint(g2D, equationTopLeft, colourScheme);
equationBox.paint(g2D, equationTopLeft, colourScheme);
transitions = null;
}
}
public void processPairs() {
if (equationBox==null) {
System.out.println(" !!! null equation box !!! ");
return;
}
TPanel.gui(equationBox);
}
private boolean animate(double fractionalTime) {
if (transitions==null) {
return false;
}
return fractionalTime < 1.0;
}
private double getBoxHeight(Box box, Offset panelRectangle) {
switch (view) {
case TOP:
return KetPanel.TOP_BORDER_SIZE;
case CENTRE:
double centre = (panelRectangle.height-KetPanel.BORDER_OFFSET.height) / 2.0;
double heightAboveCentre = box.getOuterRectangle().height / 2.0;
return centre - heightAboveCentre;
case BOTTOM:
double bottom = panelRectangle.height - KetPanel.BORDER_OFFSET.height;
double heightAboveBottom = box.getOuterRectangle().height;
return bottom - heightAboveBottom;
default:
throw new RuntimeException("Illegal display state.");
}
}
@Override
public boolean isArgumentVisible(Argument argument) {
return equationBox.containsArgument(argument);
}
}