mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Make waypoints draggable.
This commit is contained in:
parent
95b107ffad
commit
53cb68f82c
@ -177,8 +177,6 @@ class PackageModel(QAbstractListModel):
|
|||||||
def set_tot(self, tot: datetime.timedelta) -> None:
|
def set_tot(self, tot: datetime.timedelta) -> None:
|
||||||
self.package.time_over_target = tot
|
self.package.time_over_target = tot
|
||||||
self.update_tot()
|
self.update_tot()
|
||||||
# For some reason this is needed to make the UI update quickly.
|
|
||||||
self.layoutChanged.emit()
|
|
||||||
|
|
||||||
def set_asap(self, asap: bool) -> None:
|
def set_asap(self, asap: bool) -> None:
|
||||||
self.package.auto_asap = asap
|
self.package.auto_asap = asap
|
||||||
@ -188,6 +186,8 @@ class PackageModel(QAbstractListModel):
|
|||||||
if self.package.auto_asap:
|
if self.package.auto_asap:
|
||||||
self.package.set_tot_asap()
|
self.package.set_tot_asap()
|
||||||
self.tot_changed.emit()
|
self.tot_changed.emit()
|
||||||
|
# For some reason this is needed to make the UI update quickly.
|
||||||
|
self.layoutChanged.emit()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mission_target(self) -> MissionTarget:
|
def mission_target(self) -> MissionTarget:
|
||||||
@ -291,6 +291,12 @@ class AtoModel(QAbstractListModel):
|
|||||||
"""Returns a model for the package at the given index."""
|
"""Returns a model for the package at the given index."""
|
||||||
return self.package_models.acquire(self.package_at_index(index))
|
return self.package_models.acquire(self.package_at_index(index))
|
||||||
|
|
||||||
|
def find_matching_package_model(self, package: Package) -> Optional[PackageModel]:
|
||||||
|
for model in self.packages:
|
||||||
|
if model.package == package:
|
||||||
|
return model
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def packages(self) -> Iterator[PackageModel]:
|
def packages(self) -> Iterator[PackageModel]:
|
||||||
"""Iterates over all the packages in the ATO."""
|
"""Iterates over all the packages in the ATO."""
|
||||||
@ -376,6 +382,11 @@ class GameModel:
|
|||||||
self.ato_model = AtoModel(self, self.game.blue_ato)
|
self.ato_model = AtoModel(self, self.game.blue_ato)
|
||||||
self.red_ato_model = AtoModel(self, self.game.red_ato)
|
self.red_ato_model = AtoModel(self, self.game.red_ato)
|
||||||
|
|
||||||
|
def ato_model_for(self, player: bool) -> AtoModel:
|
||||||
|
if player:
|
||||||
|
return self.ato_model
|
||||||
|
return self.red_ato_model
|
||||||
|
|
||||||
def set(self, game: Optional[Game]) -> None:
|
def set(self, game: Optional[Game]) -> None:
|
||||||
"""Updates the managed Game object.
|
"""Updates the managed Game object.
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ from gen.ato import AirTaskingOrder
|
|||||||
from gen.flights.flight import Flight, FlightWaypoint, FlightWaypointType
|
from gen.flights.flight import Flight, FlightWaypoint, FlightWaypointType
|
||||||
from gen.flights.flightplan import FlightPlan, PatrollingFlightPlan
|
from gen.flights.flightplan import FlightPlan, PatrollingFlightPlan
|
||||||
from qt_ui.dialogs import Dialog
|
from qt_ui.dialogs import Dialog
|
||||||
from qt_ui.models import GameModel
|
from qt_ui.models import GameModel, AtoModel
|
||||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||||
from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2
|
from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2
|
||||||
from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu
|
from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu
|
||||||
@ -316,20 +316,27 @@ class WaypointJs(QObject):
|
|||||||
altitudeReferenceChanged = Signal()
|
altitudeReferenceChanged = Signal()
|
||||||
nameChanged = Signal()
|
nameChanged = Signal()
|
||||||
timingChanged = Signal()
|
timingChanged = Signal()
|
||||||
|
isTakeoffChanged = Signal()
|
||||||
isDivertChanged = Signal()
|
isDivertChanged = Signal()
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
waypoint: FlightWaypoint,
|
waypoint: FlightWaypoint,
|
||||||
number: int,
|
number: int,
|
||||||
flight_plan: FlightPlan,
|
flight: Flight,
|
||||||
theater: ConflictTheater,
|
theater: ConflictTheater,
|
||||||
|
ato_model: AtoModel,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.waypoint = waypoint
|
self.waypoint = waypoint
|
||||||
self._number = number
|
self._number = number
|
||||||
self.flight_plan = flight_plan
|
self.flight = flight
|
||||||
self.theater = theater
|
self.theater = theater
|
||||||
|
self.ato_model = ato_model
|
||||||
|
|
||||||
|
@property
|
||||||
|
def flight_plan(self) -> FlightPlan:
|
||||||
|
return self.flight.flight_plan
|
||||||
|
|
||||||
@Property(int, notify=numberChanged)
|
@Property(int, notify=numberChanged)
|
||||||
def number(self) -> int:
|
def number(self) -> int:
|
||||||
@ -363,10 +370,26 @@ class WaypointJs(QObject):
|
|||||||
return ""
|
return ""
|
||||||
return f"{prefix} T+{timedelta(seconds=int(time.total_seconds()))}"
|
return f"{prefix} T+{timedelta(seconds=int(time.total_seconds()))}"
|
||||||
|
|
||||||
|
@Property(bool, notify=isTakeoffChanged)
|
||||||
|
def isTakeoff(self) -> bool:
|
||||||
|
return self.waypoint.waypoint_type is FlightWaypointType.TAKEOFF
|
||||||
|
|
||||||
@Property(bool, notify=isDivertChanged)
|
@Property(bool, notify=isDivertChanged)
|
||||||
def isDivert(self) -> bool:
|
def isDivert(self) -> bool:
|
||||||
return self.waypoint.waypoint_type is FlightWaypointType.DIVERT
|
return self.waypoint.waypoint_type is FlightWaypointType.DIVERT
|
||||||
|
|
||||||
|
@Slot(list, result=str)
|
||||||
|
def setPosition(self, position: LeafletLatLon) -> str:
|
||||||
|
point = self.theater.ll_to_point(LatLon(*position))
|
||||||
|
self.waypoint.x = point.x
|
||||||
|
self.waypoint.y = point.y
|
||||||
|
package = self.ato_model.find_matching_package_model(self.flight.package)
|
||||||
|
if package is None:
|
||||||
|
return "Could not find package model containing modified flight"
|
||||||
|
package.update_tot()
|
||||||
|
self.positionChanged.emit()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
class FlightJs(QObject):
|
class FlightJs(QObject):
|
||||||
flightPlanChanged = Signal()
|
flightPlanChanged = Signal()
|
||||||
@ -375,16 +398,26 @@ class FlightJs(QObject):
|
|||||||
commitBoundaryChanged = Signal()
|
commitBoundaryChanged = Signal()
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, flight: Flight, selected: bool, theater: ConflictTheater, faction: Faction
|
self,
|
||||||
|
flight: Flight,
|
||||||
|
selected: bool,
|
||||||
|
theater: ConflictTheater,
|
||||||
|
faction: Faction,
|
||||||
|
ato_model: AtoModel,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
self._selected = selected
|
self._selected = selected
|
||||||
self.theater = theater
|
self.theater = theater
|
||||||
self.faction = faction
|
self.faction = faction
|
||||||
|
self.ato_model = ato_model
|
||||||
self._waypoints = self.make_waypoints()
|
self._waypoints = self.make_waypoints()
|
||||||
self._commit_boundary = self.make_commit_boundary()
|
self._commit_boundary = self.make_commit_boundary()
|
||||||
|
|
||||||
|
def update_waypoints(self) -> None:
|
||||||
|
for waypoint in self._waypoints:
|
||||||
|
waypoint.timingChanged.emit()
|
||||||
|
|
||||||
def make_waypoints(self) -> List[WaypointJs]:
|
def make_waypoints(self) -> List[WaypointJs]:
|
||||||
departure = FlightWaypoint(
|
departure = FlightWaypoint(
|
||||||
FlightWaypointType.TAKEOFF,
|
FlightWaypointType.TAKEOFF,
|
||||||
@ -393,10 +426,12 @@ class FlightJs(QObject):
|
|||||||
meters(0),
|
meters(0),
|
||||||
)
|
)
|
||||||
departure.alt_type = "RADIO"
|
departure.alt_type = "RADIO"
|
||||||
return [
|
waypoints = []
|
||||||
WaypointJs(p, i, self.flight.flight_plan, self.theater)
|
for idx, point in enumerate([departure] + self.flight.points):
|
||||||
for i, p in enumerate([departure] + self.flight.points)
|
waypoint = WaypointJs(point, idx, self.flight, self.theater, self.ato_model)
|
||||||
]
|
waypoint.positionChanged.connect(self.update_waypoints)
|
||||||
|
waypoints.append(waypoint)
|
||||||
|
return waypoints
|
||||||
|
|
||||||
def make_commit_boundary(self) -> Optional[List[LeafletLatLon]]:
|
def make_commit_boundary(self) -> Optional[List[LeafletLatLon]]:
|
||||||
if not isinstance(self.flight.flight_plan, PatrollingFlightPlan):
|
if not isinstance(self.flight.flight_plan, PatrollingFlightPlan):
|
||||||
@ -533,6 +568,7 @@ class MapModel(QObject):
|
|||||||
selected=blue and (p_idx, f_idx) == self._selected_flight_index,
|
selected=blue and (p_idx, f_idx) == self._selected_flight_index,
|
||||||
theater=self.game.theater,
|
theater=self.game.theater,
|
||||||
faction=self.game.faction_for(blue),
|
faction=self.game.faction_for(blue),
|
||||||
|
ato_model=self.game_model.ato_model_for(blue),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return flights
|
return flights
|
||||||
|
|||||||
@ -16,6 +16,7 @@ const Colors = Object.freeze({
|
|||||||
Blue: "#0084ff",
|
Blue: "#0084ff",
|
||||||
Red: "#c85050",
|
Red: "#c85050",
|
||||||
Green: "#80BA80",
|
Green: "#80BA80",
|
||||||
|
Highlight: "#ffff00",
|
||||||
});
|
});
|
||||||
|
|
||||||
function metersToNauticalMiles(meters) {
|
function metersToNauticalMiles(meters) {
|
||||||
@ -401,44 +402,140 @@ function drawFrontLines() {
|
|||||||
|
|
||||||
const SHOW_WAYPOINT_INFO_AT_ZOOM = 9;
|
const SHOW_WAYPOINT_INFO_AT_ZOOM = 9;
|
||||||
|
|
||||||
function drawFlightPlan(flight) {
|
class Waypoint {
|
||||||
const layer = flight.blue ? blueFlightPlansLayer : redFlightPlansLayer;
|
constructor(waypoint, flight) {
|
||||||
const color = flight.blue ? Colors.Blue : Colors.Red;
|
this.waypoint = waypoint;
|
||||||
const highlight = "#ffff00";
|
this.flight = flight;
|
||||||
|
this.marker = this.makeMarker();
|
||||||
|
this.waypoint.positionChanged.connect(() => this.relocate());
|
||||||
|
this.waypoint.timingChanged.connect(() => this.updateDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
position() {
|
||||||
|
return this.waypoint.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldMark() {
|
||||||
// We don't need a marker for the departure waypoint (and it's likely
|
// We don't need a marker for the departure waypoint (and it's likely
|
||||||
// coincident with the landing waypoint, so hard to see). We do want to draw
|
// coincident with the landing waypoint, so hard to see). We do want to draw
|
||||||
// the path from it though.
|
// the path from it though.
|
||||||
const points = [flight.flightPlan[0].position];
|
return !this.waypoint.isTakeoff;
|
||||||
const zoom = map.getZoom();
|
|
||||||
flight.flightPlan.slice(1).forEach((waypoint) => {
|
|
||||||
if (!waypoint.isDivert) {
|
|
||||||
points.push(waypoint.position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flight.selected) {
|
description(dragging) {
|
||||||
L.marker(waypoint.position)
|
const timing = dragging
|
||||||
.bindTooltip(
|
? "Waiting to recompute TOT..."
|
||||||
`${waypoint.number} ${waypoint.name}<br />` +
|
: this.waypoint.timing;
|
||||||
`${waypoint.altitudeFt} ft ${waypoint.altitudeReference}<br />` +
|
return (
|
||||||
`${waypoint.timing}`,
|
`${this.waypoint.number} ${this.waypoint.name}<br />` +
|
||||||
{ permanent: zoom >= SHOW_WAYPOINT_INFO_AT_ZOOM }
|
`${this.waypoint.altitudeFt} ft ${this.waypoint.altitudeReference}<br />` +
|
||||||
)
|
`${timing}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
relocate() {
|
||||||
|
this.marker.setLatLng(this.waypoint.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDescription(dragging) {
|
||||||
|
this.marker.setTooltipContent(this.description(dragging));
|
||||||
|
}
|
||||||
|
|
||||||
|
makeMarker() {
|
||||||
|
const zoom = map.getZoom();
|
||||||
|
return L.marker(this.waypoint.position, { draggable: true })
|
||||||
|
.bindTooltip(this.description(), {
|
||||||
|
permanent: zoom >= SHOW_WAYPOINT_INFO_AT_ZOOM,
|
||||||
|
})
|
||||||
|
.on("dragstart", (e) => {
|
||||||
|
this.updateDescription(true);
|
||||||
|
})
|
||||||
|
.on("drag", (e) => {
|
||||||
|
const marker = e.target;
|
||||||
|
const destination = marker.getLatLng();
|
||||||
|
this.flight.updatePath(this.waypoint.number, destination);
|
||||||
|
})
|
||||||
|
.on("dragend", (e) => {
|
||||||
|
const marker = e.target;
|
||||||
|
const destination = marker.getLatLng();
|
||||||
|
this.waypoint
|
||||||
|
.setPosition([destination.lat, destination.lng])
|
||||||
|
.then((err) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
marker.bindPopup(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
includeInPath() {
|
||||||
|
return !this.waypoint.isDivert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Flight {
|
||||||
|
constructor(flight) {
|
||||||
|
this.flight = flight;
|
||||||
|
this.flightPlan = this.flight.flightPlan.map((p) => new Waypoint(p, this));
|
||||||
|
this.path = null;
|
||||||
|
this.flight.flightPlanChanged.connect(() => this.draw());
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldMark(waypoint) {
|
||||||
|
return this.flight.selected && waypoint.shouldMark();
|
||||||
|
}
|
||||||
|
|
||||||
|
flightPlanLayer() {
|
||||||
|
return this.flight.blue ? blueFlightPlansLayer : redFlightPlansLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePath(idx, position) {
|
||||||
|
const points = this.path.getLatLngs();
|
||||||
|
points[idx] = position;
|
||||||
|
this.path.setLatLngs(points);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawPath(path) {
|
||||||
|
const color = this.flight.blue ? Colors.Blue : Colors.Red;
|
||||||
|
const layer = this.flightPlanLayer();
|
||||||
|
if (this.flight.selected) {
|
||||||
|
this.path = L.polyline(path, { color: Colors.Highlight })
|
||||||
.addTo(layer)
|
.addTo(layer)
|
||||||
.addTo(selectedFlightPlansLayer);
|
.addTo(selectedFlightPlansLayer);
|
||||||
|
} else {
|
||||||
|
this.path = L.polyline(path, { color: color }).addTo(layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawCommitBoundary() {
|
||||||
|
if (this.flight.selected) {
|
||||||
|
if (this.flight.commitBoundary) {
|
||||||
|
L.polyline(this.flight.commitBoundary, {
|
||||||
|
color: Colors.Highlight,
|
||||||
|
weight: 1,
|
||||||
|
})
|
||||||
|
.addTo(this.flightPlanLayer())
|
||||||
|
.addTo(selectedFlightPlansLayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
const path = [];
|
||||||
|
this.flightPlan.forEach((waypoint) => {
|
||||||
|
if (waypoint.includeInPath()) {
|
||||||
|
path.push(waypoint.position());
|
||||||
|
}
|
||||||
|
if (this.shouldMark(waypoint)) {
|
||||||
|
waypoint.marker
|
||||||
|
.addTo(this.flightPlanLayer())
|
||||||
|
.addTo(selectedFlightPlansLayer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (flight.selected) {
|
this.drawPath(path);
|
||||||
L.polyline(points, { color: highlight })
|
this.drawCommitBoundary();
|
||||||
.addTo(layer)
|
|
||||||
.addTo(selectedFlightPlansLayer);
|
|
||||||
if (flight.commitBoundary) {
|
|
||||||
L.polyline(flight.commitBoundary, { color: highlight, weight: 1 }).addTo(
|
|
||||||
layer.addTo(selectedFlightPlansLayer)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
L.polyline(points, { color: color, weight: 1 }).addTo(layer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,12 +552,12 @@ function drawFlightPlans() {
|
|||||||
if (flight.selected) {
|
if (flight.selected) {
|
||||||
selected = flight;
|
selected = flight;
|
||||||
} else {
|
} else {
|
||||||
drawFlightPlan(flight);
|
new Flight(flight).draw();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (selected != null) {
|
if (selected != null) {
|
||||||
drawFlightPlan(selected);
|
new Flight(selected).draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user