devexpress-diagram
Version:
DevExpress Diagram Control
234 lines (217 loc) • 11.6 kB
text/typescript
import { Style, StrokeStyle } from "../Model/Style";
import { RectanglePrimitive } from "./Primitives/RectaglePrimitive";
import { SvgPrimitive } from "./Primitives/Primitive";
import { PathPrimitiveMoveToCommand, PathPrimitiveLineToCommand, PathPrimitive, PathPrimitiveCommand } from "./Primitives/PathPrimitive";
import { PatternPrimitive } from "./Primitives/PatternPrimitive";
import { ClipPathPrimitive } from "./Primitives/ClipPathPrimitive";
import { CanvasManagerBase } from "./CanvasManagerBase";
import { Rectangle } from "@devexpress/utils/lib/geometry/rectangle";
import { Offsets } from "@devexpress/utils/lib/geometry/offsets";
import { Size } from "@devexpress/utils/lib/geometry/size";
import { Point } from "@devexpress/utils/lib/geometry/point";
import { IModelSizeListener, IModelChangesListener } from "../Model/ModelManipulator";
import { ItemChange } from "../Model/ModelChange";
import { IViewChangesListener, AutoZoomMode } from "../Settings";
import { ICanvasViewListener } from "./CanvasViewManager";
import { RenderUtils } from "./Utils";
import { DOMManipulator } from "./DOMManipulator";
import { ColorUtils } from "@devexpress/utils/lib/utils/color";
import { UnitConverter } from "@devexpress/utils/lib/class/unit-converter";
const GRID_PAGES_LINEWIDTH = 2;
export class CanvasPageManager extends CanvasManagerBase implements IModelSizeListener, IModelChangesListener, ICanvasViewListener, IViewChangesListener {
private backgroundContainer: SVGElement;
private gridRectElement: SVGElement;
private gridPatternElement: SVGElement;
private pagesGridClipPathElement: SVGElement;
private pagesGridPatternElement: SVGElement;
private pageSize: Size;
private gridSize: number;
private gridVisible: boolean;
private modelSize: Size;
private simpleView: boolean;
private pageColor: number;
private canvasViewOffset: Point;
private snapPoint: Point = new Point(0, 0);
readonly gridPatternId = RenderUtils.generateSvgElementId("gridPattern");
readonly pagesGridPatternId = RenderUtils.generateSvgElementId("pagesGridPattern");
readonly pagesGridClipId = RenderUtils.generateSvgElementId("pagesGridClip");
constructor(parent: SVGElement, settings: ICanvasPageManagerSettings, dom: DOMManipulator) {
super(settings.zoomLevel, dom);
this.pageColor = settings.pageColor;
this.modelSize = settings.modelSize;
this.simpleView = settings.simpleView;
this.gridSize = settings.gridSize;
this.gridVisible = settings.gridVisible;
this.pageSize = settings.pageLandscape ? new Size(settings.pageSize.height, settings.pageSize.width) : settings.pageSize;
this.canvasViewOffset = new Point(0, 0);
this.initContainers(parent);
}
private initContainers(parent: SVGElement) {
this.backgroundContainer = parent;
}
public redraw() {
this.redrawPage(this.pageColor);
this.redrawGrid();
}
private redrawPage(color: number) {
const style = new Style();
style["fill"] = ColorUtils.colorToHash(color);
this.getOrCreateElement("page-bg", new RectanglePrimitive(0, 0, "100%", "100%", style, "page"), this.backgroundContainer);
this.createTextFloodFilter("page-bg-textflood-filter", this.backgroundContainer, color);
}
private redrawGrid() {
this.updateGridElements();
this.updatePagesGridElements();
}
private getGridRectElement(primitive: SvgPrimitive<SVGElement>) {
if(this.gridRectElement === undefined)
this.gridRectElement = this.createPrimitiveElement(primitive, this.backgroundContainer);
return this.gridRectElement;
}
private getGridPatternElement(primitive: SvgPrimitive<SVGElement>) {
if(this.gridPatternElement === undefined)
this.gridPatternElement = this.createPrimitiveElement(primitive, this.backgroundContainer);
return this.gridPatternElement;
}
private updateGridElements() {
const gridRectPrimitive = new RectanglePrimitive("0", "0", "100%", "100%", null, "grid", null, element => {
element.style.setProperty("fill", RenderUtils.getUrlPathById(this.gridPatternId));
});
const rectEl = this.getGridRectElement(gridRectPrimitive);
if(!this.gridVisible)
rectEl.style.display = "none";
else {
rectEl.style.display = "";
this.changePrimitiveElement(gridRectPrimitive, rectEl);
const absGridSize = UnitConverter.twipsToPixelsF(this.gridSize) * this.actualZoom;
const sizes = [0, 1, 2, 3, 4].map(i => Math.round(absGridSize * i));
const outerPathCommands = [
new PathPrimitiveMoveToCommand(sizes[4].toString(), "0"),
new PathPrimitiveLineToCommand(sizes[4].toString(), sizes[4].toString()),
new PathPrimitiveLineToCommand("0", sizes[4].toString())
];
const innerPathCommands = [];
for(let i = 1; i <= 3; i++) {
innerPathCommands.push(new PathPrimitiveMoveToCommand(sizes[i].toString(), "0"));
innerPathCommands.push(new PathPrimitiveLineToCommand(sizes[i].toString(), sizes[4].toString()));
}
for(let i = 1; i <= 3; i++) {
innerPathCommands.push(new PathPrimitiveMoveToCommand("0", sizes[i].toString()));
innerPathCommands.push(new PathPrimitiveLineToCommand(sizes[4].toString(), sizes[i].toString()));
}
const commonSize = absGridSize * 4;
const canvasViewOffset = this.simpleView ? this.canvasViewOffset : Point.zero();
const gridPatternPrimitive = new PatternPrimitive(this.gridPatternId,
[
this.createGridPathPrimitive(outerPathCommands, "grid-outer-line"),
this.createGridPathPrimitive(innerPathCommands, "grid-inner-line")
],
this.createGridPatternPrimitivePosition(canvasViewOffset.x, this.snapPoint.x, commonSize),
this.createGridPatternPrimitivePosition(canvasViewOffset.y, this.snapPoint.y, commonSize),
commonSize.toString(), commonSize.toString());
this.changePrimitiveElement(gridPatternPrimitive, this.getGridPatternElement(gridPatternPrimitive));
}
}
private createGridPatternPrimitivePosition(offset: number, coord: number, commonSize: number) : string {
return (((offset + coord * this.actualZoom) % commonSize - commonSize) % commonSize).toString();
}
private createGridPathPrimitive(commands: PathPrimitiveCommand[], className: string) : PathPrimitive {
return new PathPrimitive(commands, StrokeStyle.default1pxNegativeOffsetInstance, className);
}
private getPagesGridRectElement(primitive: SvgPrimitive<SVGElement>) {
return this.getOrCreateElement("grid-pages-rect", primitive, this.backgroundContainer);
}
private getPagesGridClipPathElement(primitive: SvgPrimitive<SVGElement>) {
if(this.pagesGridClipPathElement === undefined)
this.pagesGridClipPathElement = this.createPrimitiveElement(primitive, this.backgroundContainer);
return this.pagesGridClipPathElement;
}
private getPagesGridPatternElement(primitive: SvgPrimitive<SVGElement>) {
if(this.pagesGridPatternElement === undefined)
this.pagesGridPatternElement = this.createPrimitiveElement(primitive, this.backgroundContainer);
return this.pagesGridPatternElement;
}
private updatePagesGridElements() {
const pageAbsSize = this.getAbsoluteSize(this.pageSize);
const rectPrimitive = new RectanglePrimitive("0", "0", "100%", "100%", null, "grid-page", this.pagesGridClipId,
element => {
element.style.setProperty("fill", RenderUtils.getUrlPathById(this.pagesGridPatternId));
element.style.setProperty("display", this.simpleView ? "none" : "");
}
);
this.getPagesGridRectElement(rectPrimitive);
if(!this.simpleView) {
const modelSize = this.modelSize.clone().multiply(this.actualZoom, this.actualZoom);
const pageGridPathCommands = [
new PathPrimitiveMoveToCommand((pageAbsSize.width - GRID_PAGES_LINEWIDTH / 2).toString(), "0"),
new PathPrimitiveLineToCommand(
(pageAbsSize.width - GRID_PAGES_LINEWIDTH / 2).toString(),
(pageAbsSize.height - GRID_PAGES_LINEWIDTH / 2).toString()
),
new PathPrimitiveLineToCommand("0", (pageAbsSize.height - GRID_PAGES_LINEWIDTH / 2).toString())
];
const pagesGridPatternPrimitive = new PatternPrimitive(this.pagesGridPatternId, [
new PathPrimitive(pageGridPathCommands, null, "pages-grid-line")
], 0, 0, pageAbsSize.width.toString(), pageAbsSize.height.toString());
this.changePrimitiveElement(pagesGridPatternPrimitive, this.getPagesGridPatternElement(pagesGridPatternPrimitive));
const pagesGridClipPathPrimitive = new ClipPathPrimitive(this.pagesGridClipId, [
new RectanglePrimitive(0, 0,
(UnitConverter.twipsToPixelsF(modelSize.width) - GRID_PAGES_LINEWIDTH * 2).toString(),
(UnitConverter.twipsToPixelsF(modelSize.height) - GRID_PAGES_LINEWIDTH * 2).toString())
]);
this.changePrimitiveElement(pagesGridClipPathPrimitive, this.getPagesGridClipPathElement(pagesGridClipPathPrimitive));
}
}
notifyModelSizeChanged(size: Size, offset?: Offsets) {
this.modelSize = size.clone();
this.redraw();
}
notifyModelRectangleChanged(rectangle: Rectangle) { }
notifySnapPointPositionChanged(point: Point) {
this.snapPoint = point.clone().applyConverter(UnitConverter.twipsToPixelsF);
this.redrawGrid();
}
notifyPageColorChanged(color: number) {
this.pageColor = color;
this.redrawPage(this.pageColor);
}
notifyModelChanged(changes: ItemChange[]) { }
notifyPageSizeChanged(pageSize: Size, pageLandscape: boolean) {
this.pageSize = pageLandscape ? new Size(pageSize.height, pageSize.width) : pageSize.clone();
this.redraw();
}
notifyActualZoomChanged(actualZoom: number) {
this.actualZoom = actualZoom;
this.redraw();
}
notifyViewAdjusted(canvasViewOffset: Point) {
if(!this.canvasViewOffset.equals(canvasViewOffset)) {
this.canvasViewOffset = canvasViewOffset;
if(this.simpleView)
this.redraw();
}
}
notifyViewChanged(simpleView: boolean) {
this.simpleView = simpleView;
this.redraw();
}
notifyGridChanged(showGrid: boolean, gridSize: number) {
this.gridVisible = showGrid;
this.gridSize = gridSize;
this.redraw();
}
}
export interface ICanvasPageManagerSettings {
modelSize: Size;
rectangle: Rectangle;
zoomLevel: number;
simpleView: boolean;
autoZoom: AutoZoomMode;
readOnly: boolean;
contextMenuEnabled: boolean;
pageColor: number;
pageLandscape: boolean;
pageSize: Size;
gridVisible: boolean;
gridSize: number;
}