PaletteViewerPanel.java

1
package com.rox.ppu.debug;
2
3
import com.rox.ppu.NesPalette;
4
import com.rox.ppu.PPU;
5
6
import javax.swing.JLabel;
7
import javax.swing.JPanel;
8
import javax.swing.SwingConstants;
9
import java.awt.BorderLayout;
10
import java.awt.Color;
11
import java.awt.Dimension;
12
import java.awt.GridLayout;
13
14
/**
15
 * All 8 of the PPU's palettes ({@code $3F00-$3F1F}) at once, one row per palette (background palettes
16
 * 0-3, then sprite palettes 0-3 - the same 8 names as {@link ChrViewerPanel}'s selector), each row
17
 * labelled to its left and gapped from its neighbours for readability. A header row along the top
18
 * numbers the 4 swatch columns, aligned with the swatches beneath it - both the header and every row
19
 * lay their 4 columns out with a plain {@link GridLayout} that always fills 100% of its available
20
 * width, rather than one of them centering a scaled image within its own space, so the columns can't
21
 * drift out of alignment regardless of how much extra width/height the sidebar ends up giving this
22
 * panel. Colours are resolved live from {@link PPU#paletteSnapshot()} through {@code NesPalette} via
23
 * {@link #refresh()}, called once per frame by {@link PpuDebugFrame}'s timer. Swatch 0 of each Sprite
24
 * row mirrors swatch 0 of its corresponding Background row (Sprite 1's colour 0 is Background 1's, not
25
 * Background 0's) - real hardware's backdrop-mirror quirk pairs each sprite palette with its
26
 * same-numbered background palette, not all 8 with a single shared entry (see
27
 * {@link PPU#paletteSnapshot()}'s own documentation).
28
 */
29
final class PaletteViewerPanel extends JPanel {
30
    private static final int COLORS_PER_PALETTE = 4;
31
    private static final int SWATCH_PX = 24;
32
    private static final int ROW_GAP_PX = 8;
33
    private static final float LABEL_FONT_SIZE = 24f;
34
    private static final int SWATCHES_WIDTH_PX = COLORS_PER_PALETTE * SWATCH_PX;
35
36
    private final PPU ppu;
37
    private final int labelColumnPx;
38
    private final PaletteRow[] rows = new PaletteRow[ChrViewerPanel.PALETTE_NAMES.length];
39
40
    PaletteViewerPanel(final PPU ppu){
41
        this.ppu = ppu;
42
        this.labelColumnPx = widestLabelWidth();
43
44
        setLayout(new BorderLayout());
45
        add(header(), BorderLayout.NORTH);
46
47
        final JPanel rowsPanel = new JPanel(new GridLayout(rows.length, 1, 0, ROW_GAP_PX));
48
        for (int paletteIndex = 0; paletteIndex < rows.length; paletteIndex++){
49
            rows[paletteIndex] = new PaletteRow(ChrViewerPanel.PALETTE_NAMES[paletteIndex]);
50
            rowsPanel.add(rows[paletteIndex]);
51
        }
52
        add(rowsPanel, BorderLayout.CENTER);
53
54
        refresh();
55
    }
56
57
    /** Pulls the PPU's current palette RAM into every row's swatches - called once per frame, not on every repaint, since colours only change when the game writes new ones. */
58
    void refresh(){
59
        final int[] paletteSnapshot = ppu.paletteSnapshot();
60
        for (int paletteIndex = 0; paletteIndex < rows.length; paletteIndex++){
61
            rows[paletteIndex].refresh(paletteSnapshot, paletteIndex * COLORS_PER_PALETTE);
62
        }
63
    }
64
65
    /** Width (in px) of the widest palette name at {@link #LABEL_FONT_SIZE} - keeps every row's label column, and the header's spacer above them, the same width so the swatch columns line up. */
66
    private static int widestLabelWidth(){
67
        final JLabel measuring = new JLabel();
68
        measuring.setFont(measuring.getFont().deriveFont(LABEL_FONT_SIZE));
69
        int widest = 0;
70
        for (final String name : ChrViewerPanel.PALETTE_NAMES){
71
            measuring.setText(name);
72
            widest = Math.max(widest, measuring.getPreferredSize().width);
73
        }
74
        return widest;
75
    }
76
77
    /** A blank spacer matching the row labels' width, plus swatch-column numbers 0-3 above the swatches. */
78
    private JPanel header(){
79
        final JPanel spacer = new JPanel();
80
        spacer.setPreferredSize(new Dimension(labelColumnPx, 1));
81
82
        final JPanel numbers = new JPanel(new GridLayout(1, COLORS_PER_PALETTE));
83
        numbers.setPreferredSize(new Dimension(SWATCHES_WIDTH_PX, 1));
84
        for (int color = 0; color < COLORS_PER_PALETTE; color++){
85
            final JLabel number = new JLabel(String.valueOf(color), SwingConstants.CENTER);
86
            number.setFont(number.getFont().deriveFont(LABEL_FONT_SIZE));
87
            numbers.add(number);
88
        }
89
90
        final JPanel header = new JPanel(new BorderLayout());
91
        header.add(spacer, BorderLayout.WEST);
92
        header.add(numbers, BorderLayout.CENTER);
93
        return header;
94
    }
95
96
    /** One palette's name label plus its 4-swatch strip, side by side. */
97
    private final class PaletteRow extends JPanel {
98
        private final JPanel[] swatches = new JPanel[COLORS_PER_PALETTE];
99
100
        PaletteRow(final String name){
101
            final JLabel label = new JLabel(name);
102 1 1. <init> : removed call to javax/swing/JLabel::setFont → NO_COVERAGE
            label.setFont(label.getFont().deriveFont(LABEL_FONT_SIZE));
103 1 1. <init> : removed call to javax/swing/JLabel::setPreferredSize → NO_COVERAGE
            label.setPreferredSize(new Dimension(labelColumnPx, label.getPreferredSize().height));
104
105
            final JPanel swatchesPanel = new JPanel(new GridLayout(1, COLORS_PER_PALETTE));
106 1 1. <init> : removed call to javax/swing/JPanel::setPreferredSize → NO_COVERAGE
            swatchesPanel.setPreferredSize(new Dimension(SWATCHES_WIDTH_PX, SWATCH_PX));
107 2 1. <init> : changed conditional boundary → NO_COVERAGE
2. <init> : negated conditional → NO_COVERAGE
            for (int color = 0; color < COLORS_PER_PALETTE; color++){
108
                final JPanel swatch = new JPanel();
109 1 1. <init> : removed call to javax/swing/JPanel::setOpaque → NO_COVERAGE
                swatch.setOpaque(true);
110
                swatches[color] = swatch;
111
                swatchesPanel.add(swatch);
112
            }
113
114 1 1. <init> : removed call to com/rox/ppu/debug/PaletteViewerPanel$PaletteRow::setLayout → NO_COVERAGE
            setLayout(new BorderLayout());
115 1 1. <init> : removed call to com/rox/ppu/debug/PaletteViewerPanel$PaletteRow::add → NO_COVERAGE
            add(label, BorderLayout.WEST);
116 1 1. <init> : removed call to com/rox/ppu/debug/PaletteViewerPanel$PaletteRow::add → NO_COVERAGE
            add(swatchesPanel, BorderLayout.CENTER);
117
        }
118
119
        void refresh(final int[] paletteSnapshot, final int base){
120 2 1. refresh : negated conditional → NO_COVERAGE
2. refresh : changed conditional boundary → NO_COVERAGE
            for (int color = 0; color < COLORS_PER_PALETTE; color++){
121 2 1. refresh : removed call to javax/swing/JPanel::setBackground → NO_COVERAGE
2. refresh : Replaced integer addition with subtraction → NO_COVERAGE
                swatches[color].setBackground(new Color(NesPalette.rgb(paletteSnapshot[base + color])));
122
            }
123
        }
124
    }
125
}

Mutations

102

1.1
Location : <init>
Killed by : none
removed call to javax/swing/JLabel::setFont → NO_COVERAGE

103

1.1
Location : <init>
Killed by : none
removed call to javax/swing/JLabel::setPreferredSize → NO_COVERAGE

106

1.1
Location : <init>
Killed by : none
removed call to javax/swing/JPanel::setPreferredSize → NO_COVERAGE

107

1.1
Location : <init>
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : <init>
Killed by : none
negated conditional → NO_COVERAGE

109

1.1
Location : <init>
Killed by : none
removed call to javax/swing/JPanel::setOpaque → NO_COVERAGE

114

1.1
Location : <init>
Killed by : none
removed call to com/rox/ppu/debug/PaletteViewerPanel$PaletteRow::setLayout → NO_COVERAGE

115

1.1
Location : <init>
Killed by : none
removed call to com/rox/ppu/debug/PaletteViewerPanel$PaletteRow::add → NO_COVERAGE

116

1.1
Location : <init>
Killed by : none
removed call to com/rox/ppu/debug/PaletteViewerPanel$PaletteRow::add → NO_COVERAGE

120

1.1
Location : refresh
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : refresh
Killed by : none
changed conditional boundary → NO_COVERAGE

121

1.1
Location : refresh
Killed by : none
removed call to javax/swing/JPanel::setBackground → NO_COVERAGE

2.2
Location : refresh
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.22.1