mirror of
				https://github.com/tobspr/shapez.io.git
				synced 2025-06-13 13:04:03 +00:00 
			
		
		
		
	Allow pinning shapes
This commit is contained in:
		
							parent
							
								
									89b11398df
								
							
						
					
					
						commit
						7bccdb829c
					
				
							
								
								
									
										
											BIN
										
									
								
								res/ui/icons/current_goal_marker.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								res/ui/icons/current_goal_marker.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 878 B | 
							
								
								
									
										
											BIN
										
									
								
								res/ui/icons/pin.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								res/ui/icons/pin.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								res/ui/icons/unpin.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								res/ui/icons/unpin.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 854 B | 
| @ -1,8 +0,0 @@ | ||||
| body.ingameDialogOpen { | ||||
|     #ingame_Canvas, | ||||
|     #ingame_HUD_GameMenu, | ||||
|     #ingame_HUD_KeybindingOverlay, | ||||
|     #ingame_HUD_buildings_toolbar { | ||||
|         filter: blur(5px); | ||||
|     } | ||||
| } | ||||
| @ -20,7 +20,7 @@ | ||||
|             @include S(height, 10px); | ||||
|             width: 1px; | ||||
|             @include S(margin, 0, 3px); | ||||
|             background-color: #ccc; | ||||
|             background-color: #888; | ||||
|             transform: rotate(10deg); | ||||
|             // @include S(margin, 0, 3px); | ||||
|         } | ||||
|  | ||||
							
								
								
									
										54
									
								
								src/css/ingame_hud/pinned_shapes.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/css/ingame_hud/pinned_shapes.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| #ingame_HUD_PinnedShapes { | ||||
|     position: absolute; | ||||
|     @include S(left, 9px); | ||||
|     @include S(top, 120px); | ||||
|     @include PlainText; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: flex-start; | ||||
|     justify-content: flex-start; | ||||
| 
 | ||||
|     > .shape { | ||||
|         position: relative; | ||||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         align-items: center; | ||||
|         justify-content: center; | ||||
|         @include S(margin-bottom, 5px); | ||||
| 
 | ||||
|         &.unpinable { | ||||
|             > canvas { | ||||
|                 cursor: pointer; | ||||
|                 pointer-events: all; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         > canvas { | ||||
|             @include S(width, 25px); | ||||
|             @include S(height, 25px); | ||||
|         } | ||||
| 
 | ||||
|         > .amountLabel { | ||||
|             @include S(margin-left, 5px); | ||||
|             @include SuperSmallText; | ||||
|             font-weight: bold; | ||||
|             display: inline-flex; | ||||
|             align-items: center; | ||||
|             flex-direction: row; | ||||
|         } | ||||
| 
 | ||||
|         &.marked .amountLabel { | ||||
|             &::after { | ||||
|                 content: " "; | ||||
|                 position: absolute; | ||||
|                 display: inline-block; | ||||
|                 @include S(width, 9px); | ||||
|                 @include S(height, 9px); | ||||
|                 opacity: 0.8; | ||||
|                 @include S(top, -4px); | ||||
|                 @include S(left, -4px); | ||||
|                 background: uiResource("icons/current_goal_marker.png") center center / contain no-repeat; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -100,6 +100,50 @@ | ||||
|                     flex-direction: column; | ||||
|                     align-items: center; | ||||
| 
 | ||||
|                     button.pin { | ||||
|                         @include S(width, 12px); | ||||
|                         @include S(height, 12px); | ||||
|                         background: uiResource("icons/pin.png") center center / 95% no-repeat; | ||||
|                         position: absolute; | ||||
|                         @include S(top, -2px); | ||||
|                         @include S(right, -2px); | ||||
|                         opacity: 0.6; | ||||
|                         cursor: pointer; | ||||
|                         pointer-events: all; | ||||
|                         @include IncreasedClickArea(5px); | ||||
|                         transition: opacity 0.12s ease-in-out; | ||||
|                         &:hover { | ||||
|                             opacity: 0.7; | ||||
|                         } | ||||
| 
 | ||||
|                         &.alreadyPinned { | ||||
|                             opacity: 0.1 !important; | ||||
|                             pointer-events: none; | ||||
|                             cursor: default; | ||||
|                         } | ||||
| 
 | ||||
|                         &.pinned { | ||||
|                             opacity: 0.1; | ||||
|                             pointer-events: none; | ||||
|                             cursor: default; | ||||
|                             @include InlineAnimation(0.3s ease-in-out) { | ||||
|                                 0% { | ||||
|                                     opacity: 1; | ||||
|                                     transform: scale(0.8); | ||||
|                                 } | ||||
| 
 | ||||
|                                 30% { | ||||
|                                     opacity: 1; | ||||
|                                     transform: scale(1.2); | ||||
|                                 } | ||||
| 
 | ||||
|                                 100% { | ||||
|                                     transform: scale(1); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     canvas { | ||||
|                         @include S(width, 40px); | ||||
|                         @include S(height, 40px); | ||||
|  | ||||
| @ -31,16 +31,16 @@ | ||||
| @import "ingame_hud/unlock_notification"; | ||||
| @import "ingame_hud/shop"; | ||||
| @import "ingame_hud/game_menu"; | ||||
| @import "ingame_hud/blur_overlay"; | ||||
| @import "ingame_hud/dialogs"; | ||||
| @import "ingame_hud/mass_selector"; | ||||
| @import "ingame_hud/vignette_overlay"; | ||||
| @import "ingame_hud/statistics"; | ||||
| @import "ingame_hud/pinned_shapes"; | ||||
| 
 | ||||
| // Z-Index | ||||
| $elements: ingame_Canvas, ingame_VignetteOverlay, ingame_HUD_building_placer, ingame_HUD_buildings_toolbar, | ||||
|     ingame_HUD_GameMenu, ingame_HUD_KeybindingOverlay, ingame_HUD_Shop, ingame_HUD_Statistics, | ||||
|     ingame_HUD_BetaOverlay, ingame_HUD_MassSelector, ingame_HUD_UnlockNotification; | ||||
| $elements: ingame_Canvas, ingame_VignetteOverlay, ingame_HUD_building_placer, ingame_HUD_PinnedShapes, | ||||
|     ingame_HUD_buildings_toolbar, ingame_HUD_GameMenu, ingame_HUD_KeybindingOverlay, ingame_HUD_Shop, | ||||
|     ingame_HUD_Statistics, ingame_HUD_BetaOverlay, ingame_HUD_MassSelector, ingame_HUD_UnlockNotification; | ||||
| 
 | ||||
| $zindex: 100; | ||||
| 
 | ||||
| @ -56,7 +56,17 @@ body.uiHidden { | ||||
|     #ingame_HUD_buildings_toolbar, | ||||
|     #ingame_HUD_building_placer, | ||||
|     #ingame_HUD_GameMenu, | ||||
|     #ingame_HUD_MassSelector { | ||||
|     #ingame_HUD_MassSelector, | ||||
|     #ingame_HUD_PinnedShapes { | ||||
|         display: none !important; | ||||
|     } | ||||
| } | ||||
| body.ingameDialogOpen { | ||||
|     #ingame_Canvas, | ||||
|     #ingame_HUD_GameMenu, | ||||
|     #ingame_HUD_KeybindingOverlay, | ||||
|     #ingame_HUD_buildings_toolbar, | ||||
|     #ingame_HUD_PinnedShapes { | ||||
|         filter: blur(5px); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -75,6 +75,14 @@ export class HubGoals extends BasicSerializableObject { | ||||
|     getShapesStored(definition) { | ||||
|         return this.storedShapes[definition.getHash()] || 0; | ||||
|     } | ||||
|     /** | ||||
|      * Returns how much of the current shape is stored | ||||
|      * @param {string} key | ||||
|      * @returns {number} | ||||
|      */ | ||||
|     getShapesStoredByKey(key) { | ||||
|         return this.storedShapes[key] || 0; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns how much of the current goal was already delivered | ||||
| @ -158,11 +166,12 @@ export class HubGoals extends BasicSerializableObject { | ||||
|     onGoalCompleted() { | ||||
|         const reward = this.currentGoal.reward; | ||||
|         this.gainedRewards[reward] = (this.gainedRewards[reward] || 0) + 1; | ||||
|         this.root.signals.storyGoalCompleted.dispatch(this.level, reward); | ||||
| 
 | ||||
|         this.root.app.gameAnalytics.handleLevelCompleted(this.level); | ||||
|         ++this.level; | ||||
|         this.createNextGoal(); | ||||
| 
 | ||||
|         this.root.signals.storyGoalCompleted.dispatch(this.level - 1, reward); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -17,6 +17,8 @@ import { HUDMassSelector } from "./parts/mass_selector"; | ||||
| import { HUDVignetteOverlay } from "./parts/vignette_overlay"; | ||||
| import { HUDStatistics } from "./parts/statistics"; | ||||
| import { MetaBuilding } from "../meta_building"; | ||||
| import { HUDPinnedShapes } from "./parts/pinned_shapes"; | ||||
| import { ShapeDefinition } from "../shape_definition"; | ||||
| 
 | ||||
| export class GameHUD { | ||||
|     /** | ||||
| @ -47,11 +49,14 @@ export class GameHUD { | ||||
| 
 | ||||
|             vignetteOverlay: new HUDVignetteOverlay(this.root), | ||||
| 
 | ||||
|             pinnedShapes: new HUDPinnedShapes(this.root), | ||||
| 
 | ||||
|             // betaOverlay: new HUDBetaOverlay(this.root),
 | ||||
|         }; | ||||
| 
 | ||||
|         this.signals = { | ||||
|             selectedPlacementBuildingChanged: /** @type {TypedSignal<[MetaBuilding|null]>} */ (new Signal()), | ||||
|             shapePinRequested: /** @type {TypedSignal<[ShapeDefinition]>} */ (new Signal()), | ||||
|         }; | ||||
| 
 | ||||
|         if (!IS_MOBILE) { | ||||
|  | ||||
							
								
								
									
										128
									
								
								src/js/game/hud/parts/pinned_shapes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/js/game/hud/parts/pinned_shapes.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,128 @@ | ||||
| import { BaseHUDPart } from "../base_hud_part"; | ||||
| import { makeDiv, removeAllChildren, formatBigNumber } from "../../../core/utils"; | ||||
| import { ClickDetector } from "../../../core/click_detector"; | ||||
| import { ShapeDefinition } from "../../shape_definition"; | ||||
| 
 | ||||
| export class HUDPinnedShapes extends BaseHUDPart { | ||||
|     createElements(parent) { | ||||
|         this.element = makeDiv(parent, "ingame_HUD_PinnedShapes", []); | ||||
|     } | ||||
| 
 | ||||
|     initialize() { | ||||
|         this.pinnedShapes = []; | ||||
| 
 | ||||
|         /** @type {Array<{key: string, amountLabel: HTMLElement, lastRenderedValue: number, element: HTMLElement, detector?: ClickDetector}>} */ | ||||
|         this.handles = []; | ||||
|         this.rerenderFull(); | ||||
| 
 | ||||
|         this.root.signals.storyGoalCompleted.add(this.rerenderFull, this); | ||||
|         this.root.hud.signals.shapePinRequested.add(this.pinNewShape, this); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns whether a given shape is pinned | ||||
|      * @param {string} key | ||||
|      */ | ||||
|     isShapePinned(key) { | ||||
|         if (!this.pinnedShapes) { | ||||
|             return false; | ||||
|         } | ||||
|         return ( | ||||
|             this.pinnedShapes.indexOf(key) >= 0 || key === this.root.hubGoals.currentGoal.definition.getHash() | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     rerenderFull() { | ||||
|         const currentGoal = this.root.hubGoals.currentGoal.definition.getHash(); | ||||
| 
 | ||||
|         // First, remove old ones
 | ||||
|         for (let i = 0; i < this.handles.length; ++i) { | ||||
|             this.handles[i].element.remove(); | ||||
|             const detector = this.handles[i].detector; | ||||
|             if (detector) { | ||||
|                 detector.cleanup(); | ||||
|             } | ||||
|         } | ||||
|         this.handles = []; | ||||
| 
 | ||||
|         this.internalPinShape(currentGoal, false); | ||||
| 
 | ||||
|         for (let i = 0; i < this.pinnedShapes.length; ++i) { | ||||
|             const key = this.pinnedShapes[i]; | ||||
|             if (key !== currentGoal) { | ||||
|                 this.internalPinShape(key); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Pins a shape | ||||
|      * @param {string} key | ||||
|      * @param {boolean} canUnpin | ||||
|      */ | ||||
|     internalPinShape(key, canUnpin = true) { | ||||
|         const definition = this.root.shapeDefinitionMgr.getShapeFromShortKey(key); | ||||
| 
 | ||||
|         const element = makeDiv(this.element, null, ["shape"]); | ||||
|         const canvas = definition.generateAsCanvas(120); | ||||
|         element.appendChild(canvas); | ||||
| 
 | ||||
|         let detector = null; | ||||
|         if (canUnpin) { | ||||
|             element.classList.add("unpinable"); | ||||
|             detector = new ClickDetector(element, { | ||||
|                 consumeEvents: true, | ||||
|                 preventDefault: true, | ||||
|             }); | ||||
|             detector.click.add(() => this.unpinShape(key)); | ||||
|         } else { | ||||
|             element.classList.add("marked"); | ||||
|         } | ||||
| 
 | ||||
|         const amountLabel = makeDiv(element, null, ["amountLabel"], "123"); | ||||
| 
 | ||||
|         this.handles.push({ | ||||
|             key, | ||||
|             element, | ||||
|             amountLabel, | ||||
|             lastRenderedValue: -1, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     update() { | ||||
|         for (let i = 0; i < this.handles.length; ++i) { | ||||
|             const handle = this.handles[i]; | ||||
| 
 | ||||
|             const currentValue = this.root.hubGoals.getShapesStoredByKey(handle.key); | ||||
|             if (currentValue !== handle.lastRenderedValue) { | ||||
|                 handle.lastRenderedValue = currentValue; | ||||
|                 handle.amountLabel.innerText = formatBigNumber(currentValue); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     unpinShape(key) { | ||||
|         const index = this.pinnedShapes.indexOf(key); | ||||
|         if (index >= 0) { | ||||
|             const key = this.pinnedShapes[index]; | ||||
|             this.pinnedShapes.splice(index, 1); | ||||
|             this.rerenderFull(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param {ShapeDefinition} definition | ||||
|      */ | ||||
|     pinNewShape(definition) { | ||||
|         const key = definition.getHash(); | ||||
|         if (key === this.root.hubGoals.currentGoal.definition.getHash()) { | ||||
|             // Can not pin current goal
 | ||||
|             return; | ||||
|         } | ||||
|         if (this.pinnedShapes.indexOf(key) < 0) { | ||||
|             // Pin
 | ||||
|             this.pinnedShapes.push(key); | ||||
|             this.rerenderFull(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -6,6 +6,7 @@ import { DynamicDomAttach } from "../dynamic_dom_attach"; | ||||
| import { InputReceiver } from "../../../core/input_receiver"; | ||||
| import { KeyActionMapper } from "../../key_action_mapper"; | ||||
| import { Math_min } from "../../../core/builtins"; | ||||
| import { ClickDetector } from "../../../core/click_detector"; | ||||
| 
 | ||||
| export class HUDShop extends BaseHUDPart { | ||||
|     createElements(parent) { | ||||
| @ -61,7 +62,6 @@ export class HUDShop extends BaseHUDPart { | ||||
|         for (const upgradeId in this.upgradeToElements) { | ||||
|             const handle = this.upgradeToElements[upgradeId]; | ||||
|             const { description, tiers } = UPGRADES[upgradeId]; | ||||
|             // removeAllChildren(handle.elem);
 | ||||
| 
 | ||||
|             const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId); | ||||
|             const tierHandle = tiers[currentTier]; | ||||
| @ -70,9 +70,15 @@ export class HUDShop extends BaseHUDPart { | ||||
|             handle.elemTierLabel.innerText = "Tier " + TIER_LABELS[currentTier]; | ||||
|             handle.elemTierLabel.setAttribute("data-tier", currentTier); | ||||
| 
 | ||||
|             // Cleanup detectors
 | ||||
|             for (let i = 0; i < handle.requireIndexToElement.length; ++i) { | ||||
|                 const requiredHandle = handle.requireIndexToElement[i]; | ||||
|                 requiredHandle.container.remove(); | ||||
|                 requiredHandle.pinDetector.cleanup(); | ||||
|             } | ||||
| 
 | ||||
|             // Cleanup
 | ||||
|             handle.requireIndexToElement = []; | ||||
|             removeAllChildren(handle.elemRequirements); | ||||
| 
 | ||||
|             handle.elem.classList.toggle("maxLevel", !tierHandle); | ||||
| 
 | ||||
| @ -86,14 +92,14 @@ export class HUDShop extends BaseHUDPart { | ||||
|             handle.elemDescription.innerText = description(tierHandle.improvement); | ||||
| 
 | ||||
|             tierHandle.required.forEach(({ shape, amount }) => { | ||||
|                 const requireDiv = makeDiv(handle.elemRequirements, null, ["requirement"]); | ||||
|                 const container = makeDiv(handle.elemRequirements, null, ["requirement"]); | ||||
| 
 | ||||
|                 const shapeDef = this.root.shapeDefinitionMgr.getShapeFromShortKey(shape); | ||||
|                 const shapeCanvas = shapeDef.generateAsCanvas(120); | ||||
|                 shapeCanvas.classList.add(); | ||||
|                 requireDiv.appendChild(shapeCanvas); | ||||
|                 container.appendChild(shapeCanvas); | ||||
| 
 | ||||
|                 const progressContainer = makeDiv(requireDiv, null, ["amount"]); | ||||
|                 const progressContainer = makeDiv(container, null, ["amount"]); | ||||
|                 const progressBar = document.createElement("label"); | ||||
|                 progressBar.classList.add("progressBar"); | ||||
|                 progressContainer.appendChild(progressBar); | ||||
| @ -101,11 +107,31 @@ export class HUDShop extends BaseHUDPart { | ||||
|                 const progressLabel = document.createElement("label"); | ||||
|                 progressContainer.appendChild(progressLabel); | ||||
| 
 | ||||
|                 const pinButton = document.createElement("button"); | ||||
|                 pinButton.classList.add("pin"); | ||||
|                 container.appendChild(pinButton); | ||||
| 
 | ||||
|                 if (this.root.hud.parts.pinnedShapes.isShapePinned(shape)) { | ||||
|                     console.log("ALREADY PINNED:", shape); | ||||
|                     pinButton.classList.add("alreadyPinned"); | ||||
|                 } | ||||
| 
 | ||||
|                 const pinDetector = new ClickDetector(pinButton, { | ||||
|                     consumeEvents: true, | ||||
|                     preventDefault: true, | ||||
|                 }); | ||||
|                 pinDetector.click.add(() => { | ||||
|                     this.root.hud.signals.shapePinRequested.dispatch(shapeDef); | ||||
|                     pinButton.classList.add("pinned"); | ||||
|                 }); | ||||
| 
 | ||||
|                 handle.requireIndexToElement.push({ | ||||
|                     container, | ||||
|                     progressLabel, | ||||
|                     progressBar, | ||||
|                     definition: shapeDef, | ||||
|                     required: amount, | ||||
|                     pinDetector, | ||||
|                 }); | ||||
|             }); | ||||
|         } | ||||
| @ -148,6 +174,17 @@ export class HUDShop extends BaseHUDPart { | ||||
| 
 | ||||
|     cleanup() { | ||||
|         document.body.classList.remove("ingameDialogOpen"); | ||||
| 
 | ||||
|         // Cleanup detectors
 | ||||
|         for (const upgradeId in this.upgradeToElements) { | ||||
|             const handle = this.upgradeToElements[upgradeId]; | ||||
|             for (let i = 0; i < handle.requireIndexToElement.length; ++i) { | ||||
|                 const requiredHandle = handle.requireIndexToElement[i]; | ||||
|                 requiredHandle.container.remove(); | ||||
|                 requiredHandle.pinDetector.cleanup(); | ||||
|             } | ||||
|             handle.requireIndexToElement = []; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     show() { | ||||
| @ -155,7 +192,7 @@ export class HUDShop extends BaseHUDPart { | ||||
|         document.body.classList.add("ingameDialogOpen"); | ||||
|         // this.background.classList.add("visible");
 | ||||
|         this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); | ||||
|         this.update(); | ||||
|         this.rerenderFull(); | ||||
|     } | ||||
| 
 | ||||
|     close() { | ||||
|  | ||||
| @ -153,6 +153,9 @@ export class HUDStatistics extends BaseHUDPart { | ||||
|      * Performs a full rerender, regenerating everything | ||||
|      */ | ||||
|     rerenderFull() { | ||||
|         for (const key in this.activeHandles) { | ||||
|             this.activeHandles[key].detach(); | ||||
|         } | ||||
|         removeAllChildren(this.contentDiv); | ||||
| 
 | ||||
|         // Now, attach new ones
 | ||||
| @ -170,8 +173,6 @@ export class HUDStatistics extends BaseHUDPart { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // const entries = Object.entries(this.root.hubGoals.storedShapes);
 | ||||
| 
 | ||||
|         entries.sort((a, b) => b[1] - a[1]); | ||||
| 
 | ||||
|         let rendered = new Set(); | ||||
| @ -179,7 +180,6 @@ export class HUDStatistics extends BaseHUDPart { | ||||
|         for (let i = 0; i < Math_min(entries.length, 200); ++i) { | ||||
|             const entry = entries[i]; | ||||
|             const shapeKey = entry[0]; | ||||
|             const amount = entry[1]; | ||||
| 
 | ||||
|             let handle = this.activeHandles[shapeKey]; | ||||
|             if (!handle) { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 tobspr
						tobspr