*
* The MIT License (MIT)
*
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*/
package jexer.backend;
+import java.awt.image.BufferedImage;
+
+import jexer.backend.GlyphMaker;
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
import jexer.bits.GraphicsChars;
+import jexer.bits.StringUtils;
/**
* A logical screen composed of a 2D array of Cells.
*/
protected int cursorY;
+ /**
+ * The last used height of a character cell in pixels, only used for
+ * full-width chars.
+ */
+ private int lastTextHeight = -1;
+
+ /**
+ * The glyph drawer for full-width chars.
+ */
+ private GlyphMaker glyphMaker = null;
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
// Screen -----------------------------------------------------------------
// ------------------------------------------------------------------------
+ /**
+ * Get the width of a character cell in pixels.
+ *
+ * @return the width in pixels of a character cell
+ */
+ public int getTextWidth() {
+ // Default width is 16 pixels.
+ return 16;
+ }
+
+ /**
+ * Get the height of a character cell in pixels.
+ *
+ * @return the height in pixels of a character cell
+ */
+ public int getTextHeight() {
+ // Default height is 20 pixels.
+ return 20;
+ }
+
/**
* Set drawing offset for x.
*
// If this happens to be the cursor position, make the position
// dirty.
if ((cursorX == X) && (cursorY == Y)) {
- if (physical[cursorX][cursorY].getChar() == 'Q') {
- physical[cursorX][cursorY].setChar('X');
- } else {
- physical[cursorX][cursorY].setChar('Q');
- }
+ physical[cursorX][cursorY].unset();
+ unsetImageRow(cursorY);
}
}
}
* @param ch character + attributes to draw
*/
public final void putCharXY(final int x, final int y, final Cell ch) {
- putCharXY(x, y, ch.getChar(), ch);
+ if ((x < clipLeft)
+ || (x >= clipRight)
+ || (y < clipTop)
+ || (y >= clipBottom)
+ ) {
+ return;
+ }
+
+ if ((StringUtils.width(ch.getChar()) == 2) && (!ch.isImage())) {
+ putFullwidthCharXY(x, y, ch);
+ return;
+ }
+
+ int X = x + offsetX;
+ int Y = y + offsetY;
+
+ // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
+
+ if ((X >= 0) && (X < width) && (Y >= 0) && (Y < height)) {
+
+ // Do not put control characters on the display
+ if (!ch.isImage()) {
+ assert (ch.getChar() >= 0x20);
+ assert (ch.getChar() != 0x7F);
+ }
+ logical[X][Y].setTo(ch);
+
+ // If this happens to be the cursor position, make the position
+ // dirty.
+ if ((cursorX == X) && (cursorY == Y)) {
+ physical[cursorX][cursorY].unset();
+ unsetImageRow(cursorY);
+ }
+ }
}
/**
return;
}
+ if (StringUtils.width(ch) == 2) {
+ putFullwidthCharXY(x, y, ch, attr);
+ return;
+ }
+
int X = x + offsetX;
int Y = y + offsetY;
// If this happens to be the cursor position, make the position
// dirty.
if ((cursorX == X) && (cursorY == Y)) {
- if (physical[cursorX][cursorY].getChar() == 'Q') {
- physical[cursorX][cursorY].setChar('X');
- } else {
- physical[cursorX][cursorY].setChar('Q');
- }
+ physical[cursorX][cursorY].unset();
+ unsetImageRow(cursorY);
}
}
}
return;
}
+ if (StringUtils.width(ch) == 2) {
+ putFullwidthCharXY(x, y, ch);
+ return;
+ }
+
int X = x + offsetX;
int Y = y + offsetY;
// If this happens to be the cursor position, make the position
// dirty.
if ((cursorX == X) && (cursorY == Y)) {
- if (physical[cursorX][cursorY].getChar() == 'Q') {
- physical[cursorX][cursorY].setChar('X');
- } else {
- physical[cursorX][cursorY].setChar('Q');
- }
+ physical[cursorX][cursorY].unset();
+ unsetImageRow(cursorY);
}
}
}
*/
public final void setDimensions(final int width, final int height) {
reallocate(width, height);
+ resizeToScreen();
+ }
+
+ /**
+ * Resize the physical screen to match the logical screen dimensions.
+ */
+ public void resizeToScreen() {
+ // Subclasses are expected to override this.
}
/**
/**
* 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
/**
* 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
/**
* 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
// Shadows do not honor clipping but they DO honor offset.
int oldClipRight = clipRight;
int oldClipBottom = clipBottom;
- /*
- clipRight = boxWidth + 2;
- clipBottom = boxHeight + 1;
- */
- clipRight = width;
- clipBottom = height;
+ // When offsetX or offsetY go negative, we need to increase the clip
+ // bounds.
+ clipRight = width - offsetX;
+ clipBottom = height - offsetY;
for (int i = 0; i < boxHeight; i++) {
putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr);
&& (cursorX <= width - 1)
) {
// Make the current cursor position dirty
- if (physical[cursorX][cursorY].getChar() == 'Q') {
- physical[cursorX][cursorY].setChar('X');
- } else {
- physical[cursorX][cursorY].setChar('Q');
- }
+ physical[cursorX][cursorY].unset();
+ unsetImageRow(cursorY);
}
cursorVisible = visible;
public final void clearPhysical() {
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
- physical[col][row].reset();
+ physical[col][row].unset();
}
}
}
+ /**
+ * Unset every image cell on one row of the physical screen, forcing
+ * images on that row to be redrawn.
+ *
+ * @param y row coordinate. 0 is the top-most row.
+ */
+ public final void unsetImageRow(final int y) {
+ if ((y < 0) || (y >= height)) {
+ return;
+ }
+ for (int x = 0; x < width; x++) {
+ if (logical[x][y].isImage()) {
+ physical[x][y].unset();
+ }
+ }
+ }
+
+ /**
+ * Render one fullwidth cell.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param cell the cell to draw
+ */
+ public final void putFullwidthCharXY(final int x, final int y,
+ final Cell cell) {
+
+ if (lastTextHeight != getTextHeight()) {
+ glyphMaker = GlyphMaker.getInstance(getTextHeight());
+ lastTextHeight = getTextHeight();
+ }
+ BufferedImage image = glyphMaker.getImage(cell, getTextWidth() * 2,
+ getTextHeight());
+ BufferedImage leftImage = image.getSubimage(0, 0, getTextWidth(),
+ getTextHeight());
+ BufferedImage rightImage = image.getSubimage(getTextWidth(), 0,
+ getTextWidth(), getTextHeight());
+
+ Cell left = new Cell();
+ left.setTo(cell);
+ left.setImage(leftImage);
+ left.setWidth(Cell.Width.LEFT);
+ // Blank out the char itself, so that shadows do not leave artifacts.
+ left.setChar(' ');
+ putCharXY(x, y, left);
+
+ Cell right = new Cell();
+ right.setTo(cell);
+ right.setImage(rightImage);
+ right.setWidth(Cell.Width.RIGHT);
+ // Blank out the char itself, so that shadows do not leave artifacts.
+ right.setChar(' ');
+ putCharXY(x + 1, y, right);
+ }
+
+ /**
+ * Render one fullwidth character with attributes.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param ch character to draw
+ * @param attr attributes to use (bold, foreColor, backColor)
+ */
+ public final void putFullwidthCharXY(final int x, final int y,
+ final char ch, final CellAttributes attr) {
+
+ Cell cell = new Cell(ch);
+ cell.setAttr(attr);
+ putFullwidthCharXY(x, y, cell);
+ }
+
+ /**
+ * Render one fullwidth character with attributes.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param ch character to draw
+ */
+ public final void putFullwidthCharXY(final int x, final int y,
+ final char ch) {
+
+ Cell cell = new Cell(ch);
+ cell.setAttr(getAttrXY(x, y));
+ putFullwidthCharXY(x, y, cell);
+ }
+
}