package com.xenoage.zong.layout.frames;
import static com.xenoage.utils.math.geom.Point2f.origin;
import static com.xenoage.utils.math.geom.Size2f.size0;
import static com.xenoage.zong.layout.LayoutPos.layoutPos;
import static java.lang.Math.max;
import lombok.Data;
import com.xenoage.utils.annotations.MaybeNull;
import com.xenoage.utils.annotations.Untested;
import com.xenoage.utils.color.Color;
import com.xenoage.utils.math.MathUtils;
import com.xenoage.utils.math.geom.Point2f;
import com.xenoage.utils.math.geom.Size2f;
import com.xenoage.zong.layout.LayoutPos;
import com.xenoage.zong.layout.Layout;
import com.xenoage.zong.layout.LayoutContainer;
import com.xenoage.zong.layout.Page;
/**
* A frame is the abstract base class for
* object in a layout of a score document.
*
* It is basically a rectangle with a position and size
* and rotation and an optional background.
*
* @author Andreas Wenger
* @author Uli Teschemacher
*/
@Data public abstract class Frame {
/** The parent of this frame, or null if not in a layout. */
protected LayoutContainer parent = null;
/** Center point of the frame in mm, relative to its parent. */
protected Point2f position = origin;
/** Size of the frame in mm. */
protected Size2f size = size0;
/** Ccw. rotation of the frame in degrees, relative to its parent. */
protected float rotation = 0;
/** Background of the frame, or null. */
@MaybeNull protected Color background = null;
/**
* Gets the parent layout of this frame, or null, if not part of a layout.
*/
public Layout getParentLayout() {
return (parent != null ? parent.getParentLayout() : null);
}
/**
* Gets the parent page of this frame, or null, if not part of a page.
*/
public Page getParentPage() {
return (parent != null ? parent.getParentPage() : null);
}
/**
* Gets the center position of the frame in mm, relative to the page.
*/
public final Point2f getAbsolutePosition() {
Point2f ret = position;
if (parent != null) {
ret = MathUtils.rotate(ret, parent.getAbsoluteRotation());
ret = ret.add(parent.getAbsolutePosition());
}
return ret;
}
/**
* Gets the {@link LayoutPos} of the center of the frame.
* If this frame is not part of a layout, the page index -1 is returned.
*/
public final LayoutPos getCenterLP() {
Point2f pos = getAbsolutePosition();
Layout layout = getParentLayout();
Page page = getParentPage();
int pageIndex = (page != null ? page.getIndex() : -1);
return layoutPos(layout, pageIndex, pos);
}
/**
* Gets the counter clockwise rotation of the frame in degrees.
*/
public final float getAbsoluteRotation() {
float ret = rotation;
if (parent != null) {
ret += parent.getAbsoluteRotation();
}
return ret;
}
/**
* Transforms the given coordinates in page space to a {@link FP}.
* If the given coordinates are not within this frame, null is returned.
*/
public FP getFP(Point2f p) {
return getFP(p, false);
}
/**
* Transforms the given coordinates in page space to a {@link FP}.
* If the given coordinates are not within this frame and force is false,
* null is returned. Otherwise the computed coordinates are returned, even if
* they are outside the frame.
*/
public FP getFP(Point2f p, boolean force) {
Point2f pos = getAbsolutePosition();
float rot = getAbsoluteRotation();
float hw = size.width / 2;
float hh = size.height / 2;
//two cases: no rotation or rotation
if (rot == 0f) {
//no rotation. this is easy to compute.
if (force ||
(p.x >= pos.x - hw && p.x <= pos.x + hw && p.y >= pos.y - hh && p.y <= pos.y + hh)) {
return new FP(this, new Point2f(p.x - pos.x, p.y - pos.y));
}
}
else {
//rotated frame. this is more complicated.
//first fast check: point within circle around center point?
float radius = max(size.width, size.height);
Point2f pRel = new Point2f(p.x - pos.x, p.y - pos.y);
float distanceSq = pRel.x * pRel.x + pRel.y * pRel.y;
if (force || (distanceSq <= radius * radius)) {
//the given point could be within the frame. rotate the
//point and check again.
pRel = MathUtils.rotate(pRel, -rot);
if (force || (pRel.x >= -hw && pRel.x <= +hw && pRel.y >= -hh && pRel.y <= +hh)) {
return new FP(this, new Point2f(pRel.x, pRel.y));
}
}
}
return null;
}
/**
* Transforms the given coordinates in frame space to
* a position in page space.
*
* Untested for higher levels
*/
@Untested public final Point2f getPagePosition(Point2f p) {
Point2f ret = p;
Frame frame = this;
if (parent != null) {
if (frame.rotation != 0f)
ret = MathUtils.rotate(ret, frame.rotation);
ret = ret.add(frame.position);
}
return parent.getPagePosition(p);
}
/**
* Gets the type of this frame.
*/
public abstract FrameType getType();
}