mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-22 08:07:42 +00:00
Show object loading only when it takes a while
Old picture stays; slow loads play a block animation.
This commit is contained in:
1 parent
93f5615f00
commit
9eb56543a3
4 files changed
+293
-19
No files matched your search
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.volmit.iris.core.gui;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* A little cube of blocks building itself while something loads: the blocks drop in one by one, the cube holds a
|
||||
* moment, fades, and starts over. Loads shorter than {@link #SHOW_AFTER_MILLIS} never show it, so they don't flash.
|
||||
*/
|
||||
final class LoadingAnimation {
|
||||
static final int SHOW_AFTER_MILLIS = 300;
|
||||
static final int FRAME_MILLIS = 33;
|
||||
|
||||
// Back to front, so each block lands on or beside ones already there.
|
||||
private static final int[][] BUILD_ORDER = {
|
||||
{0, 0, 0}, {1, 0, 0}, {0, 0, 1}, {1, 0, 1},
|
||||
{0, 1, 0}, {1, 1, 0}, {0, 1, 1}, {1, 1, 1}};
|
||||
private static final int STEP_MILLIS = 140;
|
||||
private static final int DROP_MILLIS = 260;
|
||||
private static final int HOLD_MILLIS = 450;
|
||||
private static final int FADE_MILLIS = 320;
|
||||
private static final int CYCLE_MILLIS = STEP_MILLIS * (BUILD_ORDER.length - 1) + DROP_MILLIS + HOLD_MILLIS + FADE_MILLIS;
|
||||
private static final double DROP_HEIGHT_BLOCKS = 1.6D;
|
||||
private static final double ISOMETRIC_WIDTH = Math.cos(Math.PI / 6D);
|
||||
private static final double ISOMETRIC_DEPTH = 0.5D;
|
||||
private static final float SIDE_SHADE = 0.78F;
|
||||
private static final float FAR_SIDE_SHADE = 0.6F;
|
||||
|
||||
private LoadingAnimation() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a load that began at {@code loadingSinceMillis} has taken long enough to show the animation.
|
||||
*/
|
||||
static boolean isDue(long loadingSinceMillis) {
|
||||
return System.currentTimeMillis() - loadingSinceMillis >= SHOW_AFTER_MILLIS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the cube centred on a point, {@code blockSize} pixels to a block edge, as it looks
|
||||
* {@code elapsedMillis} after it first showed.
|
||||
*/
|
||||
static void paint(Graphics2D graphics, double centerX, double centerY, double blockSize, long elapsedMillis) {
|
||||
long time = Math.floorMod(elapsedMillis, CYCLE_MILLIS);
|
||||
long fadeStart = CYCLE_MILLIS - FADE_MILLIS;
|
||||
if (time < fadeStart) {
|
||||
drawCube(graphics, centerX, centerY, blockSize, time);
|
||||
return;
|
||||
}
|
||||
fadeCube(graphics, centerX, centerY, blockSize, time, 1F - (time - fadeStart) / (float) FADE_MILLIS);
|
||||
}
|
||||
|
||||
// Faded block by block, the blocks behind would show through; the whole cube is drawn first and faded as one.
|
||||
private static void fadeCube(Graphics2D graphics, double centerX, double centerY, double blockSize, long time, float opacity) {
|
||||
int extent = (int) Math.ceil(blockSize * (2D + DROP_HEIGHT_BLOCKS) * 2D);
|
||||
BufferedImage cube = new BufferedImage(extent, extent, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D cubeCanvas = cube.createGraphics();
|
||||
drawCube(cubeCanvas, extent / 2D, extent / 2D, blockSize, time);
|
||||
cubeCanvas.dispose();
|
||||
Graphics2D canvas = (Graphics2D) graphics.create();
|
||||
try {
|
||||
canvas.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, Math.max(0F, opacity)));
|
||||
canvas.drawImage(cube, (int) Math.round(centerX - extent / 2D), (int) Math.round(centerY - extent / 2D), null);
|
||||
} finally {
|
||||
canvas.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static void drawCube(Graphics2D graphics, double centerX, double centerY, double blockSize, long time) {
|
||||
Graphics2D canvas = (Graphics2D) graphics.create();
|
||||
try {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
for (int index = 0; index < BUILD_ORDER.length; index++) {
|
||||
long sinceDropStart = time - (long) index * STEP_MILLIS;
|
||||
if (sinceDropStart < 0) {
|
||||
break;
|
||||
}
|
||||
double fallen = easeOut(Math.min(1D, sinceDropStart / (double) DROP_MILLIS));
|
||||
int[] block = BUILD_ORDER[index];
|
||||
drawBlock(canvas, centerX, centerY, blockSize, block[0], block[1] + DROP_HEIGHT_BLOCKS * (1D - fallen), block[2]);
|
||||
}
|
||||
} finally {
|
||||
canvas.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static double easeOut(double progress) {
|
||||
double remaining = 1D - progress;
|
||||
return 1D - remaining * remaining * remaining;
|
||||
}
|
||||
|
||||
// The cube's middle, (1, 1, 1), lands on the centre point; the top and the two sides facing the viewer show.
|
||||
private static void drawBlock(Graphics2D canvas, double centerX, double centerY, double size, double x, double y, double z) {
|
||||
Color top = StudioTheme.ACCENT;
|
||||
canvas.setColor(top);
|
||||
canvas.fill(face(centerX, centerY, size, x, y, z, new double[][]{{0, 1, 0}, {1, 1, 0}, {1, 1, 1}, {0, 1, 1}}));
|
||||
canvas.setColor(shaded(top, SIDE_SHADE));
|
||||
canvas.fill(face(centerX, centerY, size, x, y, z, new double[][]{{1, 0, 0}, {1, 1, 0}, {1, 1, 1}, {1, 0, 1}}));
|
||||
canvas.setColor(shaded(top, FAR_SIDE_SHADE));
|
||||
canvas.fill(face(centerX, centerY, size, x, y, z, new double[][]{{0, 0, 1}, {1, 0, 1}, {1, 1, 1}, {0, 1, 1}}));
|
||||
}
|
||||
|
||||
private static Path2D face(double centerX, double centerY, double size, double x, double y, double z, double[][] corners) {
|
||||
Path2D path = new Path2D.Double();
|
||||
for (int corner = 0; corner < corners.length; corner++) {
|
||||
double cornerX = x + corners[corner][0] - 1D;
|
||||
double cornerY = y + corners[corner][1] - 1D;
|
||||
double cornerZ = z + corners[corner][2] - 1D;
|
||||
double screenX = centerX + (cornerX - cornerZ) * ISOMETRIC_WIDTH * size;
|
||||
double screenY = centerY + (cornerX + cornerZ) * ISOMETRIC_DEPTH * size - cornerY * size;
|
||||
if (corner == 0) {
|
||||
path.moveTo(screenX, screenY);
|
||||
} else {
|
||||
path.lineTo(screenX, screenY);
|
||||
}
|
||||
}
|
||||
path.closePath();
|
||||
return path;
|
||||
}
|
||||
|
||||
private static Color shaded(Color color, float shade) {
|
||||
return new Color(Math.round(color.getRed() * shade), Math.round(color.getGreen() * shade), Math.round(color.getBlue() * shade));
|
||||
}
|
||||
}
|
||||
@@ -155,7 +155,7 @@ final class ObjectExplorer {
|
||||
|
||||
// Textures load first, since the first object would otherwise be drawn in flat colours.
|
||||
void open() {
|
||||
statusBar.showActivity("Loading Minecraft textures...");
|
||||
viewport.showLoading("Loading Minecraft textures");
|
||||
loader.execute(() -> {
|
||||
loadTextures();
|
||||
List<String> keys = objectKeys();
|
||||
@@ -217,7 +217,7 @@ final class ObjectExplorer {
|
||||
variantPicker.forgetTriedSeeds();
|
||||
showVariantList(List.of(), OptionalLong.empty(), OptionalLong.empty(), 0);
|
||||
}
|
||||
long revision = startLoading(key);
|
||||
long revision = startBaking(key);
|
||||
loader.execute(() -> {
|
||||
if (revision != objectRevision.get()) {
|
||||
return;
|
||||
@@ -235,7 +235,7 @@ final class ObjectExplorer {
|
||||
int variantIndex = shownVariantIndex;
|
||||
shownSeed = OptionalLong.of(seed);
|
||||
variantPicker.showSeed(shownSeed, pack);
|
||||
long revision = startLoading(key + " with seed " + seed);
|
||||
long revision = startBaking(key + " with seed " + seed);
|
||||
loader.execute(() -> {
|
||||
if (revision == objectRevision.get()) {
|
||||
loadVariants(revision, key, pack, OptionalLong.of(seed), variantIndex);
|
||||
@@ -277,7 +277,7 @@ final class ObjectExplorer {
|
||||
return;
|
||||
}
|
||||
if (variants.isEmpty()) {
|
||||
gallery.showMessage("Loading " + shownKey + "...");
|
||||
gallery.showLoading();
|
||||
} else if (variants.size() == 1) {
|
||||
toggleComparing();
|
||||
} else {
|
||||
@@ -380,12 +380,19 @@ final class ObjectExplorer {
|
||||
return new ObjectMesh.Bounds(0, 0, 0, sizeX, sizeY, sizeZ);
|
||||
}
|
||||
|
||||
// A new list of variants is on its way, so every thumbnail is outdated, unlike when stepping to another variant.
|
||||
private long startBaking(String key) {
|
||||
if (comparingVariants) {
|
||||
gallery.showLoading();
|
||||
}
|
||||
return startLoading(key);
|
||||
}
|
||||
|
||||
private long startLoading(String key) {
|
||||
shown = null;
|
||||
wholeMesh = null;
|
||||
meshRevision.incrementAndGet();
|
||||
viewport.showMessage("Loading " + key + "...");
|
||||
statusBar.showActivity("Loading " + key + "...");
|
||||
viewport.showLoading("Loading " + key);
|
||||
return objectRevision.incrementAndGet();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.Scrollable;
|
||||
import javax.swing.Timer;
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Dimension;
|
||||
@@ -44,7 +46,8 @@ import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
* Every variant of a procedural object side by side, drawn at one scale so their sizes compare. Clicking one opens it
|
||||
* in the 3D view.
|
||||
* in the 3D view. While new thumbnails are drawn, each card keeps its old one faded until its own arrives; cards
|
||||
* still waiting after a moment play the loading animation, one after another like a wave.
|
||||
*/
|
||||
final class ObjectVariantGallery extends JPanel {
|
||||
private static final long serialVersionUID = 6153024487310558262L;
|
||||
@@ -54,14 +57,19 @@ final class ObjectVariantGallery extends JPanel {
|
||||
private static final int EDGE_MARGIN = 16;
|
||||
// The variant bar floats over the bottom edge, so the last row scrolls clear of it.
|
||||
private static final int VARIANT_BAR_CLEARANCE = 64;
|
||||
private static final float OUTDATED_THUMBNAIL_OPACITY = 0.35F;
|
||||
private static final int THUMBNAIL_SIZE_PER_LOADING_BLOCK = 14;
|
||||
private static final int LOADING_WAVE_STEP_MILLIS = 90;
|
||||
|
||||
private final JLabel title = new JLabel(" ");
|
||||
private final CardGrid grid = new CardGrid();
|
||||
private final IntConsumer onVariantOpened;
|
||||
private final Runnable onThemeChanged;
|
||||
private final List<VariantCard> cards = new ArrayList<>();
|
||||
private final Timer loadingTicker = new Timer(LoadingAnimation.FRAME_MILLIS, event -> repaintWaitingCards());
|
||||
private int coveredLeftWidth;
|
||||
private int selectedIndex;
|
||||
private long waitingSinceMillis;
|
||||
|
||||
/**
|
||||
* @param onVariantOpened told the index of a variant the user clicks
|
||||
@@ -113,10 +121,33 @@ final class ObjectVariantGallery extends JPanel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty cards for {@code count} variants, waiting for their thumbnails.
|
||||
* Cards for {@code count} variants, waiting for their thumbnails. The cards already shown stay, with their old
|
||||
* thumbnails faded, so a reroll never blanks the view.
|
||||
*/
|
||||
void showVariants(String heading, int count, int selected) {
|
||||
title.setText(heading);
|
||||
if (count != cards.size()) {
|
||||
replaceCards(count);
|
||||
}
|
||||
startWaiting();
|
||||
select(selected);
|
||||
}
|
||||
|
||||
/** Fades every thumbnail while something new loads. */
|
||||
void showLoading() {
|
||||
startWaiting();
|
||||
}
|
||||
|
||||
void showThumbnail(int index, BufferedImage thumbnail) {
|
||||
if (index < cards.size()) {
|
||||
cards.get(index).showThumbnail(thumbnail);
|
||||
}
|
||||
if (cards.stream().noneMatch(VariantCard::isWaiting)) {
|
||||
loadingTicker.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void replaceCards(int count) {
|
||||
cards.clear();
|
||||
grid.removeAll();
|
||||
for (int index = 0; index < count; index++) {
|
||||
@@ -124,14 +155,21 @@ final class ObjectVariantGallery extends JPanel {
|
||||
cards.add(card);
|
||||
grid.add(card);
|
||||
}
|
||||
select(selected);
|
||||
grid.revalidate();
|
||||
grid.repaint();
|
||||
}
|
||||
|
||||
void showThumbnail(int index, BufferedImage thumbnail) {
|
||||
if (index < cards.size()) {
|
||||
cards.get(index).showThumbnail(thumbnail);
|
||||
private void startWaiting() {
|
||||
if (!loadingTicker.isRunning()) {
|
||||
waitingSinceMillis = System.currentTimeMillis();
|
||||
loadingTicker.start();
|
||||
}
|
||||
cards.forEach(VariantCard::markOutdated);
|
||||
}
|
||||
|
||||
private void repaintWaitingCards() {
|
||||
if (LoadingAnimation.isDue(waitingSinceMillis)) {
|
||||
cards.stream().filter(VariantCard::isWaiting).forEach(VariantCard::repaint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +184,7 @@ final class ObjectVariantGallery extends JPanel {
|
||||
|
||||
void showMessage(String text) {
|
||||
title.setText(text);
|
||||
loadingTicker.stop();
|
||||
cards.clear();
|
||||
grid.removeAll();
|
||||
grid.revalidate();
|
||||
@@ -230,6 +269,7 @@ final class ObjectVariantGallery extends JPanel {
|
||||
|
||||
private final int index;
|
||||
private BufferedImage thumbnail;
|
||||
private boolean thumbnailOutdated;
|
||||
private boolean hovered;
|
||||
|
||||
VariantCard(int index) {
|
||||
@@ -256,9 +296,19 @@ final class ObjectVariantGallery extends JPanel {
|
||||
|
||||
void showThumbnail(BufferedImage image) {
|
||||
thumbnail = image;
|
||||
thumbnailOutdated = false;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void markOutdated() {
|
||||
thumbnailOutdated = true;
|
||||
repaint();
|
||||
}
|
||||
|
||||
boolean isWaiting() {
|
||||
return thumbnail == null || thumbnailOutdated;
|
||||
}
|
||||
|
||||
private void setHovered(boolean nowHovered) {
|
||||
hovered = nowHovered;
|
||||
repaint();
|
||||
@@ -277,12 +327,7 @@ final class ObjectVariantGallery extends JPanel {
|
||||
int size = thumbnailSize();
|
||||
canvas.setFont(StudioTheme.smallFont());
|
||||
FontMetrics metrics = canvas.getFontMetrics();
|
||||
if (thumbnail != null) {
|
||||
canvas.drawImage(thumbnail, padding, padding, size, size, null);
|
||||
} else {
|
||||
canvas.setColor(StudioTheme.MUTED_TEXT);
|
||||
drawCentered(canvas, "Loading...", padding + size / 2);
|
||||
}
|
||||
drawThumbnail(canvas, padding, size);
|
||||
canvas.setColor(StudioTheme.TEXT);
|
||||
drawCentered(canvas, "Variant " + (index + 1), padding * 2 + size + metrics.getAscent());
|
||||
} finally {
|
||||
@@ -290,6 +335,22 @@ final class ObjectVariantGallery extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
private void drawThumbnail(Graphics2D canvas, int padding, int size) {
|
||||
if (thumbnail != null) {
|
||||
Graphics2D faded = (Graphics2D) canvas.create();
|
||||
if (thumbnailOutdated) {
|
||||
faded.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, OUTDATED_THUMBNAIL_OPACITY));
|
||||
}
|
||||
faded.drawImage(thumbnail, padding, padding, size, size, null);
|
||||
faded.dispose();
|
||||
}
|
||||
if (isWaiting() && LoadingAnimation.isDue(waitingSinceMillis)) {
|
||||
long shownMillis = System.currentTimeMillis() - waitingSinceMillis - LoadingAnimation.SHOW_AFTER_MILLIS;
|
||||
LoadingAnimation.paint(canvas, padding + size / 2D, padding + size / 2D, size / (double) THUMBNAIL_SIZE_PER_LOADING_BLOCK,
|
||||
shownMillis - (long) index * LOADING_WAVE_STEP_MILLIS);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawCentered(Graphics2D canvas, String text, int baseline) {
|
||||
canvas.drawString(text, (getWidth() - canvas.getFontMetrics().stringWidth(text)) / 2, baseline);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import javax.swing.AbstractAction;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.Timer;
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
@@ -42,12 +44,16 @@ import java.util.function.UnaryOperator;
|
||||
/**
|
||||
* The 3D view of one object. Drag to orbit, shift-drag or middle-drag to pan, scroll to zoom; arrow keys orbit and
|
||||
* plus and minus zoom too. Frames are drawn on a background thread, one at a time, always for the latest camera.
|
||||
* While the next object loads, the last picture stays up; only a slow load dims it under the loading animation.
|
||||
*/
|
||||
final class ObjectViewport extends JComponent {
|
||||
private static final long serialVersionUID = -3175226517409913524L;
|
||||
private static final double WHEEL_ZOOM_EXPONENT = 0.12D;
|
||||
private static final double KEY_ZOOM_FACTOR = 1.1D;
|
||||
private static final int KEY_ORBIT_PIXELS = 12;
|
||||
private static final float LOADING_DIM_OPACITY = 0.88F;
|
||||
private static final double LOADING_BLOCK_SIZE = 16D;
|
||||
private static final int LOADING_TEXT_GAP = 46;
|
||||
|
||||
private final ExecutorService renderThread = Executors.newSingleThreadExecutor(runnable -> {
|
||||
Thread thread = new Thread(runnable, "Iris Object Viewer");
|
||||
@@ -62,6 +68,9 @@ final class ObjectViewport extends JComponent {
|
||||
private boolean renderAgain;
|
||||
private Point dragPoint;
|
||||
private int coveredLeftWidth;
|
||||
private final Timer loadingTicker = new Timer(LoadingAnimation.FRAME_MILLIS, event -> repaint());
|
||||
private String loadingText;
|
||||
private long loadingSinceMillis;
|
||||
|
||||
ObjectViewport() {
|
||||
setOpaque(true);
|
||||
@@ -84,15 +93,37 @@ final class ObjectViewport extends JComponent {
|
||||
requestFrame();
|
||||
}
|
||||
|
||||
// The last picture stays up until the first one of this mesh is drawn.
|
||||
void show(ObjectMesh nextMesh) {
|
||||
mesh = nextMesh;
|
||||
camera = ObjectCamera.START;
|
||||
message = nextMesh.isEmpty() ? "No visible blocks" : null;
|
||||
frame = null;
|
||||
if (nextMesh.isEmpty()) {
|
||||
frame = null;
|
||||
finishLoading();
|
||||
}
|
||||
requestFrame();
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the view as loading something new. A load already under way keeps its start, so stepping quickly through
|
||||
* objects still shows the animation once the wait adds up.
|
||||
*/
|
||||
void showLoading(String text) {
|
||||
if (loadingText == null) {
|
||||
loadingSinceMillis = System.currentTimeMillis();
|
||||
}
|
||||
loadingText = text;
|
||||
message = null;
|
||||
loadingTicker.start();
|
||||
}
|
||||
|
||||
private void finishLoading() {
|
||||
loadingText = null;
|
||||
loadingTicker.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps in another mesh of the same object, like one with its top layers cut away, keeping the camera.
|
||||
*/
|
||||
@@ -117,6 +148,7 @@ final class ObjectViewport extends JComponent {
|
||||
mesh = null;
|
||||
message = text;
|
||||
frame = null;
|
||||
finishLoading();
|
||||
repaint();
|
||||
}
|
||||
|
||||
@@ -136,6 +168,7 @@ final class ObjectViewport extends JComponent {
|
||||
}
|
||||
|
||||
void close() {
|
||||
loadingTicker.stop();
|
||||
renderThread.shutdownNow();
|
||||
}
|
||||
|
||||
@@ -168,6 +201,7 @@ final class ObjectViewport extends JComponent {
|
||||
rendering = false;
|
||||
if (frameMesh == mesh) {
|
||||
frame = image;
|
||||
finishLoading();
|
||||
repaint();
|
||||
}
|
||||
if (renderAgain) {
|
||||
@@ -183,11 +217,38 @@ final class ObjectViewport extends JComponent {
|
||||
if (frame != null) {
|
||||
graphics.drawImage(frame, 0, 0, null);
|
||||
}
|
||||
if (loadingText != null && LoadingAnimation.isDue(loadingSinceMillis)) {
|
||||
drawLoading((Graphics2D) graphics);
|
||||
}
|
||||
if (message != null) {
|
||||
drawMessage((Graphics2D) graphics);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawLoading(Graphics2D graphics) {
|
||||
Graphics2D canvas = (Graphics2D) graphics.create();
|
||||
try {
|
||||
if (frame != null) {
|
||||
canvas.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, LOADING_DIM_OPACITY));
|
||||
canvas.setColor(getBackground());
|
||||
canvas.fillRect(0, 0, getWidth(), getHeight());
|
||||
canvas.setComposite(AlphaComposite.SrcOver);
|
||||
}
|
||||
int centerX = coveredLeftWidth + (getWidth() - coveredLeftWidth) / 2;
|
||||
int centerY = getHeight() / 2;
|
||||
long shownMillis = System.currentTimeMillis() - loadingSinceMillis - LoadingAnimation.SHOW_AFTER_MILLIS;
|
||||
LoadingAnimation.paint(canvas, centerX, centerY, StudioTheme.scale(LOADING_BLOCK_SIZE), shownMillis);
|
||||
canvas.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
canvas.setFont(StudioTheme.font());
|
||||
canvas.setColor(StudioTheme.MUTED_TEXT);
|
||||
String text = loadingText + "...";
|
||||
int textWidth = canvas.getFontMetrics().stringWidth(text);
|
||||
canvas.drawString(text, centerX - textWidth / 2, centerY + StudioTheme.scale(LOADING_TEXT_GAP));
|
||||
} finally {
|
||||
canvas.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawMessage(Graphics2D canvas) {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
canvas.setFont(StudioTheme.font());
|
||||
|
||||
Reference in new issue
Block a user