Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
4b9b682
only blink selection highlighter on one token at a time
supersaiyansubtlety Oct 22, 2025
456caa7
remove initial delay from SelectionHighlightHandler
supersaiyansubtlety Oct 22, 2025
b9d24a6
make selection highlight blink count and blink delay configurable
supersaiyansubtlety Oct 22, 2025
b6dfe29
on opening new tab, wait for source to be set before highlighting fir…
supersaiyansubtlety Oct 23, 2025
c48331f
use IntegerRange instead of BoundedNumber
supersaiyansubtlety Oct 26, 2025
a1a5146
add gui for selection highlight configs
supersaiyansubtlety Oct 27, 2025
807faab
replace 'token' with 'entry' in strings explaining selection highligh…
supersaiyansubtlety Oct 27, 2025
d0c9b7b
replace blink dialog with radio selector
supersaiyansubtlety Nov 2, 2025
7c3ce8a
add elipses to blink delay menu item because it opens a dialog
supersaiyansubtlety Nov 2, 2025
1740578
fix GuiUtil::createIntConfigRadioMenu's action listener
supersaiyansubtlety Nov 18, 2025
cbd4bf0
Merge branch 'develop/2.8' into improve-selection-highlighting
supersaiyansubtlety Dec 12, 2025
c5c15fe
add POC MarkableScrollPane
supersaiyansubtlety Oct 26, 2025
dba33e4
rename and make abstract BaseEditorPanel -> AbstractEditorPanel
supersaiyansubtlety Oct 26, 2025
78af287
add markers for EditorPanel tokens
supersaiyansubtlety Oct 26, 2025
37d2b51
remove debug code from MarkableScrollPane
supersaiyansubtlety Oct 26, 2025
9d480d8
make markers for each token type toggleable
supersaiyansubtlety Oct 26, 2025
0af32ba
allow configuring markers via the gui
supersaiyansubtlety Oct 26, 2025
c1c1cdc
navigate to entry on marker click
supersaiyansubtlety Oct 27, 2025
d13e0e1
tweak paintersByPos order handling, cleanup
supersaiyansubtlety Oct 29, 2025
3133dc1
refactor+rename MarkablePredicate -> MarkerManager
supersaiyansubtlety Oct 29, 2025
0fee955
implement MarkableScrollPane.MarkerListener and use it to show entry …
supersaiyansubtlety Oct 30, 2025
2f18c90
cleanup and javadoc MarkableScrollPane
supersaiyansubtlety Oct 30, 2025
fb2f3cf
rename EntryMarkersSection.interactable -> tooltip and implement it
supersaiyansubtlety Oct 30, 2025
e872de2
don't mark constructors when only marking declarations
supersaiyansubtlety Oct 30, 2025
260db10
add separator between onlyMarkDeclarations and token type toggles
supersaiyansubtlety Oct 30, 2025
936b8ac
add new test input class
supersaiyansubtlety Oct 30, 2025
1ece814
make max markers per line configurable
supersaiyansubtlety Oct 31, 2025
3ec53af
eliminate marker overlap
supersaiyansubtlety Nov 1, 2025
a6bffe6
account for existing markers with differing positions but equal scale…
supersaiyansubtlety Nov 1, 2025
6b6d4a8
add WIP EditorPanel.MarkerTooltip
supersaiyansubtlety Nov 1, 2025
4db9659
fix marker max top + min bottom
supersaiyansubtlety Nov 1, 2025
6575a38
refine position and size of marker tooltip
supersaiyansubtlety Nov 1, 2025
708af16
pass marker area to repaint method
supersaiyansubtlety Nov 1, 2025
535f16b
fully repaint in component listener
supersaiyansubtlety Nov 1, 2025
8b1117a
attach a LineIndexer to DecompiledClassSource
supersaiyansubtlety Nov 1, 2025
236548a
remove isBounded check from SimpleSnippetPanel
supersaiyansubtlety Nov 1, 2025
553f5c7
don't refresh markers on componentShown (causes unecessary repaint)
supersaiyansubtlety Nov 2, 2025
00edab9
use GuiUtil's createIntConfigRadioMenu in EntryMarkersMenu
supersaiyansubtlety Nov 2, 2025
947301b
shift scaledPos down by this.areaY
supersaiyansubtlety Nov 9, 2025
87a406b
use jspecify annotations in new classes
supersaiyansubtlety Nov 18, 2025
e69af0e
checkstyle
supersaiyansubtlety Nov 18, 2025
c835918
copy and dispose graphics in MarkableScrollPane::paint
supersaiyansubtlety Dec 4, 2025
bf021ff
extract GuiUtil.TRANSPARENT
supersaiyansubtlety Dec 4, 2025
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
4 changes: 2 additions & 2 deletions enigma-swing/src/main/java/org/quiltmc/enigma/gui/Gui.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import org.quiltmc.enigma.gui.element.EditorTabbedPane;
import org.quiltmc.enigma.gui.element.MainWindow;
import org.quiltmc.enigma.gui.element.menu_bar.MenuBar;
import org.quiltmc.enigma.gui.panel.BaseEditorPanel;
import org.quiltmc.enigma.gui.panel.AbstractEditorPanel;
import org.quiltmc.enigma.gui.panel.EditorPanel;
import org.quiltmc.enigma.gui.panel.IdentifierPanel;
import org.quiltmc.enigma.gui.renderer.MessageListCellRenderer;
Expand Down Expand Up @@ -421,7 +421,7 @@ public void setMappingsFile(Path path) {
this.updateUiState();
}

public void showTokens(BaseEditorPanel editor, List<Token> tokens) {
public void showTokens(AbstractEditorPanel<?> editor, List<Token> tokens) {
if (tokens.size() > 1) {
this.openDocker(CallsTreeDocker.class);
this.controller.setTokenHandle(editor.getClassHandle().copy());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,15 @@ public class EditorConfig extends ReflectiveConfig {

@Comment("Settings for editors' entry tooltips.")
public final EntryTooltipsSection entryTooltips = new EntryTooltipsSection();

@Comment("Settings for markers on the right side of the editor indicating where different entry types are.")
public final EntryMarkersSection entryMarkers = new EntryMarkersSection();

@Comment(
"""
Settings for the editor's selection highlighting; used to highlight entries that have been navigated to.
The color of the highlight is defined per-theme (in themes/) by [syntax_pane_colors] > selection_highlight.\
"""
)
public final SelectionHighlightSection selectionHighlight = new SelectionHighlightSection();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.quiltmc.enigma.gui.config;

import org.quiltmc.config.api.ReflectiveConfig;
import org.quiltmc.config.api.annotations.Comment;
import org.quiltmc.config.api.annotations.IntegerRange;
import org.quiltmc.config.api.annotations.SerializedNameConvention;
import org.quiltmc.config.api.metadata.NamingSchemes;
import org.quiltmc.config.api.values.TrackedValue;

@SerializedNameConvention(NamingSchemes.SNAKE_CASE)
public class EntryMarkersSection extends ReflectiveConfig.Section {
public static final int MIN_MAX_MARKERS_PER_LINE = 0;
public static final int MAX_MAX_MARKERS_PER_LINE = 3;

@Comment("Whether markers should have tooltips showing their corresponding entries.")
public final TrackedValue<Boolean> tooltip = this.value(true);

@Comment("The maximum number of markers to show for a single line. Set to 0 to disable markers.")
@IntegerRange(min = MIN_MAX_MARKERS_PER_LINE, max = MAX_MAX_MARKERS_PER_LINE)
public final TrackedValue<Integer> maxMarkersPerLine = this.value(2);

@Comment("Whether only declaration entries should be marked.")
public final TrackedValue<Boolean> onlyMarkDeclarations = this.value(true);

@Comment("Whether obfuscated entries should be marked.")
public final TrackedValue<Boolean> markObfuscated = this.value(true);

@Comment("Whether fallback entries should be marked.")
public final TrackedValue<Boolean> markFallback = this.value(true);

@Comment("Whether proposed entries should be marked.")
public final TrackedValue<Boolean> markProposed = this.value(false);

@Comment("Whether deobfuscated entries should be marked.")
public final TrackedValue<Boolean> markDeobfuscated = this.value(false);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.quiltmc.enigma.gui.config;

import org.quiltmc.config.api.ReflectiveConfig;
import org.quiltmc.config.api.annotations.Comment;
import org.quiltmc.config.api.annotations.IntegerRange;
import org.quiltmc.config.api.annotations.SerializedNameConvention;
import org.quiltmc.config.api.metadata.NamingSchemes;
import org.quiltmc.config.api.values.TrackedValue;

@SerializedNameConvention(NamingSchemes.SNAKE_CASE)
public class SelectionHighlightSection extends ReflectiveConfig.Section {
public static final int MIN_BLINKS = 0;
public static final int MAX_BLINKS = 10;
public static final int MIN_BLINK_DELAY = 10;
public static final int MAX_BLINK_DELAY = 5000;

@Comment("The number of times the highlighting blinks. Set to 0 to disable highlighting.")
@IntegerRange(min = MIN_BLINKS, max = MAX_BLINKS)
public final TrackedValue<Integer> blinks = this.value(3);

@Comment("The milliseconds the highlighting should be on and then off when blinking.")
@IntegerRange(min = MIN_BLINK_DELAY, max = MAX_BLINK_DELAY)
public final TrackedValue<Integer> blinkDelay = this.value(200);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.util.Iterator;
import java.util.concurrent.CompletableFuture;
import javax.swing.JTabbedPane;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
Expand All @@ -41,52 +42,71 @@ public EditorTabbedPane(Gui gui) {

public EditorPanel openClass(ClassEntry entry) {
EditorPanel activeEditor = this.getActiveEditor();
EditorPanel entryEditor = this.editors.computeIfAbsent(entry, editing -> {
ClassHandle classHandle = this.gui.getController().getClassHandleProvider().openClass(editing);
if (classHandle == null) {
return null;
}

this.navigator = new NavigatorPanel(this.gui);
EditorPanel newEditor = new EditorPanel(this.gui, this.navigator);
newEditor.setClassHandle(classHandle);
this.openFiles.addTab(newEditor.getSimpleClassName(), newEditor.getUi());

ClosableTabTitlePane titlePane = new ClosableTabTitlePane(newEditor.getSimpleClassName(), newEditor.getFullClassName(), () -> this.closeEditor(newEditor));
this.openFiles.setTabComponentAt(this.openFiles.indexOfComponent(newEditor.getUi()), titlePane.getUi());
titlePane.setTabbedPane(this.openFiles);

newEditor.addListener(new EditorActionListener() {
@Override
public void onCursorReferenceChanged(EditorPanel editor, EntryReference<Entry<?>, Entry<?>> ref) {
if (editor == EditorTabbedPane.this.getActiveEditor()) {
EditorTabbedPane.this.gui.showCursorReference(ref);
}
}

@Override
public void onClassHandleChanged(EditorPanel editor, ClassEntry old, ClassHandle ch) {
EditorTabbedPane.this.editors.remove(old);
EditorTabbedPane.this.editors.put(ch.getRef(), editor);
final EditorPanel entryEditor;
final CompletableFuture<?> entryEditorReady;
{
final EditorPanel existingEditor = this.editors.get(entry);

if (existingEditor == null) {
ClassHandle classHandle = this.gui.getController().getClassHandleProvider().openClass(entry);
if (classHandle == null) {
entryEditor = null;
entryEditorReady = null;
} else {
this.navigator = new NavigatorPanel(this.gui);
final EditorPanel newEditor = new EditorPanel(this.gui, this.navigator);
entryEditorReady = newEditor.setClassHandle(classHandle);
this.openFiles.addTab(newEditor.getSimpleClassName(), newEditor.getUi());

ClosableTabTitlePane titlePane = new ClosableTabTitlePane(newEditor.getSimpleClassName(), newEditor.getFullClassName(), () -> this.closeEditor(newEditor));
this.openFiles.setTabComponentAt(this.openFiles.indexOfComponent(newEditor.getUi()), titlePane.getUi());
titlePane.setTabbedPane(this.openFiles);

newEditor.addListener(new EditorActionListener() {
@Override
public void onCursorReferenceChanged(EditorPanel editor, EntryReference<Entry<?>, Entry<?>> ref) {
if (editor == EditorTabbedPane.this.getActiveEditor()) {
EditorTabbedPane.this.gui.showCursorReference(ref);
}
}

@Override
public void onClassHandleChanged(EditorPanel editor, ClassEntry old, ClassHandle ch) {
EditorTabbedPane.this.editors.remove(old);
EditorTabbedPane.this.editors.put(ch.getRef(), editor);
}

@Override
public void onTitleChanged(EditorPanel editor, String title) {
titlePane.setText(editor.getSimpleClassName(), editor.getFullClassName());
}
});

putKeyBindAction(KeyBinds.EDITOR_CLOSE_TAB, newEditor.getEditor(), e -> this.closeEditor(newEditor));
putKeyBindAction(KeyBinds.ENTRY_NAVIGATOR_NEXT, newEditor.getEditor(), e -> newEditor.getNavigatorPanel().navigateDown());
putKeyBindAction(KeyBinds.ENTRY_NAVIGATOR_LAST, newEditor.getEditor(), e -> newEditor.getNavigatorPanel().navigateUp());

this.editors.put(entry, newEditor);

entryEditor = newEditor;
}

@Override
public void onTitleChanged(EditorPanel editor, String title) {
titlePane.setText(editor.getSimpleClassName(), editor.getFullClassName());
}
});

putKeyBindAction(KeyBinds.EDITOR_CLOSE_TAB, newEditor.getEditor(), e -> this.closeEditor(newEditor));
putKeyBindAction(KeyBinds.ENTRY_NAVIGATOR_NEXT, newEditor.getEditor(), e -> newEditor.getNavigatorPanel().navigateDown());
putKeyBindAction(KeyBinds.ENTRY_NAVIGATOR_LAST, newEditor.getEditor(), e -> newEditor.getNavigatorPanel().navigateUp());

return newEditor;
});
} else {
entryEditor = existingEditor;
entryEditorReady = null;
}
}

if (entryEditor != null && activeEditor != entryEditor) {
this.openFiles.setSelectedComponent(this.editors.get(entry).getUi());
this.openFiles.setSelectedComponent(entryEditor.getUi());
this.gui.updateStructure(entryEditor);
this.gui.showCursorReference(entryEditor.getCursorReference());

final Runnable showReference = () -> this.gui.showCursorReference(entryEditor.getCursorReference());
if (entryEditorReady == null) {
showReference.run();
} else {
entryEditorReady.thenRunAsync(showReference, SwingUtilities::invokeLater);
}
}

return entryEditor;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.quiltmc.enigma.gui.element;

import org.quiltmc.config.api.values.TrackedValue;
import org.quiltmc.enigma.gui.Gui;
import org.quiltmc.enigma.util.I18n;
import org.quiltmc.enigma.util.Utils;

import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import java.util.Optional;

public class IntRangeConfigMenuItem extends JMenuItem {
public static final String DIALOG_TITLE_TRANSLATION_KEY_SUFFIX = ".dialog_title";
public static final String DIALOG_EXPLANATION_TRANSLATION_KEY_SUFFIX = ".dialog_explanation";
private final TrackedValue<Integer> config;

private final String translationKey;

/**
* Constructs a menu item that, when clicked, prompts the user for an integer between the passed {@code min} and
* {@code max} using a dialog.<br>
* The menu item will be kept in sync with the passed {@code config}.
*
* @param gui the gui
* @param config the config value to sync with
* @param min the minimum allowed value;
* this should coincide with any minimum imposed on the passed {@code config}
* @param max the maximum allowed value
* this should coincide with any maximum imposed on the passed {@code config}
* @param rootTranslationKey a translation key for deriving translations as follows:
* <ul>
* <li> this component's text: the unmodified key
* <li> the title of the dialog: the key with
* {@value #DIALOG_TITLE_TRANSLATION_KEY_SUFFIX} appended
* <li> the explanation of the dialog: the key with
* {@value #DIALOG_EXPLANATION_TRANSLATION_KEY_SUFFIX} appended
* </ul>
*/
public IntRangeConfigMenuItem(Gui gui, TrackedValue<Integer> config, int min, int max, String rootTranslationKey) {
this(
gui, config, min, max, rootTranslationKey,
rootTranslationKey + DIALOG_TITLE_TRANSLATION_KEY_SUFFIX,
rootTranslationKey + DIALOG_EXPLANATION_TRANSLATION_KEY_SUFFIX
);
}

private IntRangeConfigMenuItem(
Gui gui, TrackedValue<Integer> config, int min, int max,
String translationKey, String dialogTitleTranslationKey, String dialogExplanationTranslationKey
) {
this.config = config;
this.translationKey = translationKey;

this.addActionListener(e ->
getRangedIntInput(
gui, config.value(), min, max,
I18n.translate(dialogTitleTranslationKey),
I18n.translate(dialogExplanationTranslationKey)
)
.ifPresent(input -> {
if (!input.equals(config.value())) {
config.setValue(input);
}
})
);

config.registerCallback(updated -> {
this.retranslate();
});
}

public void retranslate() {
this.setText(I18n.translateFormatted(this.translationKey, this.config.value()));
}

private static Optional<Integer> getRangedIntInput(
Gui gui, int initialValue, int min, int max, String title, String explanation
) {
final String prompt = I18n.translateFormatted("prompt.input.int_range", min, max);
final String input = (String) JOptionPane.showInputDialog(
gui.getFrame(),
explanation + "\n" + prompt,
title,
JOptionPane.QUESTION_MESSAGE, null, null, initialValue
);

if (input != null) {
try {
return Optional.of(Utils.clamp(Integer.parseInt(input), min, max));
} catch (NumberFormatException e) {
return Optional.empty();
}
} else {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import javax.swing.JMenu;

public class AbstractEnigmaMenu extends JMenu implements EnigmaMenu {
public abstract class AbstractEnigmaMenu extends JMenu implements EnigmaMenu {
protected final Gui gui;

protected AbstractEnigmaMenu(Gui gui) {
Expand Down
Loading