From b1e50550f6d812c044e9c1bb39e8b4bbab5f6a4d Mon Sep 17 00:00:00 2001 From: cramerL Date: Wed, 21 Feb 2024 22:59:05 +0100 Subject: [PATCH 1/7] fixed icons being off-center --- webapp/src/styles.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/webapp/src/styles.scss b/webapp/src/styles.scss index 368d5a27..94b9a875 100644 --- a/webapp/src/styles.scss +++ b/webapp/src/styles.scss @@ -16,6 +16,14 @@ $config: mat.define-typography-config(); $my-typography: mat.define-typography-config(); @include mat.typography-hierarchy($my-typography); + +// tailwind base makes angular material icon buttons off-center. This fixes it +@layer base { + button { + line-height: normal; + } +} + html * { font-family: Roboto, "Helvetica Neue", sans-serif; } From f2f03733c9283884b3ec5d020eb43970b343416a Mon Sep 17 00:00:00 2001 From: cramerL Date: Thu, 22 Feb 2024 01:24:41 +0100 Subject: [PATCH 2/7] added ui for entity grid, #292 --- webapp/src/app/app.module.ts | 4 + .../grid-menu/grid-menu.component.html | 37 +++++ .../grid-menu/grid-menu.component.scss | 19 +++ .../toolbar/grid-menu/grid-menu.component.ts | 86 +++++++++++ .../toolbar-divider.component.scss | 6 + .../toolbar-divider.component.ts | 11 ++ .../components/toolbar/toolbar.component.html | 17 ++- .../vec-input/point-input.component.html | 19 +++ .../vec-input/point-input.component.scss | 21 +++ .../vec-input/point-input.component.ts | 143 ++++++++++++++++++ webapp/src/app/services/globals.ts | 11 +- .../app/services/phaser/entities/cc-entity.ts | 14 +- .../phaser/entities/entity-manager.ts | 5 +- 13 files changed, 376 insertions(+), 17 deletions(-) create mode 100644 webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html create mode 100644 webapp/src/app/components/toolbar/grid-menu/grid-menu.component.scss create mode 100644 webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts create mode 100644 webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.scss create mode 100644 webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.ts create mode 100644 webapp/src/app/components/toolbar/vec-input/point-input.component.html create mode 100644 webapp/src/app/components/toolbar/vec-input/point-input.component.scss create mode 100644 webapp/src/app/components/toolbar/vec-input/point-input.component.ts diff --git a/webapp/src/app/app.module.ts b/webapp/src/app/app.module.ts index 6dd38bad..05d624c8 100644 --- a/webapp/src/app/app.module.ts +++ b/webapp/src/app/app.module.ts @@ -68,6 +68,8 @@ import { ResizedDirective } from './directives/resized.directive'; import { MaterialModule } from './external-modules/material.module'; import { CombinedTooltipPipe } from './pipes/combined-tooltip.pipe'; import { KeepHtmlPipe } from './pipes/keep-html.pipe'; +import { ToolbarDividerComponent } from './components/toolbar/toolbar-divider/toolbar-divider.component'; +import { GridMenuComponent } from './components/toolbar/grid-menu/grid-menu.component'; const WIDGETS = [ StringWidgetComponent, @@ -147,6 +149,8 @@ const WIDGETS = [ AutofocusDirective, CombinedTooltipPipe, InputWithButtonComponent, + ToolbarDividerComponent, + GridMenuComponent, ], bootstrap: [AppComponent], }) diff --git a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html new file mode 100644 index 00000000..eff5cff8 --- /dev/null +++ b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html @@ -0,0 +1,37 @@ +
+ + + Entity Grid + +
+ +
+
+ + Grid size + + +
+
+
diff --git a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.scss b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.scss new file mode 100644 index 00000000..826dac5e --- /dev/null +++ b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.scss @@ -0,0 +1,19 @@ +:host { + // offsets checkbox large margins + margin-left: -12px; + display: flex; + align-items: center; + height: 100%; +} + +.size-form-field { + width: 170px; +} + +.extended-menu { + overflow-x: hidden; + display: grid; + align-items: center; + min-width: 0; + height: 100%; +} diff --git a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts new file mode 100644 index 00000000..3da2aa98 --- /dev/null +++ b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts @@ -0,0 +1,86 @@ +import { Component, effect, OnInit, signal } from '@angular/core'; +import { MatCheckbox } from '@angular/material/checkbox'; +import { MatIconButton } from '@angular/material/button'; +import { MatIcon } from '@angular/material/icon'; +import { FormsModule } from '@angular/forms'; +import { Globals } from '../../../services/globals'; +import { NgIf } from '@angular/common'; +import { MatFormField, MatLabel } from '@angular/material/form-field'; +import { MatInput } from '@angular/material/input'; +import { PointInputComponent } from '../vec-input/point-input.component'; +import { Helper } from '../../../services/phaser/helper'; +import { Point } from '../../../models/cross-code-map'; +import { animate, state, style, transition, trigger } from '@angular/animations'; + +export interface GridSettings { + gridSize: Point; + enableGrid: boolean; + showSettings?: boolean; +} + +const gridSettingsKey = 'gridSettingsKey'; + +@Component({ + selector: 'app-grid-menu', + standalone: true, + animations: [ + trigger('openClose', [ + state('void', style({width: '0'})), + transition('* <=> *', [ + animate('100ms ease'), + ]), + ]) + ], + imports: [ + MatCheckbox, + MatIconButton, + MatIcon, + FormsModule, + NgIf, + MatFormField, + MatInput, + MatLabel, + PointInputComponent + ], + templateUrl: './grid-menu.component.html', + styleUrl: './grid-menu.component.scss' +}) +export class GridMenuComponent implements OnInit { + + gridSettings = Globals.gridSettings; + + constructor() { + effect(() => { + const settings = this.gridSettings(); + localStorage.setItem(gridSettingsKey, JSON.stringify(settings)); + }); + } + + ngOnInit() { + try { + const settings = JSON.parse(localStorage.getItem(gridSettingsKey)!) as Partial; + Globals.gridSettings.update(old => ({ + ...old, + ...settings + })); + } catch (e) { + } + } + + update(newSettings: Partial) { + Globals.gridSettings.update(old => { + const cpy = Helper.copy(old); + Object.assign(cpy, newSettings); + return cpy; + }); + } + + protected readonly MatCheckbox = MatCheckbox; + + toggleSettings() { + Globals.gridSettings.update(old => ({ + ...old, + showSettings: !old.showSettings + })); + } +} diff --git a/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.scss b/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.scss new file mode 100644 index 00000000..8105ca62 --- /dev/null +++ b/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.scss @@ -0,0 +1,6 @@ +:host { + width: 1px; + height: 100%; + background: rgba(255, 255, 255, 0.24); + margin: 0 32px; +} diff --git a/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.ts b/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.ts new file mode 100644 index 00000000..ec4395cb --- /dev/null +++ b/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-toolbar-divider', + standalone: true, + imports: [], + template: '', + styleUrl: './toolbar-divider.component.scss' +}) +export class ToolbarDividerComponent { +} diff --git a/webapp/src/app/components/toolbar/toolbar.component.html b/webapp/src/app/components/toolbar/toolbar.component.html index 9baa59cf..5cc37ea2 100644 --- a/webapp/src/app/components/toolbar/toolbar.component.html +++ b/webapp/src/app/components/toolbar/toolbar.component.html @@ -1,7 +1,7 @@ -
-
+
+
@@ -54,14 +54,19 @@
-
- {{map?.name}} + + + + +
+ {{ map?.name }}
-
+ +
- {{error}} + {{ error }}
diff --git a/webapp/src/app/components/toolbar/vec-input/point-input.component.html b/webapp/src/app/components/toolbar/vec-input/point-input.component.html new file mode 100644 index 00000000..fdbb4818 --- /dev/null +++ b/webapp/src/app/components/toolbar/vec-input/point-input.component.html @@ -0,0 +1,19 @@ +
+ X: + + Y: + +
diff --git a/webapp/src/app/components/toolbar/vec-input/point-input.component.scss b/webapp/src/app/components/toolbar/vec-input/point-input.component.scss new file mode 100644 index 00000000..e27c82fb --- /dev/null +++ b/webapp/src/app/components/toolbar/vec-input/point-input.component.scss @@ -0,0 +1,21 @@ +input { + border: none; + background: none; + padding: 0 0 0 4px; + outline: none; + font: inherit; + color: currentcolor; + min-width: 0; + flex: 1; + background: rgba(255, 255, 255, 0.1); + border-radius: 1px; +} + +.input-label { + opacity: 0; + transition: opacity 200ms; +} + +:host.floating .input-label { + opacity: 1; +} diff --git a/webapp/src/app/components/toolbar/vec-input/point-input.component.ts b/webapp/src/app/components/toolbar/vec-input/point-input.component.ts new file mode 100644 index 00000000..8a446702 --- /dev/null +++ b/webapp/src/app/components/toolbar/vec-input/point-input.component.ts @@ -0,0 +1,143 @@ +import { Component, ElementRef, EventEmitter, HostBinding, Input, OnDestroy, Optional, Output, Self } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, FormsModule, NgControl, ReactiveFormsModule } from '@angular/forms'; +import { Point } from '../../../models/cross-code-map'; +import { MatFormFieldControl } from '@angular/material/form-field'; +import { Subject } from 'rxjs'; +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; + +@Component({ + selector: 'app-point-input', + standalone: true, + imports: [ + FormsModule, + ReactiveFormsModule + ], + providers: [{provide: MatFormFieldControl, useExisting: PointInputComponent}], + templateUrl: './point-input.component.html', + styleUrl: './point-input.component.scss' +}) +export class PointInputComponent implements MatFormFieldControl, OnDestroy, ControlValueAccessor { + parts: FormGroup; + stateChanges = new Subject(); + + onChange = (_: any) => {}; + + @Input() + get value(): Point | null { + return this.parts.value; + } + + set value(point: Point | null) { + point = point || {x: 1, y: 1}; + this.parts.setValue({...point}); + this.stateChanges.next(); + } + + static nextId = 0; + @HostBinding() id = `point-input-${PointInputComponent.nextId++}`; + + readonly placeholder = ''; + + focused = false; + touched = false; + onTouched = () => { + }; + + onFocusIn(event: FocusEvent) { + if (!this.focused) { + this.focused = true; + this.stateChanges.next(); + } + } + + onFocusOut(event: FocusEvent) { + if (!this.elementRef.nativeElement.contains(event.relatedTarget as Element)) { + this.touched = true; + this.focused = false; + this.onTouched(); + this.stateChanges.next(); + } + } + + get empty() { + const p = this.parts.value as Point; + return isNaN(p.x) && isNaN(p.y); + } + + @HostBinding('class.floating') + get shouldLabelFloat() { + return this.focused || !this.empty; + } + + required = false; + + private _disabled = false; + + @Input() + get disabled(): boolean { + return this._disabled; + } + + set disabled(value: BooleanInput) { + this._disabled = coerceBooleanProperty(value); + if (this._disabled) { + this.parts.disable(); + } else { + this.parts.enable(); + } + this.stateChanges.next(); + } + + get errorState(): boolean { + return this.parts.invalid && this.touched; + } + + controlType = 'point-input'; + + setDescribedByIds(ids: string[]): void { + } + + onContainerClick(event: MouseEvent): void { + if ((event.target as Element).tagName.toLowerCase() !== 'input') { + this.elementRef.nativeElement.querySelector('input')?.focus(); + } + } + + constructor( + fb: FormBuilder, + private elementRef: ElementRef, + @Optional() @Self() public ngControl: NgControl, + ) { + this.parts = fb.group({ + x: 0, + y: 0 + }); + if (this.ngControl != null) { + this.ngControl.valueAccessor = this; + } + } + + writeValue(p: Point | null): void { + this.value = p; + } + + registerOnChange(fn: any): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + ngOnDestroy() { + this.stateChanges.complete(); + } + + update(){ + this.onChange(this.value); + } +} diff --git a/webapp/src/app/services/globals.ts b/webapp/src/app/services/globals.ts index c0a20497..926917ea 100644 --- a/webapp/src/app/services/globals.ts +++ b/webapp/src/app/services/globals.ts @@ -8,6 +8,8 @@ import { PhaserEventsService } from './phaser/phaser-events.service'; import { CCMap } from './phaser/tilemap/cc-map'; import { MatSnackBar } from '@angular/material/snack-bar'; import { SettingsService } from './settings.service'; +import { signal } from '@angular/core'; +import { GridSettings } from '../components/toolbar/grid-menu/grid-menu.component'; export class Globals { static isElectron = false; @@ -17,10 +19,13 @@ export class Globals { static map: CCMap; static TILE_SIZE = 16; static URL = 'http://localhost:8080/'; - static entitySettings = { - gridSize: 8, + static gridSettings = signal({ + gridSize: { + x: 8, + y: 8 + }, enableGrid: false - }; + }); static disablePhaserInput = new Set(); // TODO: remove them from global state diff --git a/webapp/src/app/services/phaser/entities/cc-entity.ts b/webapp/src/app/services/phaser/entities/cc-entity.ts index 2cbef319..f780a6c6 100644 --- a/webapp/src/app/services/phaser/entities/cc-entity.ts +++ b/webapp/src/app/services/phaser/entities/cc-entity.ts @@ -192,20 +192,20 @@ export abstract class CCEntity extends BaseObject { container.x = Math.round(p.worldX - this.startOffset.x); container.y = Math.round(p.worldY - this.startOffset.y); - const settings = Globals.entitySettings; + const settings = Globals.gridSettings(); if (settings.enableGrid) { - const diffX = container.x % settings.gridSize; - if (diffX * 2 < settings.gridSize) { + const diffX = container.x % settings.gridSize.x; + if (diffX * 2 < settings.gridSize.x) { container.x -= diffX; } else { - container.x += settings.gridSize - diffX; + container.x += settings.gridSize.x - diffX; } - const diffY = container.y % settings.gridSize; - if (diffY * 2 < settings.gridSize) { + const diffY = container.y % settings.gridSize.y; + if (diffY * 2 < settings.gridSize.y) { container.y -= diffY; } else { - container.y += settings.gridSize - diffY; + container.y += settings.gridSize.y - diffY; } } this.updateZIndex(); diff --git a/webapp/src/app/services/phaser/entities/entity-manager.ts b/webapp/src/app/services/phaser/entities/entity-manager.ts index 3d0d4af1..9b578a56 100644 --- a/webapp/src/app/services/phaser/entities/entity-manager.ts +++ b/webapp/src/app/services/phaser/entities/entity-manager.ts @@ -246,7 +246,10 @@ export class EntityManager extends BaseObject { if (Helper.isInputFocused()) { return; } - Globals.entitySettings.enableGrid = !Globals.entitySettings.enableGrid; + Globals.gridSettings.update(settings => ({ + ...settings, + enableGrid: !settings.enableGrid + })); } }); From ca8ce5243dff27060482f6a61adee692a862ca21 Mon Sep 17 00:00:00 2001 From: cramerL Date: Thu, 22 Feb 2024 14:34:28 +0100 Subject: [PATCH 3/7] added more config options to grid settings --- .../grid-menu/grid-menu.component.html | 33 +++++++++++++++---- .../grid-menu/grid-menu.component.scss | 6 +--- .../toolbar/grid-menu/grid-menu.component.ts | 11 +++++-- .../toolbar-divider.component.scss | 2 +- .../vec-input/point-input.component.html | 4 +-- .../vec-input/point-input.component.ts | 10 ++++-- webapp/src/app/services/globals.ts | 6 ++-- .../app/services/phaser/entities/cc-entity.ts | 12 +++---- 8 files changed, 53 insertions(+), 31 deletions(-) diff --git a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html index eff5cff8..a3ec2603 100644 --- a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html +++ b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html @@ -1,4 +1,4 @@ -
+
@@ -9,7 +9,7 @@ > Entity Grid -
+
diff --git a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.scss b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.scss index 826dac5e..76474a64 100644 --- a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.scss +++ b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.scss @@ -1,19 +1,15 @@ :host { - // offsets checkbox large margins - margin-left: -12px; display: flex; align-items: center; height: 100%; } -.size-form-field { +.point-form-field { width: 170px; } .extended-menu { overflow-x: hidden; - display: grid; - align-items: center; min-width: 0; height: 100%; } diff --git a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts index 3da2aa98..fb1f2acb 100644 --- a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts +++ b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts @@ -1,4 +1,4 @@ -import { Component, effect, OnInit, signal } from '@angular/core'; +import { Component, effect, OnInit } from '@angular/core'; import { MatCheckbox } from '@angular/material/checkbox'; import { MatIconButton } from '@angular/material/button'; import { MatIcon } from '@angular/material/icon'; @@ -13,9 +13,11 @@ import { Point } from '../../../models/cross-code-map'; import { animate, state, style, transition, trigger } from '@angular/animations'; export interface GridSettings { - gridSize: Point; + size: Point; + offset: Point; enableGrid: boolean; showSettings?: boolean; + visible?: boolean; } const gridSettingsKey = 'gridSettingsKey'; @@ -25,7 +27,10 @@ const gridSettingsKey = 'gridSettingsKey'; standalone: true, animations: [ trigger('openClose', [ - state('void', style({width: '0'})), + state('void', style({ + width: '0', + margin: '0' + })), transition('* <=> *', [ animate('100ms ease'), ]), diff --git a/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.scss b/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.scss index 8105ca62..d56c938f 100644 --- a/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.scss +++ b/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.scss @@ -2,5 +2,5 @@ width: 1px; height: 100%; background: rgba(255, 255, 255, 0.24); - margin: 0 32px; + margin: 0 28px; } diff --git a/webapp/src/app/components/toolbar/vec-input/point-input.component.html b/webapp/src/app/components/toolbar/vec-input/point-input.component.html index fdbb4818..34bb244d 100644 --- a/webapp/src/app/components/toolbar/vec-input/point-input.component.html +++ b/webapp/src/app/components/toolbar/vec-input/point-input.component.html @@ -4,14 +4,14 @@ (focusout)="onFocusOut($event)"> X: Y: , OnDestro parts: FormGroup; stateChanges = new Subject(); - onChange = (_: any) => {}; + @Input() + min = 1; + + onChange = (_: any) => { + }; @Input() get value(): Point | null { @@ -137,7 +141,7 @@ export class PointInputComponent implements MatFormFieldControl, OnDestro this.stateChanges.complete(); } - update(){ + update() { this.onChange(this.value); } } diff --git a/webapp/src/app/services/globals.ts b/webapp/src/app/services/globals.ts index 926917ea..6a4c2fa5 100644 --- a/webapp/src/app/services/globals.ts +++ b/webapp/src/app/services/globals.ts @@ -20,10 +20,8 @@ export class Globals { static TILE_SIZE = 16; static URL = 'http://localhost:8080/'; static gridSettings = signal({ - gridSize: { - x: 8, - y: 8 - }, + size: {x: 8, y: 8}, + offset: {x: 0, y: 0}, enableGrid: false }); static disablePhaserInput = new Set(); diff --git a/webapp/src/app/services/phaser/entities/cc-entity.ts b/webapp/src/app/services/phaser/entities/cc-entity.ts index f780a6c6..69950697 100644 --- a/webapp/src/app/services/phaser/entities/cc-entity.ts +++ b/webapp/src/app/services/phaser/entities/cc-entity.ts @@ -194,18 +194,18 @@ export abstract class CCEntity extends BaseObject { const settings = Globals.gridSettings(); if (settings.enableGrid) { - const diffX = container.x % settings.gridSize.x; - if (diffX * 2 < settings.gridSize.x) { + const diffX = container.x % settings.size.x; + if (diffX * 2 < settings.size.x) { container.x -= diffX; } else { - container.x += settings.gridSize.x - diffX; + container.x += settings.size.x - diffX; } - const diffY = container.y % settings.gridSize.y; - if (diffY * 2 < settings.gridSize.y) { + const diffY = container.y % settings.size.y; + if (diffY * 2 < settings.size.y) { container.y -= diffY; } else { - container.y += settings.gridSize.y - diffY; + container.y += settings.size.y - diffY; } } this.updateZIndex(); From 0002b5e0ce2fe290c9552d20fe8b3cfb491a5c4c Mon Sep 17 00:00:00 2001 From: cramerL Date: Thu, 22 Feb 2024 15:50:25 +0100 Subject: [PATCH 4/7] added visible grid --- webapp/package.json | 4 +- .../toolbar/grid-menu/grid-menu.component.ts | 6 +- .../src/app/services/global-events.service.ts | 9 ++- .../app/services/phaser/entities/cc-entity.ts | 4 +- webapp/src/app/services/phaser/entity-grid.ts | 65 +++++++++++++++++++ webapp/src/app/services/phaser/main-scene.ts | 18 +++-- 6 files changed, 91 insertions(+), 15 deletions(-) create mode 100644 webapp/src/app/services/phaser/entity-grid.ts diff --git a/webapp/package.json b/webapp/package.json index e847745c..c5a2d21c 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -71,8 +71,8 @@ "@angular/platform-browser": "^17.1.2", "@angular/platform-browser-dynamic": "^17.1.2", "@angular/router": "^17.1.2", - "@babylonjs/core": "^5.50.1", - "@babylonjs/gui": "^5.50.1", + "@babylonjs/core": "5.50.1", + "@babylonjs/gui": "5.50.1", "@types/earcut": "^2.1.1", "@types/jasmine": "~4.3.0", "@types/jsoneditor": "^9.9.0", diff --git a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts index fb1f2acb..ce0b61d2 100644 --- a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts +++ b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts @@ -11,6 +11,7 @@ import { PointInputComponent } from '../vec-input/point-input.component'; import { Helper } from '../../../services/phaser/helper'; import { Point } from '../../../models/cross-code-map'; import { animate, state, style, transition, trigger } from '@angular/animations'; +import { GlobalEventsService } from '../../../services/global-events.service'; export interface GridSettings { size: Point; @@ -54,10 +55,13 @@ export class GridMenuComponent implements OnInit { gridSettings = Globals.gridSettings; - constructor() { + constructor( + events: GlobalEventsService + ) { effect(() => { const settings = this.gridSettings(); localStorage.setItem(gridSettingsKey, JSON.stringify(settings)); + events.gridSettings.next(settings); }); } diff --git a/webapp/src/app/services/global-events.service.ts b/webapp/src/app/services/global-events.service.ts index edc62133..8ebedc02 100644 --- a/webapp/src/app/services/global-events.service.ts +++ b/webapp/src/app/services/global-events.service.ts @@ -3,6 +3,8 @@ import { BehaviorSubject, Subject } from 'rxjs'; import { MapEntity, Point, Point3 } from '../models/cross-code-map'; import { EditorView } from '../models/editor-view'; import { CCEntity } from './phaser/entities/cc-entity'; +import { GridSettings } from '../components/toolbar/grid-menu/grid-menu.component'; +import { Globals } from './globals'; @Injectable({ providedIn: 'root' @@ -19,14 +21,15 @@ export class GlobalEventsService { offsetEntities = new Subject(); toggleVisibility = new Subject(); showAddEntityMenu = new Subject(); - + updateCoords = new Subject(); updateTileSelectionSize = new Subject(); showIngamePreview = new BehaviorSubject(false); hasUnsavedChanges = new BehaviorSubject(false); - + gridSettings = new BehaviorSubject(Globals.gridSettings()); + babylonLoading = new BehaviorSubject(false); is3D = new BehaviorSubject(false); - + constructor() {} } diff --git a/webapp/src/app/services/phaser/entities/cc-entity.ts b/webapp/src/app/services/phaser/entities/cc-entity.ts index 69950697..def329f0 100644 --- a/webapp/src/app/services/phaser/entities/cc-entity.ts +++ b/webapp/src/app/services/phaser/entities/cc-entity.ts @@ -194,14 +194,14 @@ export abstract class CCEntity extends BaseObject { const settings = Globals.gridSettings(); if (settings.enableGrid) { - const diffX = container.x % settings.size.x; + const diffX = (container.x - settings.offset.x) % settings.size.x; if (diffX * 2 < settings.size.x) { container.x -= diffX; } else { container.x += settings.size.x - diffX; } - const diffY = container.y % settings.size.y; + const diffY = (container.y - settings.offset.y) % settings.size.y; if (diffY * 2 < settings.size.y) { container.y -= diffY; } else { diff --git a/webapp/src/app/services/phaser/entity-grid.ts b/webapp/src/app/services/phaser/entity-grid.ts new file mode 100644 index 00000000..eefd83bc --- /dev/null +++ b/webapp/src/app/services/phaser/entity-grid.ts @@ -0,0 +1,65 @@ +import { Scene } from 'phaser'; +import { Globals } from '../globals'; +import { GridSettings } from '../../components/toolbar/grid-menu/grid-menu.component'; +import { combineLatest, Subscription } from 'rxjs'; +import { Point } from '../../models/cross-code-map'; + +export class EntityGrid extends Phaser.GameObjects.GameObject { + private sub: Subscription; + private grid?: Phaser.GameObjects.Grid; + + constructor(scene: Scene) { + super(scene, 'EntityGrid'); + this.sub = combineLatest([ + Globals.globalEventsService.gridSettings, + Globals.mapLoaderService.map + ]).subscribe(([settings, _]) => this.updateGrid(settings)); + } + + updateGrid(settings: GridSettings) { + const scale = 4; + this.grid?.destroy(); + if (!settings.enableGrid || !settings.visible) { + return; + } + const pos: Point = { + x: settings.offset.x % settings.size.x, + y: settings.offset.y % settings.size.y + }; + + if (pos.x > 0) { + pos.x -= settings.size.x; + } + if (pos.y > 0) { + pos.y -= settings.size.y; + } + + const width = Globals.map.mapWidth * Globals.TILE_SIZE - pos.x; + const height = Globals.map.mapHeight * Globals.TILE_SIZE - pos.y; + + this.grid = new Phaser.GameObjects.Grid( + this.scene, + pos.x, + pos.y, + width * scale, + height * scale, + settings.size.x * scale, + settings.size.y * scale, + undefined, + 0, + 0xffffff, + 0.6 + ); + this.grid.depth = 500; + this.grid.setOrigin(0, 0); + this.grid.setScale(1 / scale, 1 / scale); + + this.scene.add.existing(this.grid); + } + + override destroy(fromScene?: boolean) { + super.destroy(fromScene); + this.grid?.destroy(); + this.sub.unsubscribe(); + } +} diff --git a/webapp/src/app/services/phaser/main-scene.ts b/webapp/src/app/services/phaser/main-scene.ts index b853df94..106db6ca 100644 --- a/webapp/src/app/services/phaser/main-scene.ts +++ b/webapp/src/app/services/phaser/main-scene.ts @@ -9,6 +9,7 @@ import { CCMap } from './tilemap/cc-map'; import { TileDrawer } from './tilemap/tile-drawer'; import { LayerParallax } from './layer-parallax'; import { IngamePreview } from './ingame-preview'; +import { EntityGrid } from './entity-grid'; export class MainScene extends Phaser.Scene { @@ -74,20 +75,23 @@ export class MainScene extends Phaser.Scene { const preview = new IngamePreview(this); this.add.existing(preview); this.add.existing(new LayerParallax(this, preview)); - + const coordsReporter = new CoordsReporter(this); this.add.existing(coordsReporter); + const grid = new EntityGrid(this); + this.add.existing(grid); + Globals.globalEventsService.currentView.subscribe(view => { tileDrawer.setActive(false); entityManager.setActive(false); switch (view) { - case EditorView.Layers: - tileDrawer.setActive(true); - break; - case EditorView.Entities: - entityManager.setActive(true); - break; + case EditorView.Layers: + tileDrawer.setActive(true); + break; + case EditorView.Entities: + entityManager.setActive(true); + break; } }); From c349144993236e87ffff60437a7f8bd7142cb6c1 Mon Sep 17 00:00:00 2001 From: cramerL Date: Thu, 22 Feb 2024 16:16:13 +0100 Subject: [PATCH 5/7] update grid on map resize --- .../map-settings/map-settings.component.ts | 9 ++++++-- .../src/app/services/global-events.service.ts | 1 + webapp/src/app/services/phaser/entity-grid.ts | 14 +++++++----- .../src/app/services/phaser/tilemap/cc-map.ts | 22 +++++++++---------- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/webapp/src/app/components/dialogs/map-settings/map-settings.component.ts b/webapp/src/app/components/dialogs/map-settings/map-settings.component.ts index e13749c2..1e669b64 100644 --- a/webapp/src/app/components/dialogs/map-settings/map-settings.component.ts +++ b/webapp/src/app/components/dialogs/map-settings/map-settings.component.ts @@ -4,6 +4,7 @@ import { CrossCodeMap } from '../../../models/cross-code-map'; import { MapLoaderService } from '../../../services/map-loader.service'; import { CCMap } from '../../../services/phaser/tilemap/cc-map'; import { OverlayRefControl } from '../overlay/overlay-ref-control'; +import { GlobalEventsService } from '../../../services/global-events.service'; @Component({ selector: 'app-map-settings', @@ -20,7 +21,8 @@ export class MapSettingsComponent { constructor( loader: MapLoaderService, - public ref: OverlayRefControl + public ref: OverlayRefControl, + private events: GlobalEventsService ) { const tileMap = loader.tileMap.getValue(); @@ -56,7 +58,10 @@ export class MapSettingsComponent { tileMap.masterLevel = settings.masterLevel; tileMap.attributes = settings.attributes; - tileMap.resize(settings.mapWidth, settings.mapHeight); + this.events.resizeMap.next({ + x: settings.mapWidth, + y: settings.mapHeight + }); this.ref.close(); } diff --git a/webapp/src/app/services/global-events.service.ts b/webapp/src/app/services/global-events.service.ts index 8ebedc02..4c9814ac 100644 --- a/webapp/src/app/services/global-events.service.ts +++ b/webapp/src/app/services/global-events.service.ts @@ -18,6 +18,7 @@ export class GlobalEventsService { loadComplete = new Subject(); generateHeights = new Subject(); offsetMap = new Subject(); + resizeMap = new Subject(); offsetEntities = new Subject(); toggleVisibility = new Subject(); showAddEntityMenu = new Subject(); diff --git a/webapp/src/app/services/phaser/entity-grid.ts b/webapp/src/app/services/phaser/entity-grid.ts index eefd83bc..d7039894 100644 --- a/webapp/src/app/services/phaser/entity-grid.ts +++ b/webapp/src/app/services/phaser/entity-grid.ts @@ -1,7 +1,6 @@ import { Scene } from 'phaser'; import { Globals } from '../globals'; -import { GridSettings } from '../../components/toolbar/grid-menu/grid-menu.component'; -import { combineLatest, Subscription } from 'rxjs'; +import { debounceTime, merge, Subscription } from 'rxjs'; import { Point } from '../../models/cross-code-map'; export class EntityGrid extends Phaser.GameObjects.GameObject { @@ -10,13 +9,16 @@ export class EntityGrid extends Phaser.GameObjects.GameObject { constructor(scene: Scene) { super(scene, 'EntityGrid'); - this.sub = combineLatest([ + + this.sub = merge( Globals.globalEventsService.gridSettings, - Globals.mapLoaderService.map - ]).subscribe(([settings, _]) => this.updateGrid(settings)); + Globals.mapLoaderService.map, + Globals.globalEventsService.resizeMap + ).pipe(debounceTime(1)).subscribe(() => this.updateGrid()); } - updateGrid(settings: GridSettings) { + updateGrid() { + const settings = Globals.gridSettings(); const scale = 4; this.grid?.destroy(); if (!settings.enableGrid || !settings.visible) { diff --git a/webapp/src/app/services/phaser/tilemap/cc-map.ts b/webapp/src/app/services/phaser/tilemap/cc-map.ts index 026d9ef4..c6b3bbfe 100644 --- a/webapp/src/app/services/phaser/tilemap/cc-map.ts +++ b/webapp/src/app/services/phaser/tilemap/cc-map.ts @@ -26,9 +26,7 @@ export class CCMap { private lastMapId = 1; private tileMap?: Phaser.Tilemaps.Tilemap; - private historySub: Subscription; - private offsetSub: Subscription; - private offsetSub2: Subscription; + private subs: Subscription[] = []; filename = ''; path?: string; @@ -41,7 +39,7 @@ export class CCMap { public entityManager: EntityManager ) { const stateHistory = Globals.stateHistoryService; - this.historySub = stateHistory.selectedState.subscribe(async container => { + this.subs.push(stateHistory.selectedState.subscribe(async container => { if (!container || !container.state) { return; } @@ -70,16 +68,18 @@ export class CCMap { selectedLayer.next(layer); } } - }); + })); - this.offsetSub = Globals.globalEventsService.offsetMap.subscribe(offset => this.offsetMap(offset)); - this.offsetSub2 = Globals.globalEventsService.offsetEntities.subscribe(offset => this.offsetEntities(offset)); + this.subs.push(Globals.globalEventsService.offsetMap.subscribe(offset => this.offsetMap(offset))); + this.subs.push(Globals.globalEventsService.offsetEntities.subscribe(offset => this.offsetEntities(offset))); + this.subs.push(Globals.globalEventsService.resizeMap.subscribe(size => this.resize(size.x, size.y))); } destroy() { - this.historySub.unsubscribe(); - this.offsetSub.unsubscribe(); - this.offsetSub2.unsubscribe(); + for (const sub of this.subs) { + sub.unsubscribe(); + } + this.subs = []; } async loadMap(map: CrossCodeMap, skipInit = false) { @@ -143,7 +143,7 @@ export class CCMap { Globals.mapLoaderService.selectedLayer.next(this.layers[0]); } - resize(width: number, height: number) { + private resize(width: number, height: number) { this.mapWidth = width; this.mapHeight = height; From 93327c86a6859096fae49ef2ead8b4bbdb57eaeb Mon Sep 17 00:00:00 2001 From: cramerL Date: Thu, 22 Feb 2024 16:24:52 +0100 Subject: [PATCH 6/7] updated changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88b05a45..4f5ee291 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added +- UI for Entity Grid (hotkey G) with additional settings [#292](https://github.com/CCDirectLink/crosscode-map-editor/issues/292) + + ## [1.3.1] 2024-02-09 ### Changed - Updated Angular and Electron versions. From 9b2b87006b02c9528f352a4bd7ab963ce557648b Mon Sep 17 00:00:00 2001 From: cramerL Date: Thu, 22 Feb 2024 16:44:42 +0100 Subject: [PATCH 7/7] added color input to grid --- CHANGELOG.md | 2 +- .../components/toolbar/grid-menu/grid-menu.component.html | 5 +++++ .../components/toolbar/grid-menu/grid-menu.component.ts | 1 + webapp/src/app/services/globals.ts | 1 + webapp/src/app/services/phaser/entity-grid.ts | 7 ++++++- 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f5ee291..482c8673 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added -- UI for Entity Grid (hotkey G) with additional settings [#292](https://github.com/CCDirectLink/crosscode-map-editor/issues/292) +- Added UI for Entity Grid (hotkey G) with additional settings [#292](https://github.com/CCDirectLink/crosscode-map-editor/issues/292) ## [1.3.1] 2024-02-09 diff --git a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html index a3ec2603..c27e28db 100644 --- a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html +++ b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html @@ -44,6 +44,11 @@ (ngModelChange)="update({offset: $event})" > + ({ size: {x: 8, y: 8}, offset: {x: 0, y: 0}, + color: '#222222', enableGrid: false }); static disablePhaserInput = new Set(); diff --git a/webapp/src/app/services/phaser/entity-grid.ts b/webapp/src/app/services/phaser/entity-grid.ts index d7039894..0aaf8907 100644 --- a/webapp/src/app/services/phaser/entity-grid.ts +++ b/webapp/src/app/services/phaser/entity-grid.ts @@ -39,6 +39,11 @@ export class EntityGrid extends Phaser.GameObjects.GameObject { const width = Globals.map.mapWidth * Globals.TILE_SIZE - pos.x; const height = Globals.map.mapHeight * Globals.TILE_SIZE - pos.y; + let color = parseInt(settings.color.substring(1), 16); + if (isNaN(color)) { + color = 0x222222; + } + this.grid = new Phaser.GameObjects.Grid( this.scene, pos.x, @@ -49,7 +54,7 @@ export class EntityGrid extends Phaser.GameObjects.GameObject { settings.size.y * scale, undefined, 0, - 0xffffff, + color, 0.6 ); this.grid.depth = 500;