Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/enums/layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ class LAYERS (StrConstant):
BANNER = 'Banner'
STRIPE = 'Stripe'

# Case
CASE = 'Case'

# Class
CLASS = 'Class'
STAGE = 'Stage'
Expand Down
4 changes: 4 additions & 0 deletions src/enums/mtg.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class LayoutCategory(StrConstant):
"""Card layout category, broad naming used for displaying on GUI elements."""
Adventure = 'Adventure'
Battle = 'Battle'
Case = 'Case'
Class = 'Class'
Leveler = 'Leveler'
MDFC = 'MDFC'
Expand All @@ -37,6 +38,7 @@ class LayoutType(StrConstant):
"""Card layout type, fine-grained naming separated by front/back where applicable."""
Adventure = 'adventure'
Battle = 'battle'
Case = 'case'
Class = 'class'
Leveler = 'leveler'
MDFCBack = 'mdfc_back'
Expand Down Expand Up @@ -65,6 +67,7 @@ class LayoutScryfall(StrConstant):
MDFC = 'modal_dfc'
Meld = 'meld'
Leveler = 'leveler'
Case = 'case'
Class = 'class'
Saga = 'saga'
Adventure = 'adventure'
Expand Down Expand Up @@ -97,6 +100,7 @@ class LayoutScryfall(StrConstant):
LayoutCategory.PlaneswalkerMDFC: [LayoutType.PlaneswalkerMDFCFront, LayoutType.PlaneswalkerMDFCBack],
LayoutCategory.PlaneswalkerTransform: [LayoutType.PlaneswalkerTransformFront, LayoutType.PlaneswalkerTransformBack],
LayoutCategory.Saga: [LayoutType.Saga],
LayoutCategory.Case: [LayoutType.Case],
LayoutCategory.Class: [LayoutType.Class],
LayoutCategory.Mutate: [LayoutType.Mutate],
LayoutCategory.Prototype: [LayoutType.Prototype],
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/position.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
# Standard Library Imports
import math
from typing import Optional, Union
from typing import Optional, Sequence, Union

# Third Party Imports
from photoshop.api import DialogModes, AnchorPosition
Expand Down Expand Up @@ -157,8 +157,8 @@ def position_between_layers(


def position_dividers(
dividers: list[Union[ArtLayer, LayerSet]],
layers: list[Union[ArtLayer, LayerSet]],
dividers: Sequence[ArtLayer | LayerSet],
layers: Sequence[ArtLayer | LayerSet],
docref: Optional[Document] = None
) -> None:
"""Positions a list of dividers between a list of layers.
Expand Down
11 changes: 11 additions & 0 deletions src/layouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,16 @@ def class_lines(self) -> list[dict]:
# Otherwise add line to the previous ability
abilities[-1]['text'] += f'\n{line}'
return abilities


class CaseLayout(NormalLayout):
"""Case card layout, introduced in Murders at Karlov Manor."""
card_class: str = LayoutType.Case

@cached_property
def case_lines(self) -> list[str]:
"""Split Case text into sections."""
return self.oracle_text.split("\n")


class BattleLayout(TransformLayout):
Expand Down Expand Up @@ -1561,6 +1571,7 @@ def card_count(self) -> Optional[int]:
LayoutScryfall.MDFC: ModalDoubleFacedLayout,
LayoutScryfall.Meld: TransformLayout,
LayoutScryfall.Leveler: LevelerLayout,
LayoutScryfall.Case: CaseLayout,
LayoutScryfall.Class: ClassLayout,
LayoutScryfall.Saga: SagaLayout,
LayoutScryfall.Adventure: AdventureLayout,
Expand Down
1 change: 1 addition & 0 deletions src/templates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from src.templates.saga import *
from src.templates.token import *
from src.templates.mutate import *
from src.templates.case import *
from src.templates.classes import *
from src.templates.battle import *
from src.templates.prototype import *
Expand Down
169 changes: 169 additions & 0 deletions src/templates/case.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
from functools import cached_property
from typing import Callable

from photoshop.api._artlayer import ArtLayer
from photoshop.api._layerSet import LayerSet

from src.enums.layers import LAYERS
from src.helpers.bounds import get_layer_height
from src.helpers.layers import getLayer, getLayerSet
from src.helpers.position import position_dividers, spread_layers_over_reference
from src.helpers.text import scale_text_layers_to_height
from src.layouts import CaseLayout
from src.templates._core import NormalTemplate
from src.text_layers import FormattedTextField


class CaseMod(NormalTemplate):
"""
* A template modifier for Case cards introduced in Murders at Karlov Manor.

Adds:
* Evenly spaced ability sections and dividers.
"""

def __init__(self, layout: CaseLayout, **kwargs: None):
self.line_layers: list[ArtLayer] = []
self.divider_layers: list[ArtLayer] = []
super().__init__(layout, **kwargs)

"""
* Checks
"""

@cached_property
def is_case_layout(self) -> bool:
"""bool: Checks if this card uses Case layout."""
return isinstance(self.layout, CaseLayout)

"""
* Mixin Methods
"""

@cached_property
def text_layer_methods(self) -> list[Callable[[], None]]:
"""Add Case text layers."""
funcs = [self.text_layers_case] if self.is_case_layout else []
return [*super().text_layer_methods, *funcs]

@cached_property
def frame_layer_methods(self) -> list[Callable[[], None]]:
"""Add Case frame layers."""
funcs = [self.frame_layers_case] if self.is_case_layout else []
return [*super().frame_layer_methods, *funcs]

@cached_property
def post_text_methods(self) -> list[Callable[[], None]]:
"""Position Case abilities and dividers."""
funcs = [self.layer_positioning_case] if self.is_case_layout else []
return [*super().post_text_methods, *funcs]

"""
* Groups
"""

@cached_property
def case_group(self) -> LayerSet | None:
return getLayerSet(LAYERS.CASE)

"""
* Text Layers
"""

@cached_property
def text_layer_ability(self) -> ArtLayer | None:
return getLayer(LAYERS.TEXT, self.case_group)

@cached_property
def case_ability_divider(self) -> ArtLayer | None:
return getLayer(LAYERS.DIVIDER, self.case_group)

"""
* Layer Methods
"""

def rules_text_and_pt_layers(self) -> None:
if self.is_case_layout and not self.is_creature:
return
return super().rules_text_and_pt_layers()

"""
* Text Layer Methods
"""

def text_layers_case(self) -> None:
"""Add and modify text layers relating to Case type cards."""

skip_divider_for = len(self.layout.case_lines) - 1

# Add text fields for each line
for i, line in enumerate(self.layout.case_lines):
# Create a new ability line
if layer := self.text_layer_ability:
line_layer: ArtLayer = layer if i == 0 else layer.duplicate()
self.line_layers.append(line_layer)
self.text.append(FormattedTextField(layer=line_layer, contents=line))

# Use existing ability divider or create a new one
if i != skip_divider_for and (layer := self.case_ability_divider):
divider: ArtLayer = (
self.case_ability_divider
if i == 0
else self.case_ability_divider.duplicate()
)
self.divider_layers.append(divider)

"""
* Frame Layer Methods
"""

def frame_layers_case(self) -> None:
"""Enable frame layers required by Case cards. None by default."""
pass

"""
* Positioning Methods
"""

def layer_positioning_case(self) -> None:
"""Positions and sizes Case ability layers and dividers."""

# Core vars
spacing = self.app.scale_by_dpi(80)
spaces = len(self.line_layers)
divider_height = (
get_layer_height(self.divider_layers[0])
if len(self.divider_layers) > 0
else 0
)
ref_height: float | int = self.textbox_reference.dims["height"]
spacing_total = (spaces * (spacing + divider_height)) + (spacing * 2)
total_height = ref_height - spacing_total

# Resize text items till they fit in the available space
scale_text_layers_to_height(
text_layers=self.line_layers, ref_height=total_height
)

# Get the exact gap between each layer left over
layer_heights = sum([get_layer_height(lyr) for lyr in self.line_layers])
gap = (ref_height - layer_heights) * (spacing / spacing_total)
inside_gap = (ref_height - layer_heights) * (
(spacing + divider_height) / spacing_total
)

# Space lines evenly apart
spread_layers_over_reference(
layers=self.line_layers,
ref=self.textbox_reference,
gap=gap,
inside_gap=inside_gap,
)

# Position a divider between each ability line
if len(self.divider_layers) == len(self.line_layers) - 1:
position_dividers(
dividers=self.divider_layers,
layers=self.line_layers,
docref=self.docref,
)