diff --git a/assets/blocks.json b/assets/blocks.json index 37fbace..4e87ac4 100644 --- a/assets/blocks.json +++ b/assets/blocks.json @@ -151,18 +151,28 @@ "message0": "set direction %1 %2", "args0": [ { - "type": "field_number", - "name": "angle", - "value": 0 + "type": "field_dropdown", + "name": "direction", + "options": [ + [ + "left", + "left" + ], + [ + "right", + "right" + ] + ] }, { "type": "input_dummy", - "name": "direction" + "name": "dummy" } ], "previousStatement": null, "nextStatement": null, - "colour": 285 + "colour": 285, + "inputsInline": true }, { "type": "controls_repeat_forever", diff --git a/src/app/components/scene/scene.component.html b/src/app/components/scene/scene.component.html index e86aeaf..db6d9e7 100644 --- a/src/app/components/scene/scene.component.html +++ b/src/app/components/scene/scene.component.html @@ -58,29 +58,44 @@ label="rotation" (valueChange)="obj.rotation = $event; drawImages();" /> + + + }

Scene Objects

+
+
+ @for (obj of sceneObjects; track obj.id) { + + } -
- @for (obj of sceneObjects; track obj.id) { - - } - - @if (modeService.getMode() === 'teacher') { -
- -
- } + @if (modeService.getMode() === 'teacher') { +
+ +
+ } +
@@ -90,7 +105,9 @@

Scene Objects

[style.left.px]="contextMenuX" [style.top.px]="contextMenuY" > - + diff --git a/src/app/components/scene/scene.component.ts b/src/app/components/scene/scene.component.ts index de31fa8..e3c8766 100644 --- a/src/app/components/scene/scene.component.ts +++ b/src/app/components/scene/scene.component.ts @@ -58,20 +58,17 @@ export class SceneComponent implements AfterViewInit { protected contextMenuY = 0; protected contextMenuObject: SceneObject | null = null; - protected selectedObject= computed(() => { + protected selectedObject = computed(() => { if (!this.selectedObjectId()) return undefined; return this.sceneObjects.find(obj => obj.id === this.selectedObjectId()); }); ngAfterViewInit(): void { - this.initCanvas().then(() => { - this.setupMouseEvents(); - this.drawImages(); - }); + this.initCanvas(); } - private async initCanvas() { + private initCanvas() { const canvasEl = this.canvas.nativeElement; const style = getComputedStyle(canvasEl); @@ -83,8 +80,17 @@ export class SceneComponent implements AfterViewInit { this.ctx = canvasEl.getContext('2d'); - this.sceneObjects.map(async obj => obj.img = await loadImage(obj.imgSrc)); - if (this.bgSrc) this.bgImage = await loadImage(this.bgSrc); + const imageLoadPromises = this.sceneObjects.map(async obj => obj.img = await loadImage(obj.imgSrc)); + if (this.bgSrc) { + const bgPromise = (async () => this.bgImage = await loadImage(this.bgSrc!)); + imageLoadPromises.push(bgPromise()) + } + + Promise.all(imageLoadPromises).then(() => { + if (this.sceneObjects.length > 0) this.objectSelected.emit(this.sceneObjects[0].id); + this.drawImages() + }); + this.setupMouseEvents(); } private setupMouseEvents() { @@ -111,7 +117,6 @@ export class SceneComponent implements AfterViewInit { const mouseX = e.offsetX; const mouseY = e.offsetY; - // Move the image based on mouse position this.draggingObject.x = mouseX - this.offsetX; this.draggingObject.y = mouseY - this.offsetY; @@ -141,6 +146,12 @@ export class SceneComponent implements AfterViewInit { this.contextMenuObject = obj; } + protected onChangeSelect(event: Event) { + const selectElement = event.target as HTMLSelectElement; + this.selectedObject()!.lookingLeft = (selectElement.value === 'left'); + this.drawImages(); + } + @HostListener('document:click') hideContextMenu() { this.contextMenuVisible = false; @@ -166,6 +177,7 @@ export class SceneComponent implements AfterViewInit { this.ctx.translate(centerX, centerY); this.ctx.rotate(angleInRadians); + if (!obj.lookingLeft) this.ctx.scale(-1, 1); if (obj.id === this.selectedObjectId()) { this.ctx.shadowColor = 'red'; @@ -174,7 +186,7 @@ export class SceneComponent implements AfterViewInit { this.ctx.shadowOffsetY = 0; } - if (obj.img) this.ctx.drawImage(obj.img!, -obj.size / 2, -obj.size / 2, obj.size, obj.size); + this.ctx.drawImage(obj.img!, -obj.size / 2, -obj.size / 2, obj.size, obj.size); this.ctx.restore(); } diff --git a/src/app/models/scene-object.ts b/src/app/models/scene-object.ts index 4f842c1..04916b5 100644 --- a/src/app/models/scene-object.ts +++ b/src/app/models/scene-object.ts @@ -7,13 +7,20 @@ export class SceneObject { public rotation: number, public size: number, public workspace: string, + public lookingLeft: boolean = true, public img?: HTMLImageElement, ) {} moveForward(steps: number) { const radians = (this.rotation * Math.PI) / 180; - this.x += Math.cos(radians) * steps; - this.y += Math.sin(radians) * steps; + + if (this.lookingLeft) { + this.x -= Math.cos(radians) * steps; + this.y -= Math.sin(radians) * steps; + } else { + this.x += Math.cos(radians) * steps; + this.y += Math.sin(radians) * steps; + } } moveTo(x: number, y: number) { @@ -21,8 +28,8 @@ export class SceneObject { this.y = y; } - setDirection(angle: number) { - + setDirection(direction: 'left' | 'right') { + this.lookingLeft = direction === 'left'; } turnLeft(angle: number) { diff --git a/src/app/pages/activity-detail/activity-detail.component.ts b/src/app/pages/activity-detail/activity-detail.component.ts index 92cf42c..6a5b801 100644 --- a/src/app/pages/activity-detail/activity-detail.component.ts +++ b/src/app/pages/activity-detail/activity-detail.component.ts @@ -97,9 +97,8 @@ export class ActivityDetailComponent implements AfterViewInit, OnDestroy { this.BLOCK_LIMITS = new Map(Object.entries(this.activity()!.toolboxInfo.BLOCK_LIMITS)); if (this.activity()!.sceneObjects.length > 0) { - const restoredObjects = this.activity()!.sceneObjects.map(obj => - new SceneObject(obj.id, obj.imgSrc, obj.x, obj.y, obj.rotation, obj.size, obj.workspace) + new SceneObject(obj.id, obj.imgSrc, obj.x, obj.y, obj.rotation, obj.size, obj.workspace, obj.lookingLeft) ); this.activity.set({...this.activity()!, sceneObjects: restoredObjects}); @@ -112,7 +111,6 @@ export class ActivityDetailComponent implements AfterViewInit, OnDestroy { this.activity.set({...this.activity()!, workspace: jsonWorkspace}); this.activity()!.sceneObjects.forEach(sceneObject => this.generateCode(sceneObject)); - if (this.activity()!.sceneObjects.length > 0) this.selectSceneObject(this.activity()!.sceneObjects[0].id); } ngOnDestroy(): void { @@ -136,6 +134,7 @@ export class ActivityDetailComponent implements AfterViewInit, OnDestroy { obj.rotation, obj.size, obj.workspace, + obj.lookingLeft, img ); } else { @@ -152,6 +151,7 @@ export class ActivityDetailComponent implements AfterViewInit, OnDestroy { 0, 100, this.activity()!.workspace, + true, img ); } @@ -361,7 +361,6 @@ export class ActivityDetailComponent implements AfterViewInit, OnDestroy { this.runningInterpreters = this.objectsCode.size; this.objectsCode.forEach((v, k) => { - console.log('Executing code of object with id ', k); this.isRunning.set(true); this.runInterpreter(v, k); }); @@ -413,7 +412,6 @@ export class ActivityDetailComponent implements AfterViewInit, OnDestroy { if (this.runningInterpreters === 0) { this.isRunning.set(false); - console.log('Execution finished'); } } } diff --git a/src/app/services/blockly.service.ts b/src/app/services/blockly.service.ts index 1931809..ec14a11 100644 --- a/src/app/services/blockly.service.ts +++ b/src/app/services/blockly.service.ts @@ -51,5 +51,10 @@ export class BlocklyService { const innerCode = javascriptGenerator.statementToCode(block, 'statement'); return `while (true) {\n${innerCode}}\n`; } + + javascriptGenerator.forBlock['movement_set_direction'] = function (block: any) { + const direction = block.getFieldValue('direction'); + return `setDirection("${direction}")\n`; + } } }