mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 10:00:44 +00:00
create basic radial menu
This commit is contained in:
@@ -15,7 +15,7 @@ export class MouseDownEvent implements GameEvent {
|
||||
) { }
|
||||
}
|
||||
|
||||
export class RightClickEvent implements GameEvent {
|
||||
export class ContextMenuEvent implements GameEvent {
|
||||
constructor(
|
||||
public readonly x: number,
|
||||
public readonly y: number,
|
||||
@@ -59,7 +59,7 @@ export class InputHandler {
|
||||
this.canvas.addEventListener("wheel", (e) => this.onScroll(e), {passive: false});
|
||||
this.canvas.addEventListener('pointermove', this.onPointerMove.bind(this));
|
||||
this.canvas.addEventListener('contextmenu', (e: MouseEvent) => {
|
||||
this.onRightClick(e);
|
||||
this.onContextMenu(e);
|
||||
});
|
||||
this.pointers.clear()
|
||||
}
|
||||
@@ -92,9 +92,15 @@ export class InputHandler {
|
||||
}
|
||||
this.pointerDown = false
|
||||
this.pointers.delete(event.pointerId);
|
||||
|
||||
const dist = Math.abs(event.x - this.lastPointerDownX) + Math.abs(event.y - this.lastPointerDownY);
|
||||
if (dist < 10) {
|
||||
this.eventBus.emit(new MouseUpEvent(event.x, event.y))
|
||||
if (event.pointerType == "touch") {
|
||||
event.preventDefault()
|
||||
this.eventBus.emit(new ContextMenuEvent(event.clientX, event.clientY))
|
||||
} else {
|
||||
this.eventBus.emit(new MouseUpEvent(event.x, event.y))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,9 +140,9 @@ export class InputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private onRightClick(event: MouseEvent) {
|
||||
private onContextMenu(event: MouseEvent) {
|
||||
event.preventDefault()
|
||||
this.eventBus.emit(new RightClickEvent(event.clientX, event.clientY))
|
||||
this.eventBus.emit(new ContextMenuEvent(event.clientX, event.clientY))
|
||||
}
|
||||
|
||||
private getPinchDistance(): number {
|
||||
|
||||
@@ -3,12 +3,12 @@ import {NameLayer} from "./layers/NameLayer";
|
||||
import {TerrainLayer} from "./layers/TerrainLayer";
|
||||
import {TerritoryLayer} from "./layers/TerritoryLayer";
|
||||
import {ClientID} from "../../core/Schemas";
|
||||
import {createCanvas} from "./Utils";
|
||||
import {UILayer} from "./layers/UILayer";
|
||||
import {EventBus} from "../../core/EventBus";
|
||||
import {TransformHandler} from "./TransformHandler";
|
||||
import {Layer} from "./layers/Layer";
|
||||
import {EventsDisplay} from "./layers/EventsDisplay";
|
||||
import {RadialMenu} from "./layers/RadialMenu";
|
||||
|
||||
|
||||
export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: EventBus, clientID: ClientID): GameRenderer {
|
||||
@@ -19,7 +19,8 @@ export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus:
|
||||
new TerritoryLayer(game, eventBus),
|
||||
new NameLayer(game, game.config().theme(), transformHandler, clientID),
|
||||
new UILayer(eventBus, game, clientID, transformHandler),
|
||||
new EventsDisplay(eventBus, game, clientID)
|
||||
new EventsDisplay(eventBus, game, clientID),
|
||||
new RadialMenu(eventBus),
|
||||
]
|
||||
|
||||
return new GameRenderer(game, eventBus, canvas, transformHandler, layers)
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import {EventBus} from "../../../core/EventBus";
|
||||
import {ContextMenuEvent} from "../../InputHandler";
|
||||
import {Layer} from "./Layer";
|
||||
import * as d3 from 'd3';
|
||||
|
||||
export class RadialMenu implements Layer {
|
||||
private menuElement: d3.Selection<HTMLDivElement, unknown, null, undefined>;
|
||||
private isVisible: boolean = false;
|
||||
private readonly menuItems = [
|
||||
{name: "sub", color: "#3498db"},
|
||||
{name: "color", color: "#e74c3c"},
|
||||
{name: "shape", color: "#2ecc71"},
|
||||
{name: "font", color: "#f39c12"},
|
||||
{name: "stroke", color: "#9b59b6"}
|
||||
];
|
||||
private readonly menuSize = 300; // Increased size
|
||||
private readonly centerButtonSize = 60;
|
||||
|
||||
constructor(private eventBus: EventBus) { }
|
||||
|
||||
init() {
|
||||
this.eventBus.on(ContextMenuEvent, e => this.onContextMenu(e))
|
||||
this.createMenuElement();
|
||||
}
|
||||
|
||||
private createMenuElement() {
|
||||
this.menuElement = d3.select(document.body)
|
||||
.append('div')
|
||||
.style('position', 'fixed')
|
||||
.style('display', 'none')
|
||||
.style('z-index', '9999')
|
||||
.style('touch-action', 'none'); // Prevent default touch actions
|
||||
|
||||
const svg = this.menuElement.append('svg')
|
||||
.attr('width', this.menuSize)
|
||||
.attr('height', this.menuSize)
|
||||
.append('g')
|
||||
.attr('transform', `translate(${this.menuSize / 2},${this.menuSize / 2})`);
|
||||
|
||||
const pie = d3.pie<any>()
|
||||
.value(() => 1)
|
||||
.padAngle(0.03);
|
||||
|
||||
const arc = d3.arc<any>()
|
||||
.innerRadius(this.centerButtonSize + 10)
|
||||
.outerRadius(this.menuSize / 2 - 10);
|
||||
|
||||
const arcs = svg.selectAll('path')
|
||||
.data(pie(this.menuItems))
|
||||
.enter()
|
||||
.append('g');
|
||||
|
||||
arcs.append('path')
|
||||
.attr('d', arc)
|
||||
.attr('fill', d => d.data.color)
|
||||
.on('click', (event, d) => this.handleMenuSelection(d.data.name))
|
||||
.on('touchstart', (event, d) => {
|
||||
event.preventDefault();
|
||||
this.handleMenuSelection(d.data.name);
|
||||
});
|
||||
|
||||
arcs.append('text')
|
||||
.attr('transform', d => `translate(${arc.centroid(d)})`)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('fill', 'white')
|
||||
.style('font-size', '14px')
|
||||
.text(d => d.data.name);
|
||||
|
||||
// Add center button
|
||||
svg.append('circle')
|
||||
.attr('r', this.centerButtonSize)
|
||||
.attr('fill', '#2c3e50')
|
||||
.on('click', () => this.handleCenterButtonClick())
|
||||
.on('touchstart', (event) => {
|
||||
event.preventDefault();
|
||||
this.handleCenterButtonClick();
|
||||
});
|
||||
|
||||
svg.append('text')
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dy', '0.3em')
|
||||
.attr('fill', 'white')
|
||||
.style('font-size', '14px')
|
||||
.text('Close');
|
||||
}
|
||||
|
||||
tick() {
|
||||
// Update logic if needed
|
||||
}
|
||||
|
||||
render(context: CanvasRenderingContext2D) {
|
||||
// No need to render anything on the canvas
|
||||
}
|
||||
|
||||
shouldTransform(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
private onContextMenu(event: ContextMenuEvent) {
|
||||
console.log('on context menu')
|
||||
|
||||
if (this.isVisible) {
|
||||
this.hideRadialMenu()
|
||||
} else {
|
||||
this.showRadialMenu(event.x, event.y);
|
||||
}
|
||||
}
|
||||
|
||||
private showRadialMenu(x: number, y: number) {
|
||||
this.menuElement
|
||||
.style('left', `${x - this.menuSize / 2}px`)
|
||||
.style('top', `${y - this.menuSize / 2}px`)
|
||||
.style('display', 'block');
|
||||
this.isVisible = true;
|
||||
}
|
||||
|
||||
private hideRadialMenu() {
|
||||
this.menuElement.style('display', 'none');
|
||||
this.isVisible = false;
|
||||
}
|
||||
|
||||
private handleMenuSelection(action: string) {
|
||||
console.log(`Selected action: ${action}`);
|
||||
this.hideRadialMenu();
|
||||
}
|
||||
|
||||
private handleCenterButtonClick() {
|
||||
console.log('Center button clicked');
|
||||
this.hideRadialMenu();
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import {EventBus, GameEvent} from "../../../core/EventBus";
|
||||
import {WinEvent} from "../../../core/execution/WinCheckExecution";
|
||||
import {AllianceRequest, AllianceRequestReplyEvent, Game, Player} from "../../../core/game/Game";
|
||||
import {ClientID} from "../../../core/Schemas";
|
||||
import {RightClickEvent} from "../../InputHandler";
|
||||
import {ContextMenuEvent} from "../../InputHandler";
|
||||
import {Layer} from "./Layer";
|
||||
import {TransformHandler} from "../TransformHandler";
|
||||
import {MessageType} from "./EventsDisplay";
|
||||
@@ -74,7 +74,7 @@ export class UILayer implements Layer {
|
||||
this.createWinModal()
|
||||
this.initRightClickMenu()
|
||||
this.eventBus.on(WinEvent, (e) => this.onWinEvent(e))
|
||||
this.eventBus.on(RightClickEvent, (e) => this.onRightClick(e))
|
||||
this.eventBus.on(ContextMenuEvent, (e) => this.onRightClick(e))
|
||||
}
|
||||
|
||||
initRightClickMenu() {
|
||||
@@ -236,7 +236,7 @@ export class UILayer implements Layer {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
private onRightClick(e: RightClickEvent) {
|
||||
private onRightClick(e: ContextMenuEvent) {
|
||||
const cell = this.transformHandler.screenToWorldCoordinates(e.x, e.y)
|
||||
if (!this.game.isOnMap(cell)) {
|
||||
return
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
</div>
|
||||
|
||||
<div id="app"></div>
|
||||
<div id="radialMenu" class="radial-menu"></div>
|
||||
|
||||
<style>
|
||||
body {
|
||||
|
||||
@@ -298,4 +298,28 @@ h3 {
|
||||
display: block;
|
||||
margin: 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Radial Menu */
|
||||
.radial-menu {
|
||||
position: absolute;
|
||||
display: none;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -10,10 +10,10 @@ export const devConfig = new class extends DefaultConfig {
|
||||
return 80
|
||||
}
|
||||
gameCreationRate(): number {
|
||||
return 10 * 1000
|
||||
return 2 * 1000
|
||||
}
|
||||
lobbyLifetime(): number {
|
||||
return 10 * 1000
|
||||
return 2 * 1000
|
||||
}
|
||||
turnIntervalMs(): number {
|
||||
return 100
|
||||
|
||||
Reference in New Issue
Block a user