Skip to content

SagaMod

src.templates.saga.SagaMod

Bases: NormalTemplate

  • A template modifier for Saga cards introduced in Dominaria.
  • Utilizes some of the same automated positioning techniques as Planeswalker templates.
Adds
  • Ability icons, layers, and dividers.
  • A reminder text layer at the top which describes how the Saga functions.
  • Also has some layer support for double faced sagas.
Source code in src\templates\saga.py
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
class SagaMod (NormalTemplate):
    """
    * A template modifier for Saga cards introduced in Dominaria.
    * Utilizes some of the same automated positioning techniques as Planeswalker templates.

    Adds:
        * Ability icons, layers, and dividers.
        * A reminder text layer at the top which describes how the Saga functions.
        * Also has some layer support for double faced sagas.
    """

    def __init__(self, layout: SagaLayout, **kwargs):
        self._abilities: list[ArtLayer] = []
        self._icons: list[list[ArtLayer]] = []
        super().__init__(layout, **kwargs)

    """
    * Layout Checks
    """

    def is_layout_saga(self) -> bool:
        """Checks whether the card matches SagaLayout."""
        return isinstance(self.layout, SagaLayout)

    """
    * Mixin Methods
    """

    @auto_prop_cached
    def text_layer_methods(self) -> list[Callable]:
        """Add Saga text layers."""
        funcs = [self.text_layers_saga] if self.is_layout_saga else []
        return [*super().text_layer_methods, *funcs]

    @auto_prop_cached
    def frame_layer_methods(self) -> list[Callable]:
        """Add Saga text layers."""
        funcs = [self.frame_layers_saga] if self.is_layout_saga else []
        return [*super().frame_layer_methods, *funcs]

    @auto_prop_cached
    def post_text_methods(self) -> list[Callable]:
        """Position Saga abilities, dividers, and icons."""
        funcs = [self.layer_positioning_saga] if self.is_layout_saga else []
        return [*super().post_text_methods, *funcs]

    """
    * Groups
    """

    @auto_prop_cached
    def saga_group(self):
        return psd.getLayerSet(LAYERS.SAGA)

    """
    * Saga Layers
    """

    @property
    def ability_layers(self) -> list[ArtLayer]:
        return self._abilities

    @ability_layers.setter
    def ability_layers(self, value):
        self._abilities = value

    @property
    def icon_layers(self) -> list[list[ArtLayer]]:
        return self._icons

    @icon_layers.setter
    def icon_layers(self, value):
        self._icons = value

    @auto_prop_cached
    def ability_divider_layer(self) -> ArtLayer:
        return psd.getLayer(LAYERS.DIVIDER, self.saga_group)

    """
    * Text Layers
    """

    @auto_prop_cached
    def text_layer_ability(self) -> ArtLayer:
        return psd.getLayer(LAYERS.TEXT, self.saga_group)

    @auto_prop_cached
    def text_layer_reminder(self) -> ArtLayer:
        return psd.getLayer("Reminder Text", self.saga_group)

    """
    * References
    """

    @auto_prop_cached
    def art_reference(self) -> ArtLayer:
        return psd.getLayer(LAYERS.ART_FRAME)

    @auto_prop_cached
    def reminder_reference(self) -> ReferenceLayer:
        return psd.get_reference_layer("Description Reference", self.saga_group)

    """
    * Text Layer Methods
    """

    def rules_text_and_pt_layers(self) -> None:
        """Skip this step for Saga cards."""
        pass

    """
    * Saga Frame Layer Methods
    """

    def frame_layers_saga(self):
        """Enable frame layers required by Saga cards."""

        # Saga stripe
        psd.getLayer(self.pinlines, LAYERS.PINLINES_AND_SAGA_STRIPE).visible = True

    """
    * Saga Text Layer Methods
    """

    def text_layers_saga(self):
        """Add and modify text layers required by Saga cards."""

        # Add description text with reminder
        self.text.append(
            text_classes.FormattedTextArea(
                layer=self.text_layer_reminder,
                contents=self.layout.saga_description,
                reference=self.reminder_reference))

        # Iterate through each saga stage and add line to text layers
        for i, line in enumerate(self.layout.saga_lines):

            # Add icon layers for this ability
            self.icon_layers.append([psd.getLayer(n, self.saga_group).duplicate() for n in line['icons']])

            # Add ability text for this ability
            layer = self.text_layer_ability if i == 0 else self.text_layer_ability.duplicate()
            self.ability_layers.append(layer)
            self.text.append(
                text_classes.FormattedTextField(
                    layer=layer, contents=line['text']))

    """
    * Saga Positioning Methods
    """

    def layer_positioning_saga(self) -> None:
        """Position Saga ability, icon, and divider layers."""

        # Core vars
        spacing = self.app.scale_by_dpi(80)
        spaces = len(self.ability_layers) - 1
        spacing_total = (spaces * 1.5) + 2
        ref_height = self.textbox_reference.dims['height']
        total_height = ref_height - (((spacing * 1.5) * spaces) + (spacing * 2))

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

        # Get the exact gap between each layer left over
        layer_heights = sum([psd.get_layer_height(lyr) for lyr in self.ability_layers])
        gap = (ref_height - layer_heights) * (1 / spacing_total)
        inside_gap = (ref_height - layer_heights) * (1.5 / spacing_total)

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

        # Align icons to respective text layers
        for i, ref_layer in enumerate(self.ability_layers):

            # Skip if no icons present or icons are invalid
            if not (icons := self.icon_layers[i]):
                continue
            if not all(icons):
                continue

            # Space multiple icons apart
            if len(icons) > 1:
                psd.space_layers_apart(
                    layers=icons,
                    gap=spacing / 3)

            # Combine icons and align them
            layer = icons[0] if len(icons) == 1 else psd.merge_layers(icons)
            psd.align_vertical(layer, ref_layer)

        # Position divider lines
        dividers = [self.ability_divider_layer.duplicate() for _ in range(len(self.ability_layers) - 1)]
        psd.position_dividers(
            dividers=dividers,
            layers=self.ability_layers,
            docref=self.docref)

Functions

frame_layer_methods() -> list[Callable]

Add Saga text layers.

Source code in src\templates\saga.py
66
67
68
69
70
@auto_prop_cached
def frame_layer_methods(self) -> list[Callable]:
    """Add Saga text layers."""
    funcs = [self.frame_layers_saga] if self.is_layout_saga else []
    return [*super().frame_layer_methods, *funcs]

frame_layers_saga()

Enable frame layers required by Saga cards.

Source code in src\templates\saga.py
146
147
148
149
150
def frame_layers_saga(self):
    """Enable frame layers required by Saga cards."""

    # Saga stripe
    psd.getLayer(self.pinlines, LAYERS.PINLINES_AND_SAGA_STRIPE).visible = True

is_layout_saga() -> bool

Checks whether the card matches SagaLayout.

Source code in src\templates\saga.py
52
53
54
def is_layout_saga(self) -> bool:
    """Checks whether the card matches SagaLayout."""
    return isinstance(self.layout, SagaLayout)

layer_positioning_saga() -> None

Position Saga ability, icon, and divider layers.

Source code in src\templates\saga.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
def layer_positioning_saga(self) -> None:
    """Position Saga ability, icon, and divider layers."""

    # Core vars
    spacing = self.app.scale_by_dpi(80)
    spaces = len(self.ability_layers) - 1
    spacing_total = (spaces * 1.5) + 2
    ref_height = self.textbox_reference.dims['height']
    total_height = ref_height - (((spacing * 1.5) * spaces) + (spacing * 2))

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

    # Get the exact gap between each layer left over
    layer_heights = sum([psd.get_layer_height(lyr) for lyr in self.ability_layers])
    gap = (ref_height - layer_heights) * (1 / spacing_total)
    inside_gap = (ref_height - layer_heights) * (1.5 / spacing_total)

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

    # Align icons to respective text layers
    for i, ref_layer in enumerate(self.ability_layers):

        # Skip if no icons present or icons are invalid
        if not (icons := self.icon_layers[i]):
            continue
        if not all(icons):
            continue

        # Space multiple icons apart
        if len(icons) > 1:
            psd.space_layers_apart(
                layers=icons,
                gap=spacing / 3)

        # Combine icons and align them
        layer = icons[0] if len(icons) == 1 else psd.merge_layers(icons)
        psd.align_vertical(layer, ref_layer)

    # Position divider lines
    dividers = [self.ability_divider_layer.duplicate() for _ in range(len(self.ability_layers) - 1)]
    psd.position_dividers(
        dividers=dividers,
        layers=self.ability_layers,
        docref=self.docref)

post_text_methods() -> list[Callable]

Position Saga abilities, dividers, and icons.

Source code in src\templates\saga.py
72
73
74
75
76
@auto_prop_cached
def post_text_methods(self) -> list[Callable]:
    """Position Saga abilities, dividers, and icons."""
    funcs = [self.layer_positioning_saga] if self.is_layout_saga else []
    return [*super().post_text_methods, *funcs]

rules_text_and_pt_layers() -> None

Skip this step for Saga cards.

Source code in src\templates\saga.py
138
139
140
def rules_text_and_pt_layers(self) -> None:
    """Skip this step for Saga cards."""
    pass

text_layer_methods() -> list[Callable]

Add Saga text layers.

Source code in src\templates\saga.py
60
61
62
63
64
@auto_prop_cached
def text_layer_methods(self) -> list[Callable]:
    """Add Saga text layers."""
    funcs = [self.text_layers_saga] if self.is_layout_saga else []
    return [*super().text_layer_methods, *funcs]

text_layers_saga()

Add and modify text layers required by Saga cards.

Source code in src\templates\saga.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def text_layers_saga(self):
    """Add and modify text layers required by Saga cards."""

    # Add description text with reminder
    self.text.append(
        text_classes.FormattedTextArea(
            layer=self.text_layer_reminder,
            contents=self.layout.saga_description,
            reference=self.reminder_reference))

    # Iterate through each saga stage and add line to text layers
    for i, line in enumerate(self.layout.saga_lines):

        # Add icon layers for this ability
        self.icon_layers.append([psd.getLayer(n, self.saga_group).duplicate() for n in line['icons']])

        # Add ability text for this ability
        layer = self.text_layer_ability if i == 0 else self.text_layer_ability.duplicate()
        self.ability_layers.append(layer)
        self.text.append(
            text_classes.FormattedTextField(
                layer=layer, contents=line['text']))