Skip to content

ClassMod

src.templates.classes.ClassMod

Bases: NormalTemplate

  • A template modifier for Class cards introduced in Adventures in the Forgotten Realms.
  • Utilizes similar automated positioning techniques as Planeswalker templates.
Adds
  • Level stage groups which contain a cost and level text layer, as well as the divider bar.
  • Level line groups which contain the ability text for each level.
  • A positioning step to evenly space the abilities and stage dividers.
Source code in src\templates\classes.py
 27
 28
 29
 30
 31
 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
class ClassMod (NormalTemplate):
    """
    * A template modifier for Class cards introduced in Adventures in the Forgotten Realms.
    * Utilizes similar automated positioning techniques as Planeswalker templates.

    Adds:
        * Level stage groups which contain a cost and level text layer, as well as the divider bar.
        * Level line groups which contain the ability text for each level.
        * A positioning step to evenly space the abilities and stage dividers.
    """

    def __init__(self, layout: ClassLayout, **kwargs):
        self._line_layers: list[ArtLayer] = []
        self._stage_layers: list[LayerSet] = []
        super().__init__(layout, **kwargs)

    """
    * Checks
    """

    @auto_prop_cached
    def is_class_layout(self) -> bool:
        """bool: Checks if this card is a ClassLayout object."""
        return isinstance(self.layout, ClassLayout)

    """
    * Mixin Methods
    """

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

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

    @auto_prop_cached
    def post_text_methods(self) -> list[Callable]:
        """Position Class abilities and stage dividers."""
        funcs = [self.layer_positioning_classes] if self.is_class_layout else []
        return [*super().post_text_methods, *funcs]

    """
    * Class Groups
    """

    @auto_prop_cached
    def class_group(self) -> LayerSet:
        return psd.getLayerSet(LAYERS.CLASS)

    @auto_prop_cached
    def stage_group(self) -> LayerSet:
        return psd.getLayerSet(LAYERS.STAGE, self.class_group)

    """
    * Class Text Layers
    """

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

    """
    * Class Abilities
    """

    @property
    def line_layers(self) -> list[ArtLayer]:
        return self._line_layers

    @line_layers.setter
    def line_layers(self, value):
        self._line_layers = value

    """
    * Class Stage Dividers
    """

    @property
    def stage_layers(self) -> list[LayerSet]:
        return self._stage_layers

    @stage_layers.setter
    def stage_layers(self, value):
        self._stage_layers = value

    """
    * Text Layer Methods
    """

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

    """
    * Class Text Layer Methods
    """

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

        # Add first static line
        self.line_layers.append(self.text_layer_ability)
        self.text.append(
            FormattedTextField(
                layer=self.text_layer_ability,
                contents=self.layout.class_lines[0]['text']
            ))

        # Add text fields for each line and class stage
        for i, line in enumerate(self.layout.class_lines[1:]):

            # Create a new ability line
            line_layer = self.text_layer_ability.duplicate()
            self.line_layers.append(line_layer)

            # Use existing stage divider or create new one
            stage = self.stage_group if i == 0 else self.stage_group.duplicate()
            cost, level = [*stage.artLayers][:2]
            self.stage_layers.append(stage)

            # Add text layers to be formatted
            self.text.extend([
                FormattedTextField(layer=line_layer, contents=line['text']),
                FormattedTextField(layer=cost, contents=f"{line['cost']}:"),
                TextField(layer=level, contents=f"Level {line['level']}")
            ])

    """
    * Class Frame Layer Methods
    """

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

    """
    * Class Positioning Methods
    """

    def layer_positioning_classes(self) -> None:
        """Positions and sizes class ability layers and stage dividers."""

        # Core vars
        spacing = self.app.scale_by_dpi(80)
        spaces = len(self.line_layers) - 1
        divider_height = psd.get_layer_height(self.stage_layers[0])
        ref_height = 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
        psd.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([psd.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 Class lines evenly apart
        psd.spread_layers_over_reference(
            layers=self.line_layers,
            ref=self.textbox_reference,
            gap=gap,
            inside_gap=inside_gap)

        # Position a class stage between each ability line
        psd.position_dividers(
            dividers=self.stage_layers,
            layers=self.line_layers,
            docref=self.docref)

Functions

frame_layer_methods() -> list[Callable]

Add Class text layers.

Source code in src\templates\classes.py
62
63
64
65
66
@auto_prop_cached
def frame_layer_methods(self) -> list[Callable]:
    """Add Class text layers."""
    funcs = [self.frame_layers_classes] if self.is_class_layout else []
    return [*super().frame_layer_methods, *funcs]

frame_layers_classes() -> None

Enable frame layers required by Class cards. None by default.

Source code in src\templates\classes.py
164
165
166
def frame_layers_classes(self) -> None:
    """Enable frame layers required by Class cards. None by default."""
    pass

is_class_layout() -> bool

Source code in src\templates\classes.py
47
48
49
50
@auto_prop_cached
def is_class_layout(self) -> bool:
    """bool: Checks if this card is a ClassLayout object."""
    return isinstance(self.layout, ClassLayout)

layer_positioning_classes() -> None

Positions and sizes class ability layers and stage dividers.

Source code in src\templates\classes.py
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
def layer_positioning_classes(self) -> None:
    """Positions and sizes class ability layers and stage dividers."""

    # Core vars
    spacing = self.app.scale_by_dpi(80)
    spaces = len(self.line_layers) - 1
    divider_height = psd.get_layer_height(self.stage_layers[0])
    ref_height = 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
    psd.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([psd.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 Class lines evenly apart
    psd.spread_layers_over_reference(
        layers=self.line_layers,
        ref=self.textbox_reference,
        gap=gap,
        inside_gap=inside_gap)

    # Position a class stage between each ability line
    psd.position_dividers(
        dividers=self.stage_layers,
        layers=self.line_layers,
        docref=self.docref)

post_text_methods() -> list[Callable]

Position Class abilities and stage dividers.

Source code in src\templates\classes.py
68
69
70
71
72
@auto_prop_cached
def post_text_methods(self) -> list[Callable]:
    """Position Class abilities and stage dividers."""
    funcs = [self.layer_positioning_classes] if self.is_class_layout else []
    return [*super().post_text_methods, *funcs]

rules_text_and_pt_layers() -> None

Skip this step for Class cards.

Source code in src\templates\classes.py
122
123
124
def rules_text_and_pt_layers(self) -> None:
    """Skip this step for Class cards."""
    pass

text_layer_methods() -> list[Callable]

Add Class text layers.

Source code in src\templates\classes.py
56
57
58
59
60
@auto_prop_cached
def text_layer_methods(self) -> list[Callable]:
    """Add Class text layers."""
    funcs = [self.text_layers_classes] if self.is_class_layout else []
    return [*super().text_layer_methods, *funcs]

text_layers_classes() -> None

Add and modify text layers relating to Class type cards.

Source code in src\templates\classes.py
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
def text_layers_classes(self) -> None:
    """Add and modify text layers relating to Class type cards."""

    # Add first static line
    self.line_layers.append(self.text_layer_ability)
    self.text.append(
        FormattedTextField(
            layer=self.text_layer_ability,
            contents=self.layout.class_lines[0]['text']
        ))

    # Add text fields for each line and class stage
    for i, line in enumerate(self.layout.class_lines[1:]):

        # Create a new ability line
        line_layer = self.text_layer_ability.duplicate()
        self.line_layers.append(line_layer)

        # Use existing stage divider or create new one
        stage = self.stage_group if i == 0 else self.stage_group.duplicate()
        cost, level = [*stage.artLayers][:2]
        self.stage_layers.append(stage)

        # Add text layers to be formatted
        self.text.extend([
            FormattedTextField(layer=line_layer, contents=line['text']),
            FormattedTextField(layer=cost, contents=f"{line['cost']}:"),
            TextField(layer=level, contents=f"Level {line['level']}")
        ])