Merge pull request #985 from Pax1601/961-hotkeys-get-stuck

961 hotkeys get stuck
This commit is contained in:
Pax1601 2024-12-11 18:08:14 +01:00 committed by GitHub
commit 76efe84be6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 165 additions and 75 deletions

View File

@ -13,6 +13,7 @@ namespace DataIndex {
country,
name,
unitName,
callsign,
groupName,
state,
task,

View File

@ -19,12 +19,12 @@ public:
~Unit();
/********** Methods **********/
void initialize(json::value json);
virtual void initialize(json::value json) final;
virtual void setDefaults(bool force = false);
void runAILoop();
void update(json::value json, double dt);
virtual void update(json::value json, double dt) final;
void refreshLeaderData(unsigned long long time);
unsigned int getID() { return ID; }
@ -71,6 +71,7 @@ public:
virtual void setCountry(unsigned char newValue) { updateValue(country, newValue, DataIndex::country); }
virtual void setName(string newValue) { updateValue(name, newValue, DataIndex::name); }
virtual void setUnitName(string newValue) { updateValue(unitName, newValue, DataIndex::unitName); }
virtual void setCallsign(string newValue) { updateValue(callsign, newValue, DataIndex::callsign); }
virtual void setGroupName(string newValue) { updateValue(groupName, newValue, DataIndex::groupName); }
virtual void setState(unsigned char newValue) { updateValue(state, newValue, DataIndex::state); };
virtual void setTask(string newValue) { updateValue(task, newValue, DataIndex::task); }
@ -117,6 +118,7 @@ public:
virtual unsigned char getCoalition() { return coalition; }
virtual unsigned char getCountry() { return country; }
virtual string getName() { return name; }
virtual string getCallsign() { return callsign; }
virtual string getUnitName() { return unitName; }
virtual string getGroupName() { return groupName; }
virtual unsigned char getState() { return state; }
@ -167,6 +169,7 @@ protected:
unsigned char country = NULL;
string name = "";
string unitName = "";
string callsign = "";
string groupName = "";
unsigned char state = State::NONE;
string task = "";

View File

@ -27,6 +27,13 @@ Unit::~Unit()
}
void Unit::initialize(json::value json)
{
update(json, 0);
setDefaults();
}
void Unit::update(json::value json, double dt)
{
if (json.has_string_field(L"name"))
setName(to_string(json[L"name"]));
@ -37,23 +44,18 @@ void Unit::initialize(json::value json)
if (json.has_string_field(L"groupName"))
setGroupName(to_string(json[L"groupName"]));
if (json.has_string_field(L"callsign"))
setCallsign(to_string(json[L"callsign"]));
if (json.has_number_field(L"coalitionID"))
setCoalition(json[L"coalitionID"].as_number().to_int32());
//if (json.has_number_field(L"Country"))
// setCountry(json[L"Country"].as_number().to_int32());
/* All units which contain the name "Olympus" are automatically under AI control */
if (getUnitName().find("Olympus") != string::npos)
setControlled(true);
update(json, 0);
setDefaults();
}
void Unit::update(json::value json, double dt)
{
if (json.has_object_field(L"position"))
{
setPosition({
@ -255,6 +257,7 @@ void Unit::getData(stringstream& ss, unsigned long long time)
case DataIndex::country: appendNumeric(ss, datumIndex, country); break;
case DataIndex::name: appendString(ss, datumIndex, name); break;
case DataIndex::unitName: appendString(ss, datumIndex, unitName); break;
case DataIndex::callsign: appendString(ss, datumIndex, callsign); break;
case DataIndex::groupName: appendString(ss, datumIndex, groupName); break;
case DataIndex::state: appendNumeric(ss, datumIndex, state); break;
case DataIndex::task: appendString(ss, datumIndex, task); break;

View File

@ -2267,7 +2267,7 @@
"coalition": "red",
"era": "Mid Cold War",
"category": "groundunit",
"label": "AK-74",
"label": "AK-74 (Type 1)",
"shortLabel": "AK-74",
"filename": "",
"type": "Infantry",
@ -5558,7 +5558,7 @@
"coalition": "red",
"era": "Early Cold War",
"category": "groundunit",
"label": "AK-74",
"label": "AK-74 (Type 4)",
"shortLabel": "AK-74",
"filename": "",
"type": "Infantry",
@ -7722,7 +7722,7 @@
"coalition": "red",
"era": "Early Cold War",
"category": "groundunit",
"label": "Insurgent AK-74",
"label": "AK-74 (Insurgent)",
"shortLabel": "AK-74 (Ins)",
"type": "Infantry",
"enabled": true,
@ -7880,7 +7880,7 @@
"coalition": "red",
"era": "Late Cold War",
"category": "groundunit",
"label": "AK-74",
"label": "AK-74 (Type 2)",
"shortLabel": "AK-74",
"type": "Infantry",
"enabled": true,
@ -7974,7 +7974,7 @@
"coalition": "red",
"era": "Late Cold War",
"category": "groundunit",
"label": "AK-74",
"label": "AK-74 (Type 3)",
"shortLabel": "AK-74",
"type": "Infantry",
"enabled": true,

View File

@ -513,7 +513,7 @@ export namespace ContextActions {
.getUnitsManager()
.addDestination(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
},
{ type: ContextActionType.MOVE, code: "ControlLeft", shiftKey: false }
{ type: ContextActionType.MOVE, code: null}
);
export const DELETE = new ContextAction(

View File

@ -12,7 +12,6 @@ export var BoxSelect = Handler.extend({
this._map = map;
this._container = map.getContainer();
this._pane = map.getPanes().overlayPane;
this._resetStateTimeout = 0;
map.on("unload", this._destroy, this);
},
@ -34,23 +33,12 @@ export var BoxSelect = Handler.extend({
},
_resetState: function () {
this._resetStateTimeout = 0;
this._moved = false;
},
_clearDeferredResetState: function () {
if (this._resetStateTimeout !== 0) {
clearTimeout(this._resetStateTimeout);
this._resetStateTimeout = 0;
}
},
_onMouseDown: function (e: any) {
if (this._map.getSelectionEnabled() && e.button == 0) {
// Clear the deferred resetState if it hasn't executed yet, otherwise it
// will interrupt the interaction and orphan a box element in the container.
this._clearDeferredResetState();
this._resetState();
if (this._moved) this._finish();
DomUtil.disableImageDrag();
this._map.dragging.disable();
@ -66,7 +54,7 @@ export var BoxSelect = Handler.extend({
touchmove: this._onMouseMove,
touchend: this._onMouseUp,
mousemove: this._onMouseMove,
mouseup: this._onMouseUp
mouseup: this._onMouseUp,
},
this
);
@ -76,20 +64,10 @@ export var BoxSelect = Handler.extend({
},
_onMouseUp: function (e: any) {
if (e.button !== 0) {
return;
}
this._finish();
if (!this._moved) {
return;
}
// Postpone to next JS tick so internal click event handling
// still see it as "moved".
window.setTimeout(Util.bind(this._resetState, this), 0);
if (e.button !== 0) return;
window.setTimeout(Util.bind(this._finish, this), 0);
if (!this._moved) return;
var bounds = new LatLngBounds(this._map.containerPointToLatLng(this._startPoint), this._map.containerPointToLatLng(this._point));
this._map.fire("selectionend", { selectionBounds: bounds });
},
@ -141,5 +119,7 @@ export var BoxSelect = Handler.extend({
},
this
);
this._resetState();
},
});

View File

@ -297,6 +297,11 @@ export class Map extends L.Map {
ContextActionChangedEvent.on((contextAction) => this.#updateDestinationPreviewMarkers());
MapOptionsChangedEvent.on((mapOptions) => this.#moveDestinationPreviewMarkers());
window.addEventListener("blur", () => {
this.setSelectionEnabled(false);
this.setPasteEnabled(false);
})
/* Pan interval */
this.#panInterval = window.setInterval(() => {
if (this.#panUp || this.#panDown || this.#panRight || this.#panLeft)
@ -806,6 +811,10 @@ export class Map extends L.Map {
return this.#contextAction;
}
getDefaultContextAction() {
return this.#contextActionSet?.getDefaultContextAction();
}
executeDefaultContextAction(targetUnit: Unit | null, targetPosition: L.LatLng | null, originalEvent?: MouseEvent) {
this.#contextActionSet?.getDefaultContextAction()?.executeCallback(targetUnit, targetPosition, originalEvent);
}

View File

@ -1,8 +1,16 @@
import { DivIcon, Map, Marker, MarkerOptions, LatLngExpression } from "leaflet";
import { SelectionEnabledChangedEvent } from "../../events";
export class CustomMarker extends Marker {
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
SelectionEnabledChangedEvent.on((enabled) => {
const el = this.getElement();
if (el === undefined) return;
if (enabled) el.classList.add("disable-pointer-events");
else el.classList.remove("disable-pointer-events");
});
}
onAdd(map: Map): this {

View File

@ -32,3 +32,7 @@
[data-awacs-mode] .airbase-icon svg * {
fill: transparent !important;
}
#map-container .leaflet-airbase-marker.airbase-disable-pointer-events {
pointer-events: none;
}

View File

@ -234,3 +234,7 @@ path.leaflet-interactive:focus {
.smoke-orange-cursor {
cursor: url("/images/cursors/smoke-orange.svg"), auto !important;
}
#map-container .disable-pointer-events {
pointer-events: none;
}

View File

@ -28,12 +28,14 @@ export class Airbase extends CustomMarker {
this.#img = document.createElement("img");
AppStateChangedEvent.on((state, subState) => {
const element = this.getElement();
if (element) element.style.pointerEvents = state === OlympusState.IDLE || state === OlympusState.AIRBASE ? "all" : "none";
const el = this.getElement();
if (el === undefined) return;
if (state === OlympusState.IDLE || state === OlympusState.AIRBASE) el.classList.remove("airbase-disable-pointer-events");
else el.classList.add("airbase-disable-pointer-events");
});
AirbaseSelectedEvent.on((airbase) => {
this.#selected = (airbase === this);
this.#selected = airbase === this;
if (this.getElement()?.querySelector(".airbase-icon"))
(this.getElement()?.querySelector(".airbase-icon") as HTMLElement).dataset.selected = `${this.#selected}`;
});

View File

@ -20,7 +20,17 @@ export class Shortcut {
document.addEventListener("keyup", (ev: any) => {
if (this.#modal) return;
if (this.#keydown && this.getOptions().code === ev.code) {
console.log(`Keydown up for shortcut ${this.#id}`)
console.log(`Keyup for shortcut ${this.#id}`)
ev.preventDefault();
this.#keydown = false;
this.getOptions().keyUpCallback(ev);
}
});
/* Forced keyup, in case the window loses focus */
document.addEventListener("blur", (ev: any) => {
if (this.#keydown) {
console.log(`Keyup (forced by blur) for shortcut ${this.#id}`)
ev.preventDefault();
this.#keydown = false;
this.getOptions().keyUpCallback(ev);

View File

@ -9,7 +9,7 @@ export function OlStateButton(props: {
buttonColor?: string | null;
checked: boolean;
icon?: IconProp;
tooltip?: string | JSX.Element | JSX.Element[];
tooltip?: string | (() => JSX.Element | JSX.Element[]);
tooltipPosition?: string;
onClick: () => void;
onMouseUp?: () => void;
@ -64,7 +64,7 @@ export function OlStateButton(props: {
{props.children}
</div>
</button>
{hover && props.tooltip && <OlTooltip buttonRef={buttonRef} content={props.tooltip} position={props.tooltipPosition}/>}
{hover && props.tooltip && <OlTooltip buttonRef={buttonRef} content={typeof(props.tooltip) === "string" ? props.tooltip: props.tooltip()} position={props.tooltipPosition}/>}
</>
);
}

View File

@ -72,6 +72,12 @@ export function OlTooltip(props: { content: string | JSX.Element | JSX.Element[]
const button = props.buttonRef.current as HTMLButtonElement;
setPosition(content, button);
const resizeObserver = new ResizeObserver(() => {
setPosition(content, button);
});
resizeObserver.observe(content);
return () => resizeObserver.disconnect(); // clean up
}
});

View File

@ -26,6 +26,7 @@ export function MapContextMenu(props: {}) {
const [latLng, setLatLng] = useState(null as null | LatLng);
const [unit, setUnit] = useState(null as null | Unit);
const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
const [height, setHeight] = useState(0);
var contentRef = useRef(null);
@ -68,6 +69,14 @@ export function MapContextMenu(props: {}) {
content.style.left = `${newXPosition}px`;
content.style.top = `${newYposition}px`;
setHeight(content.clientHeight);
const resizeObserver = new ResizeObserver(() => {
setHeight(content.clientHeight);
});
resizeObserver.observe(content);
return () => resizeObserver.disconnect(); // clean up
}
});

View File

@ -62,6 +62,7 @@ export function SpawnContextMenu(props: {}) {
const [showCost, setShowCost] = useState(false);
const [spawnCoalition, setSpawnCoalition] = useState("blue" as Coalition);
const [showMore, setShowMore] = useState(false);
const [height, setHeight] = useState(0);
useEffect(() => {
if (selectedRole) setBlueprints(getApp()?.getUnitsManager().getDatabase().getByRole(selectedRole));
@ -152,17 +153,24 @@ export function SpawnContextMenu(props: {}) {
content.style.left = `${newXPosition}px`;
content.style.top = `${newYposition}px`;
const resizeObserver = new ResizeObserver(() => {
setHeight(content.clientHeight);
});
resizeObserver.observe(content);
return () => resizeObserver.disconnect(); // clean up
}
});
// TODO fix button being moved if overflowing
return (
<>
<div
ref={contentRef}
data-hidden={appState !== OlympusState.SPAWN_CONTEXT}
className={`
absolute flex w-[395px] data- flex-wrap gap-2 rounded-md
bg-olympus-800
absolute flex w-[395px] data- max-h-[800px] flex-wrap gap-2
overflow-auto rounded-md bg-olympus-800
data-[hidden=true]:hidden
`}
>

View File

@ -152,26 +152,28 @@ export function MapToolBar(props: {}) {
key={"select"}
checked={selectionEnabled}
icon={faObjectGroup}
tooltip={
<div className="flex content-center gap-2">
{shortcutCombination(shortcuts["toggleSelectionEnabled"]?.getOptions())}
<div className="my-auto">Box selection</div>
tooltip={() => (
<div
className="overflow-hidden"
style={{ animationName: "fadeIn", animationDuration: "1s", animationFillMode: "forwards", height: "25px" }}
>
<div className="flex content-center gap-2">
{shortcutCombination(shortcuts["toggleSelectionEnabled"]?.getOptions())}
<div className="my-auto">Box selection</div>
</div>
</div>
}
)}
tooltipPosition="side"
onClick={() => {
getApp().getMap().setSelectionEnabled(!selectionEnabled);
if (!selectionEnabled) {
getApp()
.getMap()
.getContainer()
.addEventListener(
"mouseup",
() => {
getApp().getMap().setSelectionEnabled(false);
},
{ once: true, signal: controller.signal }
);
window.addEventListener(
"mouseup",
() => {
getApp().getMap().setSelectionEnabled(false);
},
{ once: true, signal: controller.signal }
);
} else {
controller.abort();
}
@ -184,12 +186,12 @@ export function MapToolBar(props: {}) {
key={"copy"}
checked={false}
icon={faCopy}
tooltip={
tooltip={() => (
<div className="flex content-center gap-2">
{shortcutCombination(shortcuts["copyUnits"]?.getOptions())}
<div className="my-auto">Copy selected units</div>
</div>
}
)}
tooltipPosition="side"
onClick={() => {
getApp().getUnitsManager().copy(selectedUnits);
@ -199,14 +201,21 @@ export function MapToolBar(props: {}) {
)}
{copiedUnitsData.length > 0 && (
<div className="flex flex-col gap-1">
<OlStateButton key={"paste"} checked={pasteEnabled} icon={faPaste} tooltip={
<OlStateButton
key={"paste"}
checked={pasteEnabled}
icon={faPaste}
tooltip={() => (
<div className="flex content-center gap-2">
{shortcutCombination(shortcuts["pasteUnits"]?.getOptions())}
<div className="my-auto">Paste copied units</div>
</div>
} tooltipPosition="side" onClick={() => {
getApp().getMap().setPasteEnabled(!pasteEnabled)
}} />
)}
tooltipPosition="side"
onClick={() => {
getApp().getMap().setPasteEnabled(!pasteEnabled);
}}
/>
</div>
)}
</>
@ -218,12 +227,18 @@ export function MapToolBar(props: {}) {
key={contextActionIt.getId()}
checked={contextActionIt === contextAction}
icon={contextActionIt.getIcon()}
tooltip={
tooltip={() => (
<div
className="overflow-hidden"
style={{ animationName: "tooltipFadeInHeight", animationDuration: "1s", animationFillMode: "forwards", height: "25px" }}
>
<div className="flex content-center gap-2">
{shortcutCombination(contextActionIt.getOptions())}
<div className="my-auto">{contextActionIt.getLabel()}</div>
</div>
}
<div className={"max-w-[200px] text-wrap"} style={{ animationName: "tooltipFadeInWidth", animationDuration: "1s", animationFillMode: "forwards", width: "1px" }}>{contextActionIt.getDescription()}</div>
</div>
)}
tooltipPosition="side"
buttonColor={CONTEXT_ACTION_COLORS[contextActionIt.getOptions().type ?? 0]}
onClick={() => {

View File

@ -103,4 +103,22 @@ input[type="range"]:focus::-moz-range-thumb {
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
@keyframes tooltipFadeInHeight {
99% {
height: 25px;
}
100% {
height: fit-content;
}
}
@keyframes tooltipFadeInWidth {
99% {
width: 0px;
}
100% {
width: 200px;
}
}

View File

@ -1372,6 +1372,14 @@ export abstract class Unit extends CustomMarker {
#onRightShortClick(e: any) {
console.log(`Right short click on ${this.getUnitName()}`);
window.clearTimeout(this.#rightMouseDownTimeout);
if (
getApp().getState() === OlympusState.UNIT_CONTROL &&
getApp().getMap().getDefaultContextAction() &&
getApp().getMap().getDefaultContextAction()?.getTarget() === ContextActionTarget.POINT
)
getApp().getMap().executeDefaultContextAction(null, this.getPosition(), e.originalEvent);
}
#onRightLongClick(e: any) {

View File

@ -1099,6 +1099,8 @@ function Olympus.setUnitsData(arg, time)
else
table["unitName"] = unit:getName()
end
-- In case of AI units the callSign and the unitName will be the same
table["callsign"] = unit:getName()
table["groupName"] = group:getName()
table["isHuman"] = (unit:getPlayerName() ~= nil)
table["hasTask"] = controller:hasTask()