From 9b2a84cb740620c54ca412a31aefec028b8fd050 Mon Sep 17 00:00:00 2001 From: Evan Date: Fri, 24 Jan 2025 15:52:42 -0800 Subject: [PATCH] separate optionsmenu from playerinfo menu --- src/client/graphics/GameRenderer.ts | 23 ++-- src/client/graphics/layers/Layer.ts | 8 +- src/client/graphics/layers/OptionsMenu.ts | 119 ++++++++++++++++++ .../graphics/layers/PlayerInfoOverlay.ts | 41 ++---- src/client/index.html | 2 +- 5 files changed, 151 insertions(+), 42 deletions(-) create mode 100644 src/client/graphics/layers/OptionsMenu.ts diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 33c842d8b..4ac998a28 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -21,6 +21,7 @@ import { RefreshGraphicsEvent as RedrawGraphicsEvent } from "../InputHandler"; import { GameView } from "../../core/game/GameView"; import { WinModal } from "./layers/WinModal"; import { SpawnTimer } from "./layers/SpawnTimer"; +import { OptionsMenu } from "./layers/OptionsMenu"; export function createRenderer(canvas: HTMLCanvasElement, game: GameView, eventBus: EventBus, clientID: ClientID): GameRenderer { @@ -77,11 +78,18 @@ export function createRenderer(canvas: HTMLCanvasElement, game: GameView, eventB const winModel = document.querySelector('win-modal') as WinModal - if (!(playerInfo instanceof WinModal)) { + if (!(winModel instanceof WinModal)) { console.error('win modal not found') } winModel.game = game + const optionsMenu = document.querySelector('options-menu') as OptionsMenu + if (!(optionsMenu instanceof OptionsMenu)) { + console.log('options menu not found') + } + optionsMenu.eventBus = eventBus + optionsMenu.game = game + const layers: Layer[] = [ new TerrainLayer(game), @@ -95,7 +103,8 @@ export function createRenderer(canvas: HTMLCanvasElement, game: GameView, eventB leaderboard, controlPanel, playerInfo, - winModel + winModel, + optionsMenu ] return new GameRenderer(game, eventBus, canvas, transformHandler, uiState, layers) @@ -148,16 +157,16 @@ export class GameRenderer { this.transformHandler.handleTransform(this.context) this.layers.forEach(l => { - if (l.shouldTransform()) { - l.renderLayer(this.context) + if (l.shouldTransform?.()) { + l.renderLayer?.(this.context) } }) this.context.restore() this.layers.forEach(l => { - if (!l.shouldTransform()) { - l.renderLayer(this.context) + if (!l.shouldTransform?.()) { + l.renderLayer?.(this.context) } }) @@ -170,7 +179,7 @@ export class GameRenderer { } tick() { - this.layers.forEach(l => l.tick()) + this.layers.forEach(l => l.tick?.()) } resize(width: number, height: number): void { diff --git a/src/client/graphics/layers/Layer.ts b/src/client/graphics/layers/Layer.ts index 55a3201de..08a2d4301 100644 --- a/src/client/graphics/layers/Layer.ts +++ b/src/client/graphics/layers/Layer.ts @@ -1,9 +1,9 @@ import { Game } from "../../../core/game/Game" export interface Layer { - init() - tick() - renderLayer(context: CanvasRenderingContext2D) - shouldTransform(): boolean + init?() + tick?() + renderLayer?(context: CanvasRenderingContext2D) + shouldTransform?(): boolean redraw?(): void } \ No newline at end of file diff --git a/src/client/graphics/layers/OptionsMenu.ts b/src/client/graphics/layers/OptionsMenu.ts new file mode 100644 index 000000000..e24e8b3cc --- /dev/null +++ b/src/client/graphics/layers/OptionsMenu.ts @@ -0,0 +1,119 @@ +import { LitElement, html, css } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { EventBus } from '../../../core/EventBus'; +import { PauseGameEvent } from '../../Transport'; +import { GameType } from '../../../core/game/Game'; +import { GameView } from '../../../core/game/GameView'; +import { Layer } from './Layer'; + +@customElement('options-menu') +export class OptionsMenu extends LitElement implements Layer { + public game: GameView; + public eventBus: EventBus; + + @state() + private showPauseButton: boolean = true; + + @state() + private isPaused: boolean = false; + + private isVisible = false + + private onExitButtonClick() { + window.location.reload(); + } + + private onPauseButtonClick() { + this.isPaused = !this.isPaused; + this.eventBus.emit(new PauseGameEvent(this.isPaused)); + } + + init() { + console.log('init called from OptionsMenu') + this.showPauseButton = this.game.config().gameConfig().gameType == GameType.Singleplayer; + this.isVisible = true + this.requestUpdate() + } + + tick() { + this.isVisible = true + this.requestUpdate() + } + + render() { + if (!this.isVisible) { + return html`` + } + return html` +
+ + +
+ `; + } + + static styles = css` + :host { + position: fixed; /* Make sure it's fixed positioning */ + top: 20px; /* Position it where you want */ + right: 10px; + z-index: 1000; /* Make sure it's higher than canvas */ + pointer-events: auto; /* Ensure it can receive clicks */ + } + .controls { + display: flex; + gap: 8px; + } + + .control-button { + background: rgba(30, 30, 30, 0.7); + border: none; + color: white; + font-size: 24px; + cursor: pointer; + padding: 4px 8px; + border-radius: 4px; + opacity: 0.7; + transition: opacity 0.2s, background-color 0.2s; + backdrop-filter: blur(5px); + } + + .control-button:hover { + opacity: 1; + background: rgba(40, 40, 40, 0.8); + } + + .pause-button { + font-size: 20px; + padding: 4px 10px; + } + + .hidden { + opacity: 0; + visibility: hidden; + pointer-events: none; + } + + @media (max-width: 768px) { + .control-button { + font-size: 16px; + padding: 3px 6px; + } + + .pause-button { + font-size: 14px; + padding: 3px 8px; + } + } + `; +} \ No newline at end of file diff --git a/src/client/graphics/layers/PlayerInfoOverlay.ts b/src/client/graphics/layers/PlayerInfoOverlay.ts index 478842813..51b00043c 100644 --- a/src/client/graphics/layers/PlayerInfoOverlay.ts +++ b/src/client/graphics/layers/PlayerInfoOverlay.ts @@ -8,7 +8,6 @@ import { TransformHandler } from '../TransformHandler'; import { MouseMoveEvent } from '../../InputHandler'; import { GameView, PlayerView, UnitView } from '../../../core/game/GameView'; import { TileRef } from '../../../core/game/GameMap'; -import { PauseGameEvent } from '../../Transport'; import { renderNumber, renderTroops } from '../../Utils'; function euclideanDistWorld(coord: { x: number, y: number }, tileRef: TileRef, game: GameView): number { @@ -50,15 +49,9 @@ export class PlayerInfoOverlay extends LitElement implements Layer { @state() private unit: UnitView | null = null; - @state() - private showPauseButton: boolean = true; - @state() private _isInfoVisible: boolean = false; - @state() - private _isPaused: boolean = false; - private _isActive = false; private lastMouseUpdate = 0 @@ -66,7 +59,6 @@ export class PlayerInfoOverlay extends LitElement implements Layer { init() { this.eventBus.on(MouseMoveEvent, (e: MouseMoveEvent) => this.onMouseEvent(e)); this._isActive = true; - this.showPauseButton = this.game.config().gameConfig().gameType == GameType.Singleplayer; } private onMouseEvent(event: MouseMoveEvent) { @@ -109,15 +101,6 @@ export class PlayerInfoOverlay extends LitElement implements Layer { } } - private onExitButtonClick() { - window.location.reload(); - } - - private onPauseButtonClick() { - this._isPaused = !this._isPaused; - this.eventBus.emit(new PauseGameEvent(this._isPaused)); - } - tick() { this.requestUpdate(); } @@ -201,19 +184,17 @@ export class PlayerInfoOverlay extends LitElement implements Layer { return html``; } return html` -
-
- - -
-
- ${this.player != null ? this.renderPlayerInfo(this.player) : ''} - ${this.unit != null ? this.renderUnitInfo(this.unit) : ''} -
+
+ +
+ ${this.player != null ? this.renderPlayerInfo(this.player) : ''} + ${this.unit != null ? this.renderUnitInfo(this.unit) : ''}
- `; +
+ `; } static styles = css` @@ -223,7 +204,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer { .container { position: fixed; - top: 10px; + top: 70px; right: 10px; z-index: 9999; display: flex; diff --git a/src/client/index.html b/src/client/index.html index a8350270f..f94a8923c 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -125,9 +125,9 @@ + -