mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Improve UI for flight properties.
Use the new data from pydcs to improve the properties UI: * Use human readable names * Use appropriate control types * Limit min and max values as appropriate for each property * Show labels Fixes https://github.com/dcs-liberation/dcs_liberation/issues/3090.
This commit is contained in:
parent
a0fdfa11e2
commit
bce6a170b8
@ -212,6 +212,7 @@ BAI/ANTISHIP/DEAD/STRIKE/BARCAP/CAS/OCA/AIR-ASSAULT (main) missions
|
||||
* **[Modding]** Factions can now specify the ship type to be used for cargo shipping. The Handy Wind will be used by default, but WW2 factions can pick something more appropriate.
|
||||
* **[UI]** An error will be displayed when invalid fast-forward options are selected rather than beginning a never ending simulation.
|
||||
* **[UI]** Added cheats for instantly repairing and destroying runways.
|
||||
* **[UI]** Improved usability of the flight properties UI. It now shows human-readable names and uses more appropriate UI elements.
|
||||
|
||||
## Fixes
|
||||
|
||||
@ -221,6 +222,8 @@ BAI/ANTISHIP/DEAD/STRIKE/BARCAP/CAS/OCA/AIR-ASSAULT (main) missions
|
||||
* **[Mission Generation]** Restored previous AI behavior for anti-ship missions. A DCS update caused only a single aircraft in a flight to attack. The full flight will now attack like they used to.
|
||||
* **[Mission Generation]** Fix generation of OCA Runway missions to allow LGBs to be used.
|
||||
* **[Plugins]** Fixed Lua errors in Skynet plugin that would occur whenever one coalition had no IADS nodes.
|
||||
* **[UI]** Fixed deleting waypoints in custom flight plans deleting the wrong waypoint.
|
||||
* **[UI]** Fixed flight properties UI to support F-15E S4+ laser codes.
|
||||
|
||||
# 8.1.0
|
||||
|
||||
|
||||
@ -11,11 +11,11 @@ from typing import Any, ClassVar, Dict, Iterator, Optional, TYPE_CHECKING, Type
|
||||
import yaml
|
||||
from dcs.helicopters import helicopter_map
|
||||
from dcs.planes import plane_map
|
||||
from dcs.unitpropertydescription import UnitPropertyDescription
|
||||
from dcs.unittype import FlyingType
|
||||
from dcs.weapons_data import weapon_ids
|
||||
|
||||
from game.data.units import UnitClass
|
||||
from game.dcs.unitproperty import UnitProperty
|
||||
from game.dcs.unittype import UnitType
|
||||
from game.persistency import user_custom_weapon_injections_dir
|
||||
from game.radio.channels import (
|
||||
@ -329,8 +329,8 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
||||
def channel_name(self, radio_id: int, channel_id: int) -> str:
|
||||
return self.channel_namer.channel_name(radio_id, channel_id)
|
||||
|
||||
def iter_props(self) -> Iterator[UnitProperty[Any]]:
|
||||
return UnitProperty.for_aircraft(self.dcs_unit_type)
|
||||
def iter_props(self) -> Iterator[UnitPropertyDescription]:
|
||||
yield from self.dcs_unit_type.properties.values()
|
||||
|
||||
def capable_of(self, task: FlightType) -> bool:
|
||||
return task in self.task_priorities
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
class MissingPropertyDataError(RuntimeError):
|
||||
...
|
||||
21
qt_ui/windows/mission/flight/payload/propertycheckbox.py
Normal file
21
qt_ui/windows/mission/flight/payload/propertycheckbox.py
Normal file
@ -0,0 +1,21 @@
|
||||
from PySide6.QtWidgets import QCheckBox
|
||||
from dcs.unitpropertydescription import UnitPropertyDescription
|
||||
|
||||
from game.ato import Flight
|
||||
from .missingpropertydataerror import MissingPropertyDataError
|
||||
|
||||
|
||||
class PropertyCheckBox(QCheckBox):
|
||||
def __init__(self, flight: Flight, prop: UnitPropertyDescription) -> None:
|
||||
super().__init__()
|
||||
self.flight = flight
|
||||
self.prop = prop
|
||||
|
||||
if prop.default is None:
|
||||
raise MissingPropertyDataError("default cannot be None")
|
||||
|
||||
self.setChecked(self.flight.props.get(self.prop.identifier, self.prop.default))
|
||||
self.toggled.connect(self.on_toggle)
|
||||
|
||||
def on_toggle(self, checked: bool) -> None:
|
||||
self.flight.props[self.prop.identifier] = checked
|
||||
29
qt_ui/windows/mission/flight/payload/propertycombobox.py
Normal file
29
qt_ui/windows/mission/flight/payload/propertycombobox.py
Normal file
@ -0,0 +1,29 @@
|
||||
from PySide6.QtWidgets import QComboBox
|
||||
from dcs.unitpropertydescription import UnitPropertyDescription
|
||||
|
||||
from game.ato import Flight
|
||||
from .missingpropertydataerror import MissingPropertyDataError
|
||||
|
||||
|
||||
class PropertyComboBox(QComboBox):
|
||||
def __init__(self, flight: Flight, prop: UnitPropertyDescription) -> None:
|
||||
super().__init__()
|
||||
self.flight = flight
|
||||
self.prop = prop
|
||||
|
||||
if prop.values is None:
|
||||
raise MissingPropertyDataError("values cannot be None")
|
||||
if prop.default is None:
|
||||
raise MissingPropertyDataError("default cannot be None")
|
||||
|
||||
current_value = self.flight.props.get(self.prop.identifier, self.prop.default)
|
||||
|
||||
for ident, text in self.prop.values.items():
|
||||
self.addItem(text, ident)
|
||||
if ident == current_value:
|
||||
self.setCurrentText(text)
|
||||
|
||||
self.currentIndexChanged.connect(self.on_selection_changed)
|
||||
|
||||
def on_selection_changed(self, _index: int) -> None:
|
||||
self.flight.props[self.prop.identifier] = self.currentData()
|
||||
@ -1,7 +1,18 @@
|
||||
import logging
|
||||
|
||||
from PySide2.QtWidgets import QGridLayout, QLabel
|
||||
from dcs.unitpropertydescription import UnitPropertyDescription
|
||||
|
||||
from game.ato import Flight
|
||||
from .propertyselector import PropertySelector
|
||||
from .missingpropertydataerror import MissingPropertyDataError
|
||||
from .propertycheckbox import PropertyCheckBox
|
||||
from .propertycombobox import PropertyComboBox
|
||||
from .propertyspinbox import PropertySpinBox
|
||||
|
||||
|
||||
class UnhandledControlTypeError(RuntimeError):
|
||||
def __init__(self, control: str) -> None:
|
||||
super().__init__(f"Unhandled control type {control}")
|
||||
|
||||
|
||||
class PropertyEditor(QGridLayout):
|
||||
@ -10,5 +21,47 @@ class PropertyEditor(QGridLayout):
|
||||
self.flight = flight
|
||||
|
||||
for row, prop in enumerate(flight.unit_type.iter_props()):
|
||||
self.addWidget(QLabel(prop.id), row, 0)
|
||||
self.addWidget(PropertySelector(self.flight, prop), row, 1)
|
||||
if prop.label is None:
|
||||
if prop.control != "label":
|
||||
logging.error(
|
||||
"Found non-label aircraft property with no display name."
|
||||
)
|
||||
continue
|
||||
|
||||
if prop.player_only and not flight.client_count:
|
||||
continue
|
||||
|
||||
try:
|
||||
widget = self.control_for_property(prop)
|
||||
except (MissingPropertyDataError, UnhandledControlTypeError):
|
||||
logging.exception(
|
||||
f"Cannot create property control for property %s of %s",
|
||||
prop.identifier,
|
||||
flight.unit_type,
|
||||
)
|
||||
continue
|
||||
|
||||
label = prop.label
|
||||
if widget is None:
|
||||
label = f"<strong>{label}</label>"
|
||||
self.addWidget(QLabel(label), row, 0)
|
||||
|
||||
# If prop.control is "label", widget will be None. We only need to add the
|
||||
# label, not the control.
|
||||
if widget is not None:
|
||||
self.addWidget(widget, row, 1)
|
||||
|
||||
def control_for_property(self, prop: UnitPropertyDescription) -> QWidget | None:
|
||||
# Valid values are:
|
||||
# "checkbox", "comboList", "groupbox", "label", "slider", "spinbox"
|
||||
match prop.control:
|
||||
case "checkbox":
|
||||
return PropertyCheckBox(self.flight, prop)
|
||||
case "comboList":
|
||||
return PropertyComboBox(self.flight, prop)
|
||||
case "groupbox" | "label":
|
||||
return None
|
||||
case "slider" | "spinbox":
|
||||
return PropertySpinBox(self.flight, prop)
|
||||
case _:
|
||||
raise UnhandledControlTypeError(prop.control)
|
||||
|
||||
28
qt_ui/windows/mission/flight/payload/propertyspinbox.py
Normal file
28
qt_ui/windows/mission/flight/payload/propertyspinbox.py
Normal file
@ -0,0 +1,28 @@
|
||||
from PySide6.QtWidgets import QSpinBox
|
||||
from dcs.unitpropertydescription import UnitPropertyDescription
|
||||
|
||||
from game.ato import Flight
|
||||
from .missingpropertydataerror import MissingPropertyDataError
|
||||
|
||||
|
||||
class PropertySpinBox(QSpinBox):
|
||||
def __init__(self, flight: Flight, prop: UnitPropertyDescription) -> None:
|
||||
super().__init__()
|
||||
self.flight = flight
|
||||
self.prop = prop
|
||||
|
||||
if prop.minimum is None:
|
||||
raise MissingPropertyDataError("minimum cannot be None")
|
||||
if prop.maximum is None:
|
||||
raise MissingPropertyDataError("maximum cannot be None")
|
||||
if prop.default is None:
|
||||
raise MissingPropertyDataError("default cannot be None")
|
||||
|
||||
self.setMinimum(prop.minimum)
|
||||
self.setMaximum(prop.maximum)
|
||||
self.setValue(self.flight.props.get(self.prop.identifier, self.prop.default))
|
||||
|
||||
self.valueChanged.connect(self.on_value_changed)
|
||||
|
||||
def on_value_changed(self, value: int) -> None:
|
||||
self.flight.props[self.prop.identifier] = value
|
||||
@ -32,7 +32,7 @@ pluggy==1.2.0
|
||||
pre-commit==3.3.3
|
||||
pydantic==2.0.3
|
||||
pydantic-settings==2.0.2
|
||||
-e git+https://github.com/dcs-retribution/pydcs@b2617f0fbeacd2e3b6066383ea9fd15a155eacb2#egg=pydcs
|
||||
-e git+https://github.com/dcs-retribution/pydcs@e0c28dd6d344e5d95066ec9b73e19ebbf34679c8#egg=pydcs
|
||||
pyinstaller==5.13.0
|
||||
pyinstaller-hooks-contrib==2023.5
|
||||
pyparsing==3.1.0
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user