mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-03 05:00:39 +00:00
Have radial menu refresh when open (#1437)
## Description: There was a regression where the radial menu would not refresh while open (eg open radial menu, then get enough gold to build something, the radial wouldn't update). Also refactored a bit ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { LitElement } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { PlayerActions, TerraNullius } from "../../../core/game/Game";
|
||||
import { PlayerActions } from "../../../core/game/Game";
|
||||
import { TileRef } from "../../../core/game/GameMap";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
@@ -31,7 +31,6 @@ export class MainRadialMenu extends LitElement implements Layer {
|
||||
private chatIntegration: ChatIntegration;
|
||||
|
||||
private clickedTile: TileRef | null = null;
|
||||
private selectedPlayer: PlayerView | TerraNullius | null = null;
|
||||
|
||||
constructor(
|
||||
private eventBus: EventBus,
|
||||
@@ -71,6 +70,7 @@ export class MainRadialMenu extends LitElement implements Layer {
|
||||
|
||||
init() {
|
||||
this.radialMenu.init();
|
||||
this.radialMenu.setRootMenuItems(rootMenuItems, centerButtonElement);
|
||||
this.eventBus.on(ContextMenuEvent, (event) => {
|
||||
const worldCoords = this.transformHandler.screenToWorldCoordinates(
|
||||
event.x,
|
||||
@@ -83,12 +83,11 @@ export class MainRadialMenu extends LitElement implements Layer {
|
||||
return;
|
||||
}
|
||||
this.clickedTile = this.game.ref(worldCoords.x, worldCoords.y);
|
||||
this.selectedPlayer = this.game.owner(this.clickedTile);
|
||||
this.game
|
||||
.myPlayer()!
|
||||
.actions(this.clickedTile)
|
||||
.then((actions) => {
|
||||
this.handlePlayerActions(
|
||||
this.updatePlayerActions(
|
||||
this.game.myPlayer()!,
|
||||
actions,
|
||||
this.clickedTile!,
|
||||
@@ -99,12 +98,12 @@ export class MainRadialMenu extends LitElement implements Layer {
|
||||
});
|
||||
}
|
||||
|
||||
private async handlePlayerActions(
|
||||
private async updatePlayerActions(
|
||||
myPlayer: PlayerView,
|
||||
actions: PlayerActions,
|
||||
tile: TileRef,
|
||||
screenX: number,
|
||||
screenY: number,
|
||||
screenX: number | null = null,
|
||||
screenY: number | null = null,
|
||||
) {
|
||||
this.buildMenu.playerActions = actions;
|
||||
|
||||
@@ -130,18 +129,27 @@ export class MainRadialMenu extends LitElement implements Layer {
|
||||
eventBus: this.eventBus,
|
||||
};
|
||||
|
||||
this.radialMenu.setRootMenuItems(rootMenuItems, centerButtonElement);
|
||||
this.radialMenu.setParams(params);
|
||||
this.radialMenu.showRadialMenu(screenX, screenY);
|
||||
if (screenX !== null && screenY !== null) {
|
||||
this.radialMenu.showRadialMenu(screenX, screenY);
|
||||
} else {
|
||||
this.radialMenu.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async tick() {
|
||||
if (!this.radialMenu.isMenuVisible() || this.clickedTile === null) return;
|
||||
if (this.selectedPlayer === null) return;
|
||||
const currentPlayer = this.game.owner(this.clickedTile);
|
||||
if (currentPlayer.id() !== this.selectedPlayer.id()) {
|
||||
this.closeMenu();
|
||||
return;
|
||||
if (this.game.ticks() % 5 === 0) {
|
||||
this.game
|
||||
.myPlayer()!
|
||||
.actions(this.clickedTile)
|
||||
.then((actions) => {
|
||||
this.updatePlayerActions(
|
||||
this.game.myPlayer()!,
|
||||
actions,
|
||||
this.clickedTile!,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -437,6 +437,8 @@ export class RadialMenu implements Layer {
|
||||
this.updateCenterButtonState("back");
|
||||
} else {
|
||||
d.data.action?.(this.params);
|
||||
// Force transition state to false to ensure menu hides
|
||||
this.isTransitioning = false;
|
||||
this.hideRadialMenu();
|
||||
}
|
||||
};
|
||||
@@ -479,6 +481,14 @@ export class RadialMenu implements Layer {
|
||||
});
|
||||
}
|
||||
|
||||
private isItemDisabled(item: MenuElement): boolean {
|
||||
return (
|
||||
this.params === null ||
|
||||
this.params.game.inSpawnPhase() ||
|
||||
item.disabled(this.params)
|
||||
);
|
||||
}
|
||||
|
||||
private renderIconsAndText(
|
||||
arcs: d3.Selection<
|
||||
SVGGElement,
|
||||
@@ -496,10 +506,7 @@ export class RadialMenu implements Layer {
|
||||
.each((d) => {
|
||||
const contentId = d.data.id;
|
||||
const content = d3.select(`g[data-id="${contentId}"]`);
|
||||
const disabled =
|
||||
this.params === null ||
|
||||
this.params.game.inSpawnPhase() ||
|
||||
d.data.disabled(this.params);
|
||||
const disabled = this.isItemDisabled(d.data);
|
||||
|
||||
if (d.data.text) {
|
||||
content
|
||||
@@ -557,24 +564,43 @@ export class RadialMenu implements Layer {
|
||||
}
|
||||
|
||||
private updateMenuGroupVisibility() {
|
||||
// Hide all menus except the current and immediate previous one
|
||||
this.updateMenuVisibility("forward");
|
||||
}
|
||||
|
||||
private updateMenuVisibility(direction: "forward" | "backward" = "backward") {
|
||||
this.menuGroups.forEach((menuGroup, level) => {
|
||||
if (level === this.currentLevel) {
|
||||
// Current level - always visible and interactive
|
||||
menuGroup.style("display", "block");
|
||||
} else if (level === this.currentLevel - 1) {
|
||||
menuGroup.style("display", "block");
|
||||
|
||||
menuGroup
|
||||
.transition()
|
||||
.duration(this.config.menuTransitionDuration * 0.8)
|
||||
.style("transform", "scale(0.5)")
|
||||
.style("transform", "scale(1)")
|
||||
.style("opacity", 1);
|
||||
|
||||
// Enable pointer events for current level
|
||||
menuGroup.selectAll("path").style("pointer-events", "auto");
|
||||
} else if (level === this.currentLevel - 1 && this.currentLevel > 0) {
|
||||
// Previous level - visible but scaled down
|
||||
menuGroup.style("display", "block");
|
||||
menuGroup
|
||||
.transition()
|
||||
.duration(this.config.menuTransitionDuration * 0.8)
|
||||
.style(
|
||||
"transform",
|
||||
`scale(${this.currentLevel === 1 ? "0.65" : "0.5"})`,
|
||||
)
|
||||
.style("opacity", 0.8);
|
||||
|
||||
menuGroup.selectAll("path").each(function () {
|
||||
const pathElement = d3.select(this);
|
||||
pathElement.style("pointer-events", "none");
|
||||
});
|
||||
} else {
|
||||
// Disable pointer events for previous level when going forward
|
||||
if (direction === "forward") {
|
||||
menuGroup.selectAll("path").each(function () {
|
||||
const pathElement = d3.select(this);
|
||||
pathElement.style("pointer-events", "none");
|
||||
});
|
||||
}
|
||||
} else if (level !== this.currentLevel + 1) {
|
||||
// Hide all other levels
|
||||
menuGroup
|
||||
.transition()
|
||||
.duration(this.config.menuTransitionDuration * 0.5)
|
||||
@@ -612,7 +638,7 @@ export class RadialMenu implements Layer {
|
||||
|
||||
this.updateMenuLevels();
|
||||
this.clearSelectedItemHoverState();
|
||||
this.updateMenuVisibility();
|
||||
this.updateMenuVisibility("backward");
|
||||
this.animateMenuTransitions();
|
||||
}
|
||||
|
||||
@@ -639,54 +665,10 @@ export class RadialMenu implements Layer {
|
||||
if (selectedPath) {
|
||||
selectedPath.attr("filter", null);
|
||||
selectedPath.attr("stroke-width", "2");
|
||||
|
||||
const item = this.findMenuItem(this.selectedItemId);
|
||||
if (item) {
|
||||
const disabled = this.params === null || item.disabled(this.params);
|
||||
const color = disabled
|
||||
? this.config.disabledColor
|
||||
: (item.color ?? "#333333");
|
||||
const opacity = disabled ? 0.5 : 0.7;
|
||||
selectedPath.attr(
|
||||
"fill",
|
||||
d3.color(color)?.copy({ opacity: opacity })?.toString() ?? color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateMenuVisibility() {
|
||||
this.menuGroups.forEach((menuGroup, level) => {
|
||||
if (level === this.currentLevel) {
|
||||
menuGroup.style("display", "block");
|
||||
menuGroup
|
||||
.transition()
|
||||
.duration(this.config.menuTransitionDuration * 0.8)
|
||||
.style("transform", "scale(1)")
|
||||
.style("opacity", 1);
|
||||
|
||||
menuGroup.selectAll("path").style("pointer-events", "auto");
|
||||
} else if (level === this.currentLevel - 1 && this.currentLevel > 0) {
|
||||
menuGroup.style("display", "block");
|
||||
menuGroup
|
||||
.transition()
|
||||
.duration(this.config.menuTransitionDuration * 0.8)
|
||||
.style(
|
||||
"transform",
|
||||
`scale(${this.currentLevel === 1 ? "0.65" : "0.5"})`,
|
||||
)
|
||||
.style("opacity", 0.8);
|
||||
} else if (level !== this.currentLevel + 1) {
|
||||
menuGroup
|
||||
.transition()
|
||||
.duration(this.config.menuTransitionDuration * 0.5)
|
||||
.style("opacity", 0)
|
||||
.on("end", function () {
|
||||
d3.select(this).style("display", "none");
|
||||
});
|
||||
}
|
||||
});
|
||||
// Use refresh() to update all item appearances consistently
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
private animateMenuTransitions() {
|
||||
@@ -767,10 +749,13 @@ export class RadialMenu implements Layer {
|
||||
}
|
||||
|
||||
public hideRadialMenu() {
|
||||
if (!this.isVisible || this.isTransitioning) {
|
||||
if (!this.isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Force transition state to false to ensure menu hides
|
||||
this.isTransitioning = false;
|
||||
|
||||
this.menuElement.style("display", "none");
|
||||
this.isVisible = false;
|
||||
this.selectedItemId = null;
|
||||
@@ -951,6 +936,49 @@ export class RadialMenu implements Layer {
|
||||
this.renderMenuItems(this.currentMenuItems, this.currentLevel);
|
||||
}
|
||||
|
||||
public refresh() {
|
||||
if (!this.isVisible || !this.params) return;
|
||||
|
||||
// Refresh the disabled state of all menu items
|
||||
this.menuPaths.forEach((path, itemId) => {
|
||||
const item = this.findMenuItem(itemId);
|
||||
if (item) {
|
||||
const disabled = this.isItemDisabled(item);
|
||||
const color = disabled
|
||||
? this.config.disabledColor
|
||||
: (item.color ?? "#333333");
|
||||
const opacity = disabled ? 0.5 : 0.7;
|
||||
|
||||
// Update path appearance
|
||||
path.attr(
|
||||
"fill",
|
||||
d3.color(color)?.copy({ opacity: opacity })?.toString() ?? color,
|
||||
);
|
||||
path.style("opacity", disabled ? 0.5 : 1);
|
||||
path.style("cursor", disabled ? "not-allowed" : "pointer");
|
||||
|
||||
// Update icon/text appearance using the same logic as renderIconsAndText
|
||||
const icon = this.menuIcons.get(itemId);
|
||||
if (icon) {
|
||||
// Update text opacity
|
||||
const textElement = icon.select("text");
|
||||
if (!textElement.empty()) {
|
||||
textElement.style("opacity", disabled ? 0.5 : 1);
|
||||
}
|
||||
|
||||
// Update image opacity
|
||||
const imageElement = icon.select("image");
|
||||
if (!imageElement.empty()) {
|
||||
imageElement.attr("opacity", disabled ? 0.5 : 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Refresh center button state
|
||||
this.updateCenterButtonState(this.centerButtonState);
|
||||
}
|
||||
|
||||
renderLayer(context: CanvasRenderingContext2D) {
|
||||
// No need to render anything on the canvas
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user