Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
afda4c8
Replace generic Ref with game-specific pointer types
ShadelessFox Mar 1, 2026
8770796
Slightly optimize streaming graph reading logic
ShadelessFox Feb 27, 2026
2f7e531
Implement BinaryWriter for files and bytes
ShadelessFox Feb 27, 2026
ba821a8
Implement a database that stores all incoming object links
ShadelessFox Feb 27, 2026
c427d5d
Fix conflicts after pull
ShadelessFox Feb 28, 2026
6b196c3
Add basic tool panel for visualizing links
ShadelessFox Feb 28, 2026
d068e47
Add support for font styles to styled component
ShadelessFox Feb 28, 2026
5e4e89c
Add "Show Links" action for object ID holders
ShadelessFox Feb 28, 2026
9fc318c
Move link database to the hfw module
ShadelessFox Feb 28, 2026
dbfbe50
Improve responsiveness of structures tree (and the link tree itself)
ShadelessFox Feb 28, 2026
f803356
Fix conflicts after pull
ShadelessFox Mar 3, 2026
47576c7
Build the database from within the links view so it's finally usable
ShadelessFox Mar 6, 2026
02c4d5b
Reveal the links panel when using the "Show Usage" action
ShadelessFox Mar 6, 2026
52c3160
Store links database in the app config directory
ShadelessFox Mar 6, 2026
95c64cd
Small cleanup
ShadelessFox Mar 6, 2026
26938ff
Gracefully handle database creation/loading errors
ShadelessFox Mar 7, 2026
609d754
Draw placeholder text over link/bookmark trees
ShadelessFox Mar 7, 2026
c594b38
Replace term "link" with "usage"
ShadelessFox Mar 7, 2026
e186677
Fix `drawCenteredString` not centering text vertically when multiline
ShadelessFox Mar 7, 2026
5f52943
Code cleanup and pluralize "results" text
ShadelessFox Mar 7, 2026
dc61020
Add missing `@Singleton` and mark `@Inject` constructors package-private
ShadelessFox Mar 7, 2026
f4b35a1
Review and clean up BinaryWriter API and implementations
ShadelessFox Mar 7, 2026
fb75505
Add extra logging
ShadelessFox Mar 7, 2026
da4d027
Add extra logging on top of extra logging
ShadelessFox Mar 7, 2026
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
1 change: 1 addition & 0 deletions odradek-app/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
provides sh.adelessfox.odradek.ui.actions.Action with
sh.adelessfox.odradek.app.ui.component.bookmarks.menu.ToggleBookmarkAction,
sh.adelessfox.odradek.app.ui.component.bookmarks.menu.RenameBookmarkAction,
sh.adelessfox.odradek.app.ui.component.usages.menu.ShowUsagesAction,
sh.adelessfox.odradek.app.ui.menu.main.MainMenu.File,
sh.adelessfox.odradek.app.ui.menu.main.MainMenu.Edit,
sh.adelessfox.odradek.app.ui.menu.main.MainMenu.Help,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@
import sh.adelessfox.odradek.app.ui.menu.main.MainMenu;
import sh.adelessfox.odradek.app.ui.settings.Settings;
import sh.adelessfox.odradek.app.ui.settings.SettingsEvent;
import sh.adelessfox.odradek.event.EventBus;
import sh.adelessfox.odradek.game.Game;
import sh.adelessfox.odradek.game.hfw.game.ForbiddenWestGame;
import sh.adelessfox.odradek.ui.actions.Actions;
import sh.adelessfox.odradek.ui.data.DataContext;
import sh.adelessfox.odradek.ui.editors.EditorManager;
import sh.adelessfox.odradek.util.OS;

import javax.swing.*;
import java.io.IOException;
import java.nio.file.Path;

public final class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
Expand All @@ -44,8 +47,10 @@ public static synchronized void start(ApplicationParameters params) throws IOExc
}

var game = (ForbiddenWestGame) Game.load(params.sourcePath());
var config = determineConfigPath("Odradek");
var component = DaggerApplicationComponent.builder()
.game(game)
.config(config)
.build();

application = new Application(component, params);
Expand Down Expand Up @@ -114,6 +119,18 @@ private static void saveFrameSettings(Settings settings, JFrame frame) {
));
}

private static Path determineConfigPath(String identifier) {
String userHome = System.getProperty("user.home");
if (userHome == null) {
throw new IllegalStateException("Unable to determine user home directory");
}
return switch (OS.name()) {
case WINDOWS -> Path.of(userHome, "AppData", "Local", identifier);
case MACOS -> Path.of(userHome, "Library", "Application Support", identifier);
case LINUX -> Path.of(userHome, ".config", identifier);
};
}

public ForbiddenWestGame game() {
return component.game();
}
Expand All @@ -126,6 +143,10 @@ public Bookmarks bookmarks() {
return component.bookmarks();
}

public EventBus events() {
return component.events();
}

public boolean isDebugMode() {
return parameters.enableDebugMode();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import dagger.BindsInstance;
import dagger.Component;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import sh.adelessfox.odradek.app.ui.bookmarks.Bookmarks;
import sh.adelessfox.odradek.app.ui.component.main.MainPresenter;
Expand All @@ -11,6 +12,8 @@
import sh.adelessfox.odradek.game.hfw.game.ForbiddenWestGame;
import sh.adelessfox.odradek.ui.editors.EditorManager;

import java.nio.file.Path;

@Singleton
@Component(modules = {ApplicationModule.class, SettingsModule.class})
interface ApplicationComponent {
Expand All @@ -22,15 +25,18 @@ interface ApplicationComponent {

Bookmarks bookmarks();

ForbiddenWestGame game();

EventBus events();

ForbiddenWestGame game();

@Component.Builder
interface Builder {
@BindsInstance
Builder game(ForbiddenWestGame game);

@BindsInstance
Builder config(@Named("config") Path config);

@SuppressWarnings("ClassEscapesDefinedScope")
ApplicationComponent build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public final class Bookmarks {
private final Map<ObjectId, Bookmark> bookmarks = new LinkedHashMap<>();

@Inject
public Bookmarks(EventBus eventBus) {
Bookmarks(EventBus eventBus) {
this.eventBus = eventBus;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sh.adelessfox.odradek.app.ui.component.bookmarks;

import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import sh.adelessfox.odradek.app.ui.Application;
import sh.adelessfox.odradek.app.ui.bookmarks.Bookmark;
import sh.adelessfox.odradek.app.ui.bookmarks.BookmarkEvent;
Expand All @@ -17,14 +18,15 @@

import javax.swing.*;

@Singleton
public class BookmarkToolPanel implements ToolPanel {
private final Bookmarks repository;
private final EventBus eventBus;

private StructuredTree<BookmarkStructure> tree;

@Inject
public BookmarkToolPanel(Bookmarks repository, EventBus eventBus) {
BookmarkToolPanel(Bookmarks repository, EventBus eventBus) {
this.repository = repository;
this.eventBus = eventBus;

Expand All @@ -41,6 +43,7 @@ public JComponent createComponent() {
tree = new StructuredTree<>(new BookmarkStructure.Root(repository));
tree.setShowsRootHandles(true);
tree.setLabelProvider(new BookmarkLabelProvider());
tree.setPlaceholderText("No bookmarks\n\nRight-click on an object to bookmark it");
tree.addActionListener(TreeActionListener.treePathClickedAdapter(event -> {
var component = event.getLastPathComponent();
if (component instanceof BookmarkStructure.Bookmark bookmark) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class GraphPresenter implements Presenter<GraphView> {
private final GraphView view;

@Inject
public GraphPresenter(
GraphPresenter(
GraphView view,
EventBus eventBus
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package sh.adelessfox.odradek.app.ui.component.graph;

import sh.adelessfox.odradek.game.Game;
import sh.adelessfox.odradek.game.ObjectHolder;
import sh.adelessfox.odradek.game.ObjectId;
import sh.adelessfox.odradek.game.ObjectIdHolder;
import sh.adelessfox.odradek.game.ObjectSupplier;
import sh.adelessfox.odradek.game.hfw.rtti.HorizonForbiddenWest.StreamingGroupData;
import sh.adelessfox.odradek.game.hfw.storage.StreamingGraphResource;
import sh.adelessfox.odradek.rtti.ClassTypeInfo;
Expand Down Expand Up @@ -358,7 +358,7 @@ record GroupObject(
StreamingGraphResource graph,
StreamingGroupData group,
int index
) implements GraphStructure, ObjectHolder, ObjectIdHolder {
) implements GraphStructure, ObjectSupplier, ObjectIdHolder {
@Override
public TypedObject readObject(Game game) throws IOException {
return game.readObject(group.groupID(), index);
Expand All @@ -369,11 +369,6 @@ public ClassTypeInfo objectType() {
return graph.types().get(group.typeStart() + index);
}

@Override
public String objectName() {
return "%s_%s_%s".formatted(objectType().name(), group.groupID(), index);
}

@Override
public ObjectId objectId() {
return new ObjectId(group.groupID(), index);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import org.slf4j.LoggerFactory;
import sh.adelessfox.odradek.app.ui.component.PreviewManager;
import sh.adelessfox.odradek.app.ui.component.common.View;
import sh.adelessfox.odradek.app.ui.component.main.MainEvent;
import sh.adelessfox.odradek.app.ui.menu.graph.GraphMenu;
import sh.adelessfox.odradek.event.EventBus;
import sh.adelessfox.odradek.game.ObjectHolder;
import sh.adelessfox.odradek.game.ObjectId;
import sh.adelessfox.odradek.game.ObjectSupplier;
import sh.adelessfox.odradek.game.hfw.game.ForbiddenWestGame;
import sh.adelessfox.odradek.rtti.TypeInfo;
import sh.adelessfox.odradek.rtti.data.TypedObject;
Expand Down Expand Up @@ -40,7 +42,7 @@ public class GraphView implements View<JComponent>, ToolPanel {
private final ValidationPopup filterValidationPopup;

@Inject
public GraphView(EventBus eventBus, ForbiddenWestGame game) {
GraphView(EventBus eventBus, ForbiddenWestGame game) {
this.eventBus = eventBus;
this.game = game;

Expand All @@ -58,7 +60,8 @@ public void actionPerformed(ActionEvent e) {
tree = createGraphTree();

var treeScrollPane = new JScrollPane(tree);
treeScrollPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("ctrl F"), "focus-in");
treeScrollPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.put(KeyStroke.getKeyStroke("ctrl F"), "focus-in");
treeScrollPane.getActionMap().put("focus-in", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
Expand Down Expand Up @@ -183,17 +186,17 @@ public Optional<Icon> getIcon(GraphStructure element) {
tree.addActionListener(TreeActionListener.treePathClickedAdapter(event -> {
var component = event.getLastPathComponent();
if (component instanceof GraphStructure.GroupObject groupObject) {
eventBus.publish(new GraphViewEvent.ShowObject(
eventBus.publish(new MainEvent.ShowObject(new ObjectId(
groupObject.group().groupID(),
groupObject.index()
groupObject.index())
));
}
}));

PreviewManager.install(tree, game, new PreviewManager.PreviewObjectProvider() {
@Override
public Optional<TypedObject> getObject(JTree tree, Object value) {
var holder = (ObjectHolder) value;
var holder = (ObjectSupplier) value;
try {
return Optional.of(holder.readObject(game));
} catch (IOException e) {
Expand All @@ -204,7 +207,7 @@ public Optional<TypedObject> getObject(JTree tree, Object value) {

@Override
public Optional<TypeInfo> getType(JTree tree, Object value) {
if (value instanceof ObjectHolder provider) {
if (value instanceof ObjectSupplier provider) {
return Optional.of(provider.objectType());
}
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import sh.adelessfox.odradek.event.Event;

public sealed interface GraphViewEvent extends Event {
record UpdateFilter(String query, boolean matchCase, boolean matchWholeWord) implements GraphViewEvent {}

record ShowObject(int groupId, int objectIndex) implements GraphViewEvent {}
record UpdateFilter(String query, boolean matchCase, boolean matchWholeWord) implements GraphViewEvent {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package sh.adelessfox.odradek.app.ui.component.main;

import sh.adelessfox.odradek.event.Event;
import sh.adelessfox.odradek.game.ObjectId;

public sealed interface MainEvent extends Event {
record ShowPanel(String id) implements MainEvent {
}

record ShowObject(ObjectId objectId) implements MainEvent {
}

record ShowLinks(ObjectId objectId) implements MainEvent {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import sh.adelessfox.odradek.app.ui.component.common.Presenter;
import sh.adelessfox.odradek.app.ui.component.graph.GraphViewEvent;
import sh.adelessfox.odradek.app.ui.editors.ObjectEditorInput;
import sh.adelessfox.odradek.app.ui.editors.ObjectEditorInputLazy;
import sh.adelessfox.odradek.app.ui.settings.Settings;
import sh.adelessfox.odradek.app.ui.settings.SettingsEvent;
import sh.adelessfox.odradek.event.EventBus;
import sh.adelessfox.odradek.game.ObjectId;
import sh.adelessfox.odradek.game.ObjectIdHolder;
import sh.adelessfox.odradek.ui.editors.Editor;
import sh.adelessfox.odradek.ui.editors.EditorManager;
import sh.adelessfox.odradek.ui.editors.stack.EditorStackContainer;
Expand All @@ -22,15 +21,15 @@ public class MainPresenter implements Presenter<MainView> {
private final EditorManager editorManager;

@Inject
public MainPresenter(
MainPresenter(
EditorManager editorManager,
MainView view,
EventBus eventBus
) {
this.view = view;
this.editorManager = editorManager;

eventBus.subscribe(GraphViewEvent.ShowObject.class, event -> openObject(event.groupId(), event.objectIndex()));
eventBus.subscribe(MainEvent.ShowObject.class, event -> openObject(event.objectId()));
eventBus.subscribe(SettingsEvent.class, event -> {
switch (event) {
case SettingsEvent.AfterLoad(var settings) -> loadEditors(settings);
Expand All @@ -44,8 +43,8 @@ public MainView getView() {
return view;
}

private void openObject(int groupId, int objectIndex) {
editorManager.openEditor(new ObjectEditorInputLazy(groupId, objectIndex));
private void openObject(ObjectId objectId) {
editorManager.openEditor(new ObjectEditorInputLazy(objectId));
}

private void loadEditors(Settings settings) {
Expand Down Expand Up @@ -95,10 +94,8 @@ private Settings.EditorState saveContainer(EditorStackContainer container) {
if (editor == selected) {
selection = objects.size();
}
switch (editor.getInput()) {
case ObjectEditorInput i -> objects.add(new ObjectId(i.groupId(), i.objectIndex()));
case ObjectEditorInputLazy i -> objects.add(new ObjectId(i.groupId(), i.objectIndex()));
default -> { /* do nothing*/ }
if (editor.getInput() instanceof ObjectIdHolder holder) {
objects.add(holder.objectId());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import sh.adelessfox.odradek.app.ui.component.bookmarks.BookmarkToolPanel;
import sh.adelessfox.odradek.app.ui.component.common.View;
import sh.adelessfox.odradek.app.ui.component.graph.GraphPresenter;
import sh.adelessfox.odradek.app.ui.component.usages.UsagesToolPanel;
import sh.adelessfox.odradek.event.EventBus;
import sh.adelessfox.odradek.ui.components.tool.ToolPanelContainer;
import sh.adelessfox.odradek.ui.editors.EditorManager;
import sh.adelessfox.odradek.ui.util.Fugue;
Expand All @@ -13,19 +15,28 @@

@Singleton
public class MainView implements View<JComponent> {
public static final String GRAPH_PANEL_ID = "graph";
public static final String BOOKMARKS_PANEL_ID = "bookmarks";
public static final String USAGES_PANEL_ID = "usages";

private final ToolPanelContainer root;

@Inject
public MainView(
MainView(
GraphPresenter graphPresenter,
BookmarkToolPanel bookmarkPanel,
EditorManager editorManager
UsagesToolPanel usagesPanel,
EditorManager editorManager,
EventBus eventBus
) {
root = new ToolPanelContainer(ToolPanelContainer.Placement.LEFT);
root.addPrimaryPanel("Graph", Fugue.getIcon("blue-document"), graphPresenter.getView());
root.addSecondaryPanel("Bookmarks", Fugue.getIcon("blue-document-bookmark"), bookmarkPanel);
root.addPrimaryPanel(GRAPH_PANEL_ID, "Graph", Fugue.getIcon("blue-document"), graphPresenter.getView());
root.addSecondaryPanel(BOOKMARKS_PANEL_ID, "Bookmarks", Fugue.getIcon("blue-document-bookmark"), bookmarkPanel);
root.addSecondaryPanel(USAGES_PANEL_ID, "Usages", Fugue.getIcon("magnifier-left"), usagesPanel);
root.setContent(editorManager.getRoot());
root.showPanel(graphPresenter.getView());
root.showPanel(GRAPH_PANEL_ID);

eventBus.subscribe(MainEvent.ShowPanel.class, event -> root.showPanel(event.id()));
}

@Override
Expand Down
Loading