Prevent rendering selection outline twice

This commit is contained in:
tobspr 2020-08-28 23:11:56 +02:00
parent 707323591b
commit 7a4fcb5c99
1 changed files with 317 additions and 308 deletions

View File

@ -1,308 +1,317 @@
import { BaseHUDPart } from "../base_hud_part"; import { BaseHUDPart } from "../base_hud_part";
import { Vector } from "../../../core/vector"; import { Vector } from "../../../core/vector";
import { STOP_PROPAGATION } from "../../../core/signal"; import { STOP_PROPAGATION } from "../../../core/signal";
import { DrawParameters } from "../../../core/draw_parameters"; import { DrawParameters } from "../../../core/draw_parameters";
import { Entity } from "../../entity"; import { Entity } from "../../entity";
import { Loader } from "../../../core/loader"; import { Loader } from "../../../core/loader";
import { globalConfig } from "../../../core/config"; import { globalConfig } from "../../../core/config";
import { makeDiv, formatBigNumber, formatBigNumberFull } from "../../../core/utils"; import { makeDiv, formatBigNumber, formatBigNumberFull } from "../../../core/utils";
import { DynamicDomAttach } from "../dynamic_dom_attach"; import { DynamicDomAttach } from "../dynamic_dom_attach";
import { createLogger } from "../../../core/logging"; import { createLogger } from "../../../core/logging";
import { enumMouseButton } from "../../camera"; import { enumMouseButton } from "../../camera";
import { T } from "../../../translations"; import { T } from "../../../translations";
import { KEYMAPPINGS } from "../../key_action_mapper"; import { KEYMAPPINGS } from "../../key_action_mapper";
import { THEME } from "../../theme"; import { THEME } from "../../theme";
import { enumHubGoalRewards } from "../../tutorial_goals"; import { enumHubGoalRewards } from "../../tutorial_goals";
import { Blueprint } from "../../blueprint"; import { Blueprint } from "../../blueprint";
const logger = createLogger("hud/mass_selector"); const logger = createLogger("hud/mass_selector");
export class HUDMassSelector extends BaseHUDPart { export class HUDMassSelector extends BaseHUDPart {
createElements(parent) {} createElements(parent) {}
initialize() { initialize() {
this.currentSelectionStartWorld = null; this.currentSelectionStartWorld = null;
this.currentSelectionEnd = null; this.currentSelectionEnd = null;
this.selectedUids = new Set(); this.selectedUids = new Set();
this.root.signals.entityQueuedForDestroy.add(this.onEntityDestroyed, this); this.root.signals.entityQueuedForDestroy.add(this.onEntityDestroyed, this);
this.root.hud.signals.pasteBlueprintRequested.add(this.clearSelection, this); this.root.hud.signals.pasteBlueprintRequested.add(this.clearSelection, this);
this.root.camera.downPreHandler.add(this.onMouseDown, this); this.root.camera.downPreHandler.add(this.onMouseDown, this);
this.root.camera.movePreHandler.add(this.onMouseMove, this); this.root.camera.movePreHandler.add(this.onMouseMove, this);
this.root.camera.upPostHandler.add(this.onMouseUp, this); this.root.camera.upPostHandler.add(this.onMouseUp, this);
this.root.keyMapper.getBinding(KEYMAPPINGS.general.back).add(this.onBack, this); this.root.keyMapper.getBinding(KEYMAPPINGS.general.back).add(this.onBack, this);
this.root.keyMapper this.root.keyMapper
.getBinding(KEYMAPPINGS.massSelect.confirmMassDelete) .getBinding(KEYMAPPINGS.massSelect.confirmMassDelete)
.add(this.confirmDelete, this); .add(this.confirmDelete, this);
this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectCut).add(this.confirmCut, this); this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectCut).add(this.confirmCut, this);
this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectCopy).add(this.startCopy, this); this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectCopy).add(this.startCopy, this);
this.root.hud.signals.selectedPlacementBuildingChanged.add(this.clearSelection, this); this.root.hud.signals.selectedPlacementBuildingChanged.add(this.clearSelection, this);
this.root.signals.editModeChanged.add(this.clearSelection, this); this.root.signals.editModeChanged.add(this.clearSelection, this);
} }
/** /**
* Handles the destroy callback and makes sure we clean our list * Handles the destroy callback and makes sure we clean our list
* @param {Entity} entity * @param {Entity} entity
*/ */
onEntityDestroyed(entity) { onEntityDestroyed(entity) {
this.selectedUids.delete(entity.uid); this.selectedUids.delete(entity.uid);
} }
/** /**
* *
*/ */
onBack() { onBack() {
// Clear entities on escape // Clear entities on escape
if (this.selectedUids.size > 0) { if (this.selectedUids.size > 0) {
this.selectedUids = new Set(); this.selectedUids = new Set();
return STOP_PROPAGATION; return STOP_PROPAGATION;
} }
} }
/** /**
* Clears the entire selection * Clears the entire selection
*/ */
clearSelection() { clearSelection() {
this.selectedUids = new Set(); this.selectedUids = new Set();
} }
confirmDelete() { confirmDelete() {
if ( if (
!this.root.app.settings.getAllSettings().disableCutDeleteWarnings && !this.root.app.settings.getAllSettings().disableCutDeleteWarnings &&
this.selectedUids.size > 100 this.selectedUids.size > 100
) { ) {
const { ok } = this.root.hud.parts.dialogs.showWarning( const { ok } = this.root.hud.parts.dialogs.showWarning(
T.dialogs.massDeleteConfirm.title, T.dialogs.massDeleteConfirm.title,
T.dialogs.massDeleteConfirm.desc.replace( T.dialogs.massDeleteConfirm.desc.replace(
"<count>", "<count>",
"" + formatBigNumberFull(this.selectedUids.size) "" + formatBigNumberFull(this.selectedUids.size)
), ),
["cancel:good:escape", "ok:bad:enter"] ["cancel:good:escape", "ok:bad:enter"]
); );
ok.add(() => this.doDelete()); ok.add(() => this.doDelete());
} else { } else {
this.doDelete(); this.doDelete();
} }
} }
doDelete() { doDelete() {
const entityUids = Array.from(this.selectedUids); const entityUids = Array.from(this.selectedUids);
for (let i = 0; i < entityUids.length; ++i) { for (let i = 0; i < entityUids.length; ++i) {
const uid = entityUids[i]; const uid = entityUids[i];
const entity = this.root.entityMgr.findByUid(uid); const entity = this.root.entityMgr.findByUid(uid);
if (!this.root.logic.tryDeleteBuilding(entity)) { if (!this.root.logic.tryDeleteBuilding(entity)) {
logger.error("Error in mass delete, could not remove building"); logger.error("Error in mass delete, could not remove building");
this.selectedUids.delete(uid); this.selectedUids.delete(uid);
} }
} }
} }
startCopy() { startCopy() {
if (this.selectedUids.size > 0) { if (this.selectedUids.size > 0) {
if (!this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) { if (!this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
this.root.hud.parts.dialogs.showInfo( this.root.hud.parts.dialogs.showInfo(
T.dialogs.blueprintsNotUnlocked.title, T.dialogs.blueprintsNotUnlocked.title,
T.dialogs.blueprintsNotUnlocked.desc T.dialogs.blueprintsNotUnlocked.desc
); );
return; return;
} }
this.root.hud.signals.buildingsSelectedForCopy.dispatch(Array.from(this.selectedUids)); this.root.hud.signals.buildingsSelectedForCopy.dispatch(Array.from(this.selectedUids));
this.selectedUids = new Set(); this.selectedUids = new Set();
this.root.soundProxy.playUiClick(); this.root.soundProxy.playUiClick();
} else { } else {
this.root.soundProxy.playUiError(); this.root.soundProxy.playUiError();
} }
} }
confirmCut() { confirmCut() {
if (!this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) { if (!this.root.hubGoals.isRewardUnlocked(enumHubGoalRewards.reward_blueprints)) {
this.root.hud.parts.dialogs.showInfo( this.root.hud.parts.dialogs.showInfo(
T.dialogs.blueprintsNotUnlocked.title, T.dialogs.blueprintsNotUnlocked.title,
T.dialogs.blueprintsNotUnlocked.desc T.dialogs.blueprintsNotUnlocked.desc
); );
} else if ( } else if (
!this.root.app.settings.getAllSettings().disableCutDeleteWarnings && !this.root.app.settings.getAllSettings().disableCutDeleteWarnings &&
this.selectedUids.size > 100 this.selectedUids.size > 100
) { ) {
const { ok } = this.root.hud.parts.dialogs.showWarning( const { ok } = this.root.hud.parts.dialogs.showWarning(
T.dialogs.massCutConfirm.title, T.dialogs.massCutConfirm.title,
T.dialogs.massCutConfirm.desc.replace( T.dialogs.massCutConfirm.desc.replace(
"<count>", "<count>",
"" + formatBigNumberFull(this.selectedUids.size) "" + formatBigNumberFull(this.selectedUids.size)
), ),
["cancel:good:escape", "ok:bad:enter"] ["cancel:good:escape", "ok:bad:enter"]
); );
ok.add(() => this.doCut()); ok.add(() => this.doCut());
} else { } else {
this.doCut(); this.doCut();
} }
} }
doCut() { doCut() {
if (this.selectedUids.size > 0) { if (this.selectedUids.size > 0) {
const entityUids = Array.from(this.selectedUids); const entityUids = Array.from(this.selectedUids);
const cutAction = () => { const cutAction = () => {
// copy code relies on entities still existing, so must copy before deleting. // copy code relies on entities still existing, so must copy before deleting.
this.root.hud.signals.buildingsSelectedForCopy.dispatch(entityUids); this.root.hud.signals.buildingsSelectedForCopy.dispatch(entityUids);
for (let i = 0; i < entityUids.length; ++i) { for (let i = 0; i < entityUids.length; ++i) {
const uid = entityUids[i]; const uid = entityUids[i];
const entity = this.root.entityMgr.findByUid(uid); const entity = this.root.entityMgr.findByUid(uid);
if (!this.root.logic.tryDeleteBuilding(entity)) { if (!this.root.logic.tryDeleteBuilding(entity)) {
logger.error("Error in mass cut, could not remove building"); logger.error("Error in mass cut, could not remove building");
this.selectedUids.delete(uid); this.selectedUids.delete(uid);
} }
} }
}; };
const blueprint = Blueprint.fromUids(this.root, entityUids); const blueprint = Blueprint.fromUids(this.root, entityUids);
if (blueprint.canAfford(this.root)) { if (blueprint.canAfford(this.root)) {
cutAction(); cutAction();
} else { } else {
const { cancel, ok } = this.root.hud.parts.dialogs.showWarning( const { cancel, ok } = this.root.hud.parts.dialogs.showWarning(
T.dialogs.massCutInsufficientConfirm.title, T.dialogs.massCutInsufficientConfirm.title,
T.dialogs.massCutInsufficientConfirm.desc, T.dialogs.massCutInsufficientConfirm.desc,
["cancel:good:escape", "ok:bad:enter"] ["cancel:good:escape", "ok:bad:enter"]
); );
ok.add(cutAction); ok.add(cutAction);
} }
this.root.soundProxy.playUiClick(); this.root.soundProxy.playUiClick();
} else { } else {
this.root.soundProxy.playUiError(); this.root.soundProxy.playUiError();
} }
} }
/** /**
* mouse down pre handler * mouse down pre handler
* @param {Vector} pos * @param {Vector} pos
* @param {enumMouseButton} mouseButton * @param {enumMouseButton} mouseButton
*/ */
onMouseDown(pos, mouseButton) { onMouseDown(pos, mouseButton) {
if (!this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectStart).pressed) { if (!this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectStart).pressed) {
return; return;
} }
if (mouseButton !== enumMouseButton.left) { if (mouseButton !== enumMouseButton.left) {
return; return;
} }
if (!this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectSelectMultiple).pressed) { if (!this.root.keyMapper.getBinding(KEYMAPPINGS.massSelect.massSelectSelectMultiple).pressed) {
// Start new selection // Start new selection
this.selectedUids = new Set(); this.selectedUids = new Set();
} }
this.currentSelectionStartWorld = this.root.camera.screenToWorld(pos.copy()); this.currentSelectionStartWorld = this.root.camera.screenToWorld(pos.copy());
this.currentSelectionEnd = pos.copy(); this.currentSelectionEnd = pos.copy();
return STOP_PROPAGATION; return STOP_PROPAGATION;
} }
/** /**
* mouse move pre handler * mouse move pre handler
* @param {Vector} pos * @param {Vector} pos
*/ */
onMouseMove(pos) { onMouseMove(pos) {
if (this.currentSelectionStartWorld) { if (this.currentSelectionStartWorld) {
this.currentSelectionEnd = pos.copy(); this.currentSelectionEnd = pos.copy();
} }
} }
onMouseUp() { onMouseUp() {
if (this.currentSelectionStartWorld) { if (this.currentSelectionStartWorld) {
const worldStart = this.currentSelectionStartWorld; const worldStart = this.currentSelectionStartWorld;
const worldEnd = this.root.camera.screenToWorld(this.currentSelectionEnd); const worldEnd = this.root.camera.screenToWorld(this.currentSelectionEnd);
const tileStart = worldStart.toTileSpace(); const tileStart = worldStart.toTileSpace();
const tileEnd = worldEnd.toTileSpace(); const tileEnd = worldEnd.toTileSpace();
const realTileStart = tileStart.min(tileEnd); const realTileStart = tileStart.min(tileEnd);
const realTileEnd = tileStart.max(tileEnd); const realTileEnd = tileStart.max(tileEnd);
for (let x = realTileStart.x; x <= realTileEnd.x; ++x) { for (let x = realTileStart.x; x <= realTileEnd.x; ++x) {
for (let y = realTileStart.y; y <= realTileEnd.y; ++y) { for (let y = realTileStart.y; y <= realTileEnd.y; ++y) {
const contents = this.root.map.getLayerContentXY(x, y, this.root.currentLayer); const contents = this.root.map.getLayerContentXY(x, y, this.root.currentLayer);
if (contents && this.root.logic.canDeleteBuilding(contents)) { if (contents && this.root.logic.canDeleteBuilding(contents)) {
this.selectedUids.add(contents.uid); this.selectedUids.add(contents.uid);
} }
} }
} }
this.currentSelectionStartWorld = null; this.currentSelectionStartWorld = null;
this.currentSelectionEnd = null; this.currentSelectionEnd = null;
} }
} }
/** /**
* *
* @param {DrawParameters} parameters * @param {DrawParameters} parameters
*/ */
draw(parameters) { draw(parameters) {
const boundsBorder = 2; const boundsBorder = 2;
if (this.currentSelectionStartWorld) { if (this.currentSelectionStartWorld) {
const worldStart = this.currentSelectionStartWorld; const worldStart = this.currentSelectionStartWorld;
const worldEnd = this.root.camera.screenToWorld(this.currentSelectionEnd); const worldEnd = this.root.camera.screenToWorld(this.currentSelectionEnd);
const realWorldStart = worldStart.min(worldEnd); const realWorldStart = worldStart.min(worldEnd);
const realWorldEnd = worldStart.max(worldEnd); const realWorldEnd = worldStart.max(worldEnd);
const tileStart = worldStart.toTileSpace(); const tileStart = worldStart.toTileSpace();
const tileEnd = worldEnd.toTileSpace(); const tileEnd = worldEnd.toTileSpace();
const realTileStart = tileStart.min(tileEnd); const realTileStart = tileStart.min(tileEnd);
const realTileEnd = tileStart.max(tileEnd); const realTileEnd = tileStart.max(tileEnd);
parameters.context.lineWidth = 1; parameters.context.lineWidth = 1;
parameters.context.fillStyle = THEME.map.selectionBackground; parameters.context.fillStyle = THEME.map.selectionBackground;
parameters.context.strokeStyle = THEME.map.selectionOutline; parameters.context.strokeStyle = THEME.map.selectionOutline;
parameters.context.beginPath(); parameters.context.beginPath();
parameters.context.rect( parameters.context.rect(
realWorldStart.x, realWorldStart.x,
realWorldStart.y, realWorldStart.y,
realWorldEnd.x - realWorldStart.x, realWorldEnd.x - realWorldStart.x,
realWorldEnd.y - realWorldStart.y realWorldEnd.y - realWorldStart.y
); );
parameters.context.fill(); parameters.context.fill();
parameters.context.stroke(); parameters.context.stroke();
parameters.context.fillStyle = THEME.map.selectionOverlay; parameters.context.fillStyle = THEME.map.selectionOverlay;
for (let x = realTileStart.x; x <= realTileEnd.x; ++x) { let renderedUids = new Set();
for (let y = realTileStart.y; y <= realTileEnd.y; ++y) {
const contents = this.root.map.getLayerContentXY(x, y, this.root.currentLayer); for (let x = realTileStart.x; x <= realTileEnd.x; ++x) {
if (contents && this.root.logic.canDeleteBuilding(contents)) { for (let y = realTileStart.y; y <= realTileEnd.y; ++y) {
const staticComp = contents.components.StaticMapEntity; const contents = this.root.map.getLayerContentXY(x, y, this.root.currentLayer);
const bounds = staticComp.getTileSpaceBounds(); if (contents && this.root.logic.canDeleteBuilding(contents)) {
parameters.context.beginRoundedRect( // Prevent rendering the overlay twice
bounds.x * globalConfig.tileSize + boundsBorder, const uid = contents.uid;
bounds.y * globalConfig.tileSize + boundsBorder, if (renderedUids.has(uid)) {
bounds.w * globalConfig.tileSize - 2 * boundsBorder, continue;
bounds.h * globalConfig.tileSize - 2 * boundsBorder, }
2 renderedUids.add(uid);
);
parameters.context.fill(); const staticComp = contents.components.StaticMapEntity;
} const bounds = staticComp.getTileSpaceBounds();
} parameters.context.beginRoundedRect(
} bounds.x * globalConfig.tileSize + boundsBorder,
} bounds.y * globalConfig.tileSize + boundsBorder,
bounds.w * globalConfig.tileSize - 2 * boundsBorder,
parameters.context.fillStyle = THEME.map.selectionOverlay; bounds.h * globalConfig.tileSize - 2 * boundsBorder,
this.selectedUids.forEach(uid => { 2
const entity = this.root.entityMgr.findByUid(uid); );
const staticComp = entity.components.StaticMapEntity; parameters.context.fill();
const bounds = staticComp.getTileSpaceBounds(); }
parameters.context.beginRoundedRect( }
bounds.x * globalConfig.tileSize + boundsBorder, }
bounds.y * globalConfig.tileSize + boundsBorder, }
bounds.w * globalConfig.tileSize - 2 * boundsBorder,
bounds.h * globalConfig.tileSize - 2 * boundsBorder, parameters.context.fillStyle = THEME.map.selectionOverlay;
2 this.selectedUids.forEach(uid => {
); const entity = this.root.entityMgr.findByUid(uid);
parameters.context.fill(); const staticComp = entity.components.StaticMapEntity;
}); const bounds = staticComp.getTileSpaceBounds();
} parameters.context.beginRoundedRect(
} bounds.x * globalConfig.tileSize + boundsBorder,
bounds.y * globalConfig.tileSize + boundsBorder,
bounds.w * globalConfig.tileSize - 2 * boundsBorder,
bounds.h * globalConfig.tileSize - 2 * boundsBorder,
2
);
parameters.context.fill();
});
}
}