diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b772862..259c0906 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ 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 +- Render dimensions when selecting multiple tiles [#310](https://github.com/CCDirectLink/crosscode-map-editor/issues/310) + +### Changed +- Increased font resolution for entity names + ## [1.2.0] 2024-01-30 ### Added - Toggle in settings that also shows the vanilla maps in the map selection menu diff --git a/webapp/src/app/components/captions/captions.component.html b/webapp/src/app/components/captions/captions.component.html index 88819bf9..0b8ff06f 100644 --- a/webapp/src/app/components/captions/captions.component.html +++ b/webapp/src/app/components/captions/captions.component.html @@ -1,2 +1,8 @@

{{ version }}

-

{{ coords }}

+

+ + + {{el.text}} + + +

diff --git a/webapp/src/app/components/captions/captions.component.scss b/webapp/src/app/components/captions/captions.component.scss index 3e845772..8c76c985 100644 --- a/webapp/src/app/components/captions/captions.component.scss +++ b/webapp/src/app/components/captions/captions.component.scss @@ -13,10 +13,10 @@ right: 0; } -.coords { +.bottom-elements { background-color: #0005; - &.inactive { + &:empty { display: none; } } diff --git a/webapp/src/app/components/captions/captions.component.ts b/webapp/src/app/components/captions/captions.component.ts index 6ec461cf..39f901aa 100644 --- a/webapp/src/app/components/captions/captions.component.ts +++ b/webapp/src/app/components/captions/captions.component.ts @@ -2,6 +2,11 @@ import { Component, OnInit } from '@angular/core'; import { environment } from '../../../environments/environment'; import { Globals } from '../../services/globals'; +export interface BottomUiElement { + text?: string; + active?: boolean; +} + @Component({ selector: 'app-captions', templateUrl: './captions.component.html', @@ -9,16 +14,23 @@ import { Globals } from '../../services/globals'; }) export class CaptionsComponent implements OnInit { version = environment.version; - coords = ''; - coordsClass = 'inactive'; - + coords: BottomUiElement = {}; + selectionSize: BottomUiElement = {}; + + uiElements: BottomUiElement[] = [ + this.coords, + this.selectionSize + ]; + ngOnInit(): void { - Globals.globalEventsService.updateCoords.subscribe((coords) => { - this.coords = !coords - ? '' - : `(${coords.x}, ${coords.y}, ${coords.z})`; - - this.coordsClass = coords ? '' : 'inactive'; + Globals.globalEventsService.updateCoords.subscribe(coords => { + this.coords.text = `(${coords?.x}, ${coords?.y}, ${coords?.z})`; + this.coords.active = !!coords; + }); + + Globals.globalEventsService.updateTileSelectionSize.subscribe(size => { + this.selectionSize.text = `${size?.x}x${size?.y}`; + this.selectionSize.active = !!size; }); } } diff --git a/webapp/src/app/components/dialogs/floating-window/tile-selector/tile-selector.scene.ts b/webapp/src/app/components/dialogs/floating-window/tile-selector/tile-selector.scene.ts index 5339b75b..4589a4c2 100644 --- a/webapp/src/app/components/dialogs/floating-window/tile-selector/tile-selector.scene.ts +++ b/webapp/src/app/components/dialogs/floating-window/tile-selector/tile-selector.scene.ts @@ -237,6 +237,10 @@ export class TileSelectorScene extends Phaser.Scene { } this.rect = this.add.rectangle(x * Globals.TILE_SIZE, y * Globals.TILE_SIZE, width * Globals.TILE_SIZE, height * Globals.TILE_SIZE); this.rect.setOrigin(0, 0); - this.rect.setStrokeStyle(1, 0xffffff, 0.6); + if (Globals.settingsService.getSettings().selectionBoxDark) { + this.rect.setStrokeStyle(2, 0x333333, 0.9); + } else { + this.rect.setStrokeStyle(2, 0xffffff, 0.6); + } } } diff --git a/webapp/src/app/components/dialogs/settings/settings.component.html b/webapp/src/app/components/dialogs/settings/settings.component.html index 81219f6c..1d861f24 100644 --- a/webapp/src/app/components/dialogs/settings/settings.component.html +++ b/webapp/src/app/components/dialogs/settings/settings.component.html @@ -15,7 +15,7 @@ @@ -24,21 +24,43 @@ Mod None - {{mod}} + {{ mod }} Maps will be stored and loaded from the selected mod
- + Include vanilla maps
- + Wrap event editor lines
+
+

Selection Box Style

+
+ + +
+
diff --git a/webapp/src/app/components/dialogs/settings/settings.component.ts b/webapp/src/app/components/dialogs/settings/settings.component.ts index 4305f06d..babb79ee 100644 --- a/webapp/src/app/components/dialogs/settings/settings.component.ts +++ b/webapp/src/app/components/dialogs/settings/settings.component.ts @@ -6,9 +6,10 @@ import { BrowserService } from '../../../services/browser.service'; import { ElectronService } from '../../../services/electron.service'; import { Globals } from '../../../services/globals'; import { HttpClientService } from '../../../services/http-client.service'; -import { SettingsService } from '../../../services/settings.service'; +import { AppSettings, SettingsService } from '../../../services/settings.service'; import { SharedService } from '../../../services/shared-service'; import { OverlayRefControl } from '../overlay/overlay-ref-control'; +import { PropListCard } from '../../widgets/shared/image-select-overlay/image-select-card/image-select-card.component'; @Component({ selector: 'app-settings', @@ -16,19 +17,28 @@ import { OverlayRefControl } from '../overlay/overlay-ref-control'; styleUrls: ['./settings.component.scss'] }) export class SettingsComponent implements OnInit { - + isElectron = Globals.isElectron; folderFormControl = new FormControl(); icon = 'help_outline'; iconCss = 'icon-undefined'; mods: string[] = []; mod = ''; - wrapEventEditorLines: boolean; - includeVanillaMaps: boolean; + settings: AppSettings; isIncludeVanillaMapsDisabled: boolean; - + + cardLight: PropListCard = { + name: 'Light', + imgSrc: 'assets/selection-light.png', + }; + + cardDark: PropListCard = { + name: 'Dark', + imgSrc: 'assets/selection-dark.png', + }; + private readonly sharedService: SharedService; - + constructor( private ref: OverlayRefControl, private electron: ElectronService, @@ -42,28 +52,27 @@ export class SettingsComponent implements OnInit { } else { this.sharedService = browser; } - + http.getMods().subscribe(mods => this.mods = mods); this.mod = this.sharedService.getSelectedMod(); this.isIncludeVanillaMapsDisabled = !this.mod; - this.wrapEventEditorLines = this.settingsService.wrapEventEditorLines; - this.includeVanillaMaps = this.settingsService.includeVanillaMaps; + this.settings = JSON.parse(JSON.stringify(this.settingsService.getSettings())); } - + ngOnInit() { if (this.isElectron) { this.folderFormControl.setValue(this.electron.getAssetsPath()); this.folderFormControl.valueChanges.subscribe(() => this.resetIcon()); } - + this.check(); } - + private resetIcon() { this.icon = 'help_outline'; this.iconCss = 'icon-undefined'; } - + private setIcon(valid: boolean) { if (valid) { this.icon = 'check'; @@ -73,14 +82,14 @@ export class SettingsComponent implements OnInit { this.iconCss = 'icon-invalid'; } } - + select() { const path = this.electron.selectCcFolder(); if (path) { this.folderFormControl.setValue(path); } } - + check() { const valid = this.electron.checkAssetsPath(this.folderFormControl.value); this.setIcon(valid); @@ -92,28 +101,27 @@ export class SettingsComponent implements OnInit { }); } } - + modSelectEvent(selectedMod: string) { this.isIncludeVanillaMapsDisabled = !selectedMod; } - + save() { if (this.isElectron) { this.electron.saveAssetsPath(this.folderFormControl.value); } this.sharedService.saveModSelect(this.mod); - this.settingsService.wrapEventEditorLines = this.wrapEventEditorLines; - this.settingsService.includeVanillaMaps = this.includeVanillaMaps; + this.settingsService.updateSettings(this.settings); this.close(); const ref = this.snackBar.open('Changing the path requires to restart the editor', 'Restart', { duration: 6000 }); - + ref.onAction().subscribe(() => this.sharedService.relaunch()); } - + close() { this.ref.close(); } - + } diff --git a/webapp/src/app/components/phaser/phaser.component.ts b/webapp/src/app/components/phaser/phaser.component.ts index 2043365e..62cb566c 100644 --- a/webapp/src/app/components/phaser/phaser.component.ts +++ b/webapp/src/app/components/phaser/phaser.component.ts @@ -12,6 +12,7 @@ import { MainScene } from '../../services/phaser/main-scene'; import { PhaserEventsService } from '../../services/phaser/phaser-events.service'; import { StateHistoryService } from '../dialogs/floating-window/history/state-history.service'; import { MatSnackBar } from '@angular/material/snack-bar'; +import { SettingsService } from '../../services/settings.service'; @Component({ selector: 'app-phaser', @@ -32,7 +33,8 @@ export class PhaserComponent implements AfterViewInit { private http: HttpClientService, snackbar: MatSnackBar, registry: EntityRegistryService, - autotile: AutotileService + autotile: AutotileService, + settingsService: SettingsService ) { Globals.stateHistoryService = stateHistory; Globals.mapLoaderService = mapLoader; @@ -42,6 +44,7 @@ export class PhaserComponent implements AfterViewInit { Globals.entityRegistry = registry; Globals.httpService = http; Globals.snackbar = snackbar; + Globals.settingsService = settingsService; } diff --git a/webapp/src/app/components/widgets/event-widget/event-editor/editor/event-editor.component.ts b/webapp/src/app/components/widgets/event-widget/event-editor/editor/event-editor.component.ts index d3a8ae69..932a4956 100644 --- a/webapp/src/app/components/widgets/event-widget/event-editor/editor/event-editor.component.ts +++ b/webapp/src/app/components/widgets/event-widget/event-editor/editor/event-editor.component.ts @@ -65,7 +65,7 @@ export class EventEditorComponent implements OnChanges, OnInit { } ngOnInit() { - this.wrapText = this.settingsService.wrapEventEditorLines; + this.wrapText = this.settingsService.getSettings().wrapEventEditorLines; } ngOnChanges() { diff --git a/webapp/src/app/services/global-events.service.ts b/webapp/src/app/services/global-events.service.ts index 0226dcf6..edc62133 100644 --- a/webapp/src/app/services/global-events.service.ts +++ b/webapp/src/app/services/global-events.service.ts @@ -21,6 +21,7 @@ export class GlobalEventsService { showAddEntityMenu = new Subject(); updateCoords = new Subject(); + updateTileSelectionSize = new Subject(); showIngamePreview = new BehaviorSubject(false); hasUnsavedChanges = new BehaviorSubject(false); diff --git a/webapp/src/app/services/globals.ts b/webapp/src/app/services/globals.ts index 2111ed55..c0a20497 100644 --- a/webapp/src/app/services/globals.ts +++ b/webapp/src/app/services/globals.ts @@ -7,6 +7,7 @@ import { EntityRegistryService } from './phaser/entities/registry/entity-registr 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'; export class Globals { static isElectron = false; @@ -30,5 +31,6 @@ export class Globals { static autotileService: AutotileService; static entityRegistry: EntityRegistryService; static httpService: HttpClientService; + static settingsService: SettingsService; static snackbar: MatSnackBar; } diff --git a/webapp/src/app/services/http-client.service.ts b/webapp/src/app/services/http-client.service.ts index 88832b7e..f4a3de0a 100644 --- a/webapp/src/app/services/http-client.service.ts +++ b/webapp/src/app/services/http-client.service.ts @@ -30,7 +30,7 @@ export class HttpClientService { } getMaps(): Observable { - const includeVanillaMaps: boolean = this.settingsService.includeVanillaMaps; + const includeVanillaMaps = this.settingsService.getSettings().includeVanillaMaps; return this.request(`api/allMaps?includeVanillaMaps=${includeVanillaMaps}`, api.getAllMaps, includeVanillaMaps); } diff --git a/webapp/src/app/services/phaser/entities/cc-entity.ts b/webapp/src/app/services/phaser/entities/cc-entity.ts index 7e044e08..2cbef319 100644 --- a/webapp/src/app/services/phaser/entities/cc-entity.ts +++ b/webapp/src/app/services/phaser/entities/cc-entity.ts @@ -649,6 +649,7 @@ export abstract class CCEntity extends BaseObject { this.text = this.scene.add.text(0, 0, '', { font: '400 18pt Roboto', color: 'white', + resolution: window.devicePixelRatio * 3 }); this.text.setOrigin(0.5, 0.5); this.text.setScale(0.3); diff --git a/webapp/src/app/services/phaser/tilemap/tile-drawer.ts b/webapp/src/app/services/phaser/tilemap/tile-drawer.ts index 47ccd924..b694b5f0 100644 --- a/webapp/src/app/services/phaser/tilemap/tile-drawer.ts +++ b/webapp/src/app/services/phaser/tilemap/tile-drawer.ts @@ -15,7 +15,7 @@ export class TileDrawer extends BaseObject { private layer?: CCMapLayer; private selectedTiles: SelectedTile[] = []; - private rect?: Phaser.GameObjects.Rectangle; + private selection?: Phaser.GameObjects.Container; private previewTileMap!: Phaser.Tilemaps.Tilemap; private previewLayer?: Phaser.Tilemaps.TilemapLayer; @@ -126,7 +126,7 @@ export class TileDrawer extends BaseObject { diff.y--; } - this.drawRect(diff.x, diff.y, start.x, start.y); + this.drawRect(diff.x, diff.y, start.x, start.y, true); return; } @@ -307,15 +307,64 @@ export class TileDrawer extends BaseObject { this.rightClickStart = p; } - private drawRect(width: number, height: number, x = 0, y = 0) { - if (this.rect) { - this.rect.destroy(); + private drawRect(width: number, height: number, x = 0, y = 0, renderSize = false) { + if (this.selection) { + this.selection.destroy(); } - this.rect = this.scene.add.rectangle(x, y, width * Globals.TILE_SIZE, height * Globals.TILE_SIZE); - this.rect.setOrigin(0, 0); - this.rect.setStrokeStyle(1, 0xffffff, 0.6); - this.container.add(this.rect); + let textColor = 'rgba(0,0,0,0.6)'; + let backgroundColor = 0xffffff; + if (Globals.settingsService.getSettings().selectionBoxDark) { + textColor = 'rgba(255,255,255,0.9)'; + backgroundColor = 0x333333; + } + + this.selection = this.scene.add.container(x, y); + + const rect = this.scene.add.rectangle(0, 0, width * Globals.TILE_SIZE, height * Globals.TILE_SIZE); + rect.setOrigin(0, 0); + rect.setStrokeStyle(1, backgroundColor, 0.6); + + this.selection.add(rect); + this.container.add(this.selection); + + if (!renderSize) { + Globals.globalEventsService.updateTileSelectionSize.next(undefined); + return; + } + + const makeText = (pos: Point, val: number) => { + const text = this.scene.add.text(pos.x, pos.y, Math.abs(val) + '', { + font: '400 10px Roboto', + color: textColor, + resolution: window.devicePixelRatio * 3, + }); + text.setOrigin(0.5, 0); + const background = this.scene.add.rectangle(pos.x, pos.y + 2, 14, 10, backgroundColor, 0.6); + background.setOrigin(0.5, 0); + + this.selection?.add(background); + this.selection?.add(text); + }; + + if (Math.abs(width) >= 3) { + makeText({ + x: width * Globals.TILE_SIZE / 2, + y: (height > 0 ? 0 : height * Globals.TILE_SIZE) - 1 + }, width); + } + + if (Math.abs(height) >= 3) { + makeText({ + x: Globals.TILE_SIZE / 2 + (width > 0 ? 0 : width * Globals.TILE_SIZE), + y: (height - 1) * Globals.TILE_SIZE / 2, + }, height); + } + + Globals.globalEventsService.updateTileSelectionSize.next({ + x: Math.abs(width), + y: Math.abs(height) + }); } private onMouseRightUp() { diff --git a/webapp/src/app/services/settings.service.ts b/webapp/src/app/services/settings.service.ts index 884feaf8..36fa95a7 100644 --- a/webapp/src/app/services/settings.service.ts +++ b/webapp/src/app/services/settings.service.ts @@ -1,28 +1,38 @@ import { Injectable } from '@angular/core'; +export interface AppSettings { + wrapEventEditorLines: boolean; + includeVanillaMaps: boolean; + selectionBoxDark: boolean; +} + @Injectable({ providedIn: 'root' }) export class SettingsService { - private static readonly wrapSettingName = 'wrapEventEditorLines'; - private static readonly includeVanillaMapsSettingName = 'includeVanillaMaps'; - - private static loadBooleanOrDefault(key: string, defaultValue: boolean): boolean { + + private settings: AppSettings = { + wrapEventEditorLines: this.loadBooleanOrDefault('wrapEventEditorLines', true), + includeVanillaMaps: this.loadBooleanOrDefault('includeVanillaMaps', false), + selectionBoxDark: this.loadBooleanOrDefault('selectionBoxDark', true), + }; + + getSettings(): Readonly { + return this.settings; + } + + private loadBooleanOrDefault(key: keyof AppSettings, defaultValue: boolean): boolean { const loadedValue = localStorage.getItem(key); return loadedValue === null ? defaultValue : (loadedValue === 'true'); } - - get wrapEventEditorLines() { - return SettingsService.loadBooleanOrDefault(SettingsService.wrapSettingName, true); - } - set wrapEventEditorLines(value: boolean) { - localStorage.setItem(SettingsService.wrapSettingName, value.toString()); - } - - get includeVanillaMaps() { - return SettingsService.loadBooleanOrDefault(SettingsService.includeVanillaMapsSettingName, false); - } - set includeVanillaMaps(value: boolean) { - localStorage.setItem(SettingsService.includeVanillaMapsSettingName, value.toString()); + + public updateSettings(newSettings: Partial) { + this.settings = { + ...this.settings, + ...newSettings + }; + for (const [key, value] of Object.entries(this.settings)) { + localStorage.setItem(key, (value as boolean).toString()); + } } } diff --git a/webapp/src/assets/selection-dark.png b/webapp/src/assets/selection-dark.png new file mode 100644 index 00000000..62e75bcf Binary files /dev/null and b/webapp/src/assets/selection-dark.png differ diff --git a/webapp/src/assets/selection-light.png b/webapp/src/assets/selection-light.png new file mode 100644 index 00000000..b260e360 Binary files /dev/null and b/webapp/src/assets/selection-light.png differ