Skip to content

Commit

Permalink
Core File: Lazy load contents of the value panel
Browse files Browse the repository at this point in the history
This is kind of a downgrade from what we have had before. We no longer
use a progress monitor to show the progress, but it won't at least
grab the focus by showing a modal dialog every time a mesh is opened.
  • Loading branch information
ShadelessFox committed May 8, 2024
1 parent f99ff73 commit e5c0547
Show file tree
Hide file tree
Showing 18 changed files with 346 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.shade.decima.ui.data.registry.ValueHandlerRegistration.Selector;
import com.shade.decima.ui.data.registry.ValueHandlerRegistration.Type;
import com.shade.decima.ui.data.registry.ValueViewerRegistration;
import com.shade.platform.model.runtime.ProgressMonitor;
import com.shade.util.NotNull;

import javax.swing.*;
Expand Down Expand Up @@ -41,8 +42,8 @@ public JComponent createComponent() {

@SuppressWarnings("unchecked")
@Override
public void refresh(@NotNull JComponent component, @NotNull ValueController<?> controller) {
public void refresh(@NotNull ProgressMonitor monitor, @NotNull JComponent component, @NotNull ValueController<?> controller) {
final ModelViewerPanel panel = (ModelViewerPanel) component;
panel.setController((ValueController<RTTIObject>) controller);
panel.refresh(monitor, (ValueController<RTTIObject>) controller);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
import com.shade.decima.ui.menu.MenuConstants;
import com.shade.platform.model.Disposable;
import com.shade.platform.model.data.DataKey;
import com.shade.platform.model.runtime.ProgressMonitor;
import com.shade.platform.ui.UIColor;
import com.shade.platform.ui.dialogs.ProgressDialog;
import com.shade.platform.ui.menus.MenuManager;
import com.shade.util.NotNull;
import com.shade.util.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -125,19 +126,16 @@ public void dispose() {
controller = null;
}

public void setController(@Nullable ValueController<RTTIObject> controller) {
public void refresh(@NotNull ProgressMonitor monitor, @Nullable ValueController<RTTIObject> controller) {
if (this.controller != controller) {
final ValueController<RTTIObject> oldController = this.controller;

this.controller = controller;

firePropertyChange("controller", oldController, controller);

updatePreview();
SwingUtilities.invokeLater(() -> firePropertyChange("controller", oldController, controller));
updatePreview(monitor);
}
}

private void updatePreview() {
private void updatePreview(@NotNull ProgressMonitor monitor) {
if (viewport == null) {
return;
}
Expand All @@ -146,16 +144,20 @@ private void updatePreview() {

if (controller != null) {
try {
node = ProgressDialog
.showProgressDialog(null, "Loading model", monitor -> SceneSerializer.serialize(monitor, controller))
.orElse(null);
node = SceneSerializer.serialize(monitor, controller);
} catch (Exception e) {
log.debug("Can't load preview for model of type {}", controller.getValueType(), e);
}
}

if (node != null) {
viewport.setModel(new NodeModel(node, viewport));
final Node finalNode = node;
SwingUtilities.invokeLater(() -> {
final ModelViewport viewport = this.viewport;
if (viewport != null) {
viewport.setModel(new NodeModel(finalNode, viewport));
}
});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ private static void serializeRegularSkinnedMeshResource(
final String uuid = RTTIUtils.uuidToString(object.get("ObjectUUID"));

if (context.meshes.containsKey(uuid)) {
log.debug("Reusing existing mesh for {}", uuid);
log.trace("Reusing existing mesh for {}", uuid);
node.setMesh(context.meshes.get(uuid));
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ public void paintGL() {
final int width = (int) (getWidth() * scaleFactor);
final int height = (int) (getHeight() * scaleFactor);

if (width <= 0 || height <= 0) {
return;
}

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, width, height);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.shade.decima.ui.data.registry.ValueHandlerRegistration.Selector;
import com.shade.decima.ui.data.registry.ValueHandlerRegistration.Type;
import com.shade.decima.ui.data.registry.ValueViewerRegistration;
import com.shade.platform.model.runtime.ProgressMonitor;
import com.shade.util.NotNull;

import javax.swing.*;
Expand All @@ -22,7 +23,7 @@ public JComponent createComponent() {

@SuppressWarnings("unchecked")
@Override
public void refresh(@NotNull JComponent component, @NotNull ValueController<?> controller) {
public void refresh(@NotNull ProgressMonitor monitor, @NotNull JComponent component, @NotNull ValueController<?> controller) {
((ShaderViewerPanel) component).setInput((ValueController<RTTIObject>) controller);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.shade.decima.ui.data.viewer.texture.reader.ImageReaderProvider;
import com.shade.decima.ui.data.viewer.texture.util.Channel;
import com.shade.decima.ui.editor.core.CoreEditor;
import com.shade.platform.model.runtime.ProgressMonitor;
import com.shade.platform.model.util.IOUtils;
import com.shade.util.NotNull;
import com.shade.util.Nullable;
Expand Down Expand Up @@ -59,18 +60,18 @@ public JComponent createComponent() {
}

@Override
public void refresh(@NotNull JComponent component, @NotNull ValueController<?> controller) {
public void refresh(@NotNull ProgressMonitor monitor, @NotNull JComponent component, @NotNull ValueController<?> controller) {
final TextureInfo info = Objects.requireNonNull(getTextureInfo(controller));
final HwTextureHeader header = info.texture.obj("Header").cast();
final TextureViewerPanel panel = (TextureViewerPanel) component;

panel.setStatusText("%sx%s (%s, %s)".formatted(
header.getWidth(), header.getHeight(),
header.getType(), header.getPixelFormat()
));
final ImageProvider provider = getImageProvider(info.texture, controller.getProject().getPackfileManager());

SwingUtilities.invokeLater(() -> {
final ImageProvider provider = getImageProvider(info.texture, controller.getProject().getPackfileManager());
panel.setStatusText("%sx%s (%s, %s)".formatted(
header.getWidth(), header.getHeight(),
header.getType(), header.getPixelFormat()
));

panel.getImagePanel().setProvider(provider, info.channels);
panel.getImagePanel().fit();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.shade.decima.ui.data;

import com.shade.platform.model.runtime.ProgressMonitor;
import com.shade.util.NotNull;

import javax.swing.*;
Expand All @@ -8,7 +9,20 @@ public interface ValueViewer {
@NotNull
JComponent createComponent();

void refresh(@NotNull JComponent component, @NotNull ValueController<?> controller);
/**
* Updates the viewer with the given {@code controller}.
* <p>
* Viewers should respect the following rules:
* <ul>
* <li>If the viewer needs to update the UI, it should do so on the EDT thread.</li>
* <li>If the monitor is canceled, the viewer should <b>not</b> attempt to update the UI as it might be disposed.</li>
* </ul>
*
* @param monitor the progress monitor to report progress
* @param component the component to update
* @param controller the value controller to use
*/
void refresh(@NotNull ProgressMonitor monitor, @NotNull JComponent component, @NotNull ValueController<?> controller);

default boolean canView(@NotNull ValueController<?> controller) {
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package com.shade.decima.ui.data;

import com.shade.platform.model.Disposable;
import com.shade.platform.model.runtime.ProgressMonitor;
import com.shade.platform.ui.icons.LoadingIcon;
import com.shade.util.NotNull;
import net.miginfocom.swing.MigLayout;

import javax.swing.*;
import java.awt.*;
import java.awt.event.HierarchyEvent;
import java.util.concurrent.Future;

/**
* Represents a panel that displays a value viewer with a support for asynchronous reload.
*/
public class ValueViewerPanel extends JComponent implements Disposable {
private static final int DELAY_BEFORE_SHOWING_LOADER_MS = 100;
private static final String PAGE_LOADING = "loading";
private static final String PAGE_VIEWER = "viewer";

public interface Callback {
void viewerChanged(@NotNull ValueViewerPanel panel);

void viewerClosed(@NotNull ValueViewerPanel panel);
}

private ValueViewer currentViewer;
private JComponent currentComponent;
private SwingWorker<?, ?> currentWorker;

public ValueViewerPanel() {
setLayout(new CardLayout());
setPreferredSize(new Dimension(400, 400));
}

/**
* Updates the current viewer, optionally swapping it with a supplied {@code viewer}, with the given {@code controller}.
*
* @param viewer the viewer to use
* @param controller the value controller to supply to the viewer
* @param callback the callback to notify about the viewer changes
*/
public void update(@NotNull ValueViewer viewer, @NotNull ValueController<?> controller, @NotNull Callback callback) {
final boolean viewerChanged = currentViewer != viewer;

if (viewerChanged) {
if (currentComponent instanceof Disposable d) {
d.dispose();
}

currentViewer = viewer;
currentComponent = viewer.createComponent();

add(currentComponent, PAGE_VIEWER);
callback.viewerChanged(this);
}

final CardLayout layout = (CardLayout) getLayout();
layout.show(this, PAGE_VIEWER);

final SwingWorker<?, ?> worker = currentWorker;
if (worker != null) {
worker.cancel(false);
}

currentWorker = new SwingWorker<>() {
@Override
protected Object doInBackground() {
currentViewer.refresh(new MyProgressMonitor(this), currentComponent, controller);
return null;
}

@Override
protected void done() {
if (isCancelled()) {
callback.viewerClosed(ValueViewerPanel.this);
} else {
layout.show(ValueViewerPanel.this, PAGE_VIEWER);
}

currentWorker = null;
}
};
currentWorker.execute();

final long start = System.currentTimeMillis();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (currentWorker == null || currentWorker.isDone() || currentWorker.isCancelled()) {
return;
}
if (System.currentTimeMillis() - start > DELAY_BEFORE_SHOWING_LOADER_MS) {
final JPanel inner = new JPanel();
inner.setLayout(new GridBagLayout());
inner.add(new LoadingPane(currentWorker));

add(inner, PAGE_LOADING);
layout.show(ValueViewerPanel.this, PAGE_LOADING);
} else {
SwingUtilities.invokeLater(this);
}
}
});
}

@Override
public void dispose() {
if (currentComponent instanceof Disposable d) {
d.dispose();
}

final SwingWorker<?, ?> worker = currentWorker;
if (worker != null) {
worker.cancel(false);
}

currentViewer = null;
currentComponent = null;
currentWorker = null;

removeAll();
}

private static class LoadingPane extends JComponent {
public LoadingPane(@NotNull Future<?> future) {
final LoadingIcon icon = new LoadingIcon();
final JLabel label = new JLabel("Loading\u2026", icon, SwingConstants.LEADING);

final Timer timer = new Timer(1000 / LoadingIcon.SEGMENTS, e -> {
label.repaint();
icon.advance();
});

label.addHierarchyListener(e -> {
if (e.getID() == HierarchyEvent.HIERARCHY_CHANGED && (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
if (label.isShowing()) {
timer.start();
} else {
timer.stop();
}
}
});

final JButton button = new JButton("Cancel");
button.addActionListener(e -> {
button.setEnabled(false);
future.cancel(false);
});

setLayout(new MigLayout("ins panel,wrap", "[center]"));
add(label);
add(button);
}
}

private static class MyProgressMonitor implements ProgressMonitor {
private final Future<?> future;

public MyProgressMonitor(@NotNull Future<?> future) {
this.future = future;
}

@NotNull
@Override
public IndeterminateTask begin(@NotNull String title) {
return new MyTask(this);
}

@NotNull
@Override
public Task begin(@NotNull String title, int total) {
return new MyTask(this);
}

private static class MyTask implements Task {
private final MyProgressMonitor monitor;

public MyTask(@NotNull MyProgressMonitor monitor) {
this.monitor = monitor;
}

@NotNull
@Override
public ProgressMonitor split(int ticks) {
return new MyProgressMonitor(monitor.future);
}

@Override
public void worked(int ticks) {
// do nothing
}

@Override
public void close() {
// do nothing
}

@Override
public boolean isCanceled() {
return monitor.future.isCancelled();
}

@NotNull
@Override
public String title() {
return "";
}
}
}
}
Loading

0 comments on commit e5c0547

Please sign in to comment.