Skip to content

Commit

Permalink
Merge pull request #407 from YourAverageLink/entrance-tooltips
Browse files Browse the repository at this point in the history
Tracker: Display computed tooltips for entrances
  • Loading branch information
CovenEsme authored Jul 27, 2024
2 parents 056bd72 + e3ec3fa commit bbebc3e
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 178 deletions.
141 changes: 141 additions & 0 deletions gui/components/tooltip_formatting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
from logic.requirements import (
TOD,
Requirement,
RequirementType,
evaluate_requirement_at_time,
)
from logic.tooltips.tooltips import pretty_name, sort_requirement
from PySide6.QtGui import QFontMetrics, QTextDocumentFragment
from PySide6.QtWidgets import QToolTip


def get_tooltip_text(tracker_label, req: Requirement) -> str:
sort_requirement(req)
match req.type:
case RequirementType.AND:
# Computed requirements have a top-level AND requirement
# We display them as a list of bullet points to the user
# This fetches a list of the terms ANDed together
text = [format_requirement(tracker_label, a) for a in req.args]
case _:
# The requirement is just one term, so format the requirement
text = [format_requirement(tracker_label, req)]

tooltip_font_metrics = QFontMetrics(QToolTip.font())
# Find the width of the longest requirement description, adding a 16px buffer for the bullet point
max_line_width = (
max(
[
tooltip_font_metrics.horizontalAdvance(
QTextDocumentFragment.fromHtml(line).toPlainText()
)
for line in text + ["Item Requirements:"]
]
)
+ 16
)
# Set the tooltip's min and max width to ensure the tooltip is the right size and line-breaks properly
tracker_label.setStyleSheet(
tracker_label.styleSheet()
.replace("MINWIDTH", str(min(max_line_width, tracker_label.width() - 3)))
.replace("MAXWIDTH", str(tracker_label.width() - 3))
)
return (
"Item Requirements:"
+ '<ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 8px; margin-right: 0px; -qt-list-indent:0;"><li>'
+ "</li><li>".join(text)
+ "</li></ul>"
)


def format_requirement(
tracker_label,
req: Requirement,
is_top_level=True,
) -> str:
match req.type:
case RequirementType.IMPOSSIBLE:
return '<span style="color:red">Impossible (please discover an entrance first)</span>'
case RequirementType.NOTHING:
return '<span style="color:dodgerblue">Nothing</span>'
case RequirementType.ITEM:
# Determine if the user has marked this item
color = (
"dodgerblue"
if evaluate_requirement_at_time(
req, tracker_label.recent_search, TOD.ALL, tracker_label.world
)
else "red"
)
# Get a pretty name for the item if it is the first stage of a progressive item
name = pretty_name(req.args[0].name, 1)
return f'<span style="color:{color}">{name}</span>'
case RequirementType.COUNT:
# Determine if the user has enough of this item marked
color = (
"dodgerblue"
if evaluate_requirement_at_time(
req, tracker_label.recent_search, TOD.ALL, tracker_label.world
)
else "red"
)
# Get a pretty name for the progressive item
name = pretty_name(req.args[1].name, req.args[0])
return f'<span style="color:{color}">{name}</span>'
case RequirementType.WALLET_CAPACITY:
# Determine if the user has enough wallet capacity for this requirement
color = (
"dodgerblue"
if evaluate_requirement_at_time(
req, tracker_label.recent_search, TOD.ALL, tracker_label.world
)
else "red"
)
# TODO: Properly expand into wallet combinations
return f'<span style="color:{color}">Wallet >= {req.args[0]}</span>'
case RequirementType.GRATITUDE_CRYSTALS:
# Determine if the user has enough gratitude crystals marked
color = (
"dodgerblue"
if evaluate_requirement_at_time(
req, tracker_label.recent_search, TOD.ALL, tracker_label.world
)
else "red"
)
return (
f'<span style="color:{color}">{req.args[0]} Gratitude Crystals</span>'
)
case RequirementType.TRACKER_NOTE:
color = (
"dodgerblue"
if evaluate_requirement_at_time(
req.args[1],
tracker_label.recent_search,
TOD.ALL,
tracker_label.world,
)
else "red"
)
return f'<span style="color:{color}">{req.args[2]}</span>'
case RequirementType.OR:
# Recursively join requirements with "or"
# Only include parentheses if not at the top level (where they'd be redundant)
return (
("" if is_top_level else "(")
+ " or ".join(
[format_requirement(tracker_label, a, False) for a in req.args]
)
+ ("" if is_top_level else ")")
)
case RequirementType.AND:
# Recursively join requirements with "and"
# Only include parentheses if not at the top level (where they'd be redundant)
return (
("" if is_top_level else "(")
+ " and ".join(
[format_requirement(tracker_label, a, False) for a in req.args]
)
+ ("" if is_top_level else ")")
)
case _:
raise ValueError("unreachable")
33 changes: 30 additions & 3 deletions gui/components/tracker_entrance_label.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
from PySide6.QtWidgets import QLabel
import platform
from PySide6.QtWidgets import QLabel, QToolTip
from PySide6.QtGui import QCursor, QMouseEvent
from PySide6 import QtCore
from PySide6.QtCore import Signal
from PySide6.QtCore import Signal, QPoint

from logic.entrance import Entrance
from logic.search import Search
from logic.requirements import *

from constants.guiconstants import TRACKER_LOCATION_TOOLTIP_STYLESHEET
from .tooltip_formatting import get_tooltip_text


class TrackerEntranceLabel(QLabel):

default_stylesheet = "border-width: 1px; border-color: gray; color: COLOR;"
default_stylesheet = (
"QLabel { border-width: 1px; border-color: gray; color: COLOR; }"
)
choose_target = Signal(Entrance, str)
disconnect_entrance = Signal(Entrance, str)

Expand All @@ -19,18 +25,21 @@ def __init__(
entrance_: Entrance,
parent_area_name_: str,
recent_search_: Search,
world_,
show_full_connection_: bool,
) -> None:
super().__init__()
self.entrance = entrance_
self.parent_area_name = parent_area_name_
self.recent_search = recent_search_
self.world = world_
self.show_full_connection = show_full_connection_
self.setCursor(QCursor(QtCore.Qt.CursorShape.PointingHandCursor))
self.setMargin(10)
self.setMinimumHeight(30)
self.setMaximumWidth(273)
self.setWordWrap(True)
self.setMouseTracking(True)
self.update_text()

def update_text(self, recent_search_: Search | None = None) -> None:
Expand All @@ -48,6 +57,11 @@ def update_text(self, recent_search_: Search | None = None) -> None:
f"{first_part}{original_connected} -> {connected_area.name if connected_area else '?'}"
)

self.update_color(recent_search_)

def update_color(self, recent_search_: Search | None = None) -> None:
if recent_search_ is not None:
self.recent_search = recent_search_
# Set the color as blue if accessible, or red if not
color = "red"
if (
Expand All @@ -65,6 +79,7 @@ def update_text(self, recent_search_: Search | None = None) -> None:

self.setStyleSheet(
TrackerEntranceLabel.default_stylesheet.replace("COLOR", color)
+ TRACKER_LOCATION_TOOLTIP_STYLESHEET
)

def mouseReleaseEvent(self, ev: QMouseEvent) -> None:
Expand All @@ -74,3 +89,15 @@ def mouseReleaseEvent(self, ev: QMouseEvent) -> None:
self.disconnect_entrance.emit(self.entrance, self.parent_area_name)
self.update_text()
return super().mouseReleaseEvent(ev)

def mouseMoveEvent(self, ev: QMouseEvent) -> None:
coords = self.mapToGlobal(QPoint(-2, self.height() - 15))
# For whatever reason, MacOS calculates this position differently,
# so we must offset the height to compensate
if platform.system() == "Darwin":
coords.setY(coords.y() - 18)
QToolTip.showText(
coords, get_tooltip_text(self, self.entrance.computed_requirement), self
)

return super().mouseMoveEvent(ev)
Loading

0 comments on commit bbebc3e

Please sign in to comment.