connecting wires
This commit is contained in:
parent
b0038fd7a7
commit
8d0129f25b
20
src/main/angular/src/app/editor/Point.ts
Normal file
20
src/main/angular/src/app/editor/Point.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import {Junction} from "./junction/Junction";
|
||||
|
||||
export class Point {
|
||||
|
||||
constructor(
|
||||
readonly x: number,
|
||||
readonly y: number,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
static fromEvent(event: MouseEvent) {
|
||||
return new Point(event.offsetX, event.offsetY);
|
||||
}
|
||||
|
||||
static fromJunction(j: Junction) {
|
||||
return new Point(j.pixelX, j.pixelY);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
<svg class="breadboard">
|
||||
<svg class="breadboard" (mousemove)="mouseMove($event)" (mouseup)="mouseUp($event)">
|
||||
|
||||
<svg *ngFor="let part of parts" [attr.height]="part.hP" [attr.width]="part.wP" [attr.x]="part.xP" [attr.y]="part.yP" [ngClass]="part.ngClass()">
|
||||
|
||||
@ -6,27 +6,52 @@
|
||||
|
||||
<ng-container *ngIf="isBattery(part)">
|
||||
<text dominant-baseline="hanging" text-anchor="middle" x="50%" y="3%">{{ asBattery(part).voltageStr }}</text>
|
||||
<line class="wire" id="wireMinus" x1="15%" x2="40%" y1="50%" y2="50%"></line>
|
||||
<line class="wire" id="wirePlus" x1="55%" x2="80%" y1="50%" y2="50%"></line>
|
||||
<line class="partWire" id="wireMinus" x1="15%" x2="40%" y1="50%" y2="50%"></line>
|
||||
<line class="partWire" id="wirePlus" x1="55%" x2="80%" y1="50%" y2="50%"></line>
|
||||
<rect height="30%" id="symbolMinus" width="10%" x="35%" y="35%"></rect>
|
||||
<rect height="60%" id="symbolPlus" width="5%" x="55%" y="20%"></rect>
|
||||
<text text-anchor="middle" x="50%" y="95%">{{ asBattery(part).currentStr }}</text>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="isLight(part)">
|
||||
<text dominant-baseline="hanging" text-anchor="middle" x="50%" y="3%">{{ asBattery(part).voltageStr }}</text>
|
||||
<text dominant-baseline="hanging" text-anchor="middle" x="50%" y="3%">{{ asLight(part).voltageStr }}</text>
|
||||
<circle [attr.fill]="asLight(part).fill()" [class.defect]="asLight(part).defect" cx="50%" cy="50%" id="circle" r="35%"></circle>
|
||||
<g class="cross">
|
||||
<line id="lineLeftRight" x1="15%" x2="85%" y1="50%" y2="50%"></line>
|
||||
<line id="lineTopDown" x1="50%" x2="50%" y1="15%" y2="85%"></line>
|
||||
</g>
|
||||
<text text-anchor="middle" x="50%" y="95%">{{ asBattery(part).currentStr }}</text>
|
||||
<text text-anchor="middle" x="50%" y="95%">{{ asLight(part).currentStr }}</text>
|
||||
</ng-container>
|
||||
|
||||
<svg *ngFor="let j of part.junctions" [attr.height]="j.hP" [attr.width]="j.wP" [attr.x]="j.xP" [attr.y]="j.yP" class="junction">
|
||||
<circle [attr.id]="'j' + j.name" cx="50%" cy="50%" fill="gray" r="35%"></circle>
|
||||
<svg
|
||||
*ngFor="let junction of part.junctions"
|
||||
class="junction"
|
||||
[attr.x]="junction.percentX + '%'"
|
||||
[attr.y]="junction.percentY + '%'"
|
||||
[attr.height]="junction.percentH + '%'"
|
||||
[attr.width]="junction.percentW + '%'"
|
||||
(mousemove)="mouseMove($event)"
|
||||
(mousedown)="junctionMouseDown(junction, $event)"
|
||||
(mouseenter)="junctionMouseEnter(junction, $event)"
|
||||
(mouseleave)="junctionMouseLeave(junction, $event)"
|
||||
(mouseup)="junctionMouseUp($event)"
|
||||
>
|
||||
<circle [attr.id]="junction.id" cx="50%" cy="50%" fill="gray" r="35%"></circle>
|
||||
</svg>
|
||||
|
||||
</svg>
|
||||
|
||||
<ng-container *ngFor="let wire of wires">
|
||||
<line class="wire" [attr.x1]="wire.start.pixelX + 'px'" [attr.y1]="wire.start.pixelY + 'px'" [attr.x2]="wire.end.pixelX + 'px'" [attr.y2]="wire.end.pixelY + 'px'"></line>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="wireStart && wireCursor && wireStartJunction">
|
||||
<ng-container *ngIf="wireEnd && wireEndJunction">
|
||||
<line class="wire" [class.wireDuplicate]="wireEndDuplicate" [class.wireEnd]="!wireEndDuplicate" [attr.x1]="wireStart.x + 'px'" [attr.y1]="wireStart.y + 'px'" [attr.x2]="wireEnd.x + 'px'" [attr.y2]="wireEnd.y + 'px'"></line>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!wireEnd">
|
||||
<line class="wire wireOpen" [attr.x1]="wireStart.x + 'px'" [attr.y1]="wireStart.y + 'px'" [attr.x2]="wireCursor.x + 'px'" [attr.y2]="wireCursor.y + 'px'"></line>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 2.9 KiB |
@ -8,9 +8,16 @@
|
||||
user-select: none;
|
||||
|
||||
.part {
|
||||
|
||||
.background {
|
||||
fill: lightgray;
|
||||
}
|
||||
|
||||
.partWire {
|
||||
stroke-width: 5px;
|
||||
stroke: black;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.part:not(:has(.junction:hover)):hover {
|
||||
@ -40,8 +47,23 @@
|
||||
}
|
||||
|
||||
.wire {
|
||||
stroke-width: 5px;
|
||||
stroke-width: 9px;
|
||||
stroke: black;
|
||||
stroke-linecap: round;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.wireOpen {
|
||||
stroke: blue;
|
||||
stroke-dasharray: 5px 15px;
|
||||
}
|
||||
|
||||
.wireEnd {
|
||||
stroke: green;
|
||||
}
|
||||
|
||||
.wireDuplicate {
|
||||
stroke: red;
|
||||
}
|
||||
|
||||
.junction {
|
||||
|
||||
@ -3,6 +3,9 @@ import {NgClass, NgForOf, NgIf} from '@angular/common';
|
||||
import {Part} from '../parts/Part';
|
||||
import {Battery} from '../parts/battery/Battery';
|
||||
import {Light} from '../parts/light/Light';
|
||||
import {Junction} from '../junction/Junction';
|
||||
import {Wire} from '../wire/Wire';
|
||||
import {Point} from '../Point';
|
||||
|
||||
@Component({
|
||||
selector: 'app-board',
|
||||
@ -16,11 +19,29 @@ import {Light} from '../parts/light/Light';
|
||||
})
|
||||
export class BreadboardComponent {
|
||||
|
||||
parts: Part[] = [
|
||||
new Battery(1, 1, 3, 0.5),
|
||||
new Light(1, 5, 3, 3, 0.5),
|
||||
private battery = new Battery(1, 1, 3, 0.5);
|
||||
|
||||
private light = new Light(1, 5, 3, 1.5, 0.5);
|
||||
|
||||
protected readonly parts: Part[] = [this.battery, this.light];
|
||||
|
||||
protected readonly wires: Wire[] = [
|
||||
new Wire(this.battery.minus, this.light.a),
|
||||
new Wire(this.battery.plus, this.light.b),
|
||||
];
|
||||
|
||||
protected wireCursor: Point | null = null;
|
||||
|
||||
protected wireStart: Point | null = null;
|
||||
|
||||
protected wireEnd: Point | null = null;
|
||||
|
||||
protected wireStartJunction: Junction | null = null;
|
||||
|
||||
protected wireEndJunction: Junction | null = null;
|
||||
|
||||
protected wireEndDuplicate: boolean = false;
|
||||
|
||||
asBattery(part: Part): Battery {
|
||||
return part as Battery;
|
||||
}
|
||||
@ -37,4 +58,75 @@ export class BreadboardComponent {
|
||||
return part instanceof Light;
|
||||
}
|
||||
|
||||
mouseMove($event: MouseEvent) {
|
||||
this.wireCursor = Point.fromEvent($event);
|
||||
}
|
||||
|
||||
mouseUp($event: MouseEvent) {
|
||||
if ($event.button === 0) {
|
||||
this.wireReset();
|
||||
}
|
||||
}
|
||||
|
||||
junctionMouseDown(j: Junction, $event: MouseEvent) {
|
||||
if ($event.button === 0) {
|
||||
this.wireCursor = Point.fromEvent($event);
|
||||
this.wireStart = Point.fromJunction(j);
|
||||
this.wireStartJunction = j;
|
||||
}
|
||||
}
|
||||
|
||||
junctionMouseEnter(j: Junction, $event: MouseEvent) {
|
||||
if ($event.button === 0 && this.wireStartJunction !== null) {
|
||||
if (this.wireStartJunction === j) {
|
||||
this.wireEndReset();
|
||||
} else {
|
||||
this.wireEnd = Point.fromJunction(j);
|
||||
this.wireEndJunction = j;
|
||||
this.wireEndDuplicate = this.wires.some(w => (w.start === this.wireStartJunction && w.end === this.wireEndJunction) || (w.start === this.wireEndJunction && w.end === this.wireStartJunction));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
junctionMouseLeave(j: Junction, $event: MouseEvent) {
|
||||
if ($event.button === 0 && this.wireStartJunction !== null && this.wireEndJunction === j) {
|
||||
this.wireEndReset();
|
||||
}
|
||||
}
|
||||
|
||||
junctionMouseUp($event: MouseEvent) {
|
||||
if ($event.button === 0) {
|
||||
if (this.wireStartJunction !== null && this.wireEndJunction !== null) {
|
||||
this.wireConnect(this.wireStartJunction, this.wireEndJunction);
|
||||
}
|
||||
this.wireReset();
|
||||
}
|
||||
}
|
||||
|
||||
private wireConnect(start: Junction, end: Junction) {
|
||||
if (start === end) {
|
||||
console.log("Not connecting junction with itself.");
|
||||
return;
|
||||
}
|
||||
if (this.wireEndDuplicate) {
|
||||
console.log("Not connecting duplicate wire.");
|
||||
return;
|
||||
}
|
||||
const wire = new Wire(start, end);
|
||||
console.log("Wire created: ", wire);
|
||||
this.wires.push(wire);
|
||||
}
|
||||
|
||||
private wireReset() {
|
||||
this.wireStart = null;
|
||||
this.wireStartJunction = null;
|
||||
this.wireEndReset();
|
||||
}
|
||||
|
||||
private wireEndReset() {
|
||||
this.wireEnd = null;
|
||||
this.wireEndJunction = null;
|
||||
this.wireEndDuplicate = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -24,3 +24,13 @@ export function parseColor(color: string): { r: number; g: number; b: number } {
|
||||
|
||||
return {r, g, b};
|
||||
}
|
||||
|
||||
export function fadeGrayToYellow(factor: number, fromColor: any, toColor: any): string {
|
||||
fromColor = parseColor(fromColor);
|
||||
toColor = parseColor(toColor);
|
||||
factor = Math.max(0, Math.min(100, factor));
|
||||
const r = Math.round(fromColor.r + factor * (toColor.r - fromColor.r));
|
||||
const g = Math.round(fromColor.g + factor * (toColor.g - fromColor.g));
|
||||
const b = Math.round(fromColor.b + factor * (toColor.b - fromColor.b));
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
import {parseColor} from './parseColor';
|
||||
|
||||
export function fadeGrayToYellow(factor: number, fromColor: any, toColor: any): string {
|
||||
fromColor = parseColor(fromColor);
|
||||
toColor = parseColor(toColor);
|
||||
factor = Math.max(0, Math.min(100, factor));
|
||||
const r = Math.round(fromColor.r + factor * (toColor.r - fromColor.r));
|
||||
const g = Math.round(fromColor.g + factor * (toColor.g - fromColor.g));
|
||||
const b = Math.round(fromColor.b + factor * (toColor.b - fromColor.b));
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
}
|
||||
@ -2,28 +2,43 @@ export const JUNCTION_RADIUS_PERCENT = 15;
|
||||
|
||||
export class Junction {
|
||||
|
||||
readonly id: string = self.crypto.randomUUID();
|
||||
|
||||
readonly percentX: number;
|
||||
|
||||
readonly percentY: number;
|
||||
|
||||
constructor(
|
||||
readonly x: number,
|
||||
readonly y: number,
|
||||
percentX: number,
|
||||
percentY: number,
|
||||
readonly name: string,
|
||||
) {
|
||||
//
|
||||
this.percentX = percentX - JUNCTION_RADIUS_PERCENT;
|
||||
this.percentY = percentY - JUNCTION_RADIUS_PERCENT;
|
||||
}
|
||||
|
||||
get xP(): string {
|
||||
return (this.x - JUNCTION_RADIUS_PERCENT) + '%';
|
||||
private boundingBox(next: (b: DOMRect) => number): number {
|
||||
const b = document.getElementById(this.id)?.getBoundingClientRect();
|
||||
if (!b) {
|
||||
return 0;
|
||||
}
|
||||
return next(b);
|
||||
}
|
||||
|
||||
get yP(): string {
|
||||
return (this.y - JUNCTION_RADIUS_PERCENT) + '%';
|
||||
get pixelX(): number {
|
||||
return this.boundingBox(b => b.x + b.width / 2);
|
||||
}
|
||||
|
||||
get wP(): string {
|
||||
return (JUNCTION_RADIUS_PERCENT * 2) + '%';
|
||||
get pixelY(): number {
|
||||
return this.boundingBox(b => b.y + b.height / 2);
|
||||
}
|
||||
|
||||
get hP(): string {
|
||||
return (JUNCTION_RADIUS_PERCENT * 2) + '%';
|
||||
get percentW(): number {
|
||||
return (JUNCTION_RADIUS_PERCENT * 2);
|
||||
}
|
||||
|
||||
get percentH(): number {
|
||||
return (JUNCTION_RADIUS_PERCENT * 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,51 +1,52 @@
|
||||
import {Junction} from '../junction/Junction';
|
||||
import {PartType} from './PartType';
|
||||
import {Junction} from '../junction/Junction';
|
||||
|
||||
export const RASTER = 50;
|
||||
|
||||
export abstract class Part {
|
||||
|
||||
protected constructor(
|
||||
readonly x: number,
|
||||
readonly y: number,
|
||||
readonly type: PartType,
|
||||
readonly junctions: Junction[],
|
||||
readonly w: number = 3,
|
||||
readonly h: number = 3,
|
||||
readonly rasterX: number,
|
||||
readonly rasterY: number,
|
||||
readonly rasterW: number = 3,
|
||||
readonly rasterH: number = 3,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
get xR(): number {
|
||||
return this.x * RASTER;
|
||||
abstract get junctions(): Junction[];
|
||||
|
||||
get x(): number {
|
||||
return this.rasterX * RASTER;
|
||||
}
|
||||
|
||||
get yR(): number {
|
||||
return this.y * RASTER;
|
||||
get y(): number {
|
||||
return this.rasterY * RASTER;
|
||||
}
|
||||
|
||||
get wR(): number {
|
||||
return this.w * RASTER;
|
||||
get w(): number {
|
||||
return this.rasterW * RASTER;
|
||||
}
|
||||
|
||||
get hR(): number {
|
||||
return this.h * RASTER;
|
||||
get h(): number {
|
||||
return this.rasterH * RASTER;
|
||||
}
|
||||
|
||||
get xP(): string {
|
||||
return this.xR + 'px';
|
||||
return this.x + 'px';
|
||||
}
|
||||
|
||||
get yP(): string {
|
||||
return this.yR + 'px';
|
||||
return this.y + 'px';
|
||||
}
|
||||
|
||||
get wP(): string {
|
||||
return this.wR + 'px';
|
||||
return this.w + 'px';
|
||||
}
|
||||
|
||||
get hP(): string {
|
||||
return this.hR + 'px';
|
||||
return this.h + 'px';
|
||||
}
|
||||
|
||||
ngClass(): string[] {
|
||||
|
||||
@ -5,21 +5,21 @@ import {siPrefix} from '../../siPrefix';
|
||||
|
||||
export class Battery extends Part {
|
||||
|
||||
readonly minus: Junction = new Junction(15, 50, "-");
|
||||
|
||||
readonly plus: Junction = new Junction(85, 50, "+");
|
||||
|
||||
constructor(
|
||||
x: number,
|
||||
y: number,
|
||||
rasterX: number,
|
||||
rasterY: number,
|
||||
readonly voltage: number,
|
||||
public current: number,
|
||||
) {
|
||||
super(
|
||||
x,
|
||||
y,
|
||||
PartType.Battery,
|
||||
[
|
||||
new Junction(15, 50, "-"),
|
||||
new Junction(85, 50, "+"),
|
||||
]
|
||||
);
|
||||
super(PartType.Battery, rasterX, rasterY);
|
||||
}
|
||||
|
||||
override get junctions(): Junction[] {
|
||||
return [this.minus, this.plus];
|
||||
}
|
||||
|
||||
get voltageStr(): string {
|
||||
|
||||
@ -2,26 +2,27 @@ import {Part} from "../Part";
|
||||
import {Junction} from '../../junction/Junction';
|
||||
import {PartType} from '../PartType';
|
||||
import {siPrefix} from '../../siPrefix';
|
||||
import {fadeGrayToYellow} from '../../fadeGrayToYellow';
|
||||
|
||||
import {fadeGrayToYellow} from '../../colorHelpers';
|
||||
|
||||
export class Light extends Part {
|
||||
|
||||
readonly a = new Junction(15, 50, "a");
|
||||
|
||||
readonly b = new Junction(85, 50, "b");
|
||||
|
||||
constructor(
|
||||
x: number,
|
||||
y: number,
|
||||
rasterX: number,
|
||||
rasterY: number,
|
||||
readonly voltageMax: number,
|
||||
readonly voltage: number,
|
||||
public current: number,
|
||||
) {
|
||||
super(
|
||||
x,
|
||||
y,
|
||||
PartType.Light,
|
||||
[
|
||||
new Junction(15, 50, "a"),
|
||||
new Junction(85, 50, "b"),
|
||||
]
|
||||
);
|
||||
super(PartType.Light, rasterX, rasterY);
|
||||
}
|
||||
|
||||
override get junctions(): Junction[] {
|
||||
return [this.a, this.b];
|
||||
}
|
||||
|
||||
get defect(): boolean {
|
||||
|
||||
12
src/main/angular/src/app/editor/wire/Wire.ts
Normal file
12
src/main/angular/src/app/editor/wire/Wire.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import {Junction} from "../junction/Junction";
|
||||
|
||||
export class Wire {
|
||||
|
||||
constructor(
|
||||
readonly start: Junction,
|
||||
readonly end: Junction,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user