Translucent windows #88

This implements:

* Translucent windows.  See issue text for some discussion.  Also a
  thank you to notcurses for the inspiration.

* Selection of different window border styles.

* Fixes for RGB output on ECMA48.

* Other performance bug fixes.
This commit is contained in:
Autumn Lamonte 2022-01-14 11:01:47 -06:00
parent f2f7eaeb0d
commit e5145580e5
29 changed files with 2180 additions and 334 deletions

View file

@ -402,6 +402,11 @@ public class TApplication implements Runnable {
*/
protected MousePointer customWidgetMousePointer;
/**
* If true. enable translucency.
*/
protected boolean translucence = true;
/**
* WidgetEventHandler is the main event consumer loop. There are at most
* two such threads in existence: the primary for normal case and a
@ -584,6 +589,21 @@ public class TApplication implements Runnable {
*/
private ArrayList<String> dirtyQueue = new ArrayList<String>();
/**
* The number of updates pushed out in this second.
*/
private int framesPerSecond = 0;
/**
* The last time a frame was rendered.
*/
private long lastFlushTime = 0;
/**
* How long it took to render the last time in millis.
*/
private long lastFrameTime = 0;
/**
* Public constructor.
*
@ -612,6 +632,7 @@ public class TApplication implements Runnable {
* The update loop.
*/
private void runImpl() {
int frameCount = 0;
// Loop forever
while (!application.quit) {
@ -620,7 +641,11 @@ public class TApplication implements Runnable {
while (!application.quit) {
synchronized (dirtyQueue) {
if (dirtyQueue.size() > 0) {
dirtyQueue.remove(dirtyQueue.size() - 1);
// Collapse all the dirty requests into one
// refresh.
while (dirtyQueue.size() > 0) {
dirtyQueue.remove(dirtyQueue.size() - 1);
}
break;
}
}
@ -641,7 +666,17 @@ public class TApplication implements Runnable {
System.currentTimeMillis(), Thread.currentThread());
}
synchronized (getScreen()) {
long before = System.currentTimeMillis();
backend.flushScreen();
long now = System.currentTimeMillis();
lastFrameTime = now - before;
if ((int) (now / 1000) == (int) (lastFlushTime / 1000)) {
frameCount++;
} else {
framesPerSecond = frameCount;
frameCount = 0;
}
lastFlushTime = now;
}
} // while (true) (main runnable loop)
@ -841,6 +876,11 @@ public class TApplication implements Runnable {
hideMenuBar = true;
}
// Translucent windows (!) option
if (System.getProperty("jexer.translucence", "true").equals("false")) {
translucence = false;
}
theme = new ColorTheme();
desktopTop = (hideMenuBar ? 0 : 1);
desktopBottom = getScreen().getHeight() - 1 + (hideStatusBar ? 1 : 0);
@ -1415,6 +1455,7 @@ public class TApplication implements Runnable {
* @see #secondaryHandleEvent(TInputEvent event)
*/
private void primaryHandleEvent(final TInputEvent event) {
assert (event != null);
if (debugEvents) {
System.err.printf("%s primaryHandleEvent: %s\n",
@ -1623,9 +1664,14 @@ public class TApplication implements Runnable {
if (debugEvents) {
System.err.printf("TApplication dispatch event: %s\n",
event);
System.err.printf(" Routed to: %s\n", window);
System.err.flush();
}
window.handleEvent(event);
if (doubleClick != null) {
if (debugEvents) {
System.err.printf(" -- DOUBLE CLICK --\n");
}
window.handleEvent(doubleClick);
}
if (mouse != null) {
@ -1657,6 +1703,8 @@ public class TApplication implements Runnable {
* @see #primaryHandleEvent(TInputEvent event)
*/
private void secondaryHandleEvent(final TInputEvent event) {
assert (event != null);
TMouseEvent doubleClick = null;
if (debugEvents) {
@ -1907,7 +1955,7 @@ public class TApplication implements Runnable {
}
/**
* Get the color theme.
* Get the global color theme.
*
* @return the theme
*/
@ -1915,6 +1963,60 @@ public class TApplication implements Runnable {
return theme;
}
/**
* Get the translucence option.
*
* @return true if translucency is enabled
*/
public boolean hasTranslucence() {
return translucence;
}
/**
* Set the translucence option.
*
* @param enabled if true, windows will be translucent
*/
public void setTranslucence(final boolean enabled) {
translucence = enabled;
}
/**
* Set the opacity of all windows. If opacity is 100, translucence is
* also disabled for performance.
*
* @param opacity a number between 10 (nearly transparent) and 100 (fully
* opaque)
*/
public void setWindowOpacity(final int opacity) {
if ((opacity < 10) || (opacity > 100)) {
return;
}
if (opacity == 100) {
translucence = false;
} else {
translucence = true;
}
int alpha = opacity * 255 / 100;
for (TWindow window: windows) {
window.setAlpha(alpha);
}
}
/**
* Get the number of frames that were emitted to output on the last
* second.
*
* @return the frames per second
*/
public int getFramesPerSecond() {
if (screenHandler != null) {
return screenHandler.framesPerSecond;
}
return 0;
}
/**
* Get the clipboard.
*
@ -1929,6 +2031,35 @@ public class TApplication implements Runnable {
*/
public void doRepaint() {
repaint = true;
synchronized (drainEventQueue) {
if (fillEventQueue.size() > 0) {
// User input is waiting, that will update the screen. Wake
// the backend reader.
if (debugEvents) {
System.err.printf("Drop: input waiting in backend\n");
}
synchronized (this) {
this.notify();
}
return;
}
}
if (screenHandler != null) {
long now = System.currentTimeMillis();
if (now - screenHandler.lastFlushTime < screenHandler.lastFrameTime) {
// We cannot update the screen this quickly. Drop this
// request.
if (debugEvents) {
System.err.printf("Drop: %d millis to render, %d since\n",
screenHandler.lastFrameTime,
now - screenHandler.lastFlushTime);
}
return;
}
}
// It's been enough time since the last frame, and no user input is
// around, so allow a screen repaint.
wakeEventHandler();
}
@ -2416,6 +2547,42 @@ public class TApplication implements Runnable {
getScreen().invertCell(x, y);
}
/**
* Draw a translucent window with a shadow on the screen.
*
* @param screen the screen
* @param window the window
*/
private void drawTranslucentWindow(final Screen screen,
final TWindow window) {
// Alpha blending: have the window draw to a snapshot of the screen
// without alpha, and then merge it on the screen with alpha.
int windowX = window.getX();
int windowY = window.getY();
int windowWidth = window.getWidth();
int windowHeight = window.getHeight();
Screen oldSnapshot = screen.snapshot(windowX, windowY,
windowWidth + 2, windowHeight + 1);
window.drawChildren();
Screen newSnapshot = screen.snapshot(windowX, windowY,
windowWidth, windowHeight);
screen.copyScreen(oldSnapshot, windowX, windowY,
windowWidth, windowHeight);
screen.blendScreen(newSnapshot, windowX, windowY,
windowWidth, windowHeight, window.getAlpha(), true);
screen.resetClipping();
// Recreate the shadow effect by blending a black rectangle over just
// the shadow region.
final int shadowOpacity = 30;
final int shadowAlpha = shadowOpacity * 255 / 100;
screen.blendRectangle(windowX + windowWidth, windowY + 1,
2, windowHeight - 1, 0x000000, shadowAlpha);
screen.blendRectangle(windowX + 2, windowY + windowHeight,
windowWidth, 1, 0x000000, shadowAlpha);
}
/**
* Draw everything.
*/
@ -2528,7 +2695,11 @@ public class TApplication implements Runnable {
Collections.reverse(sorted);
for (TWindow window: sorted) {
if (window.isShown()) {
window.drawChildren();
if (translucence) {
drawTranslucentWindow(getScreen(), window);
} else {
window.drawChildren();
}
}
}
@ -2572,7 +2743,13 @@ public class TApplication implements Runnable {
}
if (menu.isActive()) {
((TWindow) menu).drawChildren();
if (translucence) {
drawTranslucentWindow(getScreen(), menu);
} else {
((TWindow) menu).drawChildren();
}
// Reset the screen clipping so we can draw the next title.
getScreen().resetClipping();
}
@ -2582,7 +2759,12 @@ public class TApplication implements Runnable {
for (TMenu menu: subMenus) {
// Reset the screen clipping so we can draw the next sub-menu.
getScreen().resetClipping();
((TWindow) menu).drawChildren();
if (translucence) {
drawTranslucentWindow(getScreen(), menu);
} else {
((TWindow) menu).drawChildren();
}
}
if (hideMenuBar == false) {

View file

@ -32,6 +32,7 @@ import java.io.IOException;
import java.util.List;
import java.util.ResourceBundle;
import jexer.bits.BorderStyle;
import jexer.bits.Color;
import jexer.bits.ColorTheme;
import jexer.bits.CellAttributes;
@ -230,15 +231,23 @@ public class TEditColorThemeWindow extends TWindow {
CellAttributes background = getWindow().getBackground();
CellAttributes attr = new CellAttributes();
drawBox(0, 0, getWidth(), getHeight(), border, background, 1,
false);
BorderStyle borderStyle;
borderStyle = BorderStyle.getStyle(System.getProperty(
"jexer.TEditColorTheme.options.borderStyle", "single"));
drawBox(0, 0, getWidth(), getHeight(), border, background,
borderStyle, false);
attr.setTo(getTheme().getColor("twindow.background.modal"));
if (isActive()) {
attr.setForeColor(getTheme().getColor("tlabel").getForeColor());
attr.setBold(getTheme().getColor("tlabel").isBold());
}
putStringXY(1, 0, i18n.getString("foregroundLabel"), attr);
if (borderStyle.equals(BorderStyle.NONE)) {
putStringXY(0, 0, i18n.getString("foregroundLabel"), attr);
} else {
putStringXY(1, 0, i18n.getString("foregroundLabel"), attr);
}
// Have to draw the colors manually because the int value matches
// SGR, not CGA.
@ -518,15 +527,23 @@ public class TEditColorThemeWindow extends TWindow {
CellAttributes background = getWindow().getBackground();
CellAttributes attr = new CellAttributes();
drawBox(0, 0, getWidth(), getHeight(), border, background, 1,
false);
BorderStyle borderStyle;
borderStyle = BorderStyle.getStyle(System.getProperty(
"jexer.TEditColorTheme.options.borderStyle", "single"));
drawBox(0, 0, getWidth(), getHeight(), border, background,
borderStyle, false);
attr.setTo(getTheme().getColor("twindow.background.modal"));
if (isActive()) {
attr.setForeColor(getTheme().getColor("tlabel").getForeColor());
attr.setBold(getTheme().getColor("tlabel").isBold());
}
putStringXY(1, 0, i18n.getString("backgroundLabel"), attr);
if (borderStyle.equals(BorderStyle.NONE)) {
putStringXY(0, 0, i18n.getString("backgroundLabel"), attr);
} else {
putStringXY(1, 0, i18n.getString("backgroundLabel"), attr);
}
// Have to draw the colors manually because the int value matches
// SGR, not CGA.
@ -931,4 +948,77 @@ public class TEditColorThemeWindow extends TWindow {
editTheme.setColor(colorName, attr);
}
/**
* Set the border style for the window when it is the foreground window.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TEditColorTheme.borderStyle.
*/
@Override
public void setBorderStyleForeground(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TEditColorTheme.borderStyle",
"double");
super.setBorderStyleForeground(style);
} else {
super.setBorderStyleForeground(borderStyle);
}
}
/**
* Set the border style for the window when it is the modal window.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TEditColorTheme.borderStyle.
*/
@Override
public void setBorderStyleModal(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TEditColorTheme.borderStyle",
"double");
super.setBorderStyleModal(style);
} else {
super.setBorderStyleModal(borderStyle);
}
}
/**
* Set the border style for the window when it is an inactive/background
* window.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TEditColorTheme.borderStyle.
*/
@Override
public void setBorderStyleInactive(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TEditColorTheme.borderStyle",
"double");
super.setBorderStyleInactive(style);
} else {
super.setBorderStyleInactive(borderStyle);
}
}
/**
* Set the border style for the window when it is being dragged/resize.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TEditColorTheme.borderStyle.
*/
@Override
public void setBorderStyleMoving(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TEditColorTheme.borderStyle",
"double");
super.setBorderStyleMoving(style);
} else {
super.setBorderStyleMoving(borderStyle);
}
}
}

View file

@ -118,6 +118,17 @@ public class TImageWindow extends TScrollableWindow {
setTitle(file.getName());
int opacity = 100;
try {
opacity = Integer.parseInt(System.getProperty(
"jexer.TImage.opacity", "100"));
opacity = Math.max(opacity, 10);
opacity = Math.min(opacity, 100);
} catch (NumberFormatException e) {
// SQUASH
}
setAlpha(opacity * 255 / 100);
setupAfterImage();
}

View file

@ -203,8 +203,13 @@ public class TLabel extends TWidget {
mnemonicColor.setTo(getTheme().getColor("tlabel.mnemonic"));
if (useWindowBackground) {
CellAttributes background = getWindow().getBackground();
color.setBackColor(background.getBackColor());
mnemonicColor.setBackColor(background.getBackColor());
if (background.getBackColorRGB() == -1) {
color.setBackColor(background.getBackColor());
mnemonicColor.setBackColor(background.getBackColor());
} else {
color.setBackColorRGB(background.getBackColorRGB());
mnemonicColor.setBackColorRGB(background.getBackColorRGB());
}
}
putStringXY(0, 0, mnemonic.getRawLabel(), color);
if (mnemonic.getScreenShortcutIdx() >= 0) {

View file

@ -28,6 +28,7 @@
*/
package jexer;
import jexer.bits.BorderStyle;
import jexer.bits.CellAttributes;
import jexer.bits.StringUtils;
@ -133,11 +134,19 @@ public class TRadioGroup extends TWidget {
radioGroupColor = getTheme().getColor("tradiogroup.inactive");
}
BorderStyle borderStyle;
borderStyle = BorderStyle.getStyle(System.getProperty(
"jexer.TRadioGroup.borderStyle", "singleVdoubleH"));
drawBox(0, 0, getWidth(), getHeight(), radioGroupColor, radioGroupColor,
3, false);
borderStyle, false);
hLineXY(1, 0, StringUtils.width(label) + 2, ' ', radioGroupColor);
putStringXY(2, 0, label, radioGroupColor);
if (borderStyle.equals(BorderStyle.NONE)) {
putStringXY(1, 0, label, radioGroupColor);
} else {
putStringXY(2, 0, label, radioGroupColor);
}
}
// ------------------------------------------------------------------------

View file

@ -37,6 +37,7 @@ import java.util.ResourceBundle;
import jexer.backend.ECMA48Terminal;
import jexer.backend.SwingTerminal;
import jexer.bits.BorderStyle;
import jexer.bits.CellAttributes;
import jexer.bits.GraphicsChars;
import jexer.event.TKeypressEvent;
@ -137,6 +138,11 @@ public class TScreenOptionsWindow extends TWindow {
*/
private TCheckBox rgbColor;
/**
* The window opacity.
*/
private TField windowOpacity;
/**
* The original font size.
*/
@ -207,6 +213,11 @@ public class TScreenOptionsWindow extends TWindow {
*/
private boolean oldRgbColor = false;
/**
* The original window opacity.
*/
private int oldWindowOpacity;
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
@ -231,6 +242,15 @@ public class TScreenOptionsWindow extends TWindow {
ecmaTerminal = (ECMA48Terminal) getScreen();
}
addLabel(i18n.getString("windowOpacity"), 1, 0, "ttext", false,
new TAction() {
public void DO() {
if (windowOpacity != null) {
windowOpacity.activate();
}
}
});
addLabel(i18n.getString("fontName"), 3, 2, "ttext", false,
new TAction() {
public void DO() {
@ -296,6 +316,70 @@ public class TScreenOptionsWindow extends TWindow {
}
});
// Window opacity
windowOpacity = addField(31, 0, 4, true,
Integer.toString(getAlpha() * 100 / 255),
new TAction() {
public void DO() {
int currentOpacity = getAlpha() * 100 / 255;
int newOpacity = currentOpacity;
newOpacity = Math.max(newOpacity, 10);
newOpacity = Math.min(newOpacity, 100);
try {
newOpacity = Integer.parseInt(windowOpacity.getText());
} catch (NumberFormatException e) {
fontSize.setText(Integer.toString(currentOpacity));
}
if (newOpacity != currentOpacity) {
getApplication().setWindowOpacity(newOpacity);
System.setProperty("jexer.TWindow.opacity",
Integer.toString(newOpacity));
}
}
},
null);
addSpinner(35, 0,
new TAction() {
public void DO() {
int currentOpacity = getAlpha() * 100 / 255;
int newOpacity = currentOpacity;
try {
newOpacity = Integer.parseInt(windowOpacity.getText());
newOpacity++;
newOpacity = Math.min(newOpacity, 100);
} catch (NumberFormatException e) {
windowOpacity.setText(Integer.toString(currentOpacity));
}
windowOpacity.setText(Integer.toString(newOpacity));
if (newOpacity != currentOpacity) {
getApplication().setWindowOpacity(newOpacity);
System.setProperty("jexer.TWindow.opacity",
Integer.toString(newOpacity));
}
}
},
new TAction() {
public void DO() {
int currentOpacity = getAlpha() * 100 / 255;
int newOpacity = currentOpacity;
try {
newOpacity = Integer.parseInt(windowOpacity.getText());
newOpacity--;
newOpacity = Math.max(newOpacity, 10);
} catch (NumberFormatException e) {
windowOpacity.setText(Integer.toString(currentOpacity));
}
windowOpacity.setText(Integer.toString(newOpacity));
if (newOpacity != currentOpacity) {
getApplication().setWindowOpacity(newOpacity);
System.setProperty("jexer.TWindow.opacity",
Integer.toString(newOpacity));
}
}
}
);
sixel = addCheckBox(3, 15, i18n.getString("sixel"),
(ecmaTerminal != null ? ecmaTerminal.hasSixel() :
System.getProperty("jexer.ECMA48.sixel",
@ -849,6 +933,9 @@ public class TScreenOptionsWindow extends TWindow {
ecmaTerminal.setWideCharImages(oldWideCharImages);
ecmaTerminal.setRgbColor(oldRgbColor);
}
getApplication().setWindowOpacity(oldWindowOpacity);
System.setProperty("jexer.TWindow.opacity",
Integer.toString(oldWindowOpacity));
getApplication().closeWindow(this);
return;
}
@ -870,15 +957,35 @@ public class TScreenOptionsWindow extends TWindow {
int left = 34;
BorderStyle borderStyle;
borderStyle = BorderStyle.getStyle(System.getProperty(
"jexer.TScreenOptions.options.borderStyle", "single"));
CellAttributes color = getTheme().getColor("ttext");
drawBox(2, 2, left + 24, 14, color, color);
putStringXY(4, 2, i18n.getString("swingOptions"), color);
drawBox(2, 2, left + 24, 14, color, color, borderStyle, false);
if (borderStyle.equals(BorderStyle.NONE)) {
putStringXY(3, 2, i18n.getString("swingOptions"), color);
} else {
putStringXY(4, 2, i18n.getString("swingOptions"), color);
}
drawBox(2, 15, left + 12, 22, color, color);
putStringXY(4, 15, i18n.getString("xtermOptions"), color);
drawBox(left + 2, 5, left + 22, 10, color, color, 3, false);
putStringXY(left + 4, 5, i18n.getString("sample"), color);
drawBox(2, 15, left + 12, 22, color, color, borderStyle, false);
if (borderStyle.equals(BorderStyle.NONE)) {
putStringXY(3, 15, i18n.getString("xtermOptions"), color);
} else {
putStringXY(4, 15, i18n.getString("xtermOptions"), color);
}
borderStyle = BorderStyle.getStyle(System.getProperty(
"jexer.TScreenOptions.grid.borderStyle", "singleVdoubleH"));
drawBox(left + 2, 5, left + 22, 10, color, color, borderStyle, false);
if (borderStyle.equals(BorderStyle.NONE)) {
putStringXY(left + 2, 5, i18n.getString("sample"), color);
} else {
putStringXY(left + 4, 5, i18n.getString("sample"), color);
}
for (int i = 6; i < 9; i++) {
hLineXY(left + 3, i, 18, GraphicsChars.HATCH, color);
}
@ -889,4 +996,77 @@ public class TScreenOptionsWindow extends TWindow {
// TScreenOptionsWindow ---------------------------------------------------
// ------------------------------------------------------------------------
/**
* Set the border style for the window when it is the foreground window.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TScreenOptions.borderStyle.
*/
@Override
public void setBorderStyleForeground(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TScreenOptions.borderStyle",
"double");
super.setBorderStyleForeground(style);
} else {
super.setBorderStyleForeground(borderStyle);
}
}
/**
* Set the border style for the window when it is the modal window.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TScreenOptions.borderStyle.
*/
@Override
public void setBorderStyleModal(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TScreenOptions.borderStyle",
"double");
super.setBorderStyleModal(style);
} else {
super.setBorderStyleModal(borderStyle);
}
}
/**
* Set the border style for the window when it is an inactive/background
* window.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TScreenOptions.borderStyle.
*/
@Override
public void setBorderStyleInactive(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TScreenOptions.borderStyle",
"double");
super.setBorderStyleInactive(style);
} else {
super.setBorderStyleInactive(borderStyle);
}
}
/**
* Set the border style for the window when it is being dragged/resize.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TScreenOptions.borderStyle.
*/
@Override
public void setBorderStyleMoving(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TScreenOptions.borderStyle",
"double");
super.setBorderStyleMoving(style);
} else {
super.setBorderStyleMoving(borderStyle);
}
}
}

View file

@ -32,3 +32,6 @@ sixelPaletteSize=Sixel &palette size:
sixelSharedPalette=Us&e shared sixel palette
wideCharImages=D&raw fullwidth characters as images
rgbColor=Use 24-bit R&GB for all colors
xtermOptions=\ Other\
windowOpacity=W&indow opacity (translucence):

View file

@ -709,13 +709,11 @@ public class TTerminalWidget extends TScrollableWidget
if (reverse) {
if (ch.getForeColorRGB() < 0) {
newCell.setBackColor(ch.getForeColor());
newCell.setBackColorRGB(-1);
} else {
newCell.setBackColorRGB(ch.getForeColorRGB());
}
if (ch.getBackColorRGB() < 0) {
newCell.setForeColor(ch.getBackColor());
newCell.setForeColorRGB(-1);
} else {
newCell.setForeColorRGB(ch.getBackColorRGB());
}
@ -1299,6 +1297,7 @@ public class TTerminalWidget extends TScrollableWidget
} else if (getScreen() instanceof ECMA48Terminal) {
ECMA48Terminal terminal = (ECMA48Terminal) getScreen();
/* Always render double-width/height with images.
if (!terminal.hasSixel()
&& !terminal.hasJexerImages()
&& !terminal.hasIterm2Images()
@ -1309,13 +1308,17 @@ public class TTerminalWidget extends TScrollableWidget
putCharXY(x + 1, y, ' ', cell);
return;
}
*/
cursorBlinkVisible = blinkState;
/* Always render double-width/height with images.
} else {
// We don't know how to dray glyphs to this screen, draw them as
// text and bail out.
putCharXY(x, y, cell);
putCharXY(x + 1, y, ' ', cell);
return;
*/
}
if ((textWidth != lastTextWidth) || (textHeight != lastTextHeight)) {

View file

@ -160,6 +160,17 @@ public class TTerminalWindow extends TScrollableWindow {
onShellExit();
}
});
int opacity = 95;
try {
opacity = Integer.parseInt(System.getProperty(
"jexer.TTerminal.opacity", "95"));
opacity = Math.max(opacity, 10);
opacity = Math.min(opacity, 100);
} catch (NumberFormatException e) {
// SQUASH
}
setAlpha(opacity * 255 / 100);
}
/**
@ -216,6 +227,17 @@ public class TTerminalWindow extends TScrollableWindow {
onShellExit();
}
});
int opacity = 95;
try {
opacity = Integer.parseInt(System.getProperty(
"jexer.TTerminal.opacity", "95"));
opacity = Math.max(opacity, 10);
opacity = Math.min(opacity, 100);
} catch (NumberFormatException e) {
// SQUASH
}
setAlpha(opacity * 255 / 100);
}
// ------------------------------------------------------------------------

View file

@ -366,13 +366,11 @@ public class TTextPicture extends TScrollableWidget
if (reverse) {
if (ch.getForeColorRGB() < 0) {
newCell.setBackColor(ch.getForeColor());
newCell.setBackColorRGB(-1);
} else {
newCell.setBackColorRGB(ch.getForeColorRGB());
}
if (ch.getBackColorRGB() < 0) {
newCell.setForeColor(ch.getBackColor());
newCell.setForeColorRGB(-1);
} else {
newCell.setForeColorRGB(ch.getBackColorRGB());
}

View file

@ -35,6 +35,7 @@ import java.util.ArrayList;
import jexer.backend.Screen;
import jexer.bits.Animation;
import jexer.bits.BorderStyle;
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
import jexer.bits.Clipboard;
@ -1419,6 +1420,13 @@ public abstract class TWidget implements Comparable<TWidget> {
* @return the ColorTheme
*/
public final ColorTheme getTheme() {
if (this instanceof TWindow) {
TWindow window = (TWindow) this;
if (window.theme != null) {
return window.theme;
}
}
if (window != null) {
return window.getApplication().getTheme();
}
@ -2136,18 +2144,16 @@ public abstract class TWidget implements Comparable<TWidget> {
* @param bottom bottom row of the box
* @param border attributes to use for the border
* @param background attributes to use for the background
* @param borderType if 1, draw a single-line border; if 2, draw a
* double-line border; if 3, draw double-line top/bottom edges and
* single-line left/right edges (like Qmodem)
* @param borderStyle style of border
* @param shadow if true, draw a "shadow" on the box
*/
public final void drawBox(final int left, final int top,
final int right, final int bottom,
final CellAttributes border, final CellAttributes background,
final int borderType, final boolean shadow) {
final BorderStyle borderStyle, final boolean shadow) {
getScreen().drawBox(left, top, right, bottom, border, background,
borderType, shadow);
borderStyle, shadow);
}
/**

View file

@ -32,7 +32,9 @@ import java.util.HashSet;
import java.util.Set;
import jexer.backend.Screen;
import jexer.bits.BorderStyle;
import jexer.bits.CellAttributes;
import jexer.bits.ColorTheme;
import jexer.bits.GraphicsChars;
import jexer.bits.StringUtils;
import jexer.event.TCommandEvent;
@ -185,6 +187,13 @@ public class TWindow extends TWidget {
*/
boolean hidden = false;
/**
* Widgets on this window can pull colors from this ColorTheme. Note
* package private access: if null, TWidget.getTheme() will pull from
* TApplication.
*/
ColorTheme theme;
/**
* A window may have a status bar associated with it. TApplication will
* draw this status bar last, and will also route events to it first
@ -218,6 +227,31 @@ public class TWindow extends TWidget {
*/
protected Tackboard overlay = null;
/**
* The border style for an active window.
*/
protected BorderStyle borderStyleActive = BorderStyle.DOUBLE;
/**
* The border style for an active modal window.
*/
protected BorderStyle borderStyleActiveModal = BorderStyle.DOUBLE;
/**
* The border style for an inactive window.
*/
protected BorderStyle borderStyleInactive = BorderStyle.SINGLE;
/**
* The border style for a window being dragged/resized.
*/
protected BorderStyle borderStyleMoving = BorderStyle.SINGLE;
/**
* The alpha blending value.
*/
private int alpha = 255;
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
@ -307,6 +341,23 @@ public class TWindow extends TWidget {
// Add me to the application
application.addWindowToApplication(this);
// Set default borders
setBorderStyleForeground(null);
setBorderStyleInactive(null);
setBorderStyleModal(null);
setBorderStyleMoving(null);
int opacity = 95;
try {
opacity = Integer.parseInt(System.getProperty(
"jexer.TWindow.opacity", "95"));
opacity = Math.max(opacity, 10);
opacity = Math.min(opacity, 100);
} catch (NumberFormatException e) {
// SQUASH
}
setAlpha(opacity * 255 / 100);
}
// ------------------------------------------------------------------------
@ -359,6 +410,7 @@ public class TWindow extends TWidget {
*/
protected boolean mouseOnResize() {
if (((flags & RESIZABLE) != 0)
&& (getBorderStyle() != BorderStyle.NONE)
&& !isModal()
&& (mouse != null)
&& (mouse.getAbsoluteY() == getY() + getHeight() - 1)
@ -393,6 +445,12 @@ public class TWindow extends TWidget {
getChildren().remove(w);
}
}
if (underlay != null) {
underlay.clear();
}
if (overlay != null) {
overlay.clear();
}
}
/**
@ -662,9 +720,12 @@ public class TWindow extends TWidget {
}
/*
* Only permit keyboard resizing if the window was RESIZABLE.
* Only permit keyboard resizing if the window was RESIZABLE and
* there is a window border.
*/
if ((flags & RESIZABLE) != 0) {
if (((flags & RESIZABLE) != 0)
&& (getBorderStyle() != BorderStyle.NONE)
) {
if (keypress.equals(kbShiftLeft)) {
if ((getWidth() > minimumWindowWidth)
@ -938,9 +999,9 @@ public class TWindow extends TWidget {
// Draw the box and background first.
CellAttributes border = getBorder();
CellAttributes background = getBackground();
int borderType = getBorderType();
drawBox(0, 0, getWidth(), getHeight(), border, background, borderType,
true);
BorderStyle borderStyle = getBorderStyle();
drawBox(0, 0, getWidth(), getHeight(), border, background, borderStyle,
!getApplication().hasTranslucence());
if ((title != null) && (title.length() > 0)) {
// Draw the title
@ -952,11 +1013,17 @@ public class TWindow extends TWidget {
}
if (isActive()) {
int lBracket = '[';
int rBracket = ']';
if (borderStyle.equals(BorderStyle.SINGLE_ROUND)) {
lBracket = '(';
rBracket = ')';
}
// Draw the close button
if ((flags & NOCLOSEBOX) == 0) {
putCharXY(2, 0, '[', border);
putCharXY(4, 0, ']', border);
putCharXY(2, 0, lBracket, border);
putCharXY(4, 0, rBracket, border);
if (mouseOnClose() && mouse.isMouse1()) {
putCharXY(3, 0, GraphicsChars.CP437[0x0F],
getBorderControls());
@ -969,8 +1036,8 @@ public class TWindow extends TWidget {
// Draw the maximize button
if (!isModal() && ((flags & NOZOOMBOX) == 0)) {
putCharXY(getWidth() - 5, 0, '[', border);
putCharXY(getWidth() - 3, 0, ']', border);
putCharXY(getWidth() - 5, 0, lBracket, border);
putCharXY(getWidth() - 3, 0, rBracket, border);
if (mouseOnMaximize() && mouse.isMouse1()) {
putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x0F],
getBorderControls());
@ -986,12 +1053,15 @@ public class TWindow extends TWidget {
}
// Draw the resize corner
if (!isModal() && ((flags & RESIZABLE) != 0)) {
if (!isModal()
&& ((flags & RESIZABLE) != 0)
&& (getBorderStyle() != BorderStyle.NONE)
) {
if ((flags & RESIZABLE) != 0) {
putCharXY(getWidth() - 2, getHeight() - 1,
GraphicsChars.SINGLE_BAR, getBorderControls());
getBorderStyle().getHorizontal(), getBorderControls());
putCharXY(getWidth() - 1, getHeight() - 1,
GraphicsChars.LRCORNER, getBorderControls());
getBorderStyle().getBottomRight(), getBorderControls());
}
}
}
@ -1428,7 +1498,9 @@ public class TWindow extends TWidget {
* @return true if this window is resizable
*/
public final boolean isResizable() {
if ((flags & RESIZABLE) == 0) {
if (((flags & RESIZABLE) == 0)
|| (getBorderStyle() == BorderStyle.NONE)
) {
return false;
}
return true;
@ -1525,29 +1597,32 @@ public class TWindow extends TWidget {
}
/**
* Retrieve the border line type.
* Retrieve the border line style.
*
* @return the border line type
* @return the border line style
*/
private int getBorderType() {
public BorderStyle getBorderStyle() {
if (!isModal()
&& (inWindowMove || inWindowResize || inKeyboardResize)
) {
assert (isActive());
return 1;
return borderStyleMoving;
} else if (isModal() && inWindowMove) {
assert (isActive());
return 1;
// Modals cannot be resized, hence the separate check.
return borderStyleMoving;
} else if (isModal()) {
if (isActive()) {
return 2;
return borderStyleActiveModal;
} else {
return 1;
// One can stack modals, so an inactive but modal window is
// possible.
return borderStyleInactive;
}
} else if (isActive()) {
return 2;
return borderStyleActive;
} else {
return 1;
return borderStyleInactive;
}
}
@ -1631,4 +1706,158 @@ public class TWindow extends TWidget {
}
}
/**
* Set the border style for the window when it is the foreground window.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TWindow.borderStyleForeground.
*/
public void setBorderStyleForeground(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TWindow.borderStyleForeground",
"double");
borderStyleActive = BorderStyle.getStyle(style);
} else if (borderStyle.equals("default")) {
borderStyleActive = BorderStyle.DOUBLE;
} else {
borderStyleActive = BorderStyle.getStyle(borderStyle);
}
}
/**
* Get the border style for the window when it is the foreground window.
*
* @return the border style
*/
public BorderStyle getBorderStyleForeground() {
return borderStyleActive;
}
/**
* Set the border style for the window when it is the modal window.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TWindow.borderStyleModal.
*/
public void setBorderStyleModal(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TWindow.borderStyleModal",
"double");
borderStyleActiveModal = BorderStyle.getStyle(style);
} else if (borderStyle.equals("default")) {
borderStyleActiveModal = BorderStyle.DOUBLE;
} else {
borderStyleActiveModal = BorderStyle.getStyle(borderStyle);
}
}
/**
* Get the border style for the window when it is the modal window.
*
* @return the border style
*/
public BorderStyle getBorderStyleModal() {
return borderStyleActiveModal;
}
/**
* Set the border style for the window when it is an inactive/background
* window.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TWindow.borderStyleInactive.
*/
public void setBorderStyleInactive(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TWindow.borderStyleInactive",
"single");
borderStyleInactive = BorderStyle.getStyle(style);
} else if (borderStyle.equals("default")) {
borderStyleInactive = BorderStyle.SINGLE;
} else {
borderStyleInactive = BorderStyle.getStyle(borderStyle);
}
}
/**
* Get the border style for the window when it is an inactive/background
* window.
*
* @return the border style
*/
public BorderStyle getBorderStyleInactive() {
return borderStyleInactive;
}
/**
* Set the border style for the window when it is being dragged/resize.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TWindow.borderStyleMoving.
*/
public void setBorderStyleMoving(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TWindow.borderStyleMoving",
"single");
borderStyleMoving = BorderStyle.getStyle(style);
} else if (borderStyle.equals("default")) {
borderStyleMoving = BorderStyle.SINGLE;
} else {
borderStyleMoving = BorderStyle.getStyle(borderStyle);
}
}
/**
* Get the border style for the window when it is being dragged/resize.
*
* @return the border style
*/
public BorderStyle getBorderStyleMoving() {
return borderStyleMoving;
}
/**
* Get the custom color theme for this window.
*
* @return the color theme, or null if the window does not have a custom
* color theme
*/
public final ColorTheme getWindowTheme() {
return theme;
}
/**
* Set a custom color theme for this window.
*
* @param theme the custom theme, or null to use the application-level
* color theme
*/
public final void setWindowTheme(final ColorTheme theme) {
this.theme = theme;
}
/**
* Set the alpha level for this window.
*
* @param alpha a value between 0 (fully transparent) and 255 (fully
* opaque)
*/
public void setAlpha(final int alpha) {
assert ((alpha >= 0) && (alpha <= 255));
this.alpha = alpha;
}
/**
* Get the alpha level for this window.
*
* @return a value between 0 (fully transparent) and 255 (fully opaque)
*/
public int getAlpha() {
return alpha;
}
}

View file

@ -829,19 +829,31 @@ public class ECMA48Terminal extends LogicalScreen
}
if (output != null) {
if (hasSynchronizedOutput) {
// Begin Synchronized Update (BSU)
output.write("\033[?2026h");
output.write(sb.toString());
// End Synchronized Update (ESU)
output.write("\033[?2026l");
if (sb.length() > 0) {
// Begin Synchronized Update (BSU)
output.write("\033[?2026h");
if (debugToStderr) {
System.err.printf("Writing %d bytes to terminal (sync)\n",
sb.length());
}
output.write(sb.toString());
// End Synchronized Update (ESU)
output.write("\033[?2026l");
}
if (debugToStderr) {
System.err.printf("flushPhysical() \033[?2026h%s\033[?2026l\n",
sb.toString());
}
} else {
output.write(sb.toString());
if (sb.length() > 0) {
if (debugToStderr) {
System.err.printf("Writing %d bytes to terminal\n",
sb.length());
}
output.write(sb.toString());
}
}
flush();
output.flush();
}
}
@ -1073,24 +1085,41 @@ public class ECMA48Terminal extends LogicalScreen
char [] readBuffer = new char[128];
List<TInputEvent> events = new ArrayList<TInputEvent>();
// boolean debugToStderr = true;
while (!done && !stopReaderThread) {
try {
// We assume that if inputStream has bytes available, then
// input won't block on read().
if (debugToStderr) {
System.err.printf("Looking for input...");
}
int n = inputStream.available();
/*
System.err.printf("inputStream.available(): %d\n", n);
System.err.flush();
*/
if (debugToStderr) {
if (n == 0) {
System.err.println("none.");
}
if (n < 0) {
System.err.printf("WHAT?! n = %d\n", n);
}
}
if (n > 0) {
if (debugToStderr) {
System.err.printf("%d bytes to read.\n", n);
}
if (readBuffer.length < n) {
// The buffer wasn't big enough, make it huger
readBuffer = new char[readBuffer.length * 2];
}
// System.err.printf("BEFORE read()\n"); System.err.flush();
if (debugToStderr) {
System.err.printf("B4 read(): readBuffer.length = %d\n",
readBuffer.length);
}
int rc = input.read(readBuffer, 0, readBuffer.length);
@ -1100,9 +1129,22 @@ public class ECMA48Terminal extends LogicalScreen
*/
if (rc == -1) {
if (debugToStderr) {
System.err.println(" ---- EOF ----");
}
// This is EOF
done = true;
} else {
if (debugToStderr) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < rc; i++) {
sb.append(readBuffer[i]);
}
System.err.printf("%d rc = %d INPUT: ",
System.currentTimeMillis(), rc);
System.err.println(sb.toString());
}
for (int i = 0; i < rc; i++) {
int ch = readBuffer[i];
processChar(events, (char)ch);
@ -1111,27 +1153,62 @@ public class ECMA48Terminal extends LogicalScreen
if (events.size() > 0) {
// Add to the queue for the backend thread to
// be able to obtain.
if (debugToStderr) {
System.err.printf("Checking eventQueue...");
}
synchronized (eventQueue) {
eventQueue.addAll(events);
}
if (debugToStderr) {
System.err.printf("done.\n");
}
if (listener != null) {
if (debugToStderr) {
System.err.printf("Waking up listener...");
}
synchronized (listener) {
listener.notifyAll();
}
if (debugToStderr) {
System.err.printf("done.\n");
}
}
events.clear();
}
}
} else {
if (debugToStderr) {
System.err.println("Looking for idle events");
}
getIdleEvents(events);
if (events.size() > 0) {
if (debugToStderr) {
System.err.printf("Checking eventQueue...");
}
synchronized (eventQueue) {
eventQueue.addAll(events);
}
if (debugToStderr) {
System.err.printf("done.\n");
}
if (listener != null) {
if (debugToStderr) {
System.err.printf("Waking up listener...");
}
synchronized (listener) {
listener.notifyAll();
}
if (debugToStderr) {
System.err.printf("done.\n");
}
}
events.clear();
}
@ -1338,6 +1415,7 @@ public class ECMA48Terminal extends LogicalScreen
y, x, lastX, textEnd);
System.err.printf(" lCell: %s\n", lCell);
System.err.printf(" pCell: %s\n", pCell);
System.err.printf(" lastAttr: %s\n", lastAttr);
System.err.printf(" ==== \n");
}
@ -1420,158 +1498,84 @@ public class ECMA48Terminal extends LogicalScreen
assert (!lCell.isImage());
// Now emit only the modified attributes
if ((lCell.getForeColor() != lastAttr.getForeColor())
&& (lCell.getBackColor() != lastAttr.getBackColor())
&& (!lCell.isRGB())
&& (lCell.isBold() == lastAttr.isBold())
&& (lCell.isReverse() == lastAttr.isReverse())
&& (lCell.isUnderline() == lastAttr.isUnderline())
&& (lCell.isBlink() == lastAttr.isBlink())
) {
// Both colors changed, attributes the same
sb.append(color(lCell.isBold(),
lCell.getForeColor(), lCell.getBackColor()));
if (debugToStderr && reallyDebug) {
System.err.printf("1 Change only fore/back colors\n");
}
} else if (lCell.isRGB()
&& (lCell.getForeColorRGB() != lastAttr.getForeColorRGB())
&& (lCell.getBackColorRGB() != lastAttr.getBackColorRGB())
&& (lCell.isBold() == lastAttr.isBold())
&& (lCell.isReverse() == lastAttr.isReverse())
&& (lCell.isUnderline() == lastAttr.isUnderline())
&& (lCell.isBlink() == lastAttr.isBlink())
) {
// Both colors changed, attributes the same
sb.append(colorRGB(lCell.getForeColorRGB(),
lCell.getBackColorRGB()));
if (debugToStderr && reallyDebug) {
System.err.printf("1 Change only fore/back colors (RGB)\n");
}
} else if ((lCell.getForeColor() != lastAttr.getForeColor())
&& (lCell.getBackColor() != lastAttr.getBackColor())
&& (!lCell.isRGB())
&& (lCell.isBold() != lastAttr.isBold())
&& (lCell.isReverse() != lastAttr.isReverse())
&& (lCell.isUnderline() != lastAttr.isUnderline())
&& (lCell.isBlink() != lastAttr.isBlink())
) {
// Everything is different
sb.append(color(lCell.getForeColor(),
lCell.getBackColor(),
lCell.isBold(), lCell.isReverse(),
lCell.isBlink(),
lCell.isUnderline()));
if (debugToStderr && reallyDebug) {
System.err.printf("2 Set all attributes\n");
}
} else if ((lCell.getForeColor() != lastAttr.getForeColor())
&& (lCell.getBackColor() == lastAttr.getBackColor())
&& (!lCell.isRGB())
&& (lCell.isBold() == lastAttr.isBold())
&& (lCell.isReverse() == lastAttr.isReverse())
&& (lCell.isUnderline() == lastAttr.isUnderline())
&& (lCell.isBlink() == lastAttr.isBlink())
) {
// Attributes same, foreColor different
sb.append(color(lCell.isBold(),
lCell.getForeColor(), true));
if (debugToStderr && reallyDebug) {
System.err.printf("3 Change foreColor\n");
}
} else if (lCell.isRGB()
&& (lCell.getForeColorRGB() != lastAttr.getForeColorRGB())
&& (lCell.getBackColorRGB() == lastAttr.getBackColorRGB())
&& (lCell.getForeColorRGB() >= 0)
&& (lCell.getBackColorRGB() >= 0)
&& (lCell.isBold() == lastAttr.isBold())
&& (lCell.isReverse() == lastAttr.isReverse())
&& (lCell.isUnderline() == lastAttr.isUnderline())
&& (lCell.isBlink() == lastAttr.isBlink())
) {
// Attributes same, foreColor different
sb.append(colorRGB(lCell.getForeColorRGB(), true));
if (debugToStderr && reallyDebug) {
System.err.printf("3 Change foreColor (RGB)\n");
}
} else if ((lCell.getForeColor() == lastAttr.getForeColor())
&& (lCell.getBackColor() != lastAttr.getBackColor())
&& (!lCell.isRGB())
&& (lCell.isBold() == lastAttr.isBold())
&& (lCell.isReverse() == lastAttr.isReverse())
&& (lCell.isUnderline() == lastAttr.isUnderline())
&& (lCell.isBlink() == lastAttr.isBlink())
) {
// Attributes same, backColor different
sb.append(color(lCell.isBold(),
lCell.getBackColor(), false));
if (debugToStderr && reallyDebug) {
System.err.printf("4 Change backColor\n");
}
} else if (lCell.isRGB()
&& (lCell.getForeColorRGB() == lastAttr.getForeColorRGB())
&& (lCell.getBackColorRGB() != lastAttr.getBackColorRGB())
&& (lCell.isBold() == lastAttr.isBold())
&& (lCell.isReverse() == lastAttr.isReverse())
&& (lCell.isUnderline() == lastAttr.isUnderline())
&& (lCell.isBlink() == lastAttr.isBlink())
) {
// Attributes same, foreColor different
sb.append(colorRGB(lCell.getBackColorRGB(), false));
if (debugToStderr && reallyDebug) {
System.err.printf("5 Change backColor (RGB)\n");
}
} else if ((lCell.getForeColor() == lastAttr.getForeColor())
&& (lCell.getBackColor() == lastAttr.getBackColor())
&& (lCell.getForeColorRGB() == lastAttr.getForeColorRGB())
&& (lCell.getBackColorRGB() == lastAttr.getBackColorRGB())
&& (lCell.isBold() == lastAttr.isBold())
&& (lCell.isReverse() == lastAttr.isReverse())
&& (lCell.isUnderline() == lastAttr.isUnderline())
&& (lCell.isBlink() == lastAttr.isBlink())
) {
// All attributes the same, just print the char
// NOP
if (debugToStderr && reallyDebug) {
System.err.printf("6 Only emit character\n");
}
} else {
// Just reset everything again
if (!lCell.isRGB()) {
sb.append(color(lCell.getForeColor(),
lCell.getBackColor(),
lCell.isBold(),
lCell.isReverse(),
lCell.isBlink(),
lCell.isUnderline()));
if (debugToStderr && reallyDebug) {
System.err.printf("7 Change all attributes\n");
}
StringBuilder attrSgr = new StringBuilder(8);
if (lCell.isBold() != lastAttr.isBold()) {
if (lCell.isBold()) {
attrSgr.append(";1");
} else {
sb.append(colorRGB(lCell.getForeColorRGB(),
lCell.getBackColorRGB(),
lCell.isBold(),
lCell.isReverse(),
lCell.isBlink(),
lCell.isUnderline()));
if (debugToStderr && reallyDebug) {
System.err.printf("8 Change all attributes (RGB)\n");
}
attrSgr.append(";22");
}
}
if (lCell.isUnderline() != lastAttr.isUnderline()) {
if (lCell.isUnderline()) {
attrSgr.append(";4");
} else {
attrSgr.append(";24");
}
}
if (lCell.isBlink() != lastAttr.isBlink()) {
if (lCell.isBlink()) {
attrSgr.append(";5");
} else {
attrSgr.append(";25");
}
}
if (lCell.isReverse() != lastAttr.isReverse()) {
if (lCell.isReverse()) {
attrSgr.append(";7");
} else {
attrSgr.append(";27");
}
}
if (attrSgr.length() > 0) {
if (debugToStderr && reallyDebug) {
System.err.println("2 attr: " + attrSgr.substring(1));
}
sb.append("\033[");
sb.append(attrSgr.substring(1));
sb.append("m");
}
if ((lCell.getForeColorRGB() >= 0)
&& ((lCell.getForeColorRGB() != lastAttr.getForeColorRGB())
|| (lastAttr.getForeColorRGB() < 0))
) {
if (debugToStderr && reallyDebug) {
System.err.println("3 set foreColorRGB");
}
sb.append(colorRGB(lCell.getForeColorRGB(), true));
} else {
if ((lCell.getForeColorRGB() < 0)
&& ((lastAttr.getForeColorRGB() >= 0)
|| !lCell.getForeColor().equals(lastAttr.getForeColor()))
) {
if (debugToStderr && reallyDebug) {
System.err.println("4 set foreColor");
}
sb.append(color(lCell.getForeColor(), true, true));
}
}
if ((lCell.getBackColorRGB() >= 0)
&& ((lCell.getBackColorRGB() != lastAttr.getBackColorRGB())
|| (lastAttr.getBackColorRGB() < 0))
) {
if (debugToStderr && reallyDebug) {
System.err.println("5 set backColorRGB");
}
sb.append(colorRGB(lCell.getBackColorRGB(), false));
} else {
if ((lCell.getBackColorRGB() < 0)
&& ((lastAttr.getBackColorRGB() >= 0)
|| !lCell.getBackColor().equals(lastAttr.getBackColor()))
) {
if (debugToStderr && reallyDebug) {
System.err.println("6 set backColor");
}
sb.append(color(lCell.getBackColor(), false, true));
}
}
// Emit the character
if (wideCharImages
// Don't emit the right-half of full-width chars.
@ -2457,6 +2461,10 @@ public class ECMA48Terminal extends LogicalScreen
default:
break;
}
// We have changed a system color. Redraw the entire screen.
clearPhysical();
reallyCleared = true;
} catch (NumberFormatException e) {
return;
}
@ -2489,7 +2497,7 @@ public class ECMA48Terminal extends LogicalScreen
boolean alt = false;
boolean shift = false;
if (debugToStderr && false) {
if (debugToStderr) {
System.err.printf("state: %s ch %c\r\n", state, ch);
}
@ -4341,6 +4349,59 @@ public class ECMA48Terminal extends LogicalScreen
return sb.toString();
}
/**
* Create a SGR parameter sequence for several attributes. This sequence
* first resets all attributes to default, then sets attributes as per
* the parameters.
*
* @param bold if true, set bold
* @param reverse if true, set reverse
* @param blink if true, set blink
* @param underline if true, set underline
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[0;1;5m"
*/
private String attributes(final boolean bold, final boolean reverse,
final boolean blink, final boolean underline) {
StringBuilder sb = new StringBuilder();
if ( bold && reverse && blink && !underline ) {
sb.append("\033[0;1;7;5m");
} else if ( bold && reverse && !blink && !underline ) {
sb.append("\033[0;1;7m");
} else if ( !bold && reverse && blink && !underline ) {
sb.append("\033[0;7;5m");
} else if ( bold && !reverse && blink && !underline ) {
sb.append("\033[0;1;5m");
} else if ( bold && !reverse && !blink && !underline ) {
sb.append("\033[0;1m");
} else if ( !bold && reverse && !blink && !underline ) {
sb.append("\033[0;7m");
} else if ( !bold && !reverse && blink && !underline) {
sb.append("\033[0;5m");
} else if ( bold && reverse && blink && underline ) {
sb.append("\033[0;1;7;5;4m");
} else if ( bold && reverse && !blink && underline ) {
sb.append("\033[0;1;7;4m");
} else if ( !bold && reverse && blink && underline ) {
sb.append("\033[0;7;5;4m");
} else if ( bold && !reverse && blink && underline ) {
sb.append("\033[0;1;5;4m");
} else if ( bold && !reverse && !blink && underline ) {
sb.append("\033[0;1;4m");
} else if ( !bold && reverse && !blink && underline ) {
sb.append("\033[0;7;4m");
} else if ( !bold && !reverse && blink && underline) {
sb.append("\033[0;5;4m");
} else if ( !bold && !reverse && !blink && underline) {
sb.append("\033[0;4m");
} else {
assert (!bold && !reverse && !blink && !underline);
sb.append("\033[0m");
}
return sb.toString();
}
/**
* Create a SGR parameter sequence for foreground, background, and
* several attributes. This sequence first resets all attributes to

View file

@ -28,13 +28,16 @@
*/
package jexer.backend;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import jexer.backend.GlyphMaker;
import jexer.bits.BorderStyle;
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
import jexer.bits.Clipboard;
import jexer.bits.GraphicsChars;
import jexer.bits.ImageUtils;
import jexer.bits.StringUtils;
/**
@ -142,12 +145,22 @@ public class LogicalScreen implements Screen {
* Public constructor. Sets everything to not-bold, white-on-black.
*/
protected LogicalScreen() {
offsetX = 0;
offsetY = 0;
width = 80;
height = 24;
logical = null;
physical = null;
this(80, 24);
}
/**
* Public constructor. Sets everything to not-bold, white-on-black.
*
* @param width width in cells
* @param height height in cells
*/
protected LogicalScreen(final int width, final int height) {
offsetX = 0;
offsetY = 0;
this.width = 80;
this.height = 24;
logical = null;
physical = null;
reallocate(width, height);
}
@ -748,7 +761,8 @@ public class LogicalScreen implements Screen {
final int right, final int bottom,
final CellAttributes border, final CellAttributes background) {
drawBox(left, top, right, bottom, border, background, 1, false);
drawBox(left, top, right, bottom, border, background,
BorderStyle.DEFAULT, false);
}
/**
@ -760,57 +774,23 @@ public class LogicalScreen implements Screen {
* @param bottom bottom row of the box
* @param border attributes to use for the border
* @param background attributes to use for the background
* @param borderType if 1, draw a single-line border; if 2, draw a
* double-line border; if 3, draw double-line top/bottom edges and
* single-line left/right edges (like Qmodem)
* @param borderStyle style of border
* @param shadow if true, draw a "shadow" on the box
*/
public final void drawBox(final int left, final int top,
public void drawBox(final int left, final int top,
final int right, final int bottom,
final CellAttributes border, final CellAttributes background,
final int borderType, final boolean shadow) {
final BorderStyle borderStyle, final boolean shadow) {
int boxWidth = right - left;
int boxHeight = bottom - top;
char cTopLeft;
char cTopRight;
char cBottomLeft;
char cBottomRight;
char cHSide;
char cVSide;
switch (borderType) {
case 1:
cTopLeft = GraphicsChars.ULCORNER;
cTopRight = GraphicsChars.URCORNER;
cBottomLeft = GraphicsChars.LLCORNER;
cBottomRight = GraphicsChars.LRCORNER;
cHSide = GraphicsChars.SINGLE_BAR;
cVSide = GraphicsChars.WINDOW_SIDE;
break;
case 2:
cTopLeft = GraphicsChars.WINDOW_LEFT_TOP_DOUBLE;
cTopRight = GraphicsChars.WINDOW_RIGHT_TOP_DOUBLE;
cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM_DOUBLE;
cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM_DOUBLE;
cHSide = GraphicsChars.DOUBLE_BAR;
cVSide = GraphicsChars.WINDOW_SIDE_DOUBLE;
break;
case 3:
cTopLeft = GraphicsChars.WINDOW_LEFT_TOP;
cTopRight = GraphicsChars.WINDOW_RIGHT_TOP;
cBottomLeft = GraphicsChars.WINDOW_LEFT_BOTTOM;
cBottomRight = GraphicsChars.WINDOW_RIGHT_BOTTOM;
cHSide = GraphicsChars.WINDOW_TOP;
cVSide = GraphicsChars.WINDOW_SIDE;
break;
default:
throw new IllegalArgumentException("Invalid border type: "
+ borderType);
}
int cTopLeft = borderStyle.getTopLeft();
int cTopRight = borderStyle.getTopRight();
int cBottomLeft = borderStyle.getBottomLeft();
int cBottomRight = borderStyle.getBottomRight();
int cHSide = borderStyle.getHorizontal();
int cVSide = borderStyle.getVertical();
// Place the corner characters
putCharXY(left, top, cTopLeft, border);
@ -1289,8 +1269,7 @@ public class LogicalScreen implements Screen {
public Screen snapshot() {
LogicalScreen other = null;
synchronized (this) {
other = new LogicalScreen();
other.setDimensions(width, height);
other = new LogicalScreen(width, height);
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
other.logical[col][row] = new Cell(logical[col][row]);
@ -1300,6 +1279,83 @@ public class LogicalScreen implements Screen {
return other;
}
/**
* Obtain a snapshot copy of a rectangular portion of the screen.
*
* @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 snapshot(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(logical[col][row]);
}
}
}
return other;
}
/**
* Copy all of screen's data to this screen.
*
* @param other the other screen
*/
public void copyScreen(final Screen other) {
synchronized (this) {
if ((other.getWidth() != width) || (other.getHeight() != height)) {
setDimensions(other.getWidth(), other.getHeight());
}
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
logical[col][row] = new Cell(other.getCharXY(col, row));
}
}
}
}
/**
* Copy a rectangular portion of another screen to this one. Any cells
* outside this screen's dimensions will be ignored.
*
* @param other the other screen
* @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
*/
public void copyScreen(final Screen other, final int x, final int y,
final int width, final int height) {
synchronized (this) {
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;
}
logical[col][row] = new Cell(other.getCharXY(col - x, row - y));
}
}
}
}
/**
* Set the backend to associated with this screen.
*
@ -1318,4 +1374,313 @@ public class LogicalScreen implements Screen {
return backend;
}
/**
* Alpha-blend a rectangle with a specified color and alpha onto this
* screen. Any cells outside this screen's dimensions will be ignored.
*
* @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
* @param color the RGB color to blend
* @param alpha the alpha transparency level (0 - 255) to use for cells
* from the other screen
*/
public void blendRectangle(final int x, final int y,
final int width, final int height, final int color, final int alpha) {
// We just create a new blank screen and blend it.
LogicalScreen rectangle = new LogicalScreen(width, height);
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
rectangle.logical[col][row].setBackColorRGB(color);
}
}
blendScreen(rectangle, x, y, width, height, alpha, false);
}
/**
* Alpha-blend a rectangular portion of another screen onto this one.
* Any cells outside this screen's dimensions will be ignored.
*
* @param otherScreen the other screen
* @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
* @param alpha the alpha transparency level (0 - 255) to use for cells
* from the other screen
* @param filterHatch if true, prevent hatch-like characters from
* showing through
*/
public void blendScreen(final Screen otherScreen, final int x, final int y,
final int width, final int height, final int alpha,
final boolean filterHatch) {
if (alpha == 255) {
// This is a raw copy.
copyScreen(otherScreen, x, y, width, height);
return;
}
/*
* We need to blend the background colors of other's cells over the
* cells of this screen (foreground and background), honoring our
* alpha. We will create a bitmap of one pixel per cell, blend that
* via AWT, and then set the cell RGBs and char's.
*/
synchronized (this) {
BufferedImage thisForeground = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
BufferedImage thisBackground = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
BufferedImage overBackground = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
BufferedImage thisOldBackground = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
final int OPAQUE = 0xFF000000;
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;
}
Cell cell = logical[col][row];
int thisBg = cell.getBackColorRGB();
if (thisBg < 0) {
if (backend != null) {
thisBg = backend.attrToBackgroundColor(cell).getRGB();
} else {
thisBg = SwingTerminal.attrToBackgroundColor(cell).getRGB();
}
}
int thisFg = cell.getForeColorRGB();
if (thisFg < 0) {
if (backend != null) {
thisFg = backend.attrToForegroundColor(cell).getRGB();
} else {
thisFg = SwingTerminal.attrToForegroundColor(cell).getRGB();
}
}
Cell over = otherScreen.getCharXY(col - x, row - y);
int overBg = over.getBackColorRGB();
if (overBg < 0) {
if (backend != null) {
overBg = backend.attrToBackgroundColor(over).getRGB();
} else {
overBg = SwingTerminal.attrToBackgroundColor(over).getRGB();
}
}
thisFg |= OPAQUE;
thisBg |= OPAQUE;
overBg |= OPAQUE;
thisForeground.setRGB(col - x, row - y, thisFg);
thisBackground.setRGB(col - x, row - y, thisBg);
thisOldBackground.setRGB(col - x, row - y, thisBg);
overBackground.setRGB(col - x, row - y, overBg);
}
}
// The three bitmaps are ready. We have skipped over
// cells/pixels that cannot overlap. Now blit overBackground
// over both thisForeground and thisBackground, and then assign
// cell colors and cell chars/images.
float fAlpha = (float) (alpha / 255.0);
Graphics2D g2d = thisForeground.createGraphics();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
fAlpha));
g2d.drawImage(overBackground, 0, 0, null);
g2d.dispose();
g2d = thisBackground.createGraphics();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
fAlpha));
g2d.drawImage(overBackground, 0, 0, null);
g2d.dispose();
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;
}
Cell thisCell = logical[col][row];
Cell overCell = otherScreen.getCharXY(col - x, row - y);
int thisFg = thisForeground.getRGB(col - x, row - y);
int thisBg = thisBackground.getRGB(col - x, row - y);
int thisOldBg = thisOldBackground.getRGB(col - x, row - y);
int overBg = overBackground.getRGB(col - x, row - y);
thisCell.setBackColorRGB(thisBg | OPAQUE);
thisCell.setForeColorRGB(thisFg | OPAQUE);
if (!overCell.isImage() && (overCell.getChar() == ' ')) {
// The overlaying cell is invisible.
if (thisCell.isImage()) {
// Our image will show through. We need to blend
// otherBg at alpha < 255 over this image.
Cell thisCopy = new Cell(thisCell);
thisCopy.flattenImage(false, backend);
BufferedImage image = thisCopy.getImage();
BufferedImage newImage;
newImage = new BufferedImage(image.getWidth(),
image.getHeight(), BufferedImage.TYPE_INT_ARGB);
g2d = newImage.createGraphics();
g2d.drawImage(image, 0, 0, null);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
fAlpha));
g2d.setColor(new java.awt.Color(overBg));
g2d.fillRect(0, 0, image.getWidth(),
image.getHeight());
g2d.dispose();
thisCell.setImage(newImage);
thisCell.setOpaqueImage();
} else {
// Our character will show through. If the
// contrast between our foreground and background
// is small, then drop the character.
if (ImageUtils.rgbDistance(thisFg, thisBg) < 5) {
thisCell.setChar(' ');
}
if (filterHatch) {
// Special case: the hatch characters are not
// allowed to show through.
int ch = thisCell.getChar();
if ((ch == 0x2591)
|| (ch == 0x2592)
|| (ch == 0x2593)
) {
thisCell.setChar(' ');
}
}
if (cursorVisible &&
(col == cursorX) &&
(row == cursorY)
) {
// Don't surface the character behind the
// cursor.
thisCell.setChar(' ');
}
}
continue;
}
// The overlaying cell has a character, use it.
thisCell.setChar(overCell.getChar());
int fg = overCell.getForeColorRGB();
if (fg < 0) {
thisCell.setForeColor(overCell.getForeColor());
} else {
thisCell.setForeColorRGB(fg);
}
thisCell.setBold(overCell.isBold());
thisCell.setBlink(overCell.isBlink());
thisCell.setUnderline(overCell.isUnderline());
thisCell.setProtect(overCell.isProtect());
if (!overCell.isImage()) {
// If we had an image, destroy it. Text ALWAYS
// overwrites images.
thisCell.setImage(null);
continue;
}
if (overCell.isImage() && !overCell.isTransparentImage()) {
// The image from the new cell will fully cover this
// cell's image.
// We need to blit overCell's image over thisOldBg at
// alpha < 255.
Cell overCopy = new Cell(overCell);
overCopy.flattenImage(false, backend);
BufferedImage image = overCopy.getImage();
BufferedImage newImage;
newImage = new BufferedImage(image.getWidth(),
image.getHeight(), BufferedImage.TYPE_INT_ARGB);
g2d = newImage.createGraphics();
g2d.setColor(new java.awt.Color(thisOldBg));
g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
fAlpha));
g2d.drawImage(image, 0, 0, null);
g2d.dispose();
thisCell.setImage(newImage);
thisCell.setOpaqueImage();
continue;
}
if (thisCell.isImage()
&& overCell.isImage()
&& overCell.isTransparentImage()
) {
// We need to blit overCell's image over a rectangle
// of otherBg at alpha = 255, and then blit that over
// thisCell's image at alpha < 255.
Cell overCopy = new Cell(overCell);
overCopy.flattenImage(false, backend);
BufferedImage image = overCopy.getImage();
BufferedImage newImage;
newImage = new BufferedImage(image.getWidth(),
image.getHeight(), BufferedImage.TYPE_INT_ARGB);
g2d = newImage.createGraphics();
g2d.drawImage(thisCell.getImage(), 0, 0, null);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
fAlpha));
g2d.drawImage(image, 0, 0, null);
g2d.dispose();
thisCell.setImage(newImage);
thisCell.setOpaqueImage();
continue;
}
if (!thisCell.isImage()
&& overCell.isImage()
&& overCell.isTransparentImage()
) {
// We need to blit overCell's image over a rectangle
// of otherBg at alpha = 255, and blit that over
// thisOldBg at alpha < 255.
Cell overCopy = new Cell(overCell);
overCopy.flattenImage(false, backend);
BufferedImage image = overCopy.getImage();
BufferedImage newImage;
newImage = new BufferedImage(image.getWidth(),
image.getHeight(), BufferedImage.TYPE_INT_ARGB);
g2d = newImage.createGraphics();
g2d.setColor(new java.awt.Color(thisOldBg));
g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
fAlpha));
g2d.drawImage(image, 0, 0, null);
g2d.dispose();
thisCell.setImage(newImage);
thisCell.setOpaqueImage();
continue;
}
// There should be nothing to do now. We have set the
// character, or set the image, and blended backgrounds
// for each case.
}
}
}
}
}

View file

@ -31,6 +31,7 @@ package jexer.backend;
import java.util.ArrayList;
import java.util.List;
import jexer.bits.BorderStyle;
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
import jexer.bits.Clipboard;
@ -608,7 +609,7 @@ public class MultiScreen implements Screen {
/**
* Draw a box with a border and empty background.
*
* @param left left column of box. 0 is the left-most row.
* @param left left column of box. 0 is the left-most column.
* @param top top row of the box. 0 is the top-most row.
* @param right right column of box
* @param bottom bottom row of the box
@ -629,26 +630,24 @@ public class MultiScreen implements Screen {
/**
* Draw a box with a border and empty background.
*
* @param left left column of box. 0 is the left-most row.
* @param left left column of box. 0 is the left-most column.
* @param top top row of the box. 0 is the top-most row.
* @param right right column of box
* @param bottom bottom row of the box
* @param border attributes to use for the border
* @param background attributes to use for the background
* @param borderType if 1, draw a single-line border; if 2, draw a
* double-line border; if 3, draw double-line top/bottom edges and
* single-line left/right edges (like Qmodem)
* @param borderStyle style of border
* @param shadow if true, draw a "shadow" on the box
*/
public void drawBox(final int left, final int top,
final int right, final int bottom,
final CellAttributes border, final CellAttributes background,
final int borderType, final boolean shadow) {
final BorderStyle borderStyle, final boolean shadow) {
synchronized (screens) {
for (Screen screen: screens) {
screen.drawBox(left, top, right, bottom, border, background,
borderType, shadow);
borderStyle, shadow);
}
}
}
@ -656,7 +655,7 @@ public class MultiScreen implements Screen {
/**
* Draw a box shadow.
*
* @param left left column of box. 0 is the left-most row.
* @param left left column of box. 0 is the left-most column.
* @param top top row of the box. 0 is the top-most row.
* @param right right column of box
* @param bottom bottom row of the box
@ -952,6 +951,118 @@ public class MultiScreen implements Screen {
}
}
/**
* Obtain a snapshot copy of a rectangular portion of the screen.
*
* @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 snapshot(final int x, final int y, final int width,
final int height) {
synchronized (screens) {
// Only copy from the first screen.
if (screens.size() > 0) {
return screens.get(0).snapshot(x, y, width, height);
}
// No screens are defined, create a blank.
LogicalScreen other = null;
other = new LogicalScreen();
int newWidth = x + width;
int newHeight = y + 25;
other.setDimensions(newWidth, newHeight);
return other;
}
}
/**
* Copy all of screen's data to this screen.
*
* @param other the other screen
*/
public void copyScreen(final Screen other) {
synchronized (screens) {
for (Screen screen: screens) {
screen.copyScreen(other);
}
}
}
/**
* Copy a rectangular portion of another screen to this one. Any cells
* outside this screen's dimensions will be ignored.
*
* @param other the other screen
* @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
*/
public void copyScreen(final Screen other, final int x, final int y,
final int width, final int height) {
synchronized (screens) {
for (Screen screen: screens) {
screen.copyScreen(other, x, y, width, height);
}
}
}
/**
* Alpha-blend a rectangular portion of another screen onto this one.
* Any cells outside this screen's dimensions will be ignored.
*
* @param otherScreen the other screen
* @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
* @param alpha the alpha transparency level (0 - 255) to use for cells
* from the other screen
* @param filterHatch if true, prevent hatch-like characters from
* showing through
*/
public void blendScreen(final Screen otherScreen, final int x, final int y,
final int width, final int height, final int alpha,
final boolean filterHatch) {
synchronized (screens) {
for (Screen screen: screens) {
screen.blendScreen(otherScreen, x, y, width, height, alpha,
filterHatch);
}
}
}
/**
* Alpha-blend a rectangle with a specified color and alpha onto this
* screen. Any cells outside this screen's dimensions will be ignored.
*
* @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
* @param color the RGB color to blend
* @param alpha the alpha transparency level (0 - 255) to use for cells
* from the other screen
*/
public void blendRectangle(final int x, final int y,
final int width, final int height, final int color, final int alpha) {
synchronized (screens) {
for (Screen screen: screens) {
screen.blendRectangle(x, y, width, height, color, alpha);
}
}
}
/**
* Set the backend to associated with this screen.
*

View file

@ -28,6 +28,7 @@
*/
package jexer.backend;
import jexer.bits.BorderStyle;
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
import jexer.bits.Clipboard;
@ -321,7 +322,7 @@ public interface Screen {
/**
* Draw a box with a border and empty background.
*
* @param left left column of box. 0 is the left-most row.
* @param left left column of box. 0 is the left-most column.
* @param top top row of the box. 0 is the top-most row.
* @param right right column of box
* @param bottom bottom row of the box
@ -335,26 +336,24 @@ public interface Screen {
/**
* Draw a box with a border and empty background.
*
* @param left left column of box. 0 is the left-most row.
* @param left left column of box. 0 is the left-most column.
* @param top top row of the box. 0 is the top-most row.
* @param right right column of box
* @param bottom bottom row of the box
* @param border attributes to use for the border
* @param background attributes to use for the background
* @param borderType if 1, draw a single-line border; if 2, draw a
* double-line border; if 3, draw double-line top/bottom edges and
* single-line left/right edges (like Qmodem)
* @param borderStyle style of border
* @param shadow if true, draw a "shadow" on the box
*/
public void drawBox(final int left, final int top,
final int right, final int bottom,
final CellAttributes border, final CellAttributes background,
final int borderType, final boolean shadow);
final BorderStyle borderStyle, final boolean shadow);
/**
* Draw a box shadow.
*
* @param left left column of box. 0 is the left-most row.
* @param left left column of box. 0 is the left-most column.
* @param top top row of the box. 0 is the top-most row.
* @param right right column of box
* @param bottom bottom row of the box
@ -490,6 +489,72 @@ public interface Screen {
*/
public Screen snapshot();
/**
* Obtain a snapshot copy of a rectangular portion of the screen.
*
* @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 snapshot(final int x, final int y, final int width,
final int height);
/**
* Copy all of screen's data to this screen.
*
* @param other the other screen
*/
public void copyScreen(final Screen other);
/**
* Copy a rectangular portion of another screen to this one. Any cells
* outside this screen's dimensions will be ignored.
*
* @param other the other screen
* @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
*/
public void copyScreen(final Screen other, final int x, final int y,
final int width, final int height);
/**
* Alpha-blend a rectangular portion of another screen onto this one.
* Any cells outside this screen's dimensions will be ignored.
*
* @param otherScreen the other screen
* @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
* @param alpha the alpha transparency level (0 - 255) to use for cells
* from the other screen
* @param filterHatch if true, prevent hatch-like characters from
* showing through
*/
public void blendScreen(final Screen otherScreen, final int x, final int y,
final int width, final int height, final int alpha,
final boolean filterHatch);
/**
* Alpha-blend a rectangle with a specified color and alpha onto this
* screen. Any cells outside this screen's dimensions will be ignored.
*
* @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
* @param color the RGB color to blend
* @param alpha the alpha transparency level (0 - 255) to use for cells
* from the other screen
*/
public void blendRectangle(final int x, final int y,
final int width, final int height, final int color, final int alpha);
/**
* Get the backend that instantiated this screen.
*

View file

@ -119,7 +119,7 @@ public class SwingTerminal extends LogicalScreen
* A value of 25 or more feels sluggish for input, but is sustainable for
* the garbage collector.
*/
private static final long SYNC_MIN_MILLIS_SUSTAIN = 20;
private static final long SYNC_MIN_MILLIS_SUSTAIN = 10;
/**
* The number of frames that can be emitted quickly (at
@ -1611,7 +1611,7 @@ public class SwingTerminal extends LogicalScreen
xPixel -= textWidth;
break;
}
gr.setColor(attrToForegroundColor(lCell));
gr.setColor((Color.WHITE).darker());
switch (cursorStyle) {
default:
// Fall through...

View file

@ -0,0 +1,277 @@
/*
* 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.bits;
/**
* A text box border style.
*/
public class BorderStyle {
// ------------------------------------------------------------------------
// Constants --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* The default border style. Synonym for SINGLE.
*/
public static final BorderStyle DEFAULT;
/**
* The "no-border" style.
*/
public static final BorderStyle NONE;
/**
* A single-line border.
*/
public static final BorderStyle SINGLE;
/**
* A double-line border.
*/
public static final BorderStyle DOUBLE;
/**
* A single-line border on the vertical sections, double-line on the
* horizontal sections.
*/
public static final BorderStyle SINGLE_V_DOUBLE_H;
/**
* A double-line border on the vertical sections, single-line on the
* horizontal sections.
*/
public static final BorderStyle SINGLE_H_DOUBLE_V;
/**
* A single-line border with round corners.
*/
public static final BorderStyle SINGLE_ROUND;
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* The glyph for horizontal sections.
*/
private int horizontal;
/**
* The glyph for vertical sections.
*/
private int vertical;
/**
* The glyph for the top-left corner.
*/
private int topLeft;
/**
* The glyph for the top-right corner.
*/
private int topRight;
/**
* The glyph for the bottom-left corner.
*/
private int bottomLeft;
/**
* The glyph for the bottom-right corner.
*/
private int bottomRight;
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Static constructor.
*/
static {
NONE = new BorderStyle(' ', ' ', ' ', ' ', ' ', ' ');
SINGLE = new BorderStyle(GraphicsChars.SINGLE_BAR,
GraphicsChars.WINDOW_SIDE,
GraphicsChars.ULCORNER,
GraphicsChars.URCORNER,
GraphicsChars.LLCORNER,
GraphicsChars.LRCORNER);
DOUBLE = new BorderStyle(GraphicsChars.DOUBLE_BAR,
GraphicsChars.WINDOW_SIDE_DOUBLE,
GraphicsChars.WINDOW_LEFT_TOP_DOUBLE,
GraphicsChars.WINDOW_RIGHT_TOP_DOUBLE,
GraphicsChars.WINDOW_LEFT_BOTTOM_DOUBLE,
GraphicsChars.WINDOW_RIGHT_BOTTOM_DOUBLE);
SINGLE_V_DOUBLE_H = new BorderStyle(GraphicsChars.WINDOW_TOP,
GraphicsChars.WINDOW_SIDE,
GraphicsChars.WINDOW_LEFT_TOP,
GraphicsChars.WINDOW_RIGHT_TOP,
GraphicsChars.WINDOW_LEFT_BOTTOM,
GraphicsChars.WINDOW_RIGHT_BOTTOM);
SINGLE_H_DOUBLE_V = new BorderStyle(GraphicsChars.SINGLE_BAR,
GraphicsChars.WINDOW_SIDE_DOUBLE,
0x2553,
0x2556,
0x2559,
0x255C);
SINGLE_ROUND = new BorderStyle(GraphicsChars.SINGLE_BAR,
GraphicsChars.WINDOW_SIDE,
0x256D,
0x256E,
0x2570,
0x256F);
DEFAULT = SINGLE;
}
/**
* Private constructor used to make the static BorderStyle instances.
*
* @param horizontal the horizontal section glyph
* @param vertical the vertical section glyph
* @param topLeft the top-left corner glyph
* @param topRight the top-right corner glyph
* @param bottomLeft the bottom-left corner glyph
* @param bottomRight the bottom-right corner glyph
*/
private BorderStyle(final int horizontal, final int vertical,
final int topLeft, final int topRight,
final int bottomLeft, final int bottomRight) {
this.horizontal = horizontal;
this.vertical = vertical;
this.topLeft = topLeft;
this.topRight = topRight;
this.bottomLeft = bottomLeft;
this.bottomRight = bottomRight;
}
// ------------------------------------------------------------------------
// BorderStyle ------------------------------------------------------------
// ------------------------------------------------------------------------
/**
* Public constructor returns one of the static BorderStyle instances.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"
* @return BorderStyle.SINGLE, BorderStyle.DOUBLE, etc.
*/
public static final BorderStyle getStyle(final String borderStyle) {
String str = borderStyle.toLowerCase();
if (str.equals("none")) {
return NONE;
}
if (str.equals("default")) {
return SINGLE;
}
if (str.equals("single")) {
return SINGLE;
}
if (str.equals("double")) {
return DOUBLE;
}
if (str.equals("round")) {
return SINGLE_ROUND;
}
if (str.equals("singlevdoubleh")) {
return SINGLE_V_DOUBLE_H;
}
if (str.equals("singlehdoublev")) {
return SINGLE_H_DOUBLE_V;
}
// If they didn't get it right, return single.
return SINGLE;
}
/**
* Get the glyph for horizontal sections.
*
* @return the glyph for horizontal sections.
*/
public final int getHorizontal() {
return horizontal;
}
/**
*
* Get the glyph for vertical sections.
* @return the glyph for vertical sections.
*/
public final int getVertical() {
return vertical;
}
/**
* Get the glyph for the top-left corner.
*
* @return the glyph for the top-left corner.
*/
public final int getTopLeft() {
return topLeft;
}
/**
* Get the glyph for the top-right corner.
*
* @return the glyph for the top-right corner.
*/
public final int getTopRight() {
return topRight;
}
/**
* Get the glyph for the bottom-left corner.
*
* @return the glyph for the bottom-left corner.
*/
public final int getBottomLeft() {
return bottomLeft;
}
/**
* Get the glyph for the bottom-right corner.
*
* @return the glyph for the bottom-right corner.
*/
public final int getBottomRight() {
return bottomRight;
}
}

View file

@ -31,6 +31,7 @@ package jexer.bits;
import java.awt.image.BufferedImage;
import jexer.backend.Backend;
import jexer.backend.GlyphMaker;
import jexer.backend.SwingTerminal;
/**
* This class represents a single text cell or bit of image on the screen.
@ -184,7 +185,11 @@ public class Cell extends CellAttributes {
public void setImage(final BufferedImage image) {
this.image = image;
hasTransparentPixels = 0;
imageHashCode = image.hashCode();
if (image == null) {
imageHashCode = 0;
} else {
imageHashCode = image.hashCode();
}
width = Width.SINGLE;
}
@ -255,7 +260,11 @@ public class Cell extends CellAttributes {
BufferedImage newImage = new BufferedImage(textWidth,
textHeight, BufferedImage.TYPE_INT_ARGB);
java.awt.Graphics gr = newImage.getGraphics();
gr.setColor(backend.attrToBackgroundColor(this));
if (backend != null) {
gr.setColor(backend.attrToBackgroundColor(this));
} else {
gr.setColor(SwingTerminal.attrToBackgroundColor(this));
}
if (overGlyph) {
// Render this cell to a flat image. The bad news is that we
@ -273,6 +282,38 @@ public class Cell extends CellAttributes {
setImage(newImage);
}
/**
* Flatten the image on this cell by rendering it either onto a
* background color.
*
* @param background the background color to draw on
*/
private void flattenImage(final java.awt.Color background) {
assert (isImage());
if (hasTransparentPixels == 2) {
// The image already covers the entire cell.
return;
}
int textWidth = image.getWidth();
int textHeight = image.getHeight();
BufferedImage newImage = new BufferedImage(textWidth,
textHeight, BufferedImage.TYPE_INT_ARGB);
java.awt.Graphics gr = newImage.getGraphics();
gr.setColor(background);
// Put the background color behind the pixels.
gr.fillRect(0, 0, newImage.getWidth(), newImage.getHeight());
gr.drawImage(image, 0, 0, null, null);
gr.dispose();
setImage(newImage);
// We know we are opaque now.
hasTransparentPixels = 2;
}
/**
* Blit another cell's image on top of the image data for this cell.
*
@ -740,9 +781,11 @@ public class Cell extends CellAttributes {
*/
@Override
public String toString() {
return String.format("%s fore: %s back: %s bold: %s blink: %s ch %c",
return String.format("%s fore: %s RGB %06x back: %s RGB %06x bold: %s blink: %s ch %c",
(isImage() ? "IMAGE" : ""),
getForeColor(), getBackColor(), isBold(), isBlink(), ch);
getForeColor(), getForeColorRGB(),
getBackColor(), getBackColorRGB(),
isBold(), isBlink(), ch);
}
/**

View file

@ -125,7 +125,7 @@ public class CellAttributes {
* @return bold value
*/
public final boolean isBold() {
return ((flags & BOLD) == 0 ? false : true);
return ((flags & BOLD) != 0);
}
/**
@ -268,7 +268,8 @@ public class CellAttributes {
}
/**
* Getter for foreColor RGB.
* Getter for foreColor RGB. Note that this is always a RGB value,
* i.e. alpha is 0.
*
* @return foreColor value. Negative means unset.
*/
@ -282,11 +283,13 @@ public class CellAttributes {
* @param foreColorRGB new foreColor RGB value
*/
public final void setForeColorRGB(final int foreColorRGB) {
this.foreColorRGB = foreColorRGB;
this.foreColorRGB = foreColorRGB & 0xFFFFFF;
this.foreColor = Color.WHITE;
}
/**
* Getter for backColor RGB.
* Getter for backColor RGB. Note that this is always a RGB value,
* i.e. alpha is 0.
*
* @return backColor value. Negative means unset.
*/
@ -300,7 +303,8 @@ public class CellAttributes {
* @param backColorRGB new backColor RGB value
*/
public final void setBackColorRGB(final int backColorRGB) {
this.backColorRGB = backColorRGB;
this.backColorRGB = backColorRGB & 0xFFFFFF;
this.backColor = Color.BLACK;
}
/**

View file

@ -738,6 +738,68 @@ public class ColorTheme {
}
/**
* Set the theme to femme. I love pink. You can too! 💗
*/
public void setFemme() {
setDefaultTheme();
final int pink = 0xf7a8b8;
final int blue = 0x55cdfc;
for (String key: colors.keySet()) {
CellAttributes color = colors.get(key);
Color fg = color.getForeColor();
Color bg = color.getBackColor();
boolean bold = color.isBold();
if (bg.equals(Color.WHITE) && fg.equals(Color.BLACK)) {
color.setForeColor(Color.MAGENTA);
color.setBackColorRGB(pink);
} else if (bg.equals(Color.WHITE) && fg.equals(Color.WHITE)) {
color.setForeColor(Color.MAGENTA);
color.setBackColorRGB(pink);
color.setBold(true);
} else if (bg.equals(Color.WHITE) && fg.equals(Color.WHITE)) {
color.setForeColorRGB(blue);
color.setBackColorRGB(pink);
color.setBold(true);
} else if (bg.equals(Color.WHITE) && fg.equals(Color.GREEN)) {
color.setForeColor(Color.BLUE);
color.setBackColor(Color.BLACK);
color.setBold(true);
} else if (bg.equals(Color.WHITE) && fg.equals(Color.RED)) {
color.setForeColorRGB(blue);
color.setBackColorRGB(pink);
color.setBold(true);
} else if (bg.equals(Color.BLUE) && fg.equals(Color.CYAN)) {
color.setForeColor(Color.RED);
color.setBackColor(Color.MAGENTA);
color.setBold(true);
} else if (fg.equals(Color.BLUE) && bg.equals(Color.CYAN)) {
color.setForeColor(Color.MAGENTA);
color.setBackColor(Color.RED);
color.setBold(true);
} else if (bg.equals(Color.BLUE)) {
color.setBackColor(Color.BLACK);
} else if (bg.equals(Color.GREEN)) {
color.setBackColor(Color.CYAN);
} else if (fg.equals(Color.WHITE) && bold) {
color.setForeColor(Color.RED);
}
colors.put(key, color);
}
/*
CellAttributes color;
color = new CellAttributes();
color.setForeColor(Color.MAGENTA);
color.setBackColorRGB(pink);
color.setBold(false);
colors.put("twindow.background.modal", color);
*/
}
/**
* Make human-readable description of this Cell.
*

View file

@ -55,6 +55,8 @@ import org.w3c.dom.NodeList;
* - Scale an image and preserve aspect ratio.
*
* - Open an animated image as an Animation.
*
* - Compute the distance between two colors in RGB space.
*/
public class ImageUtils {
@ -445,4 +447,24 @@ public class ImageUtils {
}
}
/**
* Report the absolute distance in RGB space between two RGB colors.
*
* @param first the first color
* @param second the second color
* @return the distance
*/
public static int rgbDistance(final int first, final int second) {
int red = (first >>> 16) & 0xFF;
int green = (first >>> 8) & 0xFF;
int blue = first & 0xFF;
int red2 = (second >>> 16) & 0xFF;
int green2 = (second >>> 8) & 0xFF;
int blue2 = second & 0xFF;
double diff = Math.pow(red2 - red, 2);
diff += Math.pow(green2 - green, 2);
diff += Math.pow(blue2 - blue, 2);
return (int) Math.sqrt(diff);
}
}

View file

@ -277,6 +277,7 @@ public class DemoPixelsWindow extends TWindow {
*/
@Override
public void onClose() {
super.onClose();
getApplication().removeTimer(timer3);
}

View file

@ -34,6 +34,7 @@ import jexer.TApplication;
import jexer.TKeypress;
import jexer.TWidget;
import jexer.TWindow;
import jexer.bits.BorderStyle;
import jexer.bits.CellAttributes;
import jexer.bits.GraphicsChars;
import jexer.bits.MnemonicString;
@ -200,6 +201,22 @@ public class TMenu extends TWindow {
useIcons = true;
}
// Set the border style from the system properties
setBorderStyleForeground(null);
setBorderStyleInactive(null);
setBorderStyleModal(null);
setBorderStyleMoving(null);
int opacity = 95;
try {
opacity = Integer.parseInt(System.getProperty(
"jexer.TMenu.opacity", "95"));
opacity = Math.max(opacity, 10);
opacity = Math.min(opacity, 100);
} catch (NumberFormatException e) {
// SQUASH
}
setAlpha(opacity * 255 / 100);
}
// ------------------------------------------------------------------------
@ -389,17 +406,13 @@ public class TMenu extends TWindow {
}
// Draw the box
char cTopLeft;
char cTopRight;
char cBottomLeft;
char cBottomRight;
char cHSide;
cTopLeft = GraphicsChars.ULCORNER;
cTopRight = GraphicsChars.URCORNER;
cBottomLeft = GraphicsChars.LLCORNER;
cBottomRight = GraphicsChars.LRCORNER;
cHSide = GraphicsChars.SINGLE_BAR;
BorderStyle borderStyle = getBorderStyle();
int cTopLeft = borderStyle.getTopLeft();
int cTopRight = borderStyle.getTopRight();
int cBottomLeft = borderStyle.getBottomLeft();
int cBottomRight = borderStyle.getBottomRight();
int cHSide = borderStyle.getHorizontal();
int cVSide = borderStyle.getVertical();
// Place the corner characters
putCharXY(1, 0, cTopLeft, background);
@ -412,7 +425,9 @@ public class TMenu extends TWindow {
hLineXY(1 + 1, getHeight() - 1, getWidth() - 4, cHSide, background);
// Draw a shadow
drawBoxShadow(0, 0, getWidth(), getHeight());
if (!getApplication().hasTranslucence()) {
drawBoxShadow(0, 0, getWidth(), getHeight());
}
}
// ------------------------------------------------------------------------
@ -919,4 +934,77 @@ public class TMenu extends TWindow {
super.resetTabOrder();
}
/**
* Set the border style for the window when it is the foreground window.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TMenu.borderStyle.
*/
@Override
public void setBorderStyleForeground(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TMenu.borderStyle",
"single");
super.setBorderStyleForeground(style);
} else {
super.setBorderStyleForeground(borderStyle);
}
}
/**
* Set the border style for the window when it is the modal window.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TMenu.borderStyle.
*/
@Override
public void setBorderStyleModal(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TMenu.borderStyle",
"single");
super.setBorderStyleModal(style);
} else {
super.setBorderStyleModal(borderStyle);
}
}
/**
* Set the border style for the window when it is an inactive/background
* window.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TMenu.borderStyle.
*/
@Override
public void setBorderStyleInactive(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TMenu.borderStyle",
"single");
super.setBorderStyleInactive(style);
} else {
super.setBorderStyleInactive(borderStyle);
}
}
/**
* Set the border style for the window when it is being dragged/resize.
*
* @param borderStyle the border style string, one of: "default", "none",
* "single", "double", "singleVdoubleH", "singleHdoubleV", or "round"; or
* null to use the value from jexer.TMenu.borderStyle.
*/
@Override
public void setBorderStyleMoving(final String borderStyle) {
if (borderStyle == null) {
String style = System.getProperty("jexer.TMenu.borderStyle",
"single");
super.setBorderStyleMoving(style);
} else {
super.setBorderStyleMoving(borderStyle);
}
}
}

View file

@ -31,6 +31,7 @@ package jexer.menu;
import jexer.TKeypress;
import jexer.TWidget;
import jexer.backend.Backend;
import jexer.bits.BorderStyle;
import jexer.bits.CellAttributes;
import jexer.bits.GraphicsChars;
import jexer.bits.MnemonicString;
@ -249,7 +250,8 @@ public class TMenuItem extends TWidget {
boolean useIcons = ((TMenu) getParent()).useIcons;
char cVSide = GraphicsChars.WINDOW_SIDE;
BorderStyle borderStyle = ((TMenu) getParent()).getBorderStyle();
int cVSide = borderStyle.getVertical();
vLineXY(0, 0, 1, cVSide, background);
vLineXY(getWidth() - 1, 0, 1, cVSide, background);

View file

@ -28,6 +28,7 @@
*/
package jexer.menu;
import jexer.bits.BorderStyle;
import jexer.bits.CellAttributes;
import jexer.bits.GraphicsChars;
@ -65,9 +66,22 @@ public class TMenuSeparator extends TMenuItem {
public void draw() {
CellAttributes background = getTheme().getColor("tmenu");
putCharXY(0, 0, GraphicsChars.CP437[0xC3], background);
putCharXY(getWidth() - 1, 0, GraphicsChars.CP437[0xB4], background);
hLineXY(1, 0, getWidth() - 2, GraphicsChars.SINGLE_BAR, background);
BorderStyle borderStyle = ((TMenu) getParent()).getBorderStyle();
int cHSide = GraphicsChars.SINGLE_BAR;
int left = GraphicsChars.CP437[0xC3];
int right = GraphicsChars.CP437[0xB4];
if (borderStyle.getVertical() == GraphicsChars.WINDOW_SIDE_DOUBLE) {
left = 0x255F;
right = 0x2562;
}
if (borderStyle.equals(BorderStyle.NONE)) {
left = ' ';
right = ' ';
}
putCharXY(0, 0, left, background);
putCharXY(getWidth() - 1, 0, right, background);
hLineXY(1, 0, getWidth() - 2, cHSide, background);
}
}

View file

@ -164,6 +164,19 @@ public class Bitmap extends TackboardItem {
return renderedImage;
}
/**
* Remove this item from its board. Subclasses can use this for cleanup
* also.
*/
@Override
public void remove() {
super.remove();
if (animation != null) {
animation.stop();
}
}
// ------------------------------------------------------------------------
// Bitmap -----------------------------------------------------------------
// ------------------------------------------------------------------------

View file

@ -279,8 +279,10 @@ public class Tackboard {
backImage = new BufferedImage(cellWidth,
cellHeight, BufferedImage.TYPE_INT_ARGB);
java.awt.Graphics gr = backImage.getGraphics();
gr.setColor(screen.getBackend().
attrToBackgroundColor(oldCell));
java.awt.Color oldColor = screen.getBackend().
attrToBackgroundColor(oldCell);
gr.setColor(oldColor);
gr.fillRect(0, 0, backImage.getWidth(),
backImage.getHeight());
gr.drawImage(newImage, 0, 0, null, null);

View file

@ -3735,9 +3735,7 @@ public class ECMA48 implements Runnable {
// screen. We won't switch to a different buffer,
// instead we will just clear the screen.
currentState.attr.setForeColor(Color.WHITE);
currentState.attr.setForeColorRGB(-1);
currentState.attr.setBackColor(Color.BLACK);
currentState.attr.setBackColorRGB(-1);
eraseScreen(0, 0, height - 1, width - 1, false);
scrollRegionTop = 0;
scrollRegionBottom = height - 1;
@ -4428,9 +4426,7 @@ public class ECMA48 implements Runnable {
*/
private void sgr() {
for (int i = 0; i < collectBuffer.length(); i++) {
if ((collectBuffer.charAt(i) == '>')
|| (collectBuffer.charAt(i) == '>')
) {
if (collectBuffer.charAt(i) == '>') {
// Private-mode sequence, disregard.
return;
}
@ -4672,42 +4668,34 @@ public class ECMA48 implements Runnable {
case 30:
// Set black foreground
currentState.attr.setForeColor(Color.BLACK);
currentState.attr.setForeColorRGB(-1);
break;
case 31:
// Set red foreground
currentState.attr.setForeColor(Color.RED);
currentState.attr.setForeColorRGB(-1);
break;
case 32:
// Set green foreground
currentState.attr.setForeColor(Color.GREEN);
currentState.attr.setForeColorRGB(-1);
break;
case 33:
// Set yellow foreground
currentState.attr.setForeColor(Color.YELLOW);
currentState.attr.setForeColorRGB(-1);
break;
case 34:
// Set blue foreground
currentState.attr.setForeColor(Color.BLUE);
currentState.attr.setForeColorRGB(-1);
break;
case 35:
// Set magenta foreground
currentState.attr.setForeColor(Color.MAGENTA);
currentState.attr.setForeColorRGB(-1);
break;
case 36:
// Set cyan foreground
currentState.attr.setForeColor(Color.CYAN);
currentState.attr.setForeColorRGB(-1);
break;
case 37:
// Set white foreground
currentState.attr.setForeColor(Color.WHITE);
currentState.attr.setForeColorRGB(-1);
break;
case 38:
if (type == DeviceType.XTERM) {
@ -4758,47 +4746,38 @@ public class ECMA48 implements Runnable {
// Underscore off, default foreground color
currentState.attr.setUnderline(false);
currentState.attr.setForeColor(Color.WHITE);
currentState.attr.setForeColorRGB(-1);
break;
case 40:
// Set black background
currentState.attr.setBackColor(Color.BLACK);
currentState.attr.setBackColorRGB(-1);
break;
case 41:
// Set red background
currentState.attr.setBackColor(Color.RED);
currentState.attr.setBackColorRGB(-1);
break;
case 42:
// Set green background
currentState.attr.setBackColor(Color.GREEN);
currentState.attr.setBackColorRGB(-1);
break;
case 43:
// Set yellow background
currentState.attr.setBackColor(Color.YELLOW);
currentState.attr.setBackColorRGB(-1);
break;
case 44:
// Set blue background
currentState.attr.setBackColor(Color.BLUE);
currentState.attr.setBackColorRGB(-1);
break;
case 45:
// Set magenta background
currentState.attr.setBackColor(Color.MAGENTA);
currentState.attr.setBackColorRGB(-1);
break;
case 46:
// Set cyan background
currentState.attr.setBackColor(Color.CYAN);
currentState.attr.setBackColorRGB(-1);
break;
case 47:
// Set white background
currentState.attr.setBackColor(Color.WHITE);
currentState.attr.setBackColorRGB(-1);
break;
case 48:
if (type == DeviceType.XTERM) {
@ -4844,7 +4823,6 @@ public class ECMA48 implements Runnable {
case 49:
// Default background
currentState.attr.setBackColor(Color.BLACK);
currentState.attr.setBackColorRGB(-1);
break;
default: