Convert self-connections into asynchronous connections for better rendering [#43]

This commit is contained in:
David Evans 2018-01-27 16:55:48 +00:00
parent bbb9350e15
commit 5283c511e9
16 changed files with 223 additions and 112 deletions

View File

@ -3112,7 +3112,36 @@ define('sequence/Generator',['core/ArrayUtilities'], (array) => {
];
}
_isSelfConnect(agents) {
const gAgents = agents.map(this.toGAgent);
const expandedGAgents = this.expandGroupedGAgentConnection(gAgents);
if(expandedGAgents[0].id !== expandedGAgents[1].id) {
return false;
}
if(expandedGAgents.some((gAgent) => gAgent.isVirtualSource)) {
return false;
}
return true;
}
handleConnect({agents, label, options}) {
if(this._isSelfConnect(agents)) {
const tag = {};
this.handleConnectDelayBegin({
agent: agents[0],
tag,
options,
ln: 0,
});
this.handleConnectDelayEnd({
agent: agents[1],
tag,
label,
options,
});
return;
}
let {flags, gAgents} = this._handlePartialConnect(agents);
gAgents = this.expandGroupedGAgentConnection(gAgents);
@ -3922,13 +3951,18 @@ define('sequence/components/BaseComponent',[],() => {
}
BaseComponent.cleanRenderPreResult = ({
topShift = 0,
agentIDs = [],
topShift = 0,
y = null,
asynchronousY = null,
} = {}, currentY = null) => {
if(y !== null && currentY !== null) {
topShift = Math.max(topShift, y - currentY);
}
return {
topShift,
agentIDs,
topShift,
y,
asynchronousY: (asynchronousY !== null) ? asynchronousY : currentY,
};
};
@ -4025,7 +4059,7 @@ define('sequence/components/Block',[
'width': agentInfoR.x - agentInfoL.x,
'height': labelHeight,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}), clickable.firstChild);
if(!first) {
@ -4157,8 +4191,9 @@ define('sequence/components/Parallel',[
function mergeResults(a, b) {
array.mergeSets(a.agentIDs, b.agentIDs);
return {
topShift: Math.max(a.topShift, b.topShift),
agentIDs: a.agentIDs,
topShift: Math.max(a.topShift, b.topShift),
y: nullableMax(a.y, b.y),
asynchronousY: nullableMax(a.asynchronousY, b.asynchronousY),
};
}
@ -4309,7 +4344,7 @@ define('sequence/components/AgentCap',[
'width': width,
'height': height,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}), clickable.firstChild);
return {
@ -4347,7 +4382,7 @@ define('sequence/components/AgentCap',[
'width': d * 2,
'height': d * 2,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}));
return {
@ -4402,7 +4437,7 @@ define('sequence/components/AgentCap',[
'width': width,
'height': height,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}));
return {
@ -4463,7 +4498,7 @@ define('sequence/components/AgentCap',[
'width': config.width,
'height': config.height,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}));
return {
@ -4498,7 +4533,7 @@ define('sequence/components/AgentCap',[
'width': w,
'height': config.height,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}));
return {
@ -4872,16 +4907,12 @@ define('sequence/components/Connect',[
to.x + to.currentMaxRad + rArrow.width(env.theme),
xL + labelW
);
const y2 = Math.max(
yBegin + config.loopbackRadius * 2,
env.primaryY
);
this.renderRevArrowLine({
x1: from.x + from.currentMaxRad,
y1: yBegin,
x2: to.x + to.currentMaxRad,
y2,
y2: env.primaryY,
xR,
}, options, env);
@ -4892,12 +4923,16 @@ define('sequence/components/Connect',[
'x': from.x,
'y': yBegin - raise,
'width': xR + config.loopbackRadius - from.x,
'height': raise + y2 - yBegin + arrowDip,
'height': raise + env.primaryY - yBegin + arrowDip,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}), clickable.firstChild);
return y2 + Math.max(arrowDip, 0) + env.theme.actionMargin;
return (
env.primaryY +
Math.max(arrowDip, 0) +
env.theme.actionMargin
);
}
renderArrowLine({x1, y1, x2, y2}, options, env) {
@ -5031,7 +5066,7 @@ define('sequence/components/Connect',[
'Z'
),
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}));
this.renderSimpleLabel(label, {
@ -5122,11 +5157,21 @@ define('sequence/components/Connect',[
separation() {}
renderPre({tag}, env) {
const config = env.theme.connect;
const dc = env.state.delayedConnections;
const beginStage = dc.get(tag).stage;
return Object.assign(super.renderPre(beginStage, env), {
agentIDs: [beginStage.agentIDs[1]],
});
const begin = dc.get(tag);
const beginStage = begin.stage;
const agentIDs = [beginStage.agentIDs[1]];
if(beginStage.agentIDs[0] === beginStage.agentIDs[1]) {
return {
agentIDs,
y: begin.y + config.loopbackRadius * 2,
};
}
return Object.assign(super.renderPre(beginStage, env), {agentIDs});
}
render({tag}, env) {
@ -5241,7 +5286,7 @@ define('sequence/components/Note',['./BaseComponent', 'svg/SVGUtilities'], (Base
'width': x1 - x0,
'height': fullH,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}), clickable.firstChild);
return (
@ -5500,7 +5545,7 @@ define('sequence/components/Divider',[
'width': right.x - left.x + config.extend * 2,
'height': fullHeight,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}), clickable.firstChild);
return env.primaryY + fullHeight + env.theme.actionMargin;
@ -5815,7 +5860,7 @@ define('sequence/Renderer',[
};
const component = this.components.get(stage.type);
const result = component.renderPre(stage, envPre);
const {topShift, agentIDs, asynchronousY} =
const {agentIDs, topShift, asynchronousY} =
BaseComponent.cleanRenderPreResult(result, this.currentY);
const topY = this.checkAgentRange(agentIDs, asynchronousY);

File diff suppressed because one or more lines are too long

View File

@ -593,6 +593,19 @@ Marks elements generated by the specified line with a "focus" CSS class, which
can be used to style them. Only one line can be highlighted at a time. Calling
with no parameter (or <code>null</code>) will remove the highlighting.
</p>
<p>
The outline effect seen in the editor can be achieved by targetting
<code>.focus .outline</code>. All elements contain an element with the class
"outline", which defines a simple shape surrounding the entire element. For
example:
</p>
<pre data-lang="css">
.region.focus .outline {
stroke-width: 5px;
stroke: rgba(255, 128, 0, 0.5);
}
</pre>
<h3 id="API_addEventListener">.addEventListener</h3>

View File

@ -844,7 +844,36 @@ define(['core/ArrayUtilities'], (array) => {
];
}
_isSelfConnect(agents) {
const gAgents = agents.map(this.toGAgent);
const expandedGAgents = this.expandGroupedGAgentConnection(gAgents);
if(expandedGAgents[0].id !== expandedGAgents[1].id) {
return false;
}
if(expandedGAgents.some((gAgent) => gAgent.isVirtualSource)) {
return false;
}
return true;
}
handleConnect({agents, label, options}) {
if(this._isSelfConnect(agents)) {
const tag = {};
this.handleConnectDelayBegin({
agent: agents[0],
tag,
options,
ln: 0,
});
this.handleConnectDelayEnd({
agent: agents[1],
tag,
label,
options,
});
return;
}
let {flags, gAgents} = this._handlePartialConnect(agents);
gAgents = this.expandGroupedGAgentConnection(gAgents);

View File

@ -20,6 +20,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
const any = () => jasmine.anything();
const PARSED = {
sourceAgent: {name: '', alias: '', flags: ['source']},
blockBegin: (tag, label, {ln = 0} = {}) => {
return {type: 'block begin', blockType: tag, tag, label, ln};
},
@ -522,10 +524,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('converts source agents into virtual agents', () => {
const sequence = invoke([
PARSED.connect([
'A',
{name: '', alias: '', flags: ['source']},
]),
PARSED.connect(['A', PARSED.sourceAgent]),
]);
expect(sequence.agents).toEqual([
GENERATED.agent('['),
@ -545,14 +544,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('converts sources into distinct virtual agents', () => {
const sequence = invoke([
PARSED.connect([
'A',
{name: '', alias: '', flags: ['source']},
]),
PARSED.connect([
'A',
{name: '', alias: '', flags: ['source']},
]),
PARSED.connect(['A', PARSED.sourceAgent]),
PARSED.connect(['A', PARSED.sourceAgent]),
]);
expect(sequence.agents).toEqual([
GENERATED.agent('['),
@ -572,10 +565,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('places source agents near the connected agent', () => {
const sequence = invoke([
PARSED.beginAgents(['A', 'B', 'C']),
PARSED.connect([
'B',
{name: '', alias: '', flags: ['source']},
]),
PARSED.connect(['B', PARSED.sourceAgent]),
]);
expect(sequence.agents).toEqual([
GENERATED.agent('['),
@ -593,10 +583,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('places source agents left when connections are reversed', () => {
const sequence = invoke([
PARSED.beginAgents(['A', 'B', 'C']),
PARSED.connect([
{name: '', alias: '', flags: ['source']},
'B',
]),
PARSED.connect([PARSED.sourceAgent, 'B']),
]);
expect(sequence.agents).toEqual([
GENERATED.agent('['),
@ -613,10 +600,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('rejects connections between virtual agents', () => {
expect(() => invoke([
PARSED.connect([
{name: '', alias: '', flags: ['source']},
{name: '', alias: '', flags: ['source']},
]),
PARSED.connect([PARSED.sourceAgent, PARSED.sourceAgent]),
])).toThrow(new Error(
'Cannot connect found messages at line 1'
));
@ -624,10 +608,7 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
it('rejects connections between virtual agents and sides', () => {
expect(() => invoke([
PARSED.connect([
{name: '', alias: '', flags: ['source']},
']',
]),
PARSED.connect([PARSED.sourceAgent, ']']),
])).toThrow(new Error(
'Cannot connect found messages to special agents at line 1'
));
@ -720,6 +701,55 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
]);
});
it('converts self connections into delayed connections', () => {
const sequence = invoke([
PARSED.connect(['A', 'A'], {
ln: 0,
label: 'woo',
line: 'solid',
left: 0,
right: 1,
}),
]);
expect(sequence.stages).toEqual([
any(),
GENERATED.connectDelayBegin(['A', 'A'], {
label: 'woo!',
tag: '__0',
line: 'solid',
left: 0,
right: 1,
ln: 0,
}),
GENERATED.connectDelayEnd({
tag: '__0',
ln: 0,
}),
any(),
]);
});
it('adds parallel highlighting stages to self connections', () => {
const sequence = invoke([
PARSED.connect([
{name: 'A', alias: '', flags: ['start']},
{name: 'A', alias: '', flags: ['stop']},
], {label: 'woo'}),
]);
expect(sequence.stages).toEqual([
any(),
GENERATED.parallel([
GENERATED.highlight(['A'], true),
GENERATED.connectDelayBegin(['A', 'A'], {label: 'woo!'}),
]),
GENERATED.parallel([
GENERATED.connectDelayEnd(),
GENERATED.highlight(['A'], false),
]),
any(),
]);
});
it('merges delayed connect arrows', () => {
const sequence = invoke([
PARSED.beginAgents(['A', 'B']),
@ -1123,15 +1153,6 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
'Cannot set agent highlighting multiple times at line 1'
));
expect(() => invoke([
PARSED.connect([
{name: 'A', alias: '', flags: ['start']},
{name: 'A', alias: '', flags: ['stop']},
]),
])).toThrow(new Error(
'Cannot set agent highlighting multiple times at line 1'
));
expect(() => invoke([
PARSED.connect([
'A',
@ -1140,15 +1161,6 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
])).toThrow(new Error(
'Cannot set agent visibility multiple times at line 1'
));
expect(() => invoke([
PARSED.connect([
{name: 'A', alias: '', flags: ['begin']},
{name: 'A', alias: '', flags: ['end']},
]),
])).toThrow(new Error(
'Cannot set agent visibility multiple times at line 1'
));
});
it('adds implicit highlight end with implicit terminator', () => {
@ -1640,8 +1652,10 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
expect(sequence.stages).toEqual([
any(),
any(),
GENERATED.connect(['__BLOCK0]', '__BLOCK0]']),
GENERATED.connect(['__BLOCK0]', '__BLOCK0]']),
GENERATED.connectDelayBegin(['__BLOCK0]', '__BLOCK0]']),
GENERATED.connectDelayEnd(),
GENERATED.connectDelayBegin(['__BLOCK0]', '__BLOCK0]']),
GENERATED.connectDelayEnd(),
any(),
any(),
]);
@ -1792,14 +1806,8 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
const sequence = invoke([
PARSED.beginAgents(['A', 'B', 'C', 'D']),
PARSED.groupBegin('Bar', ['B', 'C'], {label: 'Foo'}),
PARSED.connect([
{name: '', alias: '', flags: ['source']},
'Bar',
]),
PARSED.connect([
'Bar',
{name: '', alias: '', flags: ['source']},
]),
PARSED.connect([PARSED.sourceAgent, 'Bar']),
PARSED.connect(['Bar', PARSED.sourceAgent]),
PARSED.endAgents(['Bar']),
]);

View File

@ -301,7 +301,7 @@ define([
};
const component = this.components.get(stage.type);
const result = component.renderPre(stage, envPre);
const {topShift, agentIDs, asynchronousY} =
const {agentIDs, topShift, asynchronousY} =
BaseComponent.cleanRenderPreResult(result, this.currentY);
const topY = this.checkAgentRange(agentIDs, asynchronousY);

View File

@ -57,7 +57,7 @@ define([
'width': width,
'height': height,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}), clickable.firstChild);
return {
@ -95,7 +95,7 @@ define([
'width': d * 2,
'height': d * 2,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}));
return {
@ -150,7 +150,7 @@ define([
'width': width,
'height': height,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}));
return {
@ -211,7 +211,7 @@ define([
'width': config.width,
'height': config.height,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}));
return {
@ -246,7 +246,7 @@ define([
'width': w,
'height': config.height,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}));
return {

View File

@ -65,13 +65,18 @@ define(() => {
}
BaseComponent.cleanRenderPreResult = ({
topShift = 0,
agentIDs = [],
topShift = 0,
y = null,
asynchronousY = null,
} = {}, currentY = null) => {
if(y !== null && currentY !== null) {
topShift = Math.max(topShift, y - currentY);
}
return {
topShift,
agentIDs,
topShift,
y,
asynchronousY: (asynchronousY !== null) ? asynchronousY : currentY,
};
};

View File

@ -77,7 +77,7 @@ define([
'width': agentInfoR.x - agentInfoL.x,
'height': labelHeight,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}), clickable.firstChild);
if(!first) {

View File

@ -239,16 +239,12 @@ define([
to.x + to.currentMaxRad + rArrow.width(env.theme),
xL + labelW
);
const y2 = Math.max(
yBegin + config.loopbackRadius * 2,
env.primaryY
);
this.renderRevArrowLine({
x1: from.x + from.currentMaxRad,
y1: yBegin,
x2: to.x + to.currentMaxRad,
y2,
y2: env.primaryY,
xR,
}, options, env);
@ -259,12 +255,16 @@ define([
'x': from.x,
'y': yBegin - raise,
'width': xR + config.loopbackRadius - from.x,
'height': raise + y2 - yBegin + arrowDip,
'height': raise + env.primaryY - yBegin + arrowDip,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}), clickable.firstChild);
return y2 + Math.max(arrowDip, 0) + env.theme.actionMargin;
return (
env.primaryY +
Math.max(arrowDip, 0) +
env.theme.actionMargin
);
}
renderArrowLine({x1, y1, x2, y2}, options, env) {
@ -398,7 +398,7 @@ define([
'Z'
),
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}));
this.renderSimpleLabel(label, {
@ -489,11 +489,21 @@ define([
separation() {}
renderPre({tag}, env) {
const config = env.theme.connect;
const dc = env.state.delayedConnections;
const beginStage = dc.get(tag).stage;
return Object.assign(super.renderPre(beginStage, env), {
agentIDs: [beginStage.agentIDs[1]],
});
const begin = dc.get(tag);
const beginStage = begin.stage;
const agentIDs = [beginStage.agentIDs[1]];
if(beginStage.agentIDs[0] === beginStage.agentIDs[1]) {
return {
agentIDs,
y: begin.y + config.loopbackRadius * 2,
};
}
return Object.assign(super.renderPre(beginStage, env), {agentIDs});
}
render({tag}, env) {

View File

@ -82,7 +82,7 @@ define([
'width': right.x - left.x + config.extend * 2,
'height': fullHeight,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}), clickable.firstChild);
return env.primaryY + fullHeight + env.theme.actionMargin;

View File

@ -92,7 +92,7 @@ define(['./BaseComponent', 'svg/SVGUtilities'], (BaseComponent, svg) => {
'width': x1 - x0,
'height': fullH,
'fill': 'transparent',
'class': 'vis',
'class': 'outline',
}), clickable.firstChild);
return (

View File

@ -20,8 +20,9 @@ define([
function mergeResults(a, b) {
array.mergeSets(a.agentIDs, b.agentIDs);
return {
topShift: Math.max(a.topShift, b.topShift),
agentIDs: a.agentIDs,
topShift: Math.max(a.topShift, b.topShift),
y: nullableMax(a.y, b.y),
asynchronousY: nullableMax(a.asynchronousY, b.asynchronousY),
};
}

View File

@ -36,7 +36,7 @@ define(['jshintConfig', 'specs'], (jshintConfig) => {
const OPTS_TEST = Object.assign({}, jshintConfig, {
predef: PREDEF_TEST,
maxstatements: 100, // allow lots of tests
maxstatements: 200, // allow lots of tests
});
function formatError(error) {

View File

@ -153,17 +153,17 @@ html, body {
height: 100%;
}
.pane-view .region:hover .vis {
.pane-view .region:hover .outline {
stroke-width: 5px;
stroke: rgba(255, 255, 0, 0.5);
}
.pane-view .region.focus .vis {
.pane-view .region.focus .outline {
stroke-width: 5px;
stroke: rgba(255, 128, 0, 0.5);
}
.pane-view .region.focus:hover .vis {
.pane-view .region.focus:hover .outline {
stroke-width: 5px;
stroke: rgba(255, 192, 0, 0.5);
}

View File

@ -89,7 +89,7 @@ a:hover, a:active {
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.region.focus rect {
.region.focus .outline {
stroke-width: 5px;
stroke: rgba(255, 128, 0, 0.5);
}