window fade in/out effect

This commit is contained in:
Autumn Lamonte 2022-02-05 12:01:06 -06:00
parent edc1303af1
commit a229c8cbd0
10 changed files with 519 additions and 21 deletions

View file

@ -13,8 +13,8 @@ like this:
![Terminal, Image, Table](/screenshots/new_demo1.png?raw=true "Terminal, Image, Table") ![Terminal, Image, Table](/screenshots/new_demo1.png?raw=true "Terminal, Image, Table")
...or anything in between. Translucent windows -- including layered ...or anything in between. Translucent windows -- including layered
images -- are supported and generally look like as one would expect in images -- are supported and generally look as one would expect in a
a modern graphical environment...but it's mostly text. Translucent modern graphical environment...but it's mostly text. Translucent
windows were inspired in part by windows were inspired in part by
[notcurses](https://github.com/dankamongmen/notcurses). [notcurses](https://github.com/dankamongmen/notcurses).

View file

@ -51,6 +51,9 @@ import jexer.bits.CellAttributes;
import jexer.bits.Clipboard; import jexer.bits.Clipboard;
import jexer.bits.ColorTheme; import jexer.bits.ColorTheme;
import jexer.bits.StringUtils; import jexer.bits.StringUtils;
import jexer.effect.Effect;
import jexer.effect.WindowFadeInEffect;
import jexer.effect.WindowFadeOutEffect;
import jexer.event.TCommandEvent; import jexer.event.TCommandEvent;
import jexer.event.TInputEvent; import jexer.event.TInputEvent;
import jexer.event.TKeypressEvent; import jexer.event.TKeypressEvent;
@ -407,6 +410,11 @@ public class TApplication implements Runnable {
*/ */
protected boolean translucence = true; protected boolean translucence = true;
/**
* The list of desktop/window effects to run.
*/
private List<Effect> effects = new LinkedList<Effect>();
/** /**
* WidgetEventHandler is the main event consumer loop. There are at most * WidgetEventHandler is the main event consumer loop. There are at most
* two such threads in existence: the primary for normal case and a * two such threads in existence: the primary for normal case and a
@ -905,7 +913,7 @@ public class TApplication implements Runnable {
addTimer(millis, true, addTimer(millis, true,
new TAction() { new TAction() {
public void DO() { public void DO() {
TApplication.this.doRepaint(); doRepaint();
} }
} }
); );
@ -919,9 +927,9 @@ public class TApplication implements Runnable {
addTimer(millis, true, addTimer(millis, true,
new TAction() { new TAction() {
public void DO() { public void DO() {
TApplication.this.doRepaint(); doRepaint();
// Update idle checks. // Update idle checks.
TApplication.this.getBackend().hasEvents(); getBackend().hasEvents();
} }
} }
); );
@ -932,6 +940,7 @@ public class TApplication implements Runnable {
TTimer animationTimer = addTimer(1000 / ANIMATION_FPS, true, TTimer animationTimer = addTimer(1000 / ANIMATION_FPS, true,
new TAction() { new TAction() {
public void DO() { public void DO() {
runEffects();
doRepaint(); doRepaint();
} }
} }
@ -1824,6 +1833,38 @@ public class TApplication implements Runnable {
} }
} }
/**
* Run the desktop and window effects.
*/
private void runEffects() {
// System.err.println("runEffects() enter");
synchronized (effects) {
if (effects.size() == 0) {
// System.err.println("runEffects() NOP");
return;
}
}
List<Effect> effectsToRun = new ArrayList<Effect>();
List<Effect> effectsToRemove = new ArrayList<Effect>();
synchronized (effects) {
effectsToRun.addAll(effects);
}
while (effectsToRun.size() > 0) {
Effect effect = effectsToRun.remove(0);
if (effect.isCompleted()) {
effectsToRemove.add(effect);
}
effect.update();
}
if (effectsToRemove.size() > 0) {
synchronized (effects) {
effects.removeAll(effectsToRemove);
}
}
// System.err.println("runEffects() exit");
}
/** /**
* Do stuff when there is no user input. * Do stuff when there is no user input.
*/ */
@ -1859,6 +1900,11 @@ public class TApplication implements Runnable {
timers.addAll(keepTimers); timers.addAll(keepTimers);
} }
if (debugThreads) {
System.err.printf(System.currentTimeMillis() + " " +
Thread.currentThread() + " doIdle() 3\n");
}
// Call onIdle's // Call onIdle's
for (TWindow window: windows) { for (TWindow window: windows) {
window.onIdle(); window.onIdle();
@ -1879,6 +1925,11 @@ public class TApplication implements Runnable {
} }
doRepaint(); doRepaint();
if (debugThreads) {
System.err.printf(System.currentTimeMillis() + " " +
Thread.currentThread() + " doIdle() - exit\n");
}
} }
/** /**
@ -2597,7 +2648,7 @@ public class TApplication implements Runnable {
// Recreate the shadow effect by blending a black rectangle over just // Recreate the shadow effect by blending a black rectangle over just
// the shadow region. // the shadow region.
final int shadowOpacity = 30; final int shadowOpacity = 30;
final int shadowAlpha = shadowOpacity * 255 / 100; final int shadowAlpha = shadowOpacity * window.getAlpha() / 100;
screen.blendRectangle(windowX + windowWidth, windowY + 1, screen.blendRectangle(windowX + windowWidth, windowY + 1,
2, windowHeight - 1, 0x000000, shadowAlpha); 2, windowHeight - 1, 0x000000, shadowAlpha);
screen.blendRectangle(windowX + 2, windowY + windowHeight, screen.blendRectangle(windowX + 2, windowY + windowHeight,
@ -3190,6 +3241,18 @@ public class TApplication implements Runnable {
// visible on screen. // visible on screen.
window.onPreClose(); window.onPreClose();
// If the window has a close effect, kick that off.
if (!window.disableCloseEffect()) {
String windowCloseEffect = System.getProperty("jexer.effect.windowClose",
"none").toLowerCase();
if (windowCloseEffect.equals("fade")) {
synchronized (effects) {
effects.add(new WindowFadeOutEffect(window));
}
}
}
synchronized (windows) { synchronized (windows) {
window.stopMovements(); window.stopMovements();
@ -3380,6 +3443,15 @@ public class TApplication implements Runnable {
desktop.setActive(false); desktop.setActive(false);
} }
// If the window has an open effect, kick that off.
String windowOpenEffect = System.getProperty("jexer.effect.windowOpen",
"none").toLowerCase();
if (windowOpenEffect.equals("fade")) {
synchronized (effects) {
effects.add(new WindowFadeInEffect(window));
}
}
} }
/** /**

View file

@ -252,6 +252,11 @@ public class TWindow extends TWidget {
*/ */
private int alpha = 255; private int alpha = 255;
/**
* The window open effect timer.
*/
private TTimer openEffectTimer = null;
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Constructors ----------------------------------------------------------- // Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -339,9 +344,6 @@ public class TWindow extends TWidget {
// Center window if specified // Center window if specified
center(); center();
// Add me to the application
application.addWindowToApplication(this);
// Set default borders // Set default borders
setBorderStyleForeground(null); setBorderStyleForeground(null);
setBorderStyleInactive(null); setBorderStyleInactive(null);
@ -358,6 +360,9 @@ public class TWindow extends TWidget {
// SQUASH // SQUASH
} }
setAlpha(opacity * 255 / 100); setAlpha(opacity * 255 / 100);
// Add me to the application
application.addWindowToApplication(this);
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -1860,4 +1865,15 @@ public class TWindow extends TWidget {
return alpha; return alpha;
} }
/**
* If true, disable any window closing effect. This is used by the
* window closing effects themselves so that they can be closed when
* finished.
*
* @return true if the window close effect should be disabled
*/
public boolean disableCloseEffect() {
return false;
}
} }

View file

@ -1931,10 +1931,9 @@ public class ECMA48Terminal extends LogicalScreen
if (imageThreadCount > 1) { if (imageThreadCount > 1) {
// Collect all the encoded images. // Collect all the encoded images.
while (imageResults.size() > 0) { while (imageResults.size() > 0) {
Future<String> image = imageResults.get(0);
try { try {
Future<String> image = imageResults.get(0);
sb.append(image.get()); sb.append(image.get());
imageResults.remove(0);
} catch (InterruptedException e) { } catch (InterruptedException e) {
// SQUASH // SQUASH
// e.printStackTrace(); // e.printStackTrace();
@ -1942,6 +1941,7 @@ public class ECMA48Terminal extends LogicalScreen
// SQUASH // SQUASH
// e.printStackTrace(); // e.printStackTrace();
} }
imageResults.remove(0);
} }
imageExecutor.shutdown(); imageExecutor.shutdown();
} }

View file

@ -1354,6 +1354,38 @@ public class LogicalScreen implements Screen {
return other; return other;
} }
/**
* Obtain a snapshot copy of a rectangular portion of the screen of the
* PHYSICAL screen - what was LAST emitted.
*
* @param x left column of rectangle. 0 is the left-most column.
* @param y top row of the rectangle. 0 is the top-most row.
* @param width number of columns to copy
* @param height number of rows to copy
* @return a copy of the screen's data from this rectangle. Any cells
* outside the actual screen dimensions will be blank.
*/
public Screen snapshotPhysical(final int x, final int y, final int width,
final int height) {
LogicalScreen other = null;
synchronized (this) {
other = new LogicalScreen(width, height);
for (int row = y; (row < y + height) && (row < this.height); row++) {
if (row < 0) {
continue;
}
for (int col = x; (col < x + width) && (col < this.width); col++) {
if (col < 0) {
continue;
}
other.logical[col - x][row - y] = new Cell(physical[col][row]);
}
}
}
return other;
}
/** /**
* Copy all of screen's data to this screen. * Copy all of screen's data to this screen.
* *
@ -1468,6 +1500,8 @@ public class LogicalScreen implements Screen {
return; return;
} }
long now = System.currentTimeMillis();
/* /*
* We need to blend the background colors of other's cells over the * We need to blend the background colors of other's cells over the
* cells of this screen (foreground and background), honoring our * cells of this screen (foreground and background), honoring our
@ -1480,6 +1514,8 @@ public class LogicalScreen implements Screen {
BufferedImage.TYPE_INT_ARGB); BufferedImage.TYPE_INT_ARGB);
BufferedImage thisBackground = new BufferedImage(width, height, BufferedImage thisBackground = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB); BufferedImage.TYPE_INT_ARGB);
BufferedImage overForeground = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
BufferedImage overBackground = new BufferedImage(width, height, BufferedImage overBackground = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB); BufferedImage.TYPE_INT_ARGB);
BufferedImage thisOldBackground = new BufferedImage(width, height, BufferedImage thisOldBackground = new BufferedImage(width, height,
@ -1515,6 +1551,16 @@ public class LogicalScreen implements Screen {
} }
Cell over = otherScreen.getCharXY(col - x, row - y); Cell over = otherScreen.getCharXY(col - x, row - y);
int overFg = over.getForeColorRGB();
if (over.isPulse()) {
overFg = over.getForeColorPulseRGB(backend, now);
} else if (overFg < 0) {
if (backend != null) {
overFg = backend.attrToForegroundColor(over).getRGB();
} else {
overFg = SwingTerminal.attrToForegroundColor(over).getRGB();
}
}
int overBg = over.getBackColorRGB(); int overBg = over.getBackColorRGB();
if (overBg < 0) { if (overBg < 0) {
if (backend != null) { if (backend != null) {
@ -1526,18 +1572,23 @@ public class LogicalScreen implements Screen {
thisFg |= OPAQUE; thisFg |= OPAQUE;
thisBg |= OPAQUE; thisBg |= OPAQUE;
overBg |= OPAQUE; overBg |= OPAQUE;
overFg |= OPAQUE;
thisForeground.setRGB(col - x, row - y, thisFg); thisForeground.setRGB(col - x, row - y, thisFg);
thisBackground.setRGB(col - x, row - y, thisBg); thisBackground.setRGB(col - x, row - y, thisBg);
thisOldBackground.setRGB(col - x, row - y, thisBg); thisOldBackground.setRGB(col - x, row - y, thisBg);
overForeground.setRGB(col - x, row - y, overFg);
overBackground.setRGB(col - x, row - y, overBg); overBackground.setRGB(col - x, row - y, overBg);
} }
} }
// The three bitmaps are ready. We have skipped over // The four bitmaps are ready. We have skipped over cells/pixels
// cells/pixels that cannot overlap. Now blit overBackground // that cannot overlap. Now blit overBackground over both
// over both thisForeground and thisBackground, and then assign // thisForeground and thisBackground, and then assign cell colors
// cell colors and cell chars/images. // and cell chars/images.
//
// Also blit overForeground over thisBackground to handle the new
// layer's glyph opacity.
float fAlpha = (float) (alpha / 255.0); float fAlpha = (float) (alpha / 255.0);
Graphics2D g2d = thisForeground.createGraphics(); Graphics2D g2d = thisForeground.createGraphics();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
@ -1551,6 +1602,15 @@ public class LogicalScreen implements Screen {
g2d.drawImage(overBackground, 0, 0, null); g2d.drawImage(overBackground, 0, 0, null);
g2d.dispose(); g2d.dispose();
BufferedImage glyphForeground = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
g2d = glyphForeground.createGraphics();
g2d.drawImage(thisBackground, 0, 0, null);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
fAlpha));
g2d.drawImage(overForeground, 0, 0, null);
g2d.dispose();
for (int row = y; (row < y + height) && (row < this.height); row++) { for (int row = y; (row < y + height) && (row < this.height); row++) {
if (row < 0) { if (row < 0) {
continue; continue;
@ -1565,6 +1625,7 @@ public class LogicalScreen implements Screen {
int thisBg = thisBackground.getRGB(col - x, row - y); int thisBg = thisBackground.getRGB(col - x, row - y);
int thisOldBg = thisOldBackground.getRGB(col - x, row - y); int thisOldBg = thisOldBackground.getRGB(col - x, row - y);
int overBg = overBackground.getRGB(col - x, row - y); int overBg = overBackground.getRGB(col - x, row - y);
int overFg = glyphForeground.getRGB(col - x, row - y);
thisCell.setBackColorRGB(thisBg | OPAQUE); thisCell.setBackColorRGB(thisBg | OPAQUE);
thisCell.setForeColorRGB(thisFg | OPAQUE); thisCell.setForeColorRGB(thisFg | OPAQUE);
@ -1595,6 +1656,7 @@ public class LogicalScreen implements Screen {
if (imageId > 0) { if (imageId > 0) {
thisCell.setImage(newImage, imageId); thisCell.setImage(newImage, imageId);
thisCell.mixImageId(overBg); thisCell.mixImageId(overBg);
thisCell.mixImageId(alpha);
} else { } else {
thisCell.setImage(newImage); thisCell.setImage(newImage);
} }
@ -1632,17 +1694,13 @@ public class LogicalScreen implements Screen {
// The overlaying cell has a character, use it. // The overlaying cell has a character, use it.
thisCell.setChar(overCell.getChar()); thisCell.setChar(overCell.getChar());
int fg = overCell.getForeColorRGB(); thisCell.setForeColorRGB(overFg);
if (fg < 0) {
thisCell.setForeColor(overCell.getForeColor());
} else {
thisCell.setForeColorRGB(fg);
}
thisCell.setBold(overCell.isBold()); thisCell.setBold(overCell.isBold());
thisCell.setBlink(overCell.isBlink()); thisCell.setBlink(overCell.isBlink());
thisCell.setUnderline(overCell.isUnderline()); thisCell.setUnderline(overCell.isUnderline());
thisCell.setProtect(overCell.isProtect()); thisCell.setProtect(overCell.isProtect());
thisCell.setAnimations(overCell.getAnimations()); thisCell.setAnimations(overCell.getAnimations());
thisCell.setPulse(false, false, 0);
if (!overCell.isImage()) { if (!overCell.isImage()) {
// If we had an image, destroy it. Text ALWAYS // If we had an image, destroy it. Text ALWAYS
@ -1678,6 +1736,7 @@ public class LogicalScreen implements Screen {
if (imageId > 0) { if (imageId > 0) {
thisCell.setImage(newImage, imageId); thisCell.setImage(newImage, imageId);
thisCell.mixImageId(thisOldBg); thisCell.mixImageId(thisOldBg);
thisCell.mixImageId(alpha);
} else { } else {
thisCell.setImage(newImage); thisCell.setImage(newImage);
} }
@ -1713,6 +1772,7 @@ public class LogicalScreen implements Screen {
if (imageId > 0) { if (imageId > 0) {
thisCell.setImage(newImage, imageId); thisCell.setImage(newImage, imageId);
thisCell.mixImageId(overCell); thisCell.mixImageId(overCell);
thisCell.mixImageId(alpha);
} else { } else {
thisCell.setImage(newImage); thisCell.setImage(newImage);
} }
@ -1747,6 +1807,7 @@ public class LogicalScreen implements Screen {
thisCell.setImage(newImage, imageId); thisCell.setImage(newImage, imageId);
thisCell.mixImageId(overCell); thisCell.mixImageId(overCell);
thisCell.mixImageId(overBg); thisCell.mixImageId(overBg);
thisCell.mixImageId(alpha);
} else { } else {
thisCell.setImage(newImage); thisCell.setImage(newImage);
} }
@ -1782,6 +1843,7 @@ public class LogicalScreen implements Screen {
thisCell.setImage(newImage, imageId); thisCell.setImage(newImage, imageId);
thisCell.mixImageId(overBg); thisCell.mixImageId(overBg);
thisCell.mixImageId(thisOldBg); thisCell.mixImageId(thisOldBg);
thisCell.mixImageId(alpha);
} else { } else {
thisCell.setImage(newImage); thisCell.setImage(newImage);
} }

View file

@ -511,6 +511,20 @@ public interface Screen {
*/ */
public Screen snapshot(); public Screen snapshot();
/**
* Obtain a snapshot copy of a rectangular portion of the screen of the
* PHYSICAL screen - what was LAST emitted.
*
* @param x left column of rectangle. 0 is the left-most column.
* @param y top row of the rectangle. 0 is the top-most row.
* @param width number of columns to copy
* @param height number of rows to copy
* @return a copy of the screen's data from this rectangle. Any cells
* outside the actual screen dimensions will be blank.
*/
public Screen snapshotPhysical(final int x, final int y, final int width,
final int height);
/** /**
* Obtain a snapshot copy of a rectangular portion of the screen. * Obtain a snapshot copy of a rectangular portion of the screen.
* *

View file

@ -0,0 +1,49 @@
/*
* Jexer - Java Text User Interface
*
* The MIT License (MIT)
*
* Copyright (C) 2022 Autumn Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* @author Autumn Lamonte Trans Liberation Now
* @version 1
*/
package jexer.effect;
/**
* A desktop or window effect does a blingy transformation before the screen
* is sent to the device.
*/
public interface Effect {
/**
* Update the effect.
*/
public void update();
/**
* If true, the effect is completed and can be removed.
*
* @return true if this effect is finished
*/
public boolean isCompleted();
}

View file

@ -0,0 +1,100 @@
/*
* Jexer - Java Text User Interface
*
* The MIT License (MIT)
*
* Copyright (C) 2022 Autumn Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* @author Autumn Lamonte Trans Liberation Now
* @version 1
*/
package jexer.effect;
import jexer.TWindow;
/**
* A desktop or window effect does a blingy transformation before the screen
* is sent to the device.
*/
public class WindowFadeInEffect implements Effect {
// ------------------------------------------------------------------------
// Constants --------------------------------------------------------------
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* The window to fade in.
*/
private TWindow window;
/**
* The window's original alpha value we are ramping up to.
*/
private int targetAlpha = 0;
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Public contructor.
*
* @param window the window to fade in
*/
public WindowFadeInEffect(final TWindow window) {
this.window = window;
targetAlpha = window.getAlpha();
window.setAlpha(64);
}
// ------------------------------------------------------------------------
// Effect -----------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Update the effect.
*/
public void update() {
if (!window.isShown()) {
return;
}
int alpha = window.getAlpha();
if (alpha < targetAlpha) {
// Aiming for 1/8 second, at 32 FPS = 4 frames. 256 / 4 = 64.
alpha = Math.min(alpha + 64, targetAlpha);
window.setAlpha(alpha);
}
}
/**
* If true, the effect is completed and can be removed.
*
* @return true if this effect is finished
*/
public boolean isCompleted() {
return (window.getAlpha() >= targetAlpha);
}
}

View file

@ -0,0 +1,152 @@
/*
* Jexer - Java Text User Interface
*
* The MIT License (MIT)
*
* Copyright (C) 2022 Autumn Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* @author Autumn Lamonte Trans Liberation Now
* @version 1
*/
package jexer.effect;
import jexer.TWindow;
import jexer.backend.Screen;
import jexer.event.TInputEvent;
/**
* A desktop or window effect does a blingy transformation before the screen
* is sent to the device.
*/
public class WindowFadeOutEffect implements Effect {
// ------------------------------------------------------------------------
// Constants --------------------------------------------------------------
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* The fake window to fade out.
*/
private TWindow fakeWindow;
/**
* The region of the screen the window last rendered to.
*/
private Screen oldScreen;
/**
* The alpha value to set fakeWindow to.
*/
private int alpha;
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Public contructor.
*
* @param window the window to fade in
*/
public WindowFadeOutEffect(final TWindow window) {
final Screen oldScreen = window.getScreen().snapshotPhysical(
window.getX(), window.getY(),
window.getWidth(), window.getHeight());
alpha = window.getAlpha();
final int x = window.getX();
final int y = window.getY();
window.getApplication().invokeLater(new Runnable() {
public void run() {
fakeWindow = new TWindow(window.getApplication(), "",
window.getX(), window.getY(),
window.getWidth(), window.getHeight(),
TWindow.MODAL) {
// Disable all inputs.
@Override
public void handleEvent(final TInputEvent event) {
// NOP
}
// Draw the old screen.
@Override
public void draw() {
for (int y = 0; y < getHeight(); y++) {
for (int x = 0; x < getWidth(); x++) {
putCharXY(x, y, oldScreen.getCharXY(x, y));
}
}
}
@Override
public boolean disableCloseEffect() {
return true;
}
};
fakeWindow.setX(x);
fakeWindow.setY(y);
fakeWindow.setAlpha(alpha);
}
});
}
// ------------------------------------------------------------------------
// Effect -----------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Update the effect.
*/
public void update() {
if (fakeWindow == null) {
return;
}
if (alpha > 0) {
// Aiming for 1/8 second, at 32 FPS = 4 frames. 256 / 4 = 64.
alpha = Math.max(alpha - 96, 0);
fakeWindow.setAlpha(alpha);
}
}
/**
* If true, the effect is completed and can be removed.
*
* @return true if this effect is finished
*/
public boolean isCompleted() {
if (fakeWindow != null) {
if (alpha == 0) {
fakeWindow.close();
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,33 @@
/*
* Jexer - Java Text User Interface
*
* The MIT License (MIT)
*
* Copyright (C) 2022 Autumn Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* @author Autumn Lamonte Trans Liberation Now
* @version 1
*/
/**
* Desktop and window effects.
*/
package jexer.effect;