mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Improve flight path display options.
Adds an option show only selected flight, and also changes the show all option to highlight the selected flight.
This commit is contained in:
parent
31d5e3151b
commit
2d8c8c63c9
@ -1,6 +1,6 @@
|
|||||||
"""Visibility options for the game map."""
|
"""Visibility options for the game map."""
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Iterator
|
from typing import Iterator, Optional, Union
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -27,17 +27,38 @@ class DisplayRule:
|
|||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
class DisplayGroup:
|
||||||
|
def __init__(self, name: Optional[str]) -> None:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[DisplayRule]:
|
||||||
|
# Python 3.6 enforces that __dict__ is order preserving by default.
|
||||||
|
for value in self.__dict__.values():
|
||||||
|
if isinstance(value, DisplayRule):
|
||||||
|
yield value
|
||||||
|
|
||||||
|
|
||||||
|
class FlightPathOptions(DisplayGroup):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__("Flight Paths")
|
||||||
|
self.hide = DisplayRule("Hide Flight Paths", True)
|
||||||
|
self.only_selected = DisplayRule("Show Selected Flight Path", False)
|
||||||
|
self.all = DisplayRule("Show All Flight Paths", False)
|
||||||
|
|
||||||
|
|
||||||
class DisplayOptions:
|
class DisplayOptions:
|
||||||
ground_objects = DisplayRule("Ground Objects", True)
|
ground_objects = DisplayRule("Ground Objects", True)
|
||||||
control_points = DisplayRule("Control Points", True)
|
control_points = DisplayRule("Control Points", True)
|
||||||
lines = DisplayRule("Lines", True)
|
lines = DisplayRule("Lines", True)
|
||||||
events = DisplayRule("Events", True)
|
events = DisplayRule("Events", True)
|
||||||
sam_ranges = DisplayRule("SAM Ranges", True)
|
sam_ranges = DisplayRule("SAM Ranges", True)
|
||||||
flight_paths = DisplayRule("Flight Paths", False)
|
flight_paths = FlightPathOptions()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def menu_items(cls) -> Iterator[DisplayRule]:
|
def menu_items(cls) -> Iterator[Union[DisplayGroup, DisplayRule]]:
|
||||||
# Python 3.6 enforces that __dict__ is order preserving by default.
|
# Python 3.6 enforces that __dict__ is order preserving by default.
|
||||||
for value in cls.__dict__.values():
|
for value in cls.__dict__.values():
|
||||||
if isinstance(value, DisplayRule):
|
if isinstance(value, DisplayRule):
|
||||||
yield value
|
yield value
|
||||||
|
elif isinstance(value, DisplayGroup):
|
||||||
|
yield value
|
||||||
|
|||||||
@ -109,6 +109,7 @@ class QFlightPanel(QGroupBox):
|
|||||||
"""Sets the package model to display."""
|
"""Sets the package model to display."""
|
||||||
self.package_model = model
|
self.package_model = model
|
||||||
self.flight_list.set_package(model)
|
self.flight_list.set_package(model)
|
||||||
|
self.selection_changed.connect(self.on_selection_changed)
|
||||||
self.on_selection_changed()
|
self.on_selection_changed()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -122,6 +123,15 @@ class QFlightPanel(QGroupBox):
|
|||||||
enabled = index.isValid()
|
enabled = index.isValid()
|
||||||
self.edit_button.setEnabled(enabled)
|
self.edit_button.setEnabled(enabled)
|
||||||
self.delete_button.setEnabled(enabled)
|
self.delete_button.setEnabled(enabled)
|
||||||
|
self.change_map_flight_selection(index)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def change_map_flight_selection(index: QModelIndex) -> None:
|
||||||
|
if not index.isValid():
|
||||||
|
GameUpdateSignal.get_instance().select_flight(None)
|
||||||
|
return
|
||||||
|
|
||||||
|
GameUpdateSignal.get_instance().select_flight(index.row())
|
||||||
|
|
||||||
def on_edit(self) -> None:
|
def on_edit(self) -> None:
|
||||||
"""Opens the flight edit dialog."""
|
"""Opens the flight edit dialog."""
|
||||||
@ -270,6 +280,18 @@ class QPackagePanel(QGroupBox):
|
|||||||
enabled = index.isValid()
|
enabled = index.isValid()
|
||||||
self.edit_button.setEnabled(enabled)
|
self.edit_button.setEnabled(enabled)
|
||||||
self.delete_button.setEnabled(enabled)
|
self.delete_button.setEnabled(enabled)
|
||||||
|
self.change_map_package_selection(index)
|
||||||
|
|
||||||
|
def change_map_package_selection(self, index: QModelIndex) -> None:
|
||||||
|
if not index.isValid():
|
||||||
|
GameUpdateSignal.get_instance().select_package(None)
|
||||||
|
return
|
||||||
|
|
||||||
|
package = self.ato_model.get_package_model(index)
|
||||||
|
if package.rowCount() == 0:
|
||||||
|
GameUpdateSignal.get_instance().select_package(None)
|
||||||
|
else:
|
||||||
|
GameUpdateSignal.get_instance().select_package(index.row())
|
||||||
|
|
||||||
def on_edit(self) -> None:
|
def on_edit(self) -> None:
|
||||||
"""Opens the package edit dialog."""
|
"""Opens the package edit dialog."""
|
||||||
|
|||||||
@ -42,6 +42,8 @@ class QLiberationMap(QGraphicsView):
|
|||||||
self.game: Optional[Game] = game_model.game
|
self.game: Optional[Game] = game_model.game
|
||||||
|
|
||||||
self.flight_path_items: List[QGraphicsItem] = []
|
self.flight_path_items: List[QGraphicsItem] = []
|
||||||
|
# A tuple of (package index, flight index), or none.
|
||||||
|
self.selected_flight: Optional[Tuple[int, int]] = None
|
||||||
|
|
||||||
self.setMinimumSize(800,600)
|
self.setMinimumSize(800,600)
|
||||||
self.setMaximumHeight(2160)
|
self.setMaximumHeight(2160)
|
||||||
@ -56,6 +58,25 @@ class QLiberationMap(QGraphicsView):
|
|||||||
lambda: self.draw_flight_plans(self.scene())
|
lambda: self.draw_flight_plans(self.scene())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def update_package_selection(index: Optional[int]) -> None:
|
||||||
|
self.selected_flight = index, 0
|
||||||
|
self.draw_flight_plans(self.scene())
|
||||||
|
|
||||||
|
GameUpdateSignal.get_instance().package_selection_changed.connect(
|
||||||
|
update_package_selection
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_flight_selection(index: Optional[int]) -> None:
|
||||||
|
if self.selected_flight is None:
|
||||||
|
logging.error("Flight was selected with no package selected")
|
||||||
|
return
|
||||||
|
self.selected_flight = self.selected_flight[0], index
|
||||||
|
self.draw_flight_plans(self.scene())
|
||||||
|
|
||||||
|
GameUpdateSignal.get_instance().flight_selection_changed.connect(
|
||||||
|
update_flight_selection
|
||||||
|
)
|
||||||
|
|
||||||
def init_scene(self):
|
def init_scene(self):
|
||||||
scene = QLiberationScene(self)
|
scene = QLiberationScene(self)
|
||||||
self.setScene(scene)
|
self.setScene(scene)
|
||||||
@ -198,37 +219,44 @@ class QLiberationMap(QGraphicsView):
|
|||||||
# Something may have caused those items to already be removed.
|
# Something may have caused those items to already be removed.
|
||||||
pass
|
pass
|
||||||
self.flight_path_items.clear()
|
self.flight_path_items.clear()
|
||||||
if not DisplayOptions.flight_paths:
|
if DisplayOptions.flight_paths.hide:
|
||||||
return
|
return
|
||||||
for package in self.game_model.ato_model.packages:
|
for p_idx, package in enumerate(self.game_model.ato_model.packages):
|
||||||
for flight in package.flights:
|
for f_idx, flight in enumerate(package.flights):
|
||||||
self.draw_flight_plan(scene, flight)
|
selected = (p_idx, f_idx) == self.selected_flight
|
||||||
|
if DisplayOptions.flight_paths.only_selected and not selected:
|
||||||
|
continue
|
||||||
|
highlight = selected and DisplayOptions.flight_paths.all
|
||||||
|
self.draw_flight_plan(scene, flight, highlight)
|
||||||
|
|
||||||
def draw_flight_plan(self, scene: QGraphicsScene, flight: Flight) -> None:
|
def draw_flight_plan(self, scene: QGraphicsScene, flight: Flight,
|
||||||
|
highlight: bool) -> None:
|
||||||
is_player = flight.from_cp.captured
|
is_player = flight.from_cp.captured
|
||||||
pos = self._transform_point(flight.from_cp.position)
|
pos = self._transform_point(flight.from_cp.position)
|
||||||
|
|
||||||
self.draw_waypoint(scene, pos, is_player)
|
self.draw_waypoint(scene, pos, is_player, highlight)
|
||||||
prev_pos = tuple(pos)
|
prev_pos = tuple(pos)
|
||||||
for point in flight.points:
|
for point in flight.points:
|
||||||
new_pos = self._transform_point(Point(point.x, point.y))
|
new_pos = self._transform_point(Point(point.x, point.y))
|
||||||
self.draw_flight_path(scene, prev_pos, new_pos, is_player)
|
self.draw_flight_path(scene, prev_pos, new_pos, is_player,
|
||||||
self.draw_waypoint(scene, new_pos, is_player)
|
highlight)
|
||||||
|
self.draw_waypoint(scene, new_pos, is_player, highlight)
|
||||||
prev_pos = tuple(new_pos)
|
prev_pos = tuple(new_pos)
|
||||||
self.draw_flight_path(scene, prev_pos, pos, is_player)
|
self.draw_flight_path(scene, prev_pos, pos, is_player, highlight)
|
||||||
|
|
||||||
def draw_waypoint(self, scene: QGraphicsScene, position: Tuple[int, int],
|
def draw_waypoint(self, scene: QGraphicsScene, position: Tuple[int, int],
|
||||||
player: bool) -> None:
|
player: bool, highlight: bool) -> None:
|
||||||
waypoint_pen = self.waypoint_pen(player)
|
waypoint_pen = self.waypoint_pen(player, highlight)
|
||||||
waypoint_brush = self.waypoint_brush(player)
|
waypoint_brush = self.waypoint_brush(player, highlight)
|
||||||
self.flight_path_items.append(scene.addEllipse(
|
self.flight_path_items.append(scene.addEllipse(
|
||||||
position[0], position[1], self.WAYPOINT_SIZE,
|
position[0], position[1], self.WAYPOINT_SIZE,
|
||||||
self.WAYPOINT_SIZE, waypoint_pen, waypoint_brush
|
self.WAYPOINT_SIZE, waypoint_pen, waypoint_brush
|
||||||
))
|
))
|
||||||
|
|
||||||
def draw_flight_path(self, scene: QGraphicsScene, pos0: Tuple[int, int],
|
def draw_flight_path(self, scene: QGraphicsScene, pos0: Tuple[int, int],
|
||||||
pos1: Tuple[int, int], player: bool):
|
pos1: Tuple[int, int], player: bool,
|
||||||
flight_path_pen = self.flight_path_pen(player)
|
highlight: bool) -> None:
|
||||||
|
flight_path_pen = self.flight_path_pen(player, highlight)
|
||||||
# Draw the line to the *middle* of the waypoint.
|
# Draw the line to the *middle* of the waypoint.
|
||||||
offset = self.WAYPOINT_SIZE // 2
|
offset = self.WAYPOINT_SIZE // 2
|
||||||
self.flight_path_items.append(scene.addLine(
|
self.flight_path_items.append(scene.addLine(
|
||||||
@ -317,21 +345,31 @@ class QLiberationMap(QGraphicsView):
|
|||||||
|
|
||||||
return X > treshold and X or treshold, Y > treshold and Y or treshold
|
return X > treshold and X or treshold, Y > treshold and Y or treshold
|
||||||
|
|
||||||
|
def highlight_color(self, transparent: Optional[bool] = False) -> QColor:
|
||||||
|
return QColor(255, 255, 0, 20 if transparent else 255)
|
||||||
|
|
||||||
def base_faction_color_name(self, player: bool) -> str:
|
def base_faction_color_name(self, player: bool) -> str:
|
||||||
if player:
|
if player:
|
||||||
return self.game.get_player_color()
|
return self.game.get_player_color()
|
||||||
else:
|
else:
|
||||||
return self.game.get_enemy_color()
|
return self.game.get_enemy_color()
|
||||||
|
|
||||||
def waypoint_pen(self, player: bool) -> QPen:
|
def waypoint_pen(self, player: bool, highlight: bool) -> QColor:
|
||||||
|
if highlight:
|
||||||
|
return self.highlight_color()
|
||||||
name = self.base_faction_color_name(player)
|
name = self.base_faction_color_name(player)
|
||||||
return QPen(brush=CONST.COLORS[name])
|
return CONST.COLORS[name]
|
||||||
|
|
||||||
def waypoint_brush(self, player: bool) -> QColor:
|
def waypoint_brush(self, player: bool, highlight: bool) -> QColor:
|
||||||
|
if highlight:
|
||||||
|
return self.highlight_color(transparent=True)
|
||||||
name = self.base_faction_color_name(player)
|
name = self.base_faction_color_name(player)
|
||||||
return CONST.COLORS[f"{name}_transparent"]
|
return CONST.COLORS[f"{name}_transparent"]
|
||||||
|
|
||||||
def flight_path_pen(self, player: bool) -> QPen:
|
def flight_path_pen(self, player: bool, highlight: bool) -> QPen:
|
||||||
|
if highlight:
|
||||||
|
return self.highlight_color()
|
||||||
|
|
||||||
name = self.base_faction_color_name(player)
|
name = self.base_faction_color_name(player)
|
||||||
color = CONST.COLORS[name]
|
color = CONST.COLORS[name]
|
||||||
pen = QPen(brush=color)
|
pen = QPen(brush=color)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from PySide2.QtCore import QObject, Signal
|
from PySide2.QtCore import QObject, Signal
|
||||||
|
|
||||||
@ -24,11 +24,21 @@ class GameUpdateSignal(QObject):
|
|||||||
debriefingReceived = Signal(DebriefingSignal)
|
debriefingReceived = Signal(DebriefingSignal)
|
||||||
|
|
||||||
flight_paths_changed = Signal()
|
flight_paths_changed = Signal()
|
||||||
|
package_selection_changed = Signal(int) # Optional[int]
|
||||||
|
flight_selection_changed = Signal(int) # Optional[int]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(GameUpdateSignal, self).__init__()
|
super(GameUpdateSignal, self).__init__()
|
||||||
GameUpdateSignal.instance = self
|
GameUpdateSignal.instance = self
|
||||||
|
|
||||||
|
def select_package(self, index: Optional[int]) -> None:
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
self.package_selection_changed.emit(index)
|
||||||
|
|
||||||
|
def select_flight(self, index: Optional[int]) -> None:
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
self.flight_selection_changed.emit(index)
|
||||||
|
|
||||||
def redraw_flight_paths(self) -> None:
|
def redraw_flight_paths(self) -> None:
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
self.flight_paths_changed.emit()
|
self.flight_paths_changed.emit()
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from typing import Optional
|
from typing import Optional, Union
|
||||||
|
|
||||||
from PySide2.QtCore import Qt
|
from PySide2.QtCore import Qt
|
||||||
from PySide2.QtGui import QIcon
|
from PySide2.QtGui import QIcon
|
||||||
from PySide2.QtWidgets import (
|
from PySide2.QtWidgets import (
|
||||||
QAction,
|
QAction,
|
||||||
QDesktopWidget,
|
QActionGroup, QDesktopWidget,
|
||||||
QFileDialog,
|
QFileDialog,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
QMessageBox,
|
QMenu, QMessageBox,
|
||||||
QSplitter,
|
QSplitter,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QWidget,
|
QWidget,
|
||||||
@ -19,7 +19,7 @@ from PySide2.QtWidgets import (
|
|||||||
import qt_ui.uiconstants as CONST
|
import qt_ui.uiconstants as CONST
|
||||||
from game import Game, persistency
|
from game import Game, persistency
|
||||||
from qt_ui.dialogs import Dialog
|
from qt_ui.dialogs import Dialog
|
||||||
from qt_ui.displayoptions import DisplayOptions
|
from qt_ui.displayoptions import DisplayGroup, DisplayOptions, DisplayRule
|
||||||
from qt_ui.models import GameModel
|
from qt_ui.models import GameModel
|
||||||
from qt_ui.uiconstants import URLS
|
from qt_ui.uiconstants import URLS
|
||||||
from qt_ui.widgets.QTopPanel import QTopPanel
|
from qt_ui.widgets.QTopPanel import QTopPanel
|
||||||
@ -139,17 +139,19 @@ class QLiberationWindow(QMainWindow):
|
|||||||
|
|
||||||
displayMenu = self.menu.addMenu("&Display")
|
displayMenu = self.menu.addMenu("&Display")
|
||||||
|
|
||||||
for display_rule in DisplayOptions.menu_items():
|
last_was_group = True
|
||||||
def make_check_closure():
|
for item in DisplayOptions.menu_items():
|
||||||
def closure():
|
if isinstance(item, DisplayRule):
|
||||||
display_rule.value = action.isChecked()
|
displayMenu.addAction(self.make_display_rule_action(item))
|
||||||
return closure
|
last_was_group = False
|
||||||
|
elif isinstance(item, DisplayGroup):
|
||||||
action = QAction(f"&{display_rule.menu_text}", displayMenu)
|
if not last_was_group:
|
||||||
action.setCheckable(True)
|
displayMenu.addSeparator()
|
||||||
action.setChecked(display_rule.value)
|
group = QActionGroup(displayMenu)
|
||||||
action.toggled.connect(make_check_closure())
|
for display_rule in item:
|
||||||
displayMenu.addAction(action)
|
displayMenu.addAction(
|
||||||
|
self.make_display_rule_action(display_rule, group))
|
||||||
|
last_was_group = True
|
||||||
|
|
||||||
help_menu = self.menu.addMenu("&Help")
|
help_menu = self.menu.addMenu("&Help")
|
||||||
help_menu.addAction("&Discord Server", lambda: webbrowser.open_new_tab("https://" + "discord.gg" + "/" + "bKrt" + "rkJ"))
|
help_menu.addAction("&Discord Server", lambda: webbrowser.open_new_tab("https://" + "discord.gg" + "/" + "bKrt" + "rkJ"))
|
||||||
@ -162,6 +164,21 @@ class QLiberationWindow(QMainWindow):
|
|||||||
help_menu.addSeparator()
|
help_menu.addSeparator()
|
||||||
help_menu.addAction(self.showAboutDialogAction)
|
help_menu.addAction(self.showAboutDialogAction)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_display_rule_action(
|
||||||
|
display_rule, group: Optional[QActionGroup] = None) -> QAction:
|
||||||
|
def make_check_closure():
|
||||||
|
def closure():
|
||||||
|
display_rule.value = action.isChecked()
|
||||||
|
|
||||||
|
return closure
|
||||||
|
|
||||||
|
action = QAction(f"&{display_rule.menu_text}", group)
|
||||||
|
action.setCheckable(True)
|
||||||
|
action.setChecked(display_rule.value)
|
||||||
|
action.toggled.connect(make_check_closure())
|
||||||
|
return action
|
||||||
|
|
||||||
def newGame(self):
|
def newGame(self):
|
||||||
wizard = NewGameWizard(self)
|
wizard = NewGameWizard(self)
|
||||||
wizard.show()
|
wizard.show()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user