Properly hide some hud elements when hovering
This commit is contained in:
parent
87f79a6c25
commit
a24e7b8d33
|
@ -18,12 +18,11 @@
|
|||
background-color: #55585a;
|
||||
}
|
||||
|
||||
pointer-events: all;
|
||||
|
||||
&:hover {
|
||||
opacity: 10%;
|
||||
transition: opacity 0.1s ease-out;
|
||||
&.hovered {
|
||||
opacity: 0.1;
|
||||
.buildingImage {
|
||||
opacity: 0%;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,6 +78,7 @@
|
|||
@include S(height, 100px);
|
||||
background: top left / 100% 100% no-repeat;
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
transition: opacity 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
@include StyleBelowWidth(700px) {
|
||||
|
|
|
@ -31,7 +31,8 @@
|
|||
|
||||
pointer-events: all;
|
||||
|
||||
&:hover {
|
||||
transition: opacity 0.1s ease-out;
|
||||
&.hovered {
|
||||
opacity: 10%;
|
||||
.helperGif {
|
||||
opacity: 0%;
|
||||
|
@ -57,5 +58,6 @@
|
|||
@include S(margin-top, 5px);
|
||||
@include S(height, 150px);
|
||||
background: center center / contain no-repeat;
|
||||
transition: opacity 0.1s ease-out;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,69 +1,74 @@
|
|||
#ingame_HUD_KeybindingOverlay {
|
||||
position: absolute;
|
||||
@include S(top, 10px);
|
||||
@include S(left, 10px);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
color: #333438;
|
||||
backdrop-filter: blur(D(2px));
|
||||
padding: D(3px);
|
||||
|
||||
@include DarkThemeOverride {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
> .binding {
|
||||
&:not(.visible) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
display: inline-grid;
|
||||
@include PlainText;
|
||||
align-items: center;
|
||||
@include S(margin-bottom, 3px);
|
||||
grid-auto-flow: column;
|
||||
@include S(grid-gap, 2px);
|
||||
|
||||
i {
|
||||
display: inline-block;
|
||||
@include S(height, 10px);
|
||||
width: 1px;
|
||||
@include S(margin, 0, 3px);
|
||||
background-color: #fff;
|
||||
transform: rotate(10deg);
|
||||
// @include S(margin, 0, 3px);
|
||||
}
|
||||
|
||||
code {
|
||||
position: relative;
|
||||
top: unset;
|
||||
left: unset;
|
||||
margin: 0;
|
||||
&.rightMouse {
|
||||
background: #fff uiResource("icons/mouse_right.png") center center / 85% no-repeat;
|
||||
}
|
||||
|
||||
&.leftMouse {
|
||||
background: #fff uiResource("icons/mouse_left.png") center center / 85% no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
color: #333438;
|
||||
@include SuperSmallText;
|
||||
text-transform: uppercase;
|
||||
// color: #fff;
|
||||
@include DarkThemeOverride {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@include S(margin-left, 5px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.uiHidden #ingame_HUD_KeybindingOverlay .binding:not(.hudToggle) {
|
||||
display: none;
|
||||
}
|
||||
#ingame_HUD_KeybindingOverlay {
|
||||
position: absolute;
|
||||
@include S(top, 10px);
|
||||
@include S(left, 10px);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
color: #333438;
|
||||
backdrop-filter: blur(D(2px));
|
||||
padding: D(3px);
|
||||
|
||||
@include DarkThemeOverride {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
transition: opacity 0.1s ease-out;
|
||||
&.hovered {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
> .binding {
|
||||
&:not(.visible) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
display: inline-grid;
|
||||
@include PlainText;
|
||||
align-items: center;
|
||||
@include S(margin-bottom, 3px);
|
||||
grid-auto-flow: column;
|
||||
@include S(grid-gap, 2px);
|
||||
|
||||
i {
|
||||
display: inline-block;
|
||||
@include S(height, 10px);
|
||||
width: 1px;
|
||||
@include S(margin, 0, 3px);
|
||||
background-color: #fff;
|
||||
transform: rotate(10deg);
|
||||
// @include S(margin, 0, 3px);
|
||||
}
|
||||
|
||||
code {
|
||||
position: relative;
|
||||
top: unset;
|
||||
left: unset;
|
||||
margin: 0;
|
||||
&.rightMouse {
|
||||
background: #fff uiResource("icons/mouse_right.png") center center / 85% no-repeat;
|
||||
}
|
||||
|
||||
&.leftMouse {
|
||||
background: #fff uiResource("icons/mouse_left.png") center center / 85% no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
color: #333438;
|
||||
@include SuperSmallText;
|
||||
text-transform: uppercase;
|
||||
// color: #fff;
|
||||
@include DarkThemeOverride {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@include S(margin-left, 5px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.uiHidden #ingame_HUD_KeybindingOverlay .binding:not(.hudToggle) {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ export const CHANGELOG = [
|
|||
"Updated and added new translations (Thanks to all contributors!)",
|
||||
"Added setting to be able to delete buildings while placing (inspired by hexy)",
|
||||
"You can now adjust the sound and music volumes! (inspired by Yoshie2000)",
|
||||
"Some hud elements now have reduced opacity when hovering, so you can see through (by mvb005)",
|
||||
"Mark pinned shapes in statistics dialog and show them first (inspired by davidburhans)",
|
||||
"Added setting to show chunk borders",
|
||||
"Quad painters have been reworked! They now are integrated with the wires, and only paint the shape when the value is 1 (inspired by dengr1605)",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { TrackedState } from "../../core/tracked_state";
|
||||
import { GameRoot } from "../root";
|
||||
|
||||
// Automatically attaches and detaches elements from the dom
|
||||
|
@ -7,15 +8,28 @@ import { GameRoot } from "../root";
|
|||
// Also attaches a class name if desired
|
||||
|
||||
export class DynamicDomAttach {
|
||||
constructor(root, element, { timeToKeepSeconds = 0, attachClass = null } = {}) {
|
||||
/**
|
||||
*
|
||||
* @param {GameRoot} root
|
||||
* @param {HTMLElement} element
|
||||
* @param {object} param2
|
||||
* @param {number=} param2.timeToKeepSeconds How long to keep the element visible (in ms) after it should be hidden.
|
||||
* Useful for fade-out effects
|
||||
* @param {string=} param2.attachClass If set, attaches a class while the element is visible
|
||||
* @param {boolean=} param2.trackHover If set, attaches the 'hovered' class if the cursor is above the element. Useful
|
||||
* for fading out the element if its below the cursor for example.
|
||||
*/
|
||||
constructor(root, element, { timeToKeepSeconds = 0, attachClass = null, trackHover = false } = {}) {
|
||||
/** @type {GameRoot} */
|
||||
this.root = root;
|
||||
|
||||
/** @type {HTMLElement} */
|
||||
this.element = element;
|
||||
this.parent = this.element.parentElement;
|
||||
assert(this.parent, "Dom attach created without parent");
|
||||
|
||||
this.attachClass = attachClass;
|
||||
this.trackHover = trackHover;
|
||||
|
||||
this.timeToKeepSeconds = timeToKeepSeconds;
|
||||
this.lastVisibleTime = 0;
|
||||
|
@ -26,8 +40,19 @@ export class DynamicDomAttach {
|
|||
|
||||
this.internalIsClassAttached = false;
|
||||
this.classAttachTimeout = null;
|
||||
|
||||
// Store the last bounds we computed
|
||||
/** @type {DOMRect} */
|
||||
this.lastComputedBounds = null;
|
||||
this.lastComputedBoundsTime = -1;
|
||||
|
||||
// Track the 'hovered' class
|
||||
this.trackedIsHovered = new TrackedState(this.setIsHoveredClass, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to attach the element
|
||||
*/
|
||||
internalAttach() {
|
||||
if (!this.attached) {
|
||||
this.parent.appendChild(this.element);
|
||||
|
@ -36,6 +61,9 @@ export class DynamicDomAttach {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to detach the element
|
||||
*/
|
||||
internalDetach() {
|
||||
if (this.attached) {
|
||||
assert(this.element.parentElement === this.parent, "Invalid parent #2");
|
||||
|
@ -44,14 +72,50 @@ export class DynamicDomAttach {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the element is currently attached
|
||||
*/
|
||||
isAttached() {
|
||||
return this.attached;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually sets the 'hovered' class
|
||||
* @param {boolean} isHovered
|
||||
*/
|
||||
setIsHoveredClass(isHovered) {
|
||||
this.element.classList.toggle("hovered", isHovered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this every frame, and the dom attach class will take care of
|
||||
* everything else
|
||||
* @param {boolean} isVisible Whether the element should currently be visible or not
|
||||
*/
|
||||
update(isVisible) {
|
||||
if (isVisible) {
|
||||
this.lastVisibleTime = this.root ? this.root.time.realtimeNow() : 0;
|
||||
this.internalAttach();
|
||||
|
||||
if (this.trackHover && this.root) {
|
||||
let bounds = this.lastComputedBounds;
|
||||
|
||||
// Recompute bounds only once in a while
|
||||
if (!bounds || this.root.time.realtimeNow() - this.lastComputedBoundsTime > 1.0) {
|
||||
bounds = this.lastComputedBounds = this.element.getBoundingClientRect();
|
||||
this.lastComputedBoundsTime = this.root.time.realtimeNow();
|
||||
}
|
||||
|
||||
const mousePos = this.root.app.mousePosition;
|
||||
if (mousePos) {
|
||||
this.trackedIsHovered.set(
|
||||
mousePos.x > bounds.left &&
|
||||
mousePos.x < bounds.right &&
|
||||
mousePos.y > bounds.top &&
|
||||
mousePos.y < bounds.bottom
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!this.root || this.root.time.realtimeNow() - this.lastVisibleTime >= this.timeToKeepSeconds) {
|
||||
this.internalDetach();
|
||||
|
|
|
@ -55,7 +55,7 @@ export class HUDBuildingPlacer extends HUDBuildingPlacerLogic {
|
|||
this.signals.variantChanged.add(this.rerenderVariants, this);
|
||||
this.root.hud.signals.buildingSelectedForPlacement.add(this.startSelection, this);
|
||||
|
||||
this.domAttach = new DynamicDomAttach(this.root, this.element, {});
|
||||
this.domAttach = new DynamicDomAttach(this.root, this.element, { trackHover: true });
|
||||
this.variantsAttach = new DynamicDomAttach(this.root, this.variantsElement, {});
|
||||
|
||||
this.currentInterpolatedCornerTile = new Vector();
|
||||
|
|
|
@ -1,81 +1,81 @@
|
|||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { GameRoot } from "../../root";
|
||||
import { MinerComponent } from "../../components/miner";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
import { TrackedState } from "../../../core/tracked_state";
|
||||
import { cachebust } from "../../../core/cachebust";
|
||||
import { T } from "../../../translations";
|
||||
|
||||
const tutorialsByLevel = [
|
||||
// Level 1
|
||||
[
|
||||
// 1.1. place an extractor
|
||||
{
|
||||
id: "1_1_extractor",
|
||||
condition: /** @param {GameRoot} root */ root => {
|
||||
return root.entityMgr.getAllWithComponent(MinerComponent).length === 0;
|
||||
},
|
||||
},
|
||||
// 1.2. connect to hub
|
||||
{
|
||||
id: "1_2_conveyor",
|
||||
condition: /** @param {GameRoot} root */ root => {
|
||||
return root.hubGoals.getCurrentGoalDelivered() === 0;
|
||||
},
|
||||
},
|
||||
// 1.3 wait for completion
|
||||
{
|
||||
id: "1_3_expand",
|
||||
condition: () => true,
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
export class HUDInteractiveTutorial extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
this.element = makeDiv(
|
||||
parent,
|
||||
"ingame_HUD_InteractiveTutorial",
|
||||
["animEven"],
|
||||
`
|
||||
<strong class="title">${T.ingame.interactiveTutorial.title}</strong>
|
||||
`
|
||||
);
|
||||
|
||||
this.elementDescription = makeDiv(this.element, null, ["desc"]);
|
||||
this.elementGif = makeDiv(this.element, null, ["helperGif"]);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.domAttach = new DynamicDomAttach(this.root, this.element);
|
||||
this.currentHintId = new TrackedState(this.onHintChanged, this);
|
||||
}
|
||||
|
||||
onHintChanged(hintId) {
|
||||
this.elementDescription.innerHTML = T.ingame.interactiveTutorial.hints[hintId];
|
||||
this.elementGif.style.backgroundImage =
|
||||
"url('" + cachebust("res/ui/interactive_tutorial.noinline/" + hintId + ".gif") + "')";
|
||||
this.element.classList.toggle("animEven");
|
||||
this.element.classList.toggle("animOdd");
|
||||
}
|
||||
|
||||
update() {
|
||||
// Compute current hint
|
||||
const thisLevelHints = tutorialsByLevel[this.root.hubGoals.level - 1];
|
||||
let targetHintId = null;
|
||||
|
||||
if (thisLevelHints) {
|
||||
for (let i = 0; i < thisLevelHints.length; ++i) {
|
||||
const hint = thisLevelHints[i];
|
||||
if (hint.condition(this.root)) {
|
||||
targetHintId = hint.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.currentHintId.set(targetHintId);
|
||||
this.domAttach.update(!!targetHintId);
|
||||
}
|
||||
}
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { GameRoot } from "../../root";
|
||||
import { MinerComponent } from "../../components/miner";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
import { TrackedState } from "../../../core/tracked_state";
|
||||
import { cachebust } from "../../../core/cachebust";
|
||||
import { T } from "../../../translations";
|
||||
|
||||
const tutorialsByLevel = [
|
||||
// Level 1
|
||||
[
|
||||
// 1.1. place an extractor
|
||||
{
|
||||
id: "1_1_extractor",
|
||||
condition: /** @param {GameRoot} root */ root => {
|
||||
return root.entityMgr.getAllWithComponent(MinerComponent).length === 0;
|
||||
},
|
||||
},
|
||||
// 1.2. connect to hub
|
||||
{
|
||||
id: "1_2_conveyor",
|
||||
condition: /** @param {GameRoot} root */ root => {
|
||||
return root.hubGoals.getCurrentGoalDelivered() === 0;
|
||||
},
|
||||
},
|
||||
// 1.3 wait for completion
|
||||
{
|
||||
id: "1_3_expand",
|
||||
condition: () => true,
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
export class HUDInteractiveTutorial extends BaseHUDPart {
|
||||
createElements(parent) {
|
||||
this.element = makeDiv(
|
||||
parent,
|
||||
"ingame_HUD_InteractiveTutorial",
|
||||
["animEven"],
|
||||
`
|
||||
<strong class="title">${T.ingame.interactiveTutorial.title}</strong>
|
||||
`
|
||||
);
|
||||
|
||||
this.elementDescription = makeDiv(this.element, null, ["desc"]);
|
||||
this.elementGif = makeDiv(this.element, null, ["helperGif"]);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.domAttach = new DynamicDomAttach(this.root, this.element, { trackHover: true });
|
||||
this.currentHintId = new TrackedState(this.onHintChanged, this);
|
||||
}
|
||||
|
||||
onHintChanged(hintId) {
|
||||
this.elementDescription.innerHTML = T.ingame.interactiveTutorial.hints[hintId];
|
||||
this.elementGif.style.backgroundImage =
|
||||
"url('" + cachebust("res/ui/interactive_tutorial.noinline/" + hintId + ".gif") + "')";
|
||||
this.element.classList.toggle("animEven");
|
||||
this.element.classList.toggle("animOdd");
|
||||
}
|
||||
|
||||
update() {
|
||||
// Compute current hint
|
||||
const thisLevelHints = tutorialsByLevel[this.root.hubGoals.level - 1];
|
||||
let targetHintId = null;
|
||||
|
||||
if (thisLevelHints) {
|
||||
for (let i = 0; i < thisLevelHints.length; ++i) {
|
||||
const hint = thisLevelHints[i];
|
||||
if (hint.condition(this.root)) {
|
||||
targetHintId = hint.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.currentHintId.set(targetHintId);
|
||||
this.domAttach.update(!!targetHintId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,323 +1,330 @@
|
|||
import { makeDiv } from "../../../core/utils";
|
||||
import { T } from "../../../translations";
|
||||
import {
|
||||
getStringForKeyCode,
|
||||
KEYCODE_LMB,
|
||||
KEYCODE_MMB,
|
||||
KEYCODE_RMB,
|
||||
KEYMAPPINGS,
|
||||
} from "../../key_action_mapper";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
|
||||
const DIVIDER_TOKEN = "/";
|
||||
const ADDER_TOKEN = "+";
|
||||
|
||||
/**
|
||||
* @typedef {{ keyCode: number }} KeyCode
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* condition: () => boolean,
|
||||
* keys: Array<KeyCode|number|string>,
|
||||
* label: string,
|
||||
* cachedElement?: HTMLElement,
|
||||
* cachedVisibility?: boolean
|
||||
* }} KeyBinding
|
||||
*/
|
||||
|
||||
export class HUDKeybindingOverlay extends BaseHUDPart {
|
||||
initialize() {}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if there is a building selected for placement
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get buildingPlacementActive() {
|
||||
const placer = this.root.hud.parts.buildingPlacer;
|
||||
return !this.mapOverviewActive && placer && !!placer.currentMetaBuilding.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if there is a building selected for placement and
|
||||
* it supports the belt planner
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get buildingPlacementSupportsBeltPlanner() {
|
||||
const placer = this.root.hud.parts.buildingPlacer;
|
||||
return (
|
||||
!this.mapOverviewActive &&
|
||||
placer &&
|
||||
placer.currentMetaBuilding.get() &&
|
||||
placer.currentMetaBuilding.get().getHasDirectionLockAvailable()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if there is a building selected for placement and
|
||||
* it has multiplace enabled by default
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get buildingPlacementStaysInPlacement() {
|
||||
const placer = this.root.hud.parts.buildingPlacer;
|
||||
return (
|
||||
!this.mapOverviewActive &&
|
||||
placer &&
|
||||
placer.currentMetaBuilding.get() &&
|
||||
placer.currentMetaBuilding.get().getStayInPlacementMode()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if there is a blueprint selected for placement
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get blueprintPlacementActive() {
|
||||
const placer = this.root.hud.parts.blueprintPlacer;
|
||||
return placer && !!placer.currentBlueprint.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if the belt planner is currently active
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get beltPlannerActive() {
|
||||
const placer = this.root.hud.parts.buildingPlacer;
|
||||
return !this.mapOverviewActive && placer && placer.isDirectionLockActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if there is a last blueprint available
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get lastBlueprintAvailable() {
|
||||
const placer = this.root.hud.parts.blueprintPlacer;
|
||||
return placer && !!placer.lastBlueprintUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if there is anything selected on the map
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get anythingSelectedOnMap() {
|
||||
const selector = this.root.hud.parts.massSelector;
|
||||
return selector && selector.selectedUids.size > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if there is a building or blueprint selected for placement
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get anyPlacementActive() {
|
||||
return this.buildingPlacementActive || this.blueprintPlacementActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if the map overview is active
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get mapOverviewActive() {
|
||||
return this.root.camera.getIsMapOverlayActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the element
|
||||
* @param {HTMLElement} parent
|
||||
*/
|
||||
createElements(parent) {
|
||||
const mapper = this.root.keyMapper;
|
||||
const k = KEYMAPPINGS;
|
||||
|
||||
/** @type {Array<KeyBinding>} */
|
||||
this.keybindings = [
|
||||
{
|
||||
// Move map - Including mouse
|
||||
label: T.ingame.keybindingsOverlay.moveMap,
|
||||
keys: [
|
||||
KEYCODE_LMB,
|
||||
DIVIDER_TOKEN,
|
||||
k.navigation.mapMoveUp,
|
||||
k.navigation.mapMoveLeft,
|
||||
k.navigation.mapMoveDown,
|
||||
k.navigation.mapMoveRight,
|
||||
],
|
||||
condition: () => !this.anyPlacementActive,
|
||||
},
|
||||
|
||||
{
|
||||
// Move map - No mouse
|
||||
label: T.ingame.keybindingsOverlay.moveMap,
|
||||
keys: [
|
||||
k.navigation.mapMoveUp,
|
||||
k.navigation.mapMoveLeft,
|
||||
k.navigation.mapMoveDown,
|
||||
k.navigation.mapMoveRight,
|
||||
],
|
||||
condition: () => this.anyPlacementActive,
|
||||
},
|
||||
|
||||
{
|
||||
// [OVERVIEW] Create marker with right click
|
||||
label: T.ingame.keybindingsOverlay.createMarker,
|
||||
keys: [KEYCODE_RMB],
|
||||
condition: () => this.mapOverviewActive && !this.blueprintPlacementActive,
|
||||
},
|
||||
|
||||
{
|
||||
// Pipette
|
||||
label: T.ingame.keybindingsOverlay.pipette,
|
||||
keys: [k.placement.pipette],
|
||||
condition: () => !this.mapOverviewActive && !this.blueprintPlacementActive,
|
||||
},
|
||||
|
||||
{
|
||||
// Cancel placement
|
||||
label: T.ingame.keybindingsOverlay.stopPlacement,
|
||||
keys: [KEYCODE_RMB],
|
||||
condition: () => this.anyPlacementActive,
|
||||
},
|
||||
|
||||
{
|
||||
// Delete with right click
|
||||
label: T.ingame.keybindingsOverlay.delete,
|
||||
keys: [KEYCODE_RMB],
|
||||
condition: () =>
|
||||
!this.anyPlacementActive && !this.mapOverviewActive && !this.anythingSelectedOnMap,
|
||||
},
|
||||
|
||||
{
|
||||
// Area select
|
||||
label: T.ingame.keybindingsOverlay.selectBuildings,
|
||||
keys: [k.massSelect.massSelectStart, ADDER_TOKEN, KEYCODE_LMB],
|
||||
condition: () => !this.anyPlacementActive && !this.anythingSelectedOnMap,
|
||||
},
|
||||
|
||||
{
|
||||
// Place building
|
||||
label: T.ingame.keybindingsOverlay.placeBuilding,
|
||||
keys: [KEYCODE_LMB],
|
||||
condition: () => this.anyPlacementActive,
|
||||
},
|
||||
|
||||
{
|
||||
// Rotate
|
||||
label: T.ingame.keybindingsOverlay.rotateBuilding,
|
||||
keys: [k.placement.rotateWhilePlacing],
|
||||
condition: () => this.anyPlacementActive && !this.beltPlannerActive,
|
||||
},
|
||||
|
||||
{
|
||||
// [BELT PLANNER] Flip Side
|
||||
label: T.ingame.keybindingsOverlay.plannerSwitchSide,
|
||||
keys: [k.placement.switchDirectionLockSide],
|
||||
condition: () => this.beltPlannerActive,
|
||||
},
|
||||
|
||||
{
|
||||
// Place last blueprint
|
||||
label: T.ingame.keybindingsOverlay.pasteLastBlueprint,
|
||||
keys: [k.massSelect.pasteLastBlueprint],
|
||||
condition: () => !this.blueprintPlacementActive && this.lastBlueprintAvailable,
|
||||
},
|
||||
|
||||
{
|
||||
// Belt planner
|
||||
label: T.ingame.keybindingsOverlay.lockBeltDirection,
|
||||
keys: [k.placementModifiers.lockBeltDirection],
|
||||
condition: () => this.buildingPlacementSupportsBeltPlanner && !this.beltPlannerActive,
|
||||
},
|
||||
|
||||
{
|
||||
// [SELECTION] Destroy
|
||||
label: T.ingame.keybindingsOverlay.delete,
|
||||
keys: [k.massSelect.confirmMassDelete],
|
||||
condition: () => this.anythingSelectedOnMap,
|
||||
},
|
||||
|
||||
{
|
||||
// [SELECTION] Cancel
|
||||
label: T.ingame.keybindingsOverlay.clearSelection,
|
||||
keys: [k.general.back],
|
||||
condition: () => this.anythingSelectedOnMap,
|
||||
},
|
||||
{
|
||||
// [SELECTION] Cut
|
||||
label: T.ingame.keybindingsOverlay.cutSelection,
|
||||
keys: [k.massSelect.massSelectCut],
|
||||
condition: () => this.anythingSelectedOnMap,
|
||||
},
|
||||
|
||||
{
|
||||
// [SELECTION] Copy
|
||||
label: T.ingame.keybindingsOverlay.copySelection,
|
||||
keys: [k.massSelect.massSelectCopy],
|
||||
condition: () => this.anythingSelectedOnMap,
|
||||
},
|
||||
|
||||
{
|
||||
// Switch layers
|
||||
label: T.ingame.keybindingsOverlay.switchLayers,
|
||||
keys: [k.ingame.switchLayers],
|
||||
condition: () => true,
|
||||
},
|
||||
];
|
||||
|
||||
if (!this.root.app.settings.getAllSettings().alwaysMultiplace) {
|
||||
this.keybindings.push({
|
||||
// Multiplace
|
||||
label: T.ingame.keybindingsOverlay.placeMultiple,
|
||||
keys: [k.placementModifiers.placeMultiple],
|
||||
condition: () => this.anyPlacementActive && !this.buildingPlacementStaysInPlacement,
|
||||
});
|
||||
}
|
||||
|
||||
this.element = makeDiv(parent, "ingame_HUD_KeybindingOverlay", []);
|
||||
|
||||
for (let i = 0; i < this.keybindings.length; ++i) {
|
||||
let html = "";
|
||||
const handle = this.keybindings[i];
|
||||
|
||||
for (let k = 0; k < handle.keys.length; ++k) {
|
||||
const key = handle.keys[k];
|
||||
|
||||
switch (key) {
|
||||
case KEYCODE_LMB:
|
||||
html += `<code class="keybinding leftMouse"></code>`;
|
||||
break;
|
||||
case KEYCODE_RMB:
|
||||
html += `<code class="keybinding rightMouse"></code>`;
|
||||
break;
|
||||
case KEYCODE_MMB:
|
||||
html += `<code class="keybinding middleMouse"></code>`;
|
||||
break;
|
||||
case DIVIDER_TOKEN:
|
||||
html += `<i></i>`;
|
||||
break;
|
||||
case ADDER_TOKEN:
|
||||
html += `+`;
|
||||
break;
|
||||
default:
|
||||
html += `<code class="keybinding">${getStringForKeyCode(
|
||||
mapper.getBinding(/** @type {KeyCode} */ (key)).keyCode
|
||||
)}</code>`;
|
||||
}
|
||||
}
|
||||
html += `<label>${handle.label}</label>`;
|
||||
|
||||
handle.cachedElement = makeDiv(this.element, null, ["binding"], html);
|
||||
handle.cachedVisibility = false;
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
for (let i = 0; i < this.keybindings.length; ++i) {
|
||||
const handle = this.keybindings[i];
|
||||
const visibility = handle.condition();
|
||||
if (visibility !== handle.cachedVisibility) {
|
||||
handle.cachedVisibility = visibility;
|
||||
handle.cachedElement.classList.toggle("visible", visibility);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { T } from "../../../translations";
|
||||
import {
|
||||
getStringForKeyCode,
|
||||
KEYCODE_LMB,
|
||||
KEYCODE_MMB,
|
||||
KEYCODE_RMB,
|
||||
KEYMAPPINGS,
|
||||
} from "../../key_action_mapper";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
|
||||
const DIVIDER_TOKEN = "/";
|
||||
const ADDER_TOKEN = "+";
|
||||
|
||||
/**
|
||||
* @typedef {{ keyCode: number }} KeyCode
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* condition: () => boolean,
|
||||
* keys: Array<KeyCode|number|string>,
|
||||
* label: string,
|
||||
* cachedElement?: HTMLElement,
|
||||
* cachedVisibility?: boolean
|
||||
* }} KeyBinding
|
||||
*/
|
||||
|
||||
export class HUDKeybindingOverlay extends BaseHUDPart {
|
||||
/**
|
||||
* HELPER / Returns if there is a building selected for placement
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get buildingPlacementActive() {
|
||||
const placer = this.root.hud.parts.buildingPlacer;
|
||||
return !this.mapOverviewActive && placer && !!placer.currentMetaBuilding.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if there is a building selected for placement and
|
||||
* it supports the belt planner
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get buildingPlacementSupportsBeltPlanner() {
|
||||
const placer = this.root.hud.parts.buildingPlacer;
|
||||
return (
|
||||
!this.mapOverviewActive &&
|
||||
placer &&
|
||||
placer.currentMetaBuilding.get() &&
|
||||
placer.currentMetaBuilding.get().getHasDirectionLockAvailable()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if there is a building selected for placement and
|
||||
* it has multiplace enabled by default
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get buildingPlacementStaysInPlacement() {
|
||||
const placer = this.root.hud.parts.buildingPlacer;
|
||||
return (
|
||||
!this.mapOverviewActive &&
|
||||
placer &&
|
||||
placer.currentMetaBuilding.get() &&
|
||||
placer.currentMetaBuilding.get().getStayInPlacementMode()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if there is a blueprint selected for placement
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get blueprintPlacementActive() {
|
||||
const placer = this.root.hud.parts.blueprintPlacer;
|
||||
return placer && !!placer.currentBlueprint.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if the belt planner is currently active
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get beltPlannerActive() {
|
||||
const placer = this.root.hud.parts.buildingPlacer;
|
||||
return !this.mapOverviewActive && placer && placer.isDirectionLockActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if there is a last blueprint available
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get lastBlueprintAvailable() {
|
||||
const placer = this.root.hud.parts.blueprintPlacer;
|
||||
return placer && !!placer.lastBlueprintUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if there is anything selected on the map
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get anythingSelectedOnMap() {
|
||||
const selector = this.root.hud.parts.massSelector;
|
||||
return selector && selector.selectedUids.size > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if there is a building or blueprint selected for placement
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get anyPlacementActive() {
|
||||
return this.buildingPlacementActive || this.blueprintPlacementActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* HELPER / Returns if the map overview is active
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get mapOverviewActive() {
|
||||
return this.root.camera.getIsMapOverlayActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the element
|
||||
* @param {HTMLElement} parent
|
||||
*/
|
||||
createElements(parent) {
|
||||
const mapper = this.root.keyMapper;
|
||||
const k = KEYMAPPINGS;
|
||||
|
||||
/** @type {Array<KeyBinding>} */
|
||||
this.keybindings = [
|
||||
{
|
||||
// Move map - Including mouse
|
||||
label: T.ingame.keybindingsOverlay.moveMap,
|
||||
keys: [
|
||||
KEYCODE_LMB,
|
||||
DIVIDER_TOKEN,
|
||||
k.navigation.mapMoveUp,
|
||||
k.navigation.mapMoveLeft,
|
||||
k.navigation.mapMoveDown,
|
||||
k.navigation.mapMoveRight,
|
||||
],
|
||||
condition: () => !this.anyPlacementActive,
|
||||
},
|
||||
|
||||
{
|
||||
// Move map - No mouse
|
||||
label: T.ingame.keybindingsOverlay.moveMap,
|
||||
keys: [
|
||||
k.navigation.mapMoveUp,
|
||||
k.navigation.mapMoveLeft,
|
||||
k.navigation.mapMoveDown,
|
||||
k.navigation.mapMoveRight,
|
||||
],
|
||||
condition: () => this.anyPlacementActive,
|
||||
},
|
||||
|
||||
{
|
||||
// [OVERVIEW] Create marker with right click
|
||||
label: T.ingame.keybindingsOverlay.createMarker,
|
||||
keys: [KEYCODE_RMB],
|
||||
condition: () => this.mapOverviewActive && !this.blueprintPlacementActive,
|
||||
},
|
||||
|
||||
{
|
||||
// Pipette
|
||||
label: T.ingame.keybindingsOverlay.pipette,
|
||||
keys: [k.placement.pipette],
|
||||
condition: () => !this.mapOverviewActive && !this.blueprintPlacementActive,
|
||||
},
|
||||
|
||||
{
|
||||
// Cancel placement
|
||||
label: T.ingame.keybindingsOverlay.stopPlacement,
|
||||
keys: [KEYCODE_RMB],
|
||||
condition: () => this.anyPlacementActive,
|
||||
},
|
||||
|
||||
{
|
||||
// Delete with right click
|
||||
label: T.ingame.keybindingsOverlay.delete,
|
||||
keys: [KEYCODE_RMB],
|
||||
condition: () =>
|
||||
!this.anyPlacementActive && !this.mapOverviewActive && !this.anythingSelectedOnMap,
|
||||
},
|
||||
|
||||
{
|
||||
// Area select
|
||||
label: T.ingame.keybindingsOverlay.selectBuildings,
|
||||
keys: [k.massSelect.massSelectStart, ADDER_TOKEN, KEYCODE_LMB],
|
||||
condition: () => !this.anyPlacementActive && !this.anythingSelectedOnMap,
|
||||
},
|
||||
|
||||
{
|
||||
// Place building
|
||||
label: T.ingame.keybindingsOverlay.placeBuilding,
|
||||
keys: [KEYCODE_LMB],
|
||||
condition: () => this.anyPlacementActive,
|
||||
},
|
||||
|
||||
{
|
||||
// Rotate
|
||||
label: T.ingame.keybindingsOverlay.rotateBuilding,
|
||||
keys: [k.placement.rotateWhilePlacing],
|
||||
condition: () => this.anyPlacementActive && !this.beltPlannerActive,
|
||||
},
|
||||
|
||||
{
|
||||
// [BELT PLANNER] Flip Side
|
||||
label: T.ingame.keybindingsOverlay.plannerSwitchSide,
|
||||
keys: [k.placement.switchDirectionLockSide],
|
||||
condition: () => this.beltPlannerActive,
|
||||
},
|
||||
|
||||
{
|
||||
// Place last blueprint
|
||||
label: T.ingame.keybindingsOverlay.pasteLastBlueprint,
|
||||
keys: [k.massSelect.pasteLastBlueprint],
|
||||
condition: () => !this.blueprintPlacementActive && this.lastBlueprintAvailable,
|
||||
},
|
||||
|
||||
{
|
||||
// Belt planner
|
||||
label: T.ingame.keybindingsOverlay.lockBeltDirection,
|
||||
keys: [k.placementModifiers.lockBeltDirection],
|
||||
condition: () => this.buildingPlacementSupportsBeltPlanner && !this.beltPlannerActive,
|
||||
},
|
||||
|
||||
{
|
||||
// [SELECTION] Destroy
|
||||
label: T.ingame.keybindingsOverlay.delete,
|
||||
keys: [k.massSelect.confirmMassDelete],
|
||||
condition: () => this.anythingSelectedOnMap,
|
||||
},
|
||||
|
||||
{
|
||||
// [SELECTION] Cancel
|
||||
label: T.ingame.keybindingsOverlay.clearSelection,
|
||||
keys: [k.general.back],
|
||||
condition: () => this.anythingSelectedOnMap,
|
||||
},
|
||||
{
|
||||
// [SELECTION] Cut
|
||||
label: T.ingame.keybindingsOverlay.cutSelection,
|
||||
keys: [k.massSelect.massSelectCut],
|
||||
condition: () => this.anythingSelectedOnMap,
|
||||
},
|
||||
|
||||
{
|
||||
// [SELECTION] Copy
|
||||
label: T.ingame.keybindingsOverlay.copySelection,
|
||||
keys: [k.massSelect.massSelectCopy],
|
||||
condition: () => this.anythingSelectedOnMap,
|
||||
},
|
||||
|
||||
{
|
||||
// Switch layers
|
||||
label: T.ingame.keybindingsOverlay.switchLayers,
|
||||
keys: [k.ingame.switchLayers],
|
||||
condition: () => true,
|
||||
},
|
||||
];
|
||||
|
||||
if (!this.root.app.settings.getAllSettings().alwaysMultiplace) {
|
||||
this.keybindings.push({
|
||||
// Multiplace
|
||||
label: T.ingame.keybindingsOverlay.placeMultiple,
|
||||
keys: [k.placementModifiers.placeMultiple],
|
||||
condition: () => this.anyPlacementActive && !this.buildingPlacementStaysInPlacement,
|
||||
});
|
||||
}
|
||||
|
||||
this.element = makeDiv(parent, "ingame_HUD_KeybindingOverlay", []);
|
||||
|
||||
for (let i = 0; i < this.keybindings.length; ++i) {
|
||||
let html = "";
|
||||
const handle = this.keybindings[i];
|
||||
|
||||
for (let k = 0; k < handle.keys.length; ++k) {
|
||||
const key = handle.keys[k];
|
||||
|
||||
switch (key) {
|
||||
case KEYCODE_LMB:
|
||||
html += `<code class="keybinding leftMouse"></code>`;
|
||||
break;
|
||||
case KEYCODE_RMB:
|
||||
html += `<code class="keybinding rightMouse"></code>`;
|
||||
break;
|
||||
case KEYCODE_MMB:
|
||||
html += `<code class="keybinding middleMouse"></code>`;
|
||||
break;
|
||||
case DIVIDER_TOKEN:
|
||||
html += `<i></i>`;
|
||||
break;
|
||||
case ADDER_TOKEN:
|
||||
html += `+`;
|
||||
break;
|
||||
default:
|
||||
html += `<code class="keybinding">${getStringForKeyCode(
|
||||
mapper.getBinding(/** @type {KeyCode} */ (key)).keyCode
|
||||
)}</code>`;
|
||||
}
|
||||
}
|
||||
html += `<label>${handle.label}</label>`;
|
||||
|
||||
handle.cachedElement = makeDiv(this.element, null, ["binding"], html);
|
||||
handle.cachedVisibility = false;
|
||||
}
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.domAttach = new DynamicDomAttach(this.root, this.element, {
|
||||
trackHover: true,
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
for (let i = 0; i < this.keybindings.length; ++i) {
|
||||
const handle = this.keybindings[i];
|
||||
const visibility = handle.condition();
|
||||
if (visibility !== handle.cachedVisibility) {
|
||||
handle.cachedVisibility = visibility;
|
||||
handle.cachedElement.classList.toggle("visible", visibility);
|
||||
}
|
||||
}
|
||||
|
||||
// Required for hover
|
||||
this.domAttach.update(true);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue