Allow changing time, date & weather

Resolves #103
This commit is contained in:
Raffson 2024-06-30 18:51:34 +02:00
parent 06960db5e5
commit 824312e19d
No known key found for this signature in database
GPG Key ID: B0402B2C9B764D99
12 changed files with 759 additions and 272 deletions

View File

@ -19,6 +19,7 @@
* **[UI]** Zoom level retained when switching campaigns
* **[UX]** Allow changing squadrons in flight's edit dialog
* **[Cheats]** Sink/Resurrect carriers instead of showing an error during cheat-capture (use AWCD-cheat to add squadrons upon resurrection)
* **[UI/UX]** Allow changing conditions such as Time, Date & Weather
## Fixes
* **[UI/UX]** A-10A flights can be edited again

View File

@ -0,0 +1,85 @@
from copy import deepcopy
from datetime import datetime, timedelta
from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QPushButton
from game.sim import GameUpdateEvents
from game.weather.clouds import Clouds
from qt_ui.widgets.conditions.QTimeAdjustmentWidget import QTimeAdjustmentWidget
from qt_ui.widgets.conditions.QTimeTurnWidget import QTimeTurnWidget
from qt_ui.widgets.conditions.QWeatherAdjustmentWidget import QWeatherAdjustmentWidget
from qt_ui.widgets.conditions.QWeatherWidget import QWeatherWidget
class QConditionsDialog(QDialog):
def __init__(self, time_turn: QTimeTurnWidget, weather: QWeatherWidget):
super().__init__()
self.time_turn = time_turn
self.weather = weather
self.init_ui()
def init_ui(self):
self.setWindowTitle("Time & Weather Conditions")
self.setMinimumSize(360, 380)
vbox = QVBoxLayout()
self.time_adjuster = QTimeAdjustmentWidget(self.time_turn)
vbox.addWidget(self.time_adjuster, 1)
self.weather_adjuster = QWeatherAdjustmentWidget(self.weather)
vbox.addWidget(self.weather_adjuster, 8)
hbox = QHBoxLayout()
reject_btn = QPushButton("REJECT")
reject_btn.setProperty("style", "btn-danger")
reject_btn.clicked.connect(self.close)
hbox.addWidget(reject_btn)
accept_btn = QPushButton("ACCEPT")
accept_btn.setProperty("style", "btn-success")
accept_btn.clicked.connect(self.apply_conditions)
hbox.addWidget(accept_btn)
vbox.addLayout(hbox, 1)
self.setLayout(vbox)
def apply_conditions(self) -> None:
qdt: datetime = self.time_adjuster.datetime_edit.dateTime().toPython()
sim = self.time_turn.sim_controller
current_time = sim.current_time_in_sim_if_game_loaded
if current_time:
current_time = deepcopy(current_time)
sim.game_loop.sim.time = qdt
game = sim.game_loop.game
game.date = qdt.date() - timedelta(days=game.turn // 4)
game.conditions.start_time = qdt
self.time_turn.set_current_turn(game.turn, game.conditions)
# TODO: create new weather object
new_weather_type = self.weather_adjuster.type_selector.currentData()
new_weather = new_weather_type(
seasonal_conditions=game.theater.seasonal_conditions,
day=qdt.date(),
time_of_day=game.current_turn_time_of_day,
)
# self.weather.conditions.weather = WeatherType()
preset = self.weather_adjuster.preset_selector.currentData()
new_weather.clouds = Clouds(
base=self.weather_adjuster.cloud_base.base.value(),
density=self.weather_adjuster.cloud_density.density.value(),
thickness=self.weather_adjuster.cloud_thickness.thickness.value(),
precipitation=self.weather_adjuster.precipitation.selector.currentData(),
preset=preset,
)
self.weather.conditions.weather = new_weather
self.weather.update_forecast()
if game.turn > 0 and current_time != qdt:
events = GameUpdateEvents()
game.initialize_turn(events, for_blue=True, for_red=True)
sim.sim_update.emit(events)
self.accept()

View File

@ -1,282 +1,15 @@
from datetime import datetime
from PySide6.QtGui import QPixmap
from PySide6 import QtCore, QtGui
from PySide6.QtGui import QCursor
from PySide6.QtWidgets import (
QFrame,
QGridLayout,
QGroupBox,
QHBoxLayout,
QLabel,
QVBoxLayout,
)
from dcs.weather import CloudPreset, Weather as PydcsWeather
import qt_ui.uiconstants as CONST
from game.sim.gameupdateevents import GameUpdateEvents
from game.timeofday import TimeOfDay
from game.utils import mps
from game.weather.conditions import Conditions
from qt_ui.simcontroller import SimController
class QTimeTurnWidget(QGroupBox):
"""
UI Component to display current turn and time info
"""
def __init__(self, sim_controller: SimController) -> None:
super(QTimeTurnWidget, self).__init__("Turn")
self.sim_controller = sim_controller
self.setStyleSheet(
"padding: 0px; margin-left: 5px; margin-right: 0px; margin-top: 1ex; margin-bottom: 5px; border-right: 0px"
)
self.icons = {
TimeOfDay.Dawn: CONST.ICONS["Dawn"],
TimeOfDay.Day: CONST.ICONS["Day"],
TimeOfDay.Dusk: CONST.ICONS["Dusk"],
TimeOfDay.Night: CONST.ICONS["Night"],
}
# self.setProperty('style', 'conditions__widget--turn')
self.layout = QHBoxLayout()
self.setLayout(self.layout)
self.daytime_icon = QLabel()
self.daytime_icon.setPixmap(self.icons[TimeOfDay.Dawn])
self.layout.addWidget(self.daytime_icon)
self.time_column = QVBoxLayout()
self.layout.addLayout(self.time_column)
self.date_display = QLabel()
self.time_column.addWidget(self.date_display)
self.time_display = QLabel()
self.time_column.addWidget(self.time_display)
sim_controller.sim_update.connect(self.on_sim_update)
def on_sim_update(self, _events: GameUpdateEvents) -> None:
time = self.sim_controller.current_time_in_sim_if_game_loaded
if time is None:
self.date_display.setText("")
self.time_display.setText("")
else:
self.set_date_and_time(time)
def set_current_turn(self, turn: int, conditions: Conditions) -> None:
"""Sets the turn information display.
:arg turn Current turn number.
:arg conditions Current time and weather conditions.
"""
self.daytime_icon.setPixmap(self.icons[conditions.time_of_day])
self.set_date_and_time(conditions.start_time)
self.setTitle(f"Turn {turn}")
def set_date_and_time(self, time: datetime) -> None:
self.date_display.setText(time.strftime("%d %b %Y"))
self.time_display.setText(time.strftime("%H:%M:%S Local"))
class QWeatherWidget(QGroupBox):
"""
UI Component to display current weather forecast
"""
turn = None
conditions = None
def __init__(self):
super(QWeatherWidget, self).__init__("")
self.setProperty("style", "QWeatherWidget")
self.icons = {
TimeOfDay.Dawn: CONST.ICONS["Dawn"],
TimeOfDay.Day: CONST.ICONS["Day"],
TimeOfDay.Dusk: CONST.ICONS["Dusk"],
TimeOfDay.Night: CONST.ICONS["Night"],
}
self.layout = QHBoxLayout()
self.setLayout(self.layout)
self.makeWeatherIcon()
self.makeCloudRainFogWidget()
self.makeWindsWidget()
def makeWeatherIcon(self):
"""Makes the Weather Icon Widget"""
self.weather_icon = QLabel()
self.weather_icon.setPixmap(self.icons[TimeOfDay.Dawn])
self.layout.addWidget(self.weather_icon)
def makeCloudRainFogWidget(self):
"""Makes the Cloud, Rain, Fog Widget"""
self.textLayout = QVBoxLayout()
self.layout.addLayout(self.textLayout)
self.forecastClouds = self.makeLabel()
self.textLayout.addWidget(self.forecastClouds)
self.forecastRain = self.makeLabel()
self.textLayout.addWidget(self.forecastRain)
self.forecastFog = self.makeLabel()
self.textLayout.addWidget(self.forecastFog)
def makeWindsWidget(self):
"""Factory for the winds widget."""
windsLayout = QGridLayout()
self.layout.addLayout(windsLayout)
windsLayout.addWidget(self.makeIcon(CONST.ICONS["Weather_winds"]), 0, 0, 3, 1)
windsLayout.addWidget(self.makeLabel("At GL"), 0, 1)
windsLayout.addWidget(self.makeLabel("At FL08"), 1, 1)
windsLayout.addWidget(self.makeLabel("At FL26"), 2, 1)
self.windGLSpeedLabel = self.makeLabel("0kts")
self.windGLDirLabel = self.makeLabel("")
windsLayout.addWidget(self.windGLSpeedLabel, 0, 2)
windsLayout.addWidget(self.windGLDirLabel, 0, 3)
self.windFL08SpeedLabel = self.makeLabel("0kts")
self.windFL08DirLabel = self.makeLabel("")
windsLayout.addWidget(self.windFL08SpeedLabel, 1, 2)
windsLayout.addWidget(self.windFL08DirLabel, 1, 3)
self.windFL26SpeedLabel = self.makeLabel("0kts")
self.windFL26DirLabel = self.makeLabel("")
windsLayout.addWidget(self.windFL26SpeedLabel, 2, 2)
windsLayout.addWidget(self.windFL26DirLabel, 2, 3)
def makeLabel(self, text: str = "") -> QLabel:
"""Shorthand to generate a QLabel with widget standard style
:arg pixmap QPixmap for the icon.
"""
label = QLabel(text)
label.setProperty("style", "text-sm")
return label
def makeIcon(self, pixmap: QPixmap) -> QLabel:
"""Shorthand to generate a QIcon with pixmap.
:arg pixmap QPixmap for the icon.
"""
icon = QLabel()
icon.setPixmap(pixmap)
return icon
def setCurrentTurn(self, turn: int, conditions: Conditions) -> None:
"""Sets the turn information display.
:arg turn Current turn number.
:arg conditions Current time and weather conditions.
"""
self.turn = turn
self.conditions = conditions
self.update_forecast()
self.updateWinds()
def updateWinds(self):
"""Updates the UI with the current conditions wind info."""
windGlSpeed = mps(self.conditions.weather.wind.at_0m.speed or 0)
windGlDir = str(self.conditions.weather.wind.at_0m.direction or 0).rjust(3, "0")
self.windGLSpeedLabel.setText(f"{int(windGlSpeed.knots)}kts")
self.windGLDirLabel.setText(f"{windGlDir}º")
windFL08Speed = mps(self.conditions.weather.wind.at_2000m.speed or 0)
windFL08Dir = str(self.conditions.weather.wind.at_2000m.direction or 0).rjust(
3, "0"
)
self.windFL08SpeedLabel.setText(f"{int(windFL08Speed.knots)}kts")
self.windFL08DirLabel.setText(f"{windFL08Dir}º")
windFL26Speed = mps(self.conditions.weather.wind.at_8000m.speed or 0)
windFL26Dir = str(self.conditions.weather.wind.at_8000m.direction or 0).rjust(
3, "0"
)
self.windFL26SpeedLabel.setText(f"{int(windFL26Speed.knots)}kts")
self.windFL26DirLabel.setText(f"{windFL26Dir}º")
def update_forecast_from_preset(self, preset: CloudPreset) -> None:
self.forecastFog.setText("No fog")
if "Rain" in preset.name:
self.forecastRain.setText("Rain")
self.update_forecast_icons("rain")
else:
self.forecastRain.setText("No rain")
self.update_forecast_icons("partly-cloudy")
# We get a description like the following for the cloud preset.
#
# 09 ##Two Layer Broken/Scattered \nMETAR:BKN 7.5/10 SCT 20/22 FEW41
#
# The second line is probably interesting but doesn't fit into the widget
# currently, so for now just extract the first line.
self.forecastClouds.setText(preset.description.splitlines()[0].split("##")[1])
def update_forecast(self):
"""Updates the Forecast Text and icon with the current conditions wind info."""
if (
self.conditions.weather.clouds
and self.conditions.weather.clouds.preset is not None
):
self.update_forecast_from_preset(self.conditions.weather.clouds.preset)
return
if self.conditions.weather.clouds is None:
cloud_density = 0
precipitation = None
else:
cloud_density = self.conditions.weather.clouds.density
precipitation = self.conditions.weather.clouds.precipitation
if not cloud_density:
self.forecastClouds.setText("Clear")
weather_type = "clear"
elif cloud_density < 3:
self.forecastClouds.setText("Partly Cloudy")
weather_type = "partly-cloudy"
elif cloud_density < 5:
self.forecastClouds.setText("Mostly Cloudy")
weather_type = "partly-cloudy"
else:
self.forecastClouds.setText("Totally Cloudy")
weather_type = "partly-cloudy"
if precipitation == PydcsWeather.Preceptions.Rain:
self.forecastRain.setText("Rain")
weather_type = "rain"
elif precipitation == PydcsWeather.Preceptions.Thunderstorm:
self.forecastRain.setText("Thunderstorm")
weather_type = "thunderstorm"
else:
self.forecastRain.setText("No rain")
if not self.conditions.weather.fog is not None:
self.forecastFog.setText("No fog")
else:
visibility = round(self.conditions.weather.fog.visibility.nautical_miles, 1)
self.forecastFog.setText(f"Fog vis: {visibility}nm")
if cloud_density > 1:
weather_type = "cloudy-fog"
else:
weather_type = "fog"
self.update_forecast_icons(weather_type)
def update_forecast_icons(self, weather_type: str) -> None:
time = "night" if self.conditions.time_of_day == TimeOfDay.Night else "day"
icon_key = f"Weather_{time}-{weather_type}"
icon = CONST.ICONS.get(icon_key) or CONST.ICONS["Weather_night-partly-cloudy"]
self.weather_icon.setPixmap(icon)
from qt_ui.widgets.QConditionsDialog import QConditionsDialog
from qt_ui.widgets.conditions.QTimeTurnWidget import QTimeTurnWidget
from qt_ui.widgets.conditions.QWeatherWidget import QWeatherWidget
class QConditionsWidget(QFrame):
@ -287,6 +20,7 @@ class QConditionsWidget(QFrame):
def __init__(self, sim_controller: SimController) -> None:
super(QConditionsWidget, self).__init__()
self.setProperty("style", "QConditionsWidget")
self.setCursor(QCursor(QtCore.Qt.CursorShape.PointingHandCursor))
self.layout = QGridLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
@ -305,6 +39,9 @@ class QConditionsWidget(QFrame):
self.weather_widget.hide()
self.layout.addWidget(self.weather_widget, 0, 1)
def mouseDoubleClickEvent(self, event: QtGui.QMouseEvent) -> None:
QConditionsDialog(self.time_turn_widget, self.weather_widget).exec()
def setCurrentTurn(self, turn: int, conditions: Conditions) -> None:
"""Sets the turn information display.

View File

@ -0,0 +1,89 @@
from typing import Optional
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QHBoxLayout, QLabel, QSlider, QSpinBox, QComboBox
from dcs.weather import CloudPreset
class DcsCloudBaseSelector(QHBoxLayout):
M2FT_FACTOR = 3.2808399
def __init__(self, preset: Optional[CloudPreset]) -> None:
super().__init__()
self.preset = preset
self.unit_changing = False
self.label = QLabel("Cloud Base: ")
self.addWidget(self.label)
self.base = QSlider(Qt.Orientation.Horizontal)
self.base.setValue(round(self.max_base - (self.max_base - self.min_base) / 2))
self.base.valueChanged.connect(self.on_slider_change)
self.addWidget(self.base, 1)
self.base_spinner = QSpinBox()
self.base_spinner.setValue(self.base.value())
self.base_spinner.setFixedWidth(75)
self.base_spinner.setSingleStep(100)
self.base_spinner.valueChanged.connect(self.update_slider)
self.addWidget(self.base_spinner, 1)
self.unit = QComboBox()
self.unit.insertItems(0, ["m", "ft"])
self.unit.currentIndexChanged.connect(self.on_unit_change)
self.unit.setCurrentIndex(1)
self.addWidget(self.unit)
self.update_bounds()
@property
def min_base(self) -> int:
return self.preset.min_base if self.preset else 300
@property
def max_base(self) -> int:
return self.preset.max_base if self.preset else 5000
def update_bounds(self) -> None:
self.base.setRange(self.min_base, self.max_base)
index = self.unit.currentIndex()
if index == 0:
self.base_spinner.setRange(self.min_base, self.max_base)
elif index == 1:
self.base_spinner.setRange(
self.m2ft(self.min_base), self.m2ft(self.max_base)
)
def on_slider_change(self, value: int) -> None:
if self.unit.currentIndex() == 0:
self.base_spinner.setValue(value)
elif self.unit.currentIndex() == 1 and not self.unit_changing:
self.base_spinner.setValue(self.m2ft(value))
def update_slider(self, value: int) -> None:
if self.unit_changing:
return
if self.unit.currentIndex() == 0:
self.base.setValue(value)
elif self.unit.currentIndex() == 1:
self.unit_changing = True
self.base.setValue(self.ft2m(value))
self.unit_changing = False
def on_unit_change(self, index: int) -> None:
self.unit_changing = True
if index == 0:
self.base_spinner.setRange(self.min_base, self.max_base)
self.base_spinner.setValue(self.base.value())
elif index == 1:
self.base_spinner.setRange(
self.m2ft(self.min_base), self.m2ft(self.max_base)
)
self.base_spinner.setValue(self.m2ft(self.base.value()))
self.unit_changing = False
def m2ft(self, value: int) -> int:
return round(value * self.M2FT_FACTOR)
def ft2m(self, value: int) -> int:
return round(value / self.M2FT_FACTOR)

View File

@ -0,0 +1,42 @@
from typing import Optional
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QHBoxLayout, QLabel, QSlider, QSpinBox
from dcs.weather import CloudPreset
from game.weather.clouds import Clouds
class DcsCloudDensitySelector(QHBoxLayout):
def __init__(self, clouds: Clouds) -> None:
super().__init__()
self.unit_changing = False
self.label = QLabel("Density : ")
self.addWidget(self.label)
self.density = QSlider(Qt.Orientation.Horizontal)
self.density.setRange(0, 10)
self.density.setValue(clouds.density)
self.density.valueChanged.connect(self.on_slider_change)
self.addWidget(self.density, 1)
self.density_spinner = QSpinBox()
self.density_spinner.setValue(self.density.value())
self.density_spinner.setFixedWidth(75)
self.density_spinner.valueChanged.connect(self.update_slider)
self.addWidget(self.density_spinner, 1)
def on_slider_change(self, value: int) -> None:
self.density_spinner.setValue(value)
def update_slider(self, value: int) -> None:
self.density.setValue(value)
def update_ui(self, preset: Optional[CloudPreset]) -> None:
self.label.setVisible(preset is None)
self.density.setVisible(preset is None)
self.density_spinner.setVisible(preset is None)
if preset:
self.density.setValue(0)

View File

@ -0,0 +1,87 @@
from typing import Optional
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QHBoxLayout, QLabel, QSlider, QSpinBox, QComboBox
from dcs.weather import CloudPreset
from game.weather.clouds import Clouds
class DcsCloudThicknessSelector(QHBoxLayout):
M2FT_FACTOR = 3.2808399
def __init__(self, clouds: Clouds) -> None:
super().__init__()
self.unit_changing = False
self.label = QLabel("Thickness : ")
self.addWidget(self.label)
self.thickness = QSlider(Qt.Orientation.Horizontal)
self.thickness.setRange(200, 2000)
self.thickness.setValue(clouds.thickness)
self.thickness.valueChanged.connect(self.on_slider_change)
self.addWidget(self.thickness, 1)
self.thickness_spinner = QSpinBox()
self.thickness_spinner.setValue(self.thickness.value())
self.thickness_spinner.setFixedWidth(75)
self.thickness_spinner.setSingleStep(100)
self.thickness_spinner.valueChanged.connect(self.update_slider)
self.addWidget(self.thickness_spinner, 1)
self.unit = QComboBox()
self.unit.insertItems(0, ["m", "ft"])
self.unit.currentIndexChanged.connect(self.on_unit_change)
self.unit.setCurrentIndex(1)
self.addWidget(self.unit)
def update_ui(self, preset: Optional[CloudPreset]) -> None:
self.label.setVisible(preset is None)
self.thickness.setVisible(preset is None)
self.thickness_spinner.setVisible(preset is None)
self.unit.setVisible(preset is None)
if preset:
self.thickness.setValue(0)
def on_slider_change(self, value: int) -> None:
if self.unit.currentIndex() == 0:
self.thickness_spinner.setValue(value)
elif self.unit.currentIndex() == 1 and not self.unit_changing:
self.thickness_spinner.setValue(self.m2ft(value))
def update_slider(self, value: int) -> None:
if self.unit_changing:
return
if self.unit.currentIndex() == 0:
self.thickness.setValue(value)
elif self.unit.currentIndex() == 1:
self.unit_changing = True
self.thickness.setValue(self.ft2m(value))
self.unit_changing = False
def on_unit_change(self, index: int) -> None:
self.unit_changing = True
mini = (
self.thickness.minimum()
if index == 0
else self.m2ft(self.thickness.minimum())
)
maxi = (
self.thickness.maximum()
if index == 0
else self.m2ft(self.thickness.maximum())
)
value = (
self.thickness.value() if index == 0 else self.m2ft(self.thickness.value())
)
self.thickness_spinner.setRange(mini, maxi)
self.thickness_spinner.setValue(value)
self.unit_changing = False
def m2ft(self, value: int) -> int:
return round(value * self.M2FT_FACTOR)
def ft2m(self, value: int) -> int:
return round(value / self.M2FT_FACTOR)

View File

@ -0,0 +1,28 @@
from typing import Optional
from PySide6.QtWidgets import QHBoxLayout, QLabel, QComboBox
from dcs.weather import Weather as PydcsWeather, CloudPreset
from game.weather.clouds import Clouds
class DcsPrecipitationSelector(QHBoxLayout):
def __init__(self, clouds: Clouds) -> None:
super().__init__()
self.unit_changing = False
self.label = QLabel("Precipitation : ")
self.addWidget(self.label)
self.selector = QComboBox()
for p in PydcsWeather.Preceptions:
self.selector.addItem(p.name.replace("_", ""), p)
self.selector.setCurrentText(clouds.precipitation.name.replace("_", ""))
self.addWidget(self.selector, 1)
def update_ui(self, preset: Optional[CloudPreset]) -> None:
self.selector.setEnabled(preset is None)
if preset:
self.selector.setCurrentText("None")

View File

@ -0,0 +1,38 @@
from typing import Optional
from PySide6.QtCore import QDateTime
from PySide6.QtWidgets import QVBoxLayout, QWidget, QLabel, QHBoxLayout, QDateTimeEdit
from qt_ui.widgets.conditions.QTimeTurnWidget import QTimeTurnWidget
class QTimeAdjustmentWidget(QWidget):
def __init__(
self, time_turn: QTimeTurnWidget, parent: Optional[QWidget] = None
) -> None:
super().__init__(parent)
self.current_datetime = time_turn.sim_controller.current_time_in_sim
self.init_ui()
def init_ui(self) -> None:
vbox = QVBoxLayout()
vbox.addWidget(QLabel("<h2><b>Time & Date:</b></h2>"))
vbox.addWidget(
QLabel(
'<h4 style="color:orange"><b>WARNING: CHANGING TIME/DATE WILL RE-INITIALIZE THE TURN</b></h4>'
)
)
hbox = QHBoxLayout()
t = self.current_datetime.time()
d = self.current_datetime.date()
self.datetime_edit = QDateTimeEdit(
QDateTime(d.year, d.month, d.day, t.hour, t.minute, t.second)
)
hbox.addWidget(self.datetime_edit)
vbox.addLayout(hbox)
self.setLayout(vbox)

View File

@ -0,0 +1,70 @@
from datetime import datetime
from PySide6.QtWidgets import QGroupBox, QHBoxLayout, QLabel, QVBoxLayout
from game.sim import GameUpdateEvents
from game.timeofday import TimeOfDay
from game.weather.conditions import Conditions
from qt_ui import uiconstants as CONST
from qt_ui.simcontroller import SimController
class QTimeTurnWidget(QGroupBox):
"""
UI Component to display current turn and time info
"""
def __init__(self, sim_controller: SimController) -> None:
super(QTimeTurnWidget, self).__init__("Turn")
self.sim_controller = sim_controller
self.setStyleSheet(
"padding: 0px; margin-left: 5px; margin-right: 0px; margin-top: 1ex; margin-bottom: 5px; border-right: 0px"
)
self.icons = {
TimeOfDay.Dawn: CONST.ICONS["Dawn"],
TimeOfDay.Day: CONST.ICONS["Day"],
TimeOfDay.Dusk: CONST.ICONS["Dusk"],
TimeOfDay.Night: CONST.ICONS["Night"],
}
# self.setProperty('style', 'conditions__widget--turn')
self.layout = QHBoxLayout()
self.setLayout(self.layout)
self.daytime_icon = QLabel()
self.daytime_icon.setPixmap(self.icons[TimeOfDay.Dawn])
self.layout.addWidget(self.daytime_icon)
self.time_column = QVBoxLayout()
self.layout.addLayout(self.time_column)
self.date_display = QLabel()
self.time_column.addWidget(self.date_display)
self.time_display = QLabel()
self.time_column.addWidget(self.time_display)
sim_controller.sim_update.connect(self.on_sim_update)
def on_sim_update(self, _events: GameUpdateEvents) -> None:
time = self.sim_controller.current_time_in_sim_if_game_loaded
if time is None:
self.date_display.setText("")
self.time_display.setText("")
else:
self.set_date_and_time(time)
def set_current_turn(self, turn: int, conditions: Conditions) -> None:
"""Sets the turn information display.
:arg turn Current turn number.
:arg conditions Current time and weather conditions.
"""
self.daytime_icon.setPixmap(self.icons[conditions.time_of_day])
self.set_date_and_time(conditions.start_time)
self.setTitle(f"Turn {turn}")
def set_date_and_time(self, time: datetime) -> None:
self.date_display.setText(time.strftime("%d %b %Y"))
self.time_display.setText(time.strftime("%H:%M:%S Local"))

View File

@ -0,0 +1,94 @@
from PySide6.QtWidgets import QLabel, QHBoxLayout, QComboBox, QWidget, QVBoxLayout
from dcs.cloud_presets import CLOUD_PRESETS
from game.weather.weather import ClearSkies, Cloudy, Raining, Thunderstorm
from qt_ui.widgets.conditions.DcsCloudBaseSelector import DcsCloudBaseSelector
from qt_ui.widgets.conditions.DcsCloudDensitySelector import DcsCloudDensitySelector
from qt_ui.widgets.conditions.DcsCloudThicknessSelector import DcsCloudThicknessSelector
from qt_ui.widgets.conditions.DcsPrecipitationSelector import DcsPrecipitationSelector
from qt_ui.widgets.conditions.QWeatherWidget import QWeatherWidget
class QWeatherAdjustmentWidget(QWidget):
def __init__(self, weather: QWeatherWidget) -> None:
super().__init__()
self.weather = weather
self.init_ui()
def init_ui(self) -> None:
weather = self.weather.conditions.weather
vbox = QVBoxLayout()
label = QLabel("<h2><b>Weather:</b></h2>")
label.setMaximumHeight(75)
vbox.addWidget(label)
hbox = QHBoxLayout()
hbox.addWidget(QLabel("Type"))
self.type_selector = QComboBox()
for text, w_type in [
("Clear", ClearSkies),
("Clouds", Cloudy),
("Rain", Raining),
("Thunderstorm", Thunderstorm),
]:
self.type_selector.addItem(text, w_type)
if isinstance(weather, w_type):
self.type_selector.setCurrentText(text)
self.type_selector.currentIndexChanged.connect(self.update_ui_for_type)
hbox.addWidget(self.type_selector)
vbox.addLayout(hbox)
label = QLabel("<h3><b>Clouds:</b></h3>")
label.setMaximumHeight(50)
vbox.addWidget(label)
hbox = QHBoxLayout()
hbox.addWidget(QLabel("Preset"))
self.preset_selector = QComboBox()
for _, preset in CLOUD_PRESETS.items():
self.preset_selector.addItem(preset.value.ui_name, preset.value)
self.preset_selector.addItem("Custom", None)
self.preset_selector.setCurrentText(
weather.clouds.preset.ui_name
if weather.clouds and weather.clouds.preset
else "Custom"
)
self.preset_selector.currentIndexChanged.connect(self.update_ui)
hbox.addWidget(self.preset_selector)
vbox.addLayout(hbox)
self.cloud_base = DcsCloudBaseSelector(self.preset_selector.currentData())
vbox.addLayout(self.cloud_base)
clouds = self.weather.conditions.weather.clouds
self.cloud_thickness = DcsCloudThicknessSelector(clouds)
vbox.addLayout(self.cloud_thickness)
self.cloud_density = DcsCloudDensitySelector(clouds)
vbox.addLayout(self.cloud_density)
self.precipitation = DcsPrecipitationSelector(clouds)
vbox.addLayout(self.precipitation)
self.setLayout(vbox)
self.update_ui_for_type()
def update_ui_for_type(self) -> None:
if self.type_selector.currentData() in [ClearSkies, Thunderstorm]:
self.preset_selector.setCurrentText("Custom")
self.preset_selector.setDisabled(True)
else:
self.preset_selector.setDisabled(False)
self.update_ui()
def update_ui(self) -> None:
preset = self.preset_selector.currentData()
self.cloud_base.preset = preset
self.cloud_base.update_bounds()
self.cloud_thickness.update_ui(preset)
self.cloud_density.update_ui(preset)
self.precipitation.update_ui(preset)

View File

@ -0,0 +1,207 @@
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QGroupBox, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout
from dcs.weather import CloudPreset, Weather as PydcsWeather
from game.timeofday import TimeOfDay
from game.utils import mps
from game.weather.conditions import Conditions
from qt_ui import uiconstants as CONST
class QWeatherWidget(QGroupBox):
"""
UI Component to display current weather forecast
"""
turn = None
conditions = None
def __init__(self):
super(QWeatherWidget, self).__init__("")
self.setProperty("style", "QWeatherWidget")
self.icons = {
TimeOfDay.Dawn: CONST.ICONS["Dawn"],
TimeOfDay.Day: CONST.ICONS["Day"],
TimeOfDay.Dusk: CONST.ICONS["Dusk"],
TimeOfDay.Night: CONST.ICONS["Night"],
}
self.layout = QHBoxLayout()
self.setLayout(self.layout)
self.makeWeatherIcon()
self.makeCloudRainFogWidget()
self.makeWindsWidget()
def makeWeatherIcon(self):
"""Makes the Weather Icon Widget"""
self.weather_icon = QLabel()
self.weather_icon.setPixmap(self.icons[TimeOfDay.Dawn])
self.layout.addWidget(self.weather_icon)
def makeCloudRainFogWidget(self):
"""Makes the Cloud, Rain, Fog Widget"""
self.textLayout = QVBoxLayout()
self.layout.addLayout(self.textLayout)
self.forecastClouds = self.makeLabel()
self.textLayout.addWidget(self.forecastClouds)
self.forecastRain = self.makeLabel()
self.textLayout.addWidget(self.forecastRain)
self.forecastFog = self.makeLabel()
self.textLayout.addWidget(self.forecastFog)
def makeWindsWidget(self):
"""Factory for the winds widget."""
windsLayout = QGridLayout()
self.layout.addLayout(windsLayout)
windsLayout.addWidget(self.makeIcon(CONST.ICONS["Weather_winds"]), 0, 0, 3, 1)
windsLayout.addWidget(self.makeLabel("At GL"), 0, 1)
windsLayout.addWidget(self.makeLabel("At FL08"), 1, 1)
windsLayout.addWidget(self.makeLabel("At FL26"), 2, 1)
self.windGLSpeedLabel = self.makeLabel("0kts")
self.windGLDirLabel = self.makeLabel("")
windsLayout.addWidget(self.windGLSpeedLabel, 0, 2)
windsLayout.addWidget(self.windGLDirLabel, 0, 3)
self.windFL08SpeedLabel = self.makeLabel("0kts")
self.windFL08DirLabel = self.makeLabel("")
windsLayout.addWidget(self.windFL08SpeedLabel, 1, 2)
windsLayout.addWidget(self.windFL08DirLabel, 1, 3)
self.windFL26SpeedLabel = self.makeLabel("0kts")
self.windFL26DirLabel = self.makeLabel("")
windsLayout.addWidget(self.windFL26SpeedLabel, 2, 2)
windsLayout.addWidget(self.windFL26DirLabel, 2, 3)
def makeLabel(self, text: str = "") -> QLabel:
"""Shorthand to generate a QLabel with widget standard style
:arg pixmap QPixmap for the icon.
"""
label = QLabel(text)
label.setProperty("style", "text-sm")
return label
def makeIcon(self, pixmap: QPixmap) -> QLabel:
"""Shorthand to generate a QIcon with pixmap.
:arg pixmap QPixmap for the icon.
"""
icon = QLabel()
icon.setPixmap(pixmap)
return icon
def setCurrentTurn(self, turn: int, conditions: Conditions) -> None:
"""Sets the turn information display.
:arg turn Current turn number.
:arg conditions Current time and weather conditions.
"""
self.turn = turn
self.conditions = conditions
self.update_forecast()
self.updateWinds()
def updateWinds(self):
"""Updates the UI with the current conditions wind info."""
windGlSpeed = mps(self.conditions.weather.wind.at_0m.speed or 0)
windGlDir = str(self.conditions.weather.wind.at_0m.direction or 0).rjust(3, "0")
self.windGLSpeedLabel.setText(f"{int(windGlSpeed.knots)}kts")
self.windGLDirLabel.setText(f"{windGlDir}º")
windFL08Speed = mps(self.conditions.weather.wind.at_2000m.speed or 0)
windFL08Dir = str(self.conditions.weather.wind.at_2000m.direction or 0).rjust(
3, "0"
)
self.windFL08SpeedLabel.setText(f"{int(windFL08Speed.knots)}kts")
self.windFL08DirLabel.setText(f"{windFL08Dir}º")
windFL26Speed = mps(self.conditions.weather.wind.at_8000m.speed or 0)
windFL26Dir = str(self.conditions.weather.wind.at_8000m.direction or 0).rjust(
3, "0"
)
self.windFL26SpeedLabel.setText(f"{int(windFL26Speed.knots)}kts")
self.windFL26DirLabel.setText(f"{windFL26Dir}º")
def update_forecast_from_preset(self, preset: CloudPreset) -> None:
self.forecastFog.setText("No fog")
if "Rain" in preset.name:
self.forecastRain.setText("Rain")
self.update_forecast_icons("rain")
else:
self.forecastRain.setText("No rain")
self.update_forecast_icons("partly-cloudy")
# We get a description like the following for the cloud preset.
#
# 09 ##Two Layer Broken/Scattered \nMETAR:BKN 7.5/10 SCT 20/22 FEW41
#
# The second line is probably interesting but doesn't fit into the widget
# currently, so for now just extract the first line.
self.forecastClouds.setText(preset.description.splitlines()[0].split("##")[1])
def update_forecast(self):
"""Updates the Forecast Text and icon with the current conditions wind info."""
if (
self.conditions.weather.clouds
and self.conditions.weather.clouds.preset is not None
):
self.update_forecast_from_preset(self.conditions.weather.clouds.preset)
return
if self.conditions.weather.clouds is None:
cloud_density = 0
precipitation = None
else:
cloud_density = self.conditions.weather.clouds.density
precipitation = self.conditions.weather.clouds.precipitation
if not cloud_density:
self.forecastClouds.setText("Clear")
weather_type = "clear"
elif cloud_density < 3:
self.forecastClouds.setText("Partly Cloudy")
weather_type = "partly-cloudy"
elif cloud_density < 5:
self.forecastClouds.setText("Mostly Cloudy")
weather_type = "partly-cloudy"
else:
self.forecastClouds.setText("Totally Cloudy")
weather_type = "partly-cloudy"
if precipitation == PydcsWeather.Preceptions.Rain:
self.forecastRain.setText("Rain")
weather_type = "rain"
elif precipitation == PydcsWeather.Preceptions.Thunderstorm:
self.forecastRain.setText("Thunderstorm")
weather_type = "thunderstorm"
else:
self.forecastRain.setText("No rain")
if not self.conditions.weather.fog is not None:
self.forecastFog.setText("No fog")
else:
visibility = round(self.conditions.weather.fog.visibility.nautical_miles, 1)
self.forecastFog.setText(f"Fog vis: {visibility}nm")
if cloud_density > 1:
weather_type = "cloudy-fog"
else:
weather_type = "fog"
self.update_forecast_icons(weather_type)
def update_forecast_icons(self, weather_type: str) -> None:
time = "night" if self.conditions.time_of_day == TimeOfDay.Night else "day"
icon_key = f"Weather_{time}-{weather_type}"
icon = CONST.ICONS.get(icon_key) or CONST.ICONS["Weather_night-partly-cloudy"]
self.weather_icon.setPixmap(icon)

View File

@ -590,6 +590,11 @@ QFrame[style="QConditionsWidget"] {
background: transparent;
}
QFrame[style="QConditionsWidget"]:hover {
cursor: pointer;
background: #43A6C6;
}
QGroupBox[style="QWeatherWidget"] {
padding: 0px;
margin-left: 0px;
@ -664,3 +669,7 @@ QCalendarWidget QTableView{
.comms {
padding: 2px;
}
.hidden {
visibility: hidden;
}