Skip to content

GUI Overhaul with Dark Mode - #1145

Open
SmushyTaco wants to merge 8 commits into
FabricMC:masterfrom
SmushyTaco:master
Open

GUI Overhaul with Dark Mode#1145
SmushyTaco wants to merge 8 commits into
FabricMC:masterfrom
SmushyTaco:master

Conversation

@SmushyTaco

@SmushyTaco SmushyTaco commented Apr 30, 2026

Copy link
Copy Markdown

So we all know the current GUI:
image

It isn't the most user friendly GUI and it doesn't support dark mode either. This PR overhauls the GUI and supports dark mode through setting the system property fabric.loader.gui.darkMode to true.

Here's a demo of the GUI in both dark mode and the default light mode:

Final.GUI.Demo.mp4

GUI Overhaul with Dark Mode - Change Summary

This PR replaces the old raw-tree dependency error view with a structured, user-friendly Swing UI for mod resolution failures, while preserving the existing detailed/copyable error output for support and debugging.

High-level changes

  • Adds a redesigned dependency error screen with a clearer hierarchy:
    • suggested changes first
    • affected mods as clickable rows
    • per-mod detail pages
    • missing dependency overview
    • conflict overview for breaks entries
  • Adds FlatLaf as a shaded UI dependency using the no-natives artifact.
  • Adds light/dark mode support controlled by fabric.loader.gui.darkMode=true.
  • Adds macOS-specific FlatLaf variants when running on macOS/Darwin.
  • Adds bundled Minecraft and Java icons for special built-in dependency entries.
  • Adds structured dependency GUI data so the new UI does not parse localized human-readable resolver strings.

Dependency and build changes

  • build.gradle
    • Adds com.formdev:flatlaf:3.7.1:no-natives.
    • Relocates FlatLaf into net.fabricmc.loader.impl.lib.flatlaf in the shaded/fat jar.

Structured resolver data flow

The old UI only received rendered text. This PR adds a structured data path:

  1. ResultAnalyzer builds DependencyGuiData from the resolver result.
  2. ModResolver attaches that data to ModResolutionException.
  3. FabricLoaderImpl preserves the original exception as the cause when wrapping it.
  4. FabricGuiEntry finds the structured data in the exception chain and attaches it to FabricStatusTree.
  5. FabricMainWindow renders the structured data directly.

This avoids fragile parsing of strings like Install ..., requires ..., or localized resolver messages.

GUI changes

  • FabricMainWindow
    • Installs FlatLaf with fallback to system/cross-platform Swing look and feel if needed.
    • Adds the redesigned dependency UI.
    • Keeps the original tree view for non-dependency/general error screens (like a classtweaker using the wrong namespace).
    • Shows suggested changes at the top.
    • Shows affected mods as clickable cards with hover/pressed states.
    • Adds detail pages for each affected mod.
    • Automatically sizes the initial window high enough to show the full suggested changes section, capped by screen height.
    • Renders mod icons or a gray fallback icon.

Dependency/conflict model changes

  • FabricStatusTree

    • Adds DependencyGuiData.
    • Adds structured records for:
      • suggested changes
      • suggested-change detail lines
      • dependencies
      • affected mods
      • per-mod requirements
      • requirement kind: dependency or conflict
      • icon sources and icon bytes
  • ResultAnalyzer

    • Builds structured suggested changes, dependencies, affected mods, and per-mod requirements.
    • Preserves nested details for replacement suggestions that require compatibility with other active mods.
    • Maps FMJ breaks entries to GUI conflicts.
    • Collects icon source data for affected mods and dependency targets.
    • Serializes icon bytes for mod icons, including jar-in-jar candidates.

Error propagation changes

  • ModResolutionException

    • Carries optional DependencyGuiData.
  • ModResolver

    • Creates structured dependency GUI data from the failed resolver result.
  • FabricLoaderImpl

    • Preserves the ModResolutionException cause when creating the formatted incompatible-mod exception.
  • FabricGuiEntry

    • Searches the throwable cause chain for ModResolutionException and transfers its DependencyGuiData into the status tree.

Localization and resources

  • Adds English localization keys for the new dependency UI labels, descriptions, summaries, conflict text, and fallback error labels.
  • Adds bundled icons:
    • minecraft_x32.png
    • java_x32.png

Important behavior notes

  • The existing copy-error output remains detailed and SAT-solver-derived.
  • The redesigned UI is based on structured resolver data, not string parsing.
  • The UI treats FMJ breaks as user-facing “conflicts.”
  • The older/general tree UI remains available for non-dependency errors.
  • FlatLaf is included without native libraries (for this use case they just aren't needed).

@cputnam-a11y cputnam-a11y left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ig lgtm.

Comment thread src/main/java/net/fabricmc/loader/impl/gui/FabricMainWindow.java
@modmuss50

Copy link
Copy Markdown
Member

Im not keen on adding 3rd party deps, and this PR is basically impossible to review. Please at least provide some guideance to what you have changed in the PR description, the video is great advertising but doesnt make all of the details clear. There is a huge amount of code here that we need to be able to review, test and maintain.

Did you use AI tools to code this? There is nothing wrong inherently wrong if you did, we just need to take extra care when reviewing/testing it.

@SmushyTaco

SmushyTaco commented Apr 30, 2026

Copy link
Copy Markdown
Author

I understand that you're not keen on 3rd party dependencies, if I added FlatLaf just for dark mode with no redesign, justifying the added dependency would've been much more difficult to do.

I also understand that a big PR like this is very difficult to review. Under normal circumstances I would make smaller PRs to build up to this. I had a small window of free time to work on this so even though it isn’t ideal, I wanted to get as much done as possible and taking small steps takes time that I just don’t currently have for this.

Even if this PR doesn't get merged, later on parts of it can be taken and implemented in smaller steps by other people in the community who also want to see this change, so I figured that no harm could come from making the PR anyways since it'll bring it to people's attention.

And to answer your question, yes, I heavily used AI in an iterative manner to help make the majority of these changes, then manually reviewed and tested the results.

@kikugie

kikugie commented Apr 30, 2026

Copy link
Copy Markdown

Who's going to read the thousands lines of code produced by an LLM?

Comment thread build.gradle
Comment thread src/main/java/net/fabricmc/loader/impl/gui/FabricMainWindow.java Outdated

@Gamebuster19901 Gamebuster19901 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really shouldn't be creating images in code. Just load them from src/resources.

Same goes for the icons that were already present before this pr was made, with perhaps the exception of MissingIcon

Comment thread src/main/java/net/fabricmc/loader/impl/gui/FabricMainWindow.java Outdated
Comment thread src/main/java/net/fabricmc/loader/impl/gui/FabricMainWindow.java Outdated
Comment thread src/main/java/net/fabricmc/loader/impl/gui/FabricMainWindow.java Outdated
Comment thread src/main/java/net/fabricmc/loader/impl/gui/FabricMainWindow.java Outdated

@Gamebuster19901 Gamebuster19901 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused code

Comment thread src/main/java/net/fabricmc/loader/impl/gui/FabricMainWindow.java Outdated
Comment thread src/main/java/net/fabricmc/loader/impl/gui/FabricMainWindow.java Outdated
Comment on lines +618 to +643
Path tempDir = null;
Path tempPath = null;

try {
tempDir = Files.createTempDirectory("fabric-loader-icon");
tempPath = candidate.copyToDir(tempDir, true);
return readIconBytes(Collections.singletonList(tempPath), iconPath);
} catch (IOException | RuntimeException ignored) {
return null;
} finally {
if (tempPath != null) {
try {
Files.deleteIfExists(tempPath);
} catch (IOException ignored) {
// Ignore cleanup failure.
}
}

if (tempDir != null) {
try {
Files.deleteIfExists(tempDir);
} catch (IOException ignored) {
// Ignore cleanup failure.
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we copying jars to get the icon? If we can't read it, we can't read it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was done to get the icons of jar in jar mods, but you're right that it might be overkill.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants