-
Notifications
You must be signed in to change notification settings - Fork 11
/
gate-theme.less
553 lines (482 loc) · 15.1 KB
/
gate-theme.less
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
@import "_variables";
// This file produces styles for logic gates, connectors,
// inputs, and outputs. It also has extendable selectors
// for active gates, which are reference-imported by each
// file that includes _themed-operators.less.
@remSize: 16px;
@background: #FFF3E0;
@borderColor: #FFA000;
@inactiveColor: #E0E0E0;
@inactiveHoverColor: #B3E5FC;
@activeColor: #FFC107;
@activeHoverColor: #FFB300;
@transitionDuration: @easing @duration;
@borderThickness: 2px / @remSize * 1rem;
@border: @borderThickness solid @borderColor;
@valueSize: 3rem + 2 * @borderThickness; // The size of an input or output box. Add a border thickness on each side to be covered by adjacent connectors.
@connectorThickness: .5rem; // The length in the cross axis
@connectorCenterOffset: .5 * @connectorThickness + @borderThickness; // The distance from the left/top edge to the center
@gateWidth: 4rem; // The width of the entire gate object
@gateConcaveRadius: @gateWidth * .9; // The radius of the circle carving out the concave part of OR/XOR gates
@gateInputOffset: 1rem; // The distance from the edge of the gate to the center of the input connector. Must match declared coordinates in the HTML.
@gateConcaveY: -@gateConcaveRadius * 1.8; // The Y coordinate of the concave circle, relative to the gate
@gateConcaveX: @gateWidth / 2 - @gateConcaveRadius; // The X coordinate of the concave circle, relative to the gate
@gateConcaveCenterY: -@gateConcaveY - @gateConcaveRadius; // The distance from the center of the concave circle to the top of the gate
@gateConnectorNearOffset: @gateInputOffset - @connectorCenterOffset; // The distance from the outer edge of the gate to the closest edge of the input connectors
@gateConnectorFarOffset: @gateInputOffset + @connectorCenterOffset; // The distance from the outer edge of the gate to the far edge of the input connectors
// The offset of the entire gate object, from its
// declared Y coordinate.
// This is calculated to make sure that the outer
// (away from the center of the gate) edge of the
// connector is flush with the circle.
@gateOffset: sqrt(pow(@gateConcaveRadius, 2)
- pow(@gateWidth / 2 - @gateConnectorNearOffset, 2))
- @gateConcaveCenterY;
// Applies a value at the current scale.
// This is used to set the base REM size
// and to work around an IE bug with REM
// values in line-heights for :after.
.scaleValue(@property, @value) {
@{property}: unit(@value) * @remSize;
@media (max-width: 640px), (max-height: 700px) {
@{property}: 8px * unit(@value);
}
}
:root {
.scaleValue(font-size, 1);
box-sizing: border-box;
}
.square(@size) {
width: @size;
height: @size;
}
// Draws a boxed a value (input or computed result). The
// caller must extend .ActiveValueBox when active.
.ValueBox() {
display: block;
.square(@valueSize);
box-sizing: border-box;
background-color: @inactiveColor;
border: @border;
transition+: background @transitionDuration;
position: relative;
overflow: hidden;
&:after {
// Make the text element cover the parent border
margin-left: -@borderThickness;
margin-top: -@borderThickness;
white-space: pre;
content: '0\00000a1';
font-size: 2rem;
display: block;
.square(@valueSize);
.scaleValue(line-height, @valueSize);
text-align: center;
position: absolute;
top: 0;
transition: top @transitionDuration;
}
}
// The size of concave circle atop OR and XOR gates.
.GateCircle() {
.square(2 * @gateConcaveRadius);
display: block;
border-radius: 50%;
position: absolute;
}
.ColorizeConnectors(@color) {
+ div > .Connector {
&, &:before {
background-color: @color;
}
}
}
.Connectors.InactiveHover { .ColorizeConnectors(@inactiveHoverColor); }
.Connectors.Active { .ColorizeConnectors(@activeColor); }
.Connectors.ActiveHover { .ColorizeConnectors(@activeHoverColor); }
// Applies active styles to a .ValueBox() element
.ActiveValueBox {
background-color: @activeColor;
&:after {
top: -@valueSize;
}
}
.Canvas input[type=checkbox] {
// Hide the checkbox (but make sure it's still focusable), and draw
// the subsequent label instead, since only Chrome allows :after on
// checkboxes. To preserve scrolling behavior on focus, the <input>
// must be explicitly positioned at the same location as the label.
opacity: 0;
+ label {
.square(@valueSize);
margin: -@borderThickness;
padding: 0;
cursor: pointer;
transition+: box-shadow @transitionDuration;
.ValueBox();
&:hover {
background-color: @inactiveHoverColor;
&:extend(.Connectors.InactiveHover all);
}
}
&:checked + label {
&:extend(.ActiveValueBox all);
&:extend(.Connectors.Active all);
&:hover {
background-color: @activeHoverColor;
&:extend(.Connectors.ActiveHover all);
}
}
&:focus + label {
box-shadow: 0 0 5px 2px #E65100;
}
}
.Canvas {
@padding: 1rem;
position: relative;
// Add spacing around the content, without affecting
// the offsets of absolutely positioned children, by
// making the background & border extend outside the
// element's boundaries
overflow: visible;
margin: @padding;
&:before {
content: '';
display: block;
background-color: @background;
border: @borderThickness solid #FFB74D;
position: absolute;
top: -@padding;
left: -@padding;
right: -@padding;
bottom: -@padding;
}
> *, > .Group > * {
position: absolute !important; // Override position: relative from checkboxes
}
> .Group {
position: static !important;
}
}
// A line connecting value boxes or combinators.
// These lines are laid out in style attributes,
// as if the line had zero thickness. Therefore,
// the position (top or left) sets the center of
// the line.
.Connector {
background: @inactiveColor;
transition: background @transitionDuration;
z-index: 1; // Cover the borders of subsequent connectors.
// Corners cover the joint between two connectors,
// as a class applied to the second element. They
// are centered around the overlapping centers for
// their two adjacent connectors, and have borders
// to close the edges.
&.Corner:before {
content: '';
display: block;
position: absolute;
// The corner must have the same dimensions as
// the connectors. Borders are added by every
// direction separately for the correct sides.
.square(@connectorThickness);
transition: inherit;
// Using inherit adds unwanted cascade to animations
background-color: @inactiveColor;
}
&.Vertical {
// Center the element in the cross axis, around the declared position
width: @connectorThickness;
margin-left: -@connectorCenterOffset;
border-left: @border;
border-right: @border;
&.Corner:before {
top: -@connectorCenterOffset; // Cover the thickness of the adjoining connector, which is centered at our top
left: -@borderThickness; // Cover the parent's outer border
border-top: @border;
// Don't pad the edge facing our connector,
// so that the connector's border can cover
// the pixel at the bend.
}
&.Corner.Left:before {
border-right: @border;
padding-left: @borderThickness; // Stretch over the adjoining connector's borders
}
&.Corner.Right:before {
border-left: @border;
padding-right: @borderThickness;
}
&.Corner.Bottom:before {
top: auto;
bottom: -@connectorCenterOffset; // Cover the thickness of the adjoining connector, which is centered at our right
left: -@borderThickness; // Cover the parent's outer border
border-bottom: @border;
border-top: none;
}
// A convex connector contains a chopped-off circle
// to match the concave curve at the top of a gate.
// Because of the overflow: hidden, these cannot be
// combined with corner connectors.
&.Convex {
overflow: hidden;
// The main element just serves as a container to
// chop off the sides of the circle.
background: none !important;
border: none;
padding: 0 @borderThickness; // Add padding to match the connector's border
height: 1rem; // Must be at least as tall as the visible part of the circle
&:before {
content: '';
.GateCircle();
// Using inherit adds unwanted cascade to animations
background-color: @inactiveColor;
transition: inherit;
// Shift back to the center of the connector (see
// Connector's negative margin, which centers the
// visible portion)
margin: 0 @connectorCenterOffset;
top: @gateConcaveY - @gateOffset;
}
&.Left:before {
left: @gateConcaveX - @gateInputOffset;
}
&.Right:before {
right: @gateConcaveX - @gateInputOffset;
}
// Add a border on the inner side of the connector
// to cover the additional height (from the deeper
// part of the concave circle)
&:after {
content: '';
display: block;
position: absolute;
width: @gateWidth / 2 - @gateConnectorFarOffset;
width: @gateConnectorFarOffset;
// Distance from center to inner part of circle
// minus distance from center to the outer part
// of the circle.
height: sqrt(pow(@gateConcaveRadius, 2)
- pow(@gateWidth / 2 - @gateConnectorFarOffset, 2))
- (-@gateConcaveY + @gateOffset - @gateConcaveRadius)
- @borderThickness * .15; // Subtract a pixel so the near edge of the border doesn't stretch beyond the curve
}
&.Left:after {
right: 0;
border-right: @border;
}
&.Right:after {
left: 0;
border-left: @border;
}
}
// A junction covers the portion of the borders
// that stick into the adjoining connector
&.Junction.Top:before {
content: '';
display: block;
background: @inactiveColor;
transition: inherit;
position: absolute;
top: 0;
right: -@borderThickness; // Cover the parent's outer border
width: @connectorThickness + 2 * @borderThickness;
height: @connectorThickness / 2; // Cover the distance from the center of the adjoining connector (our edge) to its border
}
}
&.Horizontal {
// Center the element in the cross axis, around the declared position
height: @connectorThickness;
margin-top: -@connectorCenterOffset;
border-top: @border;
border-bottom: @border;
&.Corner.Left:before {
left: -@connectorCenterOffset; // Cover the thickness of the adjoining connector, which is centered at our left
top: -@borderThickness; // Cover the parent's outer border
border-left: @border;
}
&.Corner.Right:before {
right: -@connectorCenterOffset; // Cover the thickness of the adjoining connector, which is centered at our right
top: -@borderThickness; // Cover the parent's outer border
border-right: @border;
}
&.Corner.Up:before {
border-bottom: @border;
padding-top: @borderThickness;
}
&.Corner.Down:before {
border-top: @border;
padding-bottom: @borderThickness;
}
// A junction covers the portion of the borders
// that stick into the adjoining connector
&.Junction.Right:before {
content: '';
display: block;
background: @inactiveColor;
transition: inherit;
position: absolute;
right: 0;
top: -@borderThickness; // Cover the parent's outer border
width: @connectorThickness / 2;
height: @connectorThickness + 2 * @borderThickness;
}
}
}
.Result {
border-radius: @valueSize / 2;
margin: -@borderThickness;
.ValueBox();
}
.ActiveGate.OR, .ActiveGate.XOR {
span:before, &:after {
background-color: @activeColor;
}
}
.ActiveGate.AND {
background-color: @activeColor;
}
.ActiveGate.Result {
+ div > .Result {
&:extend(.ActiveValueBox all);
}
}
.GateLabel() {
font-size: 1.4rem;
text-align: center;
}
.OrGate(@baseStraightHeight) {
// The height of the entire tip portion, as expected
// in element positions. The outgoing connector will
// align to this height. This height is not used in
// the actual gate layout other than that alignment.
@formalTipHeight: 3rem;
@tipRadius: @gateWidth;
// Expand the straight portion to compensate for the negative margin,
// then again to align the tip to the output connector. We calculate
// the Y coordinate of the point along the curve where the connector
// border reaches the tip.
@straightHeight: @baseStraightHeight + @gateOffset
+ (@formalTipHeight - sqrt(
pow(@gateWidth, 2)
- pow(@gateWidth / 2 + @connectorThickness / 2, 2)));
position: relative;
width: @gateWidth;
height: @straightHeight + @tipRadius;
z-index: 0;
margin-top: -@gateOffset;
overflow: hidden; // Hide the rest of the concave circle (see below)
// The straight portion is a separate element,
// because it is shorter than the full parent.
// This element also contains the label, which
// extends into the bottom part.
&:after {
content: '';
display: block;
height: @straightHeight;
background-color: @inactiveColor;
border-left: @border;
border-right: @border;
transition: background @transitionDuration;
.GateLabel();
.scaleValue(line-height, @straightHeight + @tipRadius * .75);
position: relative;
z-index: 1; // Cover the :before
}
// The concave curve on the top is implemented
// as a large circle with the background color
// to cover the top part of the straight part.
&:before {
content: '';
.BlockingGateCircle();
top: @gateConcaveY;
z-index: 2; // Cover the :after
}
.BlockingGateCircle() {
.GateCircle();
left: @gateConcaveX;
box-sizing: border-box;
background-color: @background;
border: @border;
}
// To make the lower curve stop at an angle,
// I make two curves that cover the complete
// width of the element, and cut off half of
// each one so that they meet in the middle.
span {
display: block;
position: absolute;
top: @straightHeight;
height: @tipRadius;
width: @gateWidth / 2;
overflow: hidden;
&:before {
content: '';
display: block;
background-color: @inactiveColor;
border-bottom: @border;
position: absolute;
height: @tipRadius;
width: @gateWidth;
transition: background @transitionDuration;
}
}
span:first-child {
left: 0;
&:before {
border-left: @border;
border-bottom-left-radius: @tipRadius;
}
}
span + span {
right: 0;
&:before {
right: 0;
border-right: @border;
border-bottom-right-radius: @tipRadius;
}
}
}
.Gate.OR {
.OrGate(1rem);
&:after {
content: 'OR';
}
}
.Gate.XOR {
@curveGap: .75rem; // The gap between the upper curve and the rest of the gate.
@straightHeight: 2rem - @curveGap;
.OrGate(@straightHeight + @curveGap);
&:after {
margin-top: @curveGap;
height: @straightHeight + @gateOffset;
content: 'XOR';
.scaleValue(line-height, @straightHeight + @tipRadius * .56);
}
// The original circle from .OrGate() is now the upper
// curve, and is aligned to incoming connectors
&:before {
z-index: 3; // This must be above the lower curve, or the border will be covered
}
// Add a second circle for the lower curve (the top of
// the gate body)
div {
.BlockingGateCircle();
top: @gateConcaveY + @curveGap;
z-index: 2; // THe must go between the :after (the staight part) and the :before (the upper curve)
}
}
.Gate.AND {
width: @gateWidth;
height: 5rem;
margin: -@borderThickness; // Expand vertically so the connectors cover our border
border-radius: 0 0 @gateWidth @gateWidth;
background: @inactiveColor;
border: @border;
transition: background @transitionDuration;
&:after {
display: block;
content: 'AND';
.scaleValue(line-height, 5rem - @gateWidth / 5);
.GateLabel();
}
}