From 0053d9347f6e010277771a2f1120b5bd35d97e02 Mon Sep 17 00:00:00 2001 From: Autumn Lamonte Date: Sat, 5 Feb 2022 14:42:37 -0600 Subject: [PATCH] Window plasma fade-in --- src/jexer/TApplication.java | 20 ++- src/jexer/TWidget.java | 13 +- src/jexer/TWindow.java | 20 +++ src/jexer/effect/WindowBurnInEffect.java | 197 ++++++++++++++++++++++ src/jexer/effect/WindowFadeInEffect.java | 3 +- src/jexer/effect/WindowFadeOutEffect.java | 10 +- 6 files changed, 248 insertions(+), 15 deletions(-) create mode 100644 src/jexer/effect/WindowBurnInEffect.java diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index cf178fd..2031138 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -52,6 +52,7 @@ import jexer.bits.Clipboard; import jexer.bits.ColorTheme; import jexer.bits.StringUtils; import jexer.effect.Effect; +import jexer.effect.WindowBurnInEffect; import jexer.effect.WindowFadeInEffect; import jexer.effect.WindowFadeOutEffect; import jexer.event.TCommandEvent; @@ -3443,13 +3444,20 @@ public class TApplication implements Runnable { desktop.setActive(false); } - // If the window has an open effect, kick that off. - String windowOpenEffect = System.getProperty("jexer.effect.windowOpen", - "none").toLowerCase(); + if (!window.disableOpenEffect()) { + // 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)); + if (windowOpenEffect.equals("fade")) { + synchronized (effects) { + effects.add(new WindowFadeInEffect(window)); + } + } + if (windowOpenEffect.equals("burn")) { + synchronized (effects) { + effects.add(new WindowBurnInEffect(window)); + } } } } diff --git a/src/jexer/TWidget.java b/src/jexer/TWidget.java index d4aaeaf..818b1f0 100644 --- a/src/jexer/TWidget.java +++ b/src/jexer/TWidget.java @@ -1553,15 +1553,20 @@ public abstract class TWidget implements Comparable { // The TWindow overlay has to be here so that it can cover drawn // widgets. if (this instanceof TWindow) { + screen.setClipRight(overlayClipRight); + screen.setClipBottom(overlayClipBottom); + screen.setOffsetX(overlayOffsetX); + screen.setOffsetY(overlayOffsetY); + + // Let the overlay draw. Tackboard overlay = ((TWindow) this).overlay; if (overlay != null) { - screen.setClipRight(overlayClipRight); - screen.setClipBottom(overlayClipBottom); - screen.setOffsetX(overlayOffsetX); - screen.setOffsetY(overlayOffsetY); overlay.draw(getScreen(), getApplication().getBackend().isImagesOverText()); } + + // Now let a custom window effect draw. + ((TWindow) this).onPostDraw(); } } diff --git a/src/jexer/TWindow.java b/src/jexer/TWindow.java index 8802c7f..6f5c79f 100644 --- a/src/jexer/TWindow.java +++ b/src/jexer/TWindow.java @@ -1081,6 +1081,15 @@ public class TWindow extends TWidget { } + /** + * Function called immediately after the windo is drawn. This can be + * used by subclasses of TWindow to alter the final post-rendered window + * screen area. + */ + protected void onPostDraw() { + // Default does nothing + } + // ------------------------------------------------------------------------ // TWindow ---------------------------------------------------------------- // ------------------------------------------------------------------------ @@ -1865,6 +1874,17 @@ public class TWindow extends TWidget { return alpha; } + /** + * If true, disable any window opening effect. This is used by the + * window closing effects themselves so that they are not themselves + * opened. + * + * @return true if the window open effect should be disabled + */ + public boolean disableOpenEffect() { + return false; + } + /** * If true, disable any window closing effect. This is used by the * window closing effects themselves so that they can be closed when diff --git a/src/jexer/effect/WindowBurnInEffect.java b/src/jexer/effect/WindowBurnInEffect.java new file mode 100644 index 0000000..a51df51 --- /dev/null +++ b/src/jexer/effect/WindowBurnInEffect.java @@ -0,0 +1,197 @@ +/* + * 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 java.awt.image.BufferedImage; + +import jexer.TApplication; +import jexer.TWindow; +import jexer.backend.Screen; +import jexer.event.TInputEvent; +import jexer.tackboard.Bitmap; + +/** + * Make the window look like it was was burned in with plasma fire. + */ +public class WindowBurnInEffect implements Effect { + + // ------------------------------------------------------------------------ + // Constants -------------------------------------------------------------- + // ------------------------------------------------------------------------ + + // ------------------------------------------------------------------------ + // Variables -------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * The window to burn in. + */ + private TWindow window; + + /** + * The fake window with the plasma effect. + */ + private TWindow fakeWindow; + + /** + * The bitmap for the plasma effect. + */ + private Bitmap plasma; + + /** + * The burn alpha. + */ + private int alpha = 0; + + // ------------------------------------------------------------------------ + // Constructors ----------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Public contructor. + * + * @param window the window to burn in + */ + public WindowBurnInEffect(final TWindow window) { + this.window = window; + + alpha = 220; + + final int x = window.getX(); + final int y = window.getY(); + final TApplication app = window.getApplication(); + + app.invokeLater(new Runnable() { + public void run() { + if (app.isModalThreadRunning()) { + return; + } + + 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 + } + + @Override + public void draw() { + // Draw nothing. TWidget.drawChildren() will draw + // the overlay, which contains the plasma. + } + + @Override + public boolean disableOpenEffect() { + return true; + } + + @Override + public boolean disableCloseEffect() { + return true; + } + }; + fakeWindow.setX(x); + fakeWindow.setY(y); + fakeWindow.setAlpha(alpha); + + // Generate the plasma. + Screen screen = fakeWindow.getScreen(); + int width = fakeWindow.getWidth() * screen.getTextWidth(); + int height = fakeWindow.getHeight() * screen.getTextHeight(); + BufferedImage burn = new BufferedImage(width, height, + BufferedImage.TYPE_INT_ARGB); + + // https://lodev.org/cgtutor/plasma.html has the general + // idea. I just played around and it's alright for a start. + int w = width; + int h = height; + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int red = (int) (128.0 + 2 * (Math.sin((x + y) / 2.0) + + Math.sin(x * x + y)) * 128.0); + int green = (int) (128.0 + (Math.sin(y / 7.0) + + Math.cos(Math.log(y * y / 6))) * 128.0); + int blue = (int) (128.0 + Math.cos(Math.sqrt(x * x + y * y) / 3.0) * 128.0); + red = Math.max(0, Math.min(red, 255)); + green = Math.max(0, Math.min(green, 255)); + blue = Math.max(0, Math.min(blue, 255)); + int rgb = ( 0xFF << 24) + | ( red << 16) + | (green << 8) + | blue; + burn.setRGB(x, y, rgb); + } + } + + plasma = new Bitmap(0, 0, 0, burn); + + fakeWindow.addOverlay(plasma); + } + }); + + } + + // ------------------------------------------------------------------------ + // 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 (alpha == 0) { + if (fakeWindow != null) { + fakeWindow.close(); + } + return true; + } + return false; + } + +} diff --git a/src/jexer/effect/WindowFadeInEffect.java b/src/jexer/effect/WindowFadeInEffect.java index edcd8dd..6e1ea52 100644 --- a/src/jexer/effect/WindowFadeInEffect.java +++ b/src/jexer/effect/WindowFadeInEffect.java @@ -31,8 +31,7 @@ package jexer.effect; import jexer.TWindow; /** - * A desktop or window effect does a blingy transformation before the screen - * is sent to the device. + * Make the window fade in. */ public class WindowFadeInEffect implements Effect { diff --git a/src/jexer/effect/WindowFadeOutEffect.java b/src/jexer/effect/WindowFadeOutEffect.java index 905a563..0e09980 100644 --- a/src/jexer/effect/WindowFadeOutEffect.java +++ b/src/jexer/effect/WindowFadeOutEffect.java @@ -33,8 +33,7 @@ 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. + * Make the window fade out. */ public class WindowFadeOutEffect implements Effect { @@ -68,7 +67,7 @@ public class WindowFadeOutEffect implements Effect { /** * Public contructor. * - * @param window the window to fade in + * @param window the window to fade out */ public WindowFadeOutEffect(final TWindow window) { final Screen oldScreen = window.getScreen().snapshotPhysical( @@ -103,6 +102,11 @@ public class WindowFadeOutEffect implements Effect { } } + @Override + public boolean disableOpenEffect() { + return true; + } + @Override public boolean disableCloseEffect() { return true;