Skip to content

Commit

Permalink
Refactoring Step 10: Hierarchy by -level. Multi-layer prototype
Browse files Browse the repository at this point in the history
TODO: The outline doesn't look very good. Also merging of tails isn't supported yet.
  • Loading branch information
Unknown committed Sep 27, 2019
1 parent 44f1283 commit ae2f693
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 10 deletions.
76 changes: 68 additions & 8 deletions src/bubble.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Path, Point, Color, ToolEvent, Item, Shape, project } from "paper";
import { Path, Point, Color, ToolEvent, Item, Shape, project, Layer } from "paper";
import { BubbleSpec, Tip } from "bubbleSpec";
import Comical from "./comical";
import { Tail } from "./tail";
Expand All @@ -25,6 +25,10 @@ export default class Bubble {
private innerShape: Shape;
private tails: Tail[] = [];

private lowerLayer: Layer;
private upperLayer: Layer;
private handleLayer: Layer;

// Don't use new() to create Bubble elements. Use getInstance() instead.
// The reason why is because if multiple Bubble objects get created which correspond to the same element, they will have different member variables
// (e.g. different spec variables). If multiple objects are allowed, then a lot more flushing changes and re-reading the HTML will be required to keep them in sync.
Expand All @@ -33,6 +37,11 @@ export default class Bubble {
this.content = element;

this.spec = Bubble.getBubbleSpec(this.content);

// Just a default placeholder
if (project) {
this.lowerLayer = this.upperLayer = this.handleLayer = project!.activeLayer;
}
}

private static knownInstances: [HTMLElement, Bubble][] = [];
Expand Down Expand Up @@ -114,10 +123,41 @@ export default class Bubble {
this.persistBubbleSpec();
}

public setLayers(newLowerLayer: Layer, newUpperLayer: Layer, newHandleLayer: Layer): void {
this.setLowerLayer(newLowerLayer);
this.setUpperLayer(newUpperLayer);
this.setHandleLayer(newHandleLayer);
}

// Sets the value of lowerLayer. The "outline" shapes are drawn in the lower layer.
public setLowerLayer(layer: Layer): void {
this.lowerLayer = layer;
}

public getUpperLayer(): Layer {
return this.upperLayer;
}

// Sets the value of upperLayer. The "fill" shapes are drawn in the upper layer.
public setUpperLayer(layer: Layer): void {
this.upperLayer = layer;
}

// The layer containing the tip and midpoint curve handles
public setHandleLayer(layer: Layer): void {
this.handleLayer = layer;
}


public makeShapes() {
Bubble.loadShape(this.getStyle(), (newlyLoadedShape: Shape) => {
this.wrapShapeAroundDiv(newlyLoadedShape);
}); // Note: Make sure to use arrow functions to ensure that "this" refers to the right thing.
// TODO: Cleanup if async version proves unnecessary
// Async version
// this.loadShape(this.getStyle(), (newlyLoadedShape: Shape) => {
// this.wrapShapeAroundDiv(newlyLoadedShape);
// }); // Note: Make sure to use arrow functions to ensure that "this" refers to the right thing.

var newlyLoadedShape = this.loadShapeSync(this.getStyle());
this.wrapShapeAroundDiv(newlyLoadedShape);
}

private wrapShapeAroundDiv(shape: Shape) {
Expand All @@ -140,6 +180,9 @@ export default class Bubble {
//contentHolder.fillColor = new Color("cyan");
contentHolder.strokeWidth = 0;
this.innerShape = shape.clone() as Shape;
this.innerShape.remove(); // Removes it from the current (lower) layer.
this.upperLayer.addChild(this.innerShape);

//this.innerShape.bringToFront();
this.innerShape.fillColor = Comical.backColor;
const adjustSize = () => {
Expand Down Expand Up @@ -167,6 +210,9 @@ export default class Bubble {
this.shape.position = contentCenter;
this.innerShape.position = contentCenter;

// TODO: This still produces some minor imperfections in the border. Think of how to make it look prettier.
this.innerShape.strokeWidth = 0; // Get rid of the outline

// Draw tails, if necessary
if (this.spec.tips.length > 0) {
this.spec.tips.forEach(tail => {
Expand Down Expand Up @@ -213,25 +259,38 @@ export default class Bubble {
return svg;
}

private static loadShape(
public loadShape(
bubbleStyle: string,
onShapeLoaded: (s: Shape) => void
) {
const svg = Bubble.getShapeSvgString(bubbleStyle);

this.lowerLayer.activate(); // Sets this bubble's lowerLayer as the active layer, so that the SVG will be imported into the correct layer.
project!.importSVG(svg, {
onLoad: (item: Item) => {
onShapeLoaded(item as Shape);
}
});
}

public loadShapeSync(
bubbleStyle: string
): Shape {
const svg = Bubble.getShapeSvgString(bubbleStyle);

this.lowerLayer.activate(); // Sets this bubble's lowerLayer as the active layer, so that the SVG will be imported into the correct layer.
const newlyLoadShape = project!.importSVG(svg) as Shape; // I believe Only async if the string you pass in is a URL instead of the literal string containing the contents of the SVG definition
return newlyLoadShape;
}

public drawTail(
start: Point,
mid: Point,
tip: Point
): Tail {
const tipHandle = Bubble.makeHandle(tip);
const curveHandle = Bubble.makeHandle(mid);
const tipHandle = this.makeHandle(tip);
const curveHandle = this.makeHandle(mid);
this.upperLayer.activate();
let tail = new Tail(
start,
tipHandle.position!,
Expand Down Expand Up @@ -291,7 +350,8 @@ export default class Bubble {
// TODO: Help? where should I be? I think this comes up with unique names.
static handleIndex = 0;

static makeHandle(tip: Point): Path.Circle {
private makeHandle(tip: Point): Path.Circle {
this.handleLayer.activate();
const result = new Path.Circle(tip, 8);
result.strokeColor = new Color("aqua");
result.strokeWidth = 2;
Expand Down
49 changes: 47 additions & 2 deletions src/comical.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Color, project, setup } from "paper";
import { Color, project, setup, Layer } from "paper";

import Bubble from "./bubble";
import { uniqueIds } from "./uniqueId";
Expand Down Expand Up @@ -46,21 +46,66 @@ export default class Comical {
// call after adding or deleting elements with data-bubble
// assumes convertBubbleJsonToCanvas has been called and canvas exists
public static update(parent: HTMLElement) {
project!.activeLayer.removeChildren();
while (project!.layers.length > 1) {
project!.layers.pop();
}
if (project!.layers.length > 0) {
project!.layers[0].activate();
}

const elements = parent.ownerDocument!.evaluate(
".//*[@data-bubble]",
parent,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null
);

// Enhance: we want to be able to make all the bubbles and all the tails
// as a connected set so that all overlaps happen properly.
// Eventually, we should make distinct sets for each level.
// Eventually, we should be able to handle more than one tail per bubble.
var zLevelList = [];
var bubbleList = [];
for (let i = 0; i < elements.snapshotLength; i++) {
const element = elements.snapshotItem(i) as HTMLElement;
const bubble = Bubble.getInstance(element);
bubbleList.push(bubble);

let zLevel = 0;
if (bubble.spec.level) {
zLevel = bubble.spec.level;
}
zLevelList.push(zLevel);
}

// Ensure that they are in ascending order
zLevelList.sort();

// First we need to create all the layers in order. (Because they automatically get added to the end of the project's list of layers
const levelToLayer = {};
for (let i = 0; i < zLevelList.length; ++i) {
// Check if different than previous. (Ignore duplicate z-indices)
if (i == 0 || zLevelList[i-1] != zLevelList[i]) {
const zLevel = zLevelList[i];
var lowerLayer = new Layer();
var upperLayer = new Layer();
levelToLayer[zLevel] = [lowerLayer, upperLayer];
}
}
const handleLayer = new Layer();

// Now that the layers are created, we can go back and place objects into the correct layers and ask them to draw themselves.
for (let i = 0; i < bubbleList.length; ++i) {
const bubble = bubbleList[i];

let zLevel = 0;
if (bubble.spec.level) {
zLevel = bubble.spec.level;
}

const [lowerLayer, upperLayer] = levelToLayer[zLevel];
bubble.setLayers(lowerLayer, upperLayer, handleLayer);
bubble.makeShapes();
}
}
Expand Down
61 changes: 61 additions & 0 deletions stories/index.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,67 @@ storiesOf("bubble-edit", module)
button.style.position = "absolute";
button.style.top = "600px";
button.style.left = "0";
return wrapDiv;
})
.add("overlapping bubbles", () => {
// A generic picture
// Two bubbles that are merged together (at the same layer)
// Overlapping non-merged bubbles
// Multiple tails on a bubble
const wrapDiv = document.createElement("div");
wrapDiv.style.position = "relative";
wrapDiv.style.background = "url('The Moon and The Cap_Page 031.jpg') no-repeat 0/600px";
wrapDiv.style.height = "600px";

var div1 = makeTextBlock(wrapDiv, "This should be the highest layer", 130, 80, 100);
var div2 = makeTextBlock(wrapDiv, "This should be the lowest layer", 130, 150, 100);

var div3 = makeTextBlock(wrapDiv, "This should be the middle layer", 250, 80, 200);
var div4 = makeTextBlock(wrapDiv, "This should be merged with the other middle layer", 250, 130, 100);
// MakeDefaultTip() needs to see the divs laid out in their eventual positions,
// as does convertBubbleJsonToCanvas.
window.setTimeout(() => {
const bubble1 = Bubble.getInstance(div1);
bubble1.spec = {
version: "1.0",
style: "speech",
tips: [Bubble.makeDefaultTip(div1)],
level: 3
};
bubble1.setBubbleSpec(bubble1.spec);

const bubble2 = Bubble.getInstance(div2);
bubble2.spec = {
version: "1.0",
style: "speech",
tips: [Bubble.makeDefaultTip(div2)],
level: 1
};
bubble2.setBubbleSpec(bubble2.spec);


const bubble3 = Bubble.getInstance(div3);
bubble3.spec = {
version: "1.0",
style: "speech",
tips: [Bubble.makeDefaultTip(div3)],
level: 2
};
bubble3.setBubbleSpec(bubble3.spec);

const bubble4 = Bubble.getInstance(div4);
bubble4.spec = {
version: "1.0",
style: "speech",
tips: [Bubble.makeDefaultTip(div4)],
level: 2
};
bubble4.setBubbleSpec(bubble4.spec);

Comical.convertBubbleJsonToCanvas(wrapDiv);
}, 200);


return wrapDiv;
});

Expand Down
Binary file added storyStatic/The Moon and The Cap_Page 031.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit ae2f693

Please sign in to comment.