package com.xenoage.zong.view;
import com.xenoage.utils.math.Units;
import com.xenoage.utils.math.geom.*;
import com.xenoage.zong.layout.Layout;
import com.xenoage.zong.layout.LayoutPos;
import com.xenoage.zong.layout.Page;
import lombok.val;
import java.util.ArrayList;
import java.util.List;
import static com.xenoage.utils.math.geom.Point2f.p;
import static com.xenoage.zong.layout.LayoutPos.layoutPos;
/**
* This class manages a page view of a {@link Layout}.
* It can be used by different applications that display
* page layouts.
*
* @author Andreas Wenger
*/
public class PageViewManager {
//the layout this view is showing
private Layout layout;
//special zooms
public enum SpecialZoom {
FitPage,
FitWidth,
FitHeight
};
//the display distance between two pages in mm
public final float pageDisplayDistance = 30;
//the alignment of the pages in the view
public enum PageDisplayAlignment {
Horizontal,
Vertical
};
private PageDisplayAlignment pageDisplayAlignment = PageDisplayAlignment.Horizontal;
//bounding rectangles of the pages in mm
protected ArrayList<Rectangle2f> pageRects;
/**
* Creates a {@link PageViewManager} with the given initial layout.
*/
public PageViewManager(Layout layout) {
setLayout(layout);
}
/**
* Gets the managed layout this view shows.
*/
public Layout getLayout() {
return layout;
}
/**
* Sets the managed layout.
*/
public void setLayout(Layout layout) {
this.layout = layout;
//compute the page bounding rectangles
this.pageRects = computePageRects();
}
/**
* Returns the zooming factor for the given special zoom.
* @param specialZoom the special kind of zoom
*/
public float getSpecialZoomScaling(SpecialZoom specialZoom) {
//TODO
return 1;
}
/**
* Gets the alignment of the pages in the view.
*/
public PageDisplayAlignment getPageDisplayAlignment() {
return pageDisplayAlignment;
}
/**
* Sets the alignment of the pages in the view.
*/
public void setPageDisplayAlignment(PageDisplayAlignment pageDisplayAlignment) {
this.pageDisplayAlignment = pageDisplayAlignment;
this.pageRects = computePageRects();
}
/**
* Computes the offsets of all pages in mm, relative to the upper left
* corner of the first page.
*/
private ArrayList<Rectangle2f> computePageRects() {
float offsetX = 0; // horizontal center of the current page
float offsetY = 0; // vertical center of the current page
List<Page> pages = layout.getPages();
ArrayList<Rectangle2f> ret = new ArrayList<>(pages.size());
if (pages.size() > 0) {
// compute offsets for all pages
float firstPageOffsetX = -pages.get(0).getFormat().getSize().width / 2;
float firstPageOffsetY = -pages.get(0).getFormat().getSize().height / 2;
for (val page : pages) {
Size2f pageSize = page.getFormat().getSize();
Point2f offset = null;
switch (pageDisplayAlignment) {
case Horizontal:
offset = new Point2f(offsetX + firstPageOffsetX, -pageSize.height / 2);
offsetX += pageSize.width + pageDisplayDistance;
break;
case Vertical:
offset = new Point2f(-pageSize.width / 2, offsetY + firstPageOffsetY);
offsetY += pageSize.height + pageDisplayDistance;
break;
}
ret.add(new Rectangle2f(offset, new Size2f(pageSize)));
}
}
return ret;
}
/**
* Computes the page index and the page coordinates at the given position in
* px. If there is no page at the given position, null is returned.
*/
public LayoutPos computeLP(Point2i pPx, ViewState viewState) {
Point2f pMm = computePositionMm(pPx, viewState);
//TODO: performance? binary search?
for (int page = pageRects.size() - 1; page >= 0; page--) {
if (pageRects.get(page).contains(pMm)) {
pMm = pMm.sub(getPageDisplayOffset(page));
return layoutPos(layout, page, p(pMm.x, pMm.y));
}
}
return null;
}
/**
* Returns the display position in mm of the given position in px,
* relative to the center point of the first page.
*/
public Point2f computePositionMm(Point2i pPx, ViewState viewState) {
Point2f pMm = new Point2f(Units.pxToMm(pPx.x - viewState.sizePx.width / 2, viewState.scaling),
Units.pxToMm(pPx.y - viewState.sizePx.height / 2, viewState.scaling));
pMm = pMm.add(viewState.scrollMm);
return pMm;
}
/**
* Computes the page coordinates at the given position in px, relative to
* the upper left corner of the given page.
*/
public LayoutPos computeLP(Point2i px, Page page, ViewState viewState) {
int pageIndex = layout.getPages().indexOf(page);
return computeLP(px, pageIndex, viewState);
}
/**
* Computes the page coordinates at the given position in px, relative to
* the upper left corner of the given page.
*/
public LayoutPos computeLP(Point2i pPx, int page, ViewState viewState) {
Point2f pMm = computePositionMm(pPx, viewState);
Point2f pageOffset = pageRects.get(page).position;
pMm = pMm.sub(pageOffset);
return layoutPos(layout, page, p(pMm.x, pMm.y));
}
/**
* Gets the display position in mm of the page with the given index,
* relative to the center of the first page.
* @param pageIndex the index of the page, beginning at 0
*/
public Point2f getPageDisplayOffset(int pageIndex) {
return pageRects.get(pageIndex).position;
}
/**
* Transforms the given layout coordinates to screen coordinates in px. If
* not possible, null is returned.
*/
public Point2i computePositionPx(LayoutPos p, ViewState viewState) {
Point2f pos = getPageDisplayOffset(p.pageIndex);
pos = pos.add(p.position);
pos = pos.sub(viewState.scrollMm);
Point2i ret = new Point2i(Units.mmToPxInt(pos.x, viewState.scaling), Units.mmToPxInt(pos.y,
viewState.scaling));
ret = ret.add(viewState.sizePx.width / 2, viewState.sizePx.height / 2);
return ret;
}
/**
* Transforms the given layout coordinates to screen coordinates in px. If
* not possible, null is returned.
*/
public Rectangle2i computeRectangleMm(int page, Rectangle2f rect, ViewState viewState) {
Point2f nw = rect.position;
Point2f se = nw.add(rect.size);
Point2i nwPx = computePositionPx(layoutPos(layout, page, nw), viewState);
Point2i sePx = computePositionPx(layoutPos(layout, page, se), viewState);
return new Rectangle2i(nwPx, new Size2i(sePx.sub(nwPx)));
}
/**
* Computes the index of the page that is behind the given coordinates in
* px. If it is before the first page, the first page is returned. If it is
* between two pages, the left/top one is returned.
*/
public int getNearestPageIndex(Point2f pMm) {
// relevant axis: x when using horizontal page alignment, else y
float p = (pageDisplayAlignment == PageDisplayAlignment.Horizontal ? pMm.x : pMm.y);
// TODO (when closures are available): use binary search instead to find
// page very fast
if (pageRects.size() == 0) {
return 0;
}
else {
int ret = 0;
for (int i = 1; i < pageRects.size(); i++) {
Point2f po = getPageDisplayOffset(i);
float poc = (pageDisplayAlignment == PageDisplayAlignment.Horizontal ? po.x : po.y);
if (p > poc)
ret++;
else
break;
}
return ret;
}
}
/**
* Returns the coordinates in px of the given page.
*/
public Rectangle2i computePageRect(int pageIndex, ViewState viewState) {
//get page coordinates in px
Point2i pagePositionUpperLeftPx = computePositionPx(layoutPos(layout, pageIndex, p(0, 0)), viewState);
Size2f size = layout.getPages().get(pageIndex).getFormat().getSize();
Point2i pagePositionLowerRightPx = computePositionPx(
layoutPos(layout, pageIndex, p(size.width, size.height)), viewState);
//create rectangle
Rectangle2i ret = new Rectangle2i(pagePositionUpperLeftPx, new Size2i(
pagePositionLowerRightPx.x - pagePositionUpperLeftPx.x, pagePositionLowerRightPx.y -
pagePositionUpperLeftPx.y));
return ret;
}
}