| 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 |
|
| 103 |
1.1 |
|
| 106 |
1.1 |
|
| 107 |
1.1 2.2 |
|
| 109 |
1.1 |
|
| 114 |
1.1 |
|
| 115 |
1.1 |
|
| 116 |
1.1 |
|
| 120 |
1.1 2.2 |
|
| 121 |
1.1 2.2 |