connecting wires

This commit is contained in:
Patrick Haßel 2025-01-30 12:47:50 +01:00
parent b0038fd7a7
commit 8d0129f25b
11 changed files with 261 additions and 74 deletions

View 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);
}
}

View File

@ -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()"> <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)"> <ng-container *ngIf="isBattery(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%">{{ asBattery(part).voltageStr }}</text>
<line class="wire" id="wireMinus" x1="15%" x2="40%" y1="50%" y2="50%"></line> <line class="partWire" 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="wirePlus" x1="55%" x2="80%" y1="50%" y2="50%"></line>
<rect height="30%" id="symbolMinus" width="10%" x="35%" y="35%"></rect> <rect height="30%" id="symbolMinus" width="10%" x="35%" y="35%"></rect>
<rect height="60%" id="symbolPlus" width="5%" x="55%" y="20%"></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> <text text-anchor="middle" x="50%" y="95%">{{ asBattery(part).currentStr }}</text>
</ng-container> </ng-container>
<ng-container *ngIf="isLight(part)"> <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> <circle [attr.fill]="asLight(part).fill()" [class.defect]="asLight(part).defect" cx="50%" cy="50%" id="circle" r="35%"></circle>
<g class="cross"> <g class="cross">
<line id="lineLeftRight" x1="15%" x2="85%" y1="50%" y2="50%"></line> <line id="lineLeftRight" x1="15%" x2="85%" y1="50%" y2="50%"></line>
<line id="lineTopDown" x1="50%" x2="50%" y1="15%" y2="85%"></line> <line id="lineTopDown" x1="50%" x2="50%" y1="15%" y2="85%"></line>
</g> </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> </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"> <svg
<circle [attr.id]="'j' + j.name" cx="50%" cy="50%" fill="gray" r="35%"></circle> *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>
</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> </svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -8,9 +8,16 @@
user-select: none; user-select: none;
.part { .part {
.background { .background {
fill: lightgray; fill: lightgray;
} }
.partWire {
stroke-width: 5px;
stroke: black;
}
} }
.part:not(:has(.junction:hover)):hover { .part:not(:has(.junction:hover)):hover {
@ -40,8 +47,23 @@
} }
.wire { .wire {
stroke-width: 5px; stroke-width: 9px;
stroke: black; stroke: black;
stroke-linecap: round;
pointer-events: none;
}
.wireOpen {
stroke: blue;
stroke-dasharray: 5px 15px;
}
.wireEnd {
stroke: green;
}
.wireDuplicate {
stroke: red;
} }
.junction { .junction {

View File

@ -3,6 +3,9 @@ import {NgClass, NgForOf, NgIf} from '@angular/common';
import {Part} from '../parts/Part'; import {Part} from '../parts/Part';
import {Battery} from '../parts/battery/Battery'; import {Battery} from '../parts/battery/Battery';
import {Light} from '../parts/light/Light'; import {Light} from '../parts/light/Light';
import {Junction} from '../junction/Junction';
import {Wire} from '../wire/Wire';
import {Point} from '../Point';
@Component({ @Component({
selector: 'app-board', selector: 'app-board',
@ -16,11 +19,29 @@ import {Light} from '../parts/light/Light';
}) })
export class BreadboardComponent { export class BreadboardComponent {
parts: Part[] = [ private battery = new Battery(1, 1, 3, 0.5);
new Battery(1, 1, 3, 0.5),
new Light(1, 5, 3, 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 { asBattery(part: Part): Battery {
return part as Battery; return part as Battery;
} }
@ -37,4 +58,75 @@ export class BreadboardComponent {
return part instanceof Light; 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;
}
} }

View File

@ -24,3 +24,13 @@ export function parseColor(color: string): { r: number; g: number; b: number } {
return {r, g, b}; 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})`;
}

View File

@ -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})`;
}

View File

@ -2,28 +2,43 @@ export const JUNCTION_RADIUS_PERCENT = 15;
export class Junction { export class Junction {
readonly id: string = self.crypto.randomUUID();
readonly percentX: number;
readonly percentY: number;
constructor( constructor(
readonly x: number, percentX: number,
readonly y: number, percentY: number,
readonly name: string, readonly name: string,
) { ) {
// this.percentX = percentX - JUNCTION_RADIUS_PERCENT;
this.percentY = percentY - JUNCTION_RADIUS_PERCENT;
} }
get xP(): string { private boundingBox(next: (b: DOMRect) => number): number {
return (this.x - JUNCTION_RADIUS_PERCENT) + '%'; const b = document.getElementById(this.id)?.getBoundingClientRect();
if (!b) {
return 0;
}
return next(b);
} }
get yP(): string { get pixelX(): number {
return (this.y - JUNCTION_RADIUS_PERCENT) + '%'; return this.boundingBox(b => b.x + b.width / 2);
} }
get wP(): string { get pixelY(): number {
return (JUNCTION_RADIUS_PERCENT * 2) + '%'; return this.boundingBox(b => b.y + b.height / 2);
} }
get hP(): string { get percentW(): number {
return (JUNCTION_RADIUS_PERCENT * 2) + '%'; return (JUNCTION_RADIUS_PERCENT * 2);
}
get percentH(): number {
return (JUNCTION_RADIUS_PERCENT * 2);
} }
} }

View File

@ -1,51 +1,52 @@
import {Junction} from '../junction/Junction';
import {PartType} from './PartType'; import {PartType} from './PartType';
import {Junction} from '../junction/Junction';
export const RASTER = 50; export const RASTER = 50;
export abstract class Part { export abstract class Part {
protected constructor( protected constructor(
readonly x: number,
readonly y: number,
readonly type: PartType, readonly type: PartType,
readonly junctions: Junction[], readonly rasterX: number,
readonly w: number = 3, readonly rasterY: number,
readonly h: number = 3, readonly rasterW: number = 3,
readonly rasterH: number = 3,
) { ) {
// //
} }
get xR(): number { abstract get junctions(): Junction[];
return this.x * RASTER;
get x(): number {
return this.rasterX * RASTER;
} }
get yR(): number { get y(): number {
return this.y * RASTER; return this.rasterY * RASTER;
} }
get wR(): number { get w(): number {
return this.w * RASTER; return this.rasterW * RASTER;
} }
get hR(): number { get h(): number {
return this.h * RASTER; return this.rasterH * RASTER;
} }
get xP(): string { get xP(): string {
return this.xR + 'px'; return this.x + 'px';
} }
get yP(): string { get yP(): string {
return this.yR + 'px'; return this.y + 'px';
} }
get wP(): string { get wP(): string {
return this.wR + 'px'; return this.w + 'px';
} }
get hP(): string { get hP(): string {
return this.hR + 'px'; return this.h + 'px';
} }
ngClass(): string[] { ngClass(): string[] {

View File

@ -5,21 +5,21 @@ import {siPrefix} from '../../siPrefix';
export class Battery extends Part { export class Battery extends Part {
readonly minus: Junction = new Junction(15, 50, "-");
readonly plus: Junction = new Junction(85, 50, "+");
constructor( constructor(
x: number, rasterX: number,
y: number, rasterY: number,
readonly voltage: number, readonly voltage: number,
public current: number, public current: number,
) { ) {
super( super(PartType.Battery, rasterX, rasterY);
x, }
y,
PartType.Battery, override get junctions(): Junction[] {
[ return [this.minus, this.plus];
new Junction(15, 50, "-"),
new Junction(85, 50, "+"),
]
);
} }
get voltageStr(): string { get voltageStr(): string {

View File

@ -2,26 +2,27 @@ import {Part} from "../Part";
import {Junction} from '../../junction/Junction'; import {Junction} from '../../junction/Junction';
import {PartType} from '../PartType'; import {PartType} from '../PartType';
import {siPrefix} from '../../siPrefix'; import {siPrefix} from '../../siPrefix';
import {fadeGrayToYellow} from '../../fadeGrayToYellow';
import {fadeGrayToYellow} from '../../colorHelpers';
export class Light extends Part { export class Light extends Part {
readonly a = new Junction(15, 50, "a");
readonly b = new Junction(85, 50, "b");
constructor( constructor(
x: number, rasterX: number,
y: number, rasterY: number,
readonly voltageMax: number, readonly voltageMax: number,
readonly voltage: number, readonly voltage: number,
public current: number, public current: number,
) { ) {
super( super(PartType.Light, rasterX, rasterY);
x, }
y,
PartType.Light, override get junctions(): Junction[] {
[ return [this.a, this.b];
new Junction(15, 50, "a"),
new Junction(85, 50, "b"),
]
);
} }
get defect(): boolean { get defect(): boolean {

View File

@ -0,0 +1,12 @@
import {Junction} from "../junction/Junction";
export class Wire {
constructor(
readonly start: Junction,
readonly end: Junction,
) {
//
}
}