(rough) move BoardView's Canvas into a new class so that, ideally, it

can be used separately e.g. to draw thumbnails without opening a game
into a board.  Works well enough to render a full board, but not
otherwise tested.
This commit is contained in:
Eric House 2013-10-31 08:08:41 -07:00
parent bfa6132ade
commit 45ad3499c5
3 changed files with 929 additions and 842 deletions

View file

@ -1768,13 +1768,15 @@ public class BoardActivity extends XWActivity
CommonPrefs cp = CommonPrefs.get( this );
if ( null == stream ||
! XwJNI.game_makeFromStream( m_jniGamePtr, stream,
m_gi, dictNames, pairs.m_bytes,
pairs.m_paths, langName, m_utils,
m_jniu, m_view, cp, m_xport ) ) {
XwJNI.game_makeNewGame( m_jniGamePtr, m_gi, m_utils, m_jniu,
m_view, cp, m_xport, dictNames,
pairs.m_bytes, pairs.m_paths,
langName );
m_gi, dictNames,
pairs.m_bytes,
pairs.m_paths, langName,
m_utils, m_jniu,
null, cp, m_xport ) ) {
XwJNI.game_makeNewGame( m_jniGamePtr, m_gi, m_utils,
m_jniu, null, cp, m_xport,
dictNames, pairs.m_bytes,
pairs.m_paths, langName );
}
Handler handler = new Handler() {

View file

@ -0,0 +1,905 @@
/* -*- compile-command: "cd ../../../../../; ant debug install"; -*- */
/*
* Copyright 2009 - 2013 by Eric House (xwords@eehouse.org). All
* rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.eehouse.android.xw4;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Handler;
import android.graphics.RectF;
import android.graphics.Paint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.BitmapDrawable;
import org.eehouse.android.xw4.jni.DrawCtx;
import org.eehouse.android.xw4.jni.DrawScoreInfo;
import org.eehouse.android.xw4.jni.CommonPrefs;
import org.eehouse.android.xw4.jni.JNIThread;
import org.eehouse.android.xw4.jni.XwJNI;
import junit.framework.Assert;
public class BoardCanvas extends Canvas implements DrawCtx {
private static final int BLACK = 0xFF000000;
private static final int WHITE = 0xFFFFFFFF;
private static final int FRAME_GREY = 0xFF101010;
private static final int SCORE_HT_DROP = 2;
private static final boolean DEBUG_DRAWFRAMES = false;
private static final int NOT_TURN_ALPHA = 0x3FFFFFFF;
private static final int IN_TRADE_ALPHA = 0x3FFFFFFF;
private static final boolean FRAME_TRAY_RECTS = false; // for debugging
private Bitmap m_bitmap;
private JNIThread m_jniThread;
private Paint m_fillPaint;
private Paint m_strokePaint;
private Paint m_drawPaint;
private Paint m_tileStrokePaint;
private String[][] m_scores;
private String m_remText;
private int m_mediumFontHt;
private Rect m_boundsScratch = new Rect();
private Rect m_letterRect;
private Rect m_valRect;
private int[] m_bonusColors;
private int[] m_playerColors;
private int[] m_otherColors;
private String[] m_bonusSummaries;
private int m_defaultFontHt;
private CommonPrefs m_prefs;
private int m_lastSecsLeft;
private int m_lastTimerPlayer;
private boolean m_inTrade;
private boolean m_darkOnLight;
private Drawable m_origin;
private boolean m_blackArrow;
private Drawable m_rightArrow;
private Drawable m_downArrow;
private Context m_context;
private int m_trayOwner = -1;
private int m_pendingScore;
private int m_dictPtr = 0;
private String[] m_dictChars;
private boolean m_hasSmallScreen;
private int m_backgroundUsed = 0x00000000;
// FontDims: exists to translate space available to the largest
// font we can draw within that space taking advantage of our use
// being limited to a known small subset of glyphs. We need two
// numbers from this: the textHeight to pass to Paint.setTextSize,
// and the descent to use when drawing. Both can be calculated
// proportionally. We know the ht we passed to Paint to get the
// height we've now measured; that gives a percent to multiply any
// future wantHt by. Ditto for the descent
private class FontDims {
FontDims( float askedHt, int topRow, int bottomRow, float width ) {
// DbgUtils.logf( "FontDims(): askedHt=" + askedHt );
// DbgUtils.logf( "FontDims(): topRow=" + topRow );
// DbgUtils.logf( "FontDims(): bottomRow=" + bottomRow );
// DbgUtils.logf( "FontDims(): width=" + width );
float gotHt = bottomRow - topRow + 1;
m_htProportion = gotHt / askedHt;
Assert.assertTrue( (bottomRow+1) >= askedHt );
float descent = (bottomRow+1) - askedHt;
// DbgUtils.logf( "descent: " + descent );
m_descentProportion = descent / askedHt;
Assert.assertTrue( m_descentProportion >= 0 );
m_widthProportion = width / askedHt;
// DbgUtils.logf( "m_htProportion: " + m_htProportion );
// DbgUtils.logf( "m_descentProportion: " + m_descentProportion );
}
private float m_htProportion;
private float m_descentProportion;
private float m_widthProportion;
int heightFor( int ht ) { return (int)(ht / m_htProportion); }
int descentFor( int ht ) { return (int)(ht * m_descentProportion); }
int widthFor( int width ) { return (int)(width / m_widthProportion); }
}
private FontDims m_fontDims;
public BoardCanvas( Context context, Bitmap bitmap, JNIThread jniThread )
{
super( bitmap );
m_context = context;
m_bitmap = bitmap;
m_jniThread = jniThread;
m_hasSmallScreen = Utils.hasSmallScreen( context );
m_drawPaint = new Paint();
m_fillPaint = new Paint( Paint.ANTI_ALIAS_FLAG );
m_strokePaint = new Paint();
m_strokePaint.setStyle( Paint.Style.STROKE );
m_tileStrokePaint = new Paint();
m_tileStrokePaint.setStyle( Paint.Style.STROKE );
float curWidth = m_tileStrokePaint.getStrokeWidth();
curWidth *= 2;
if ( curWidth < 2 ) {
curWidth = 2;
}
m_tileStrokePaint.setStrokeWidth( curWidth );
Resources res = context.getResources();
m_origin = res.getDrawable( R.drawable.origin );
m_prefs = CommonPrefs.get( context );
m_playerColors = m_prefs.playerColors;
m_bonusColors = m_prefs.bonusColors;
m_otherColors = m_prefs.otherColors;
fillRect( new Rect( 0, 0, bitmap.getWidth(), bitmap.getHeight() ),
WHITE );
}
// DrawCtxt interface implementation
public boolean scoreBegin( Rect rect, int numPlayers, int[] scores,
int remCount )
{
fillRectOther( rect, CommonPrefs.COLOR_BACKGRND );
m_scores = new String[numPlayers][];
return true;
}
public boolean measureRemText( Rect r, int nTilesLeft, int[] width,
int[] height )
{
boolean showREM = 0 <= nTilesLeft;
if ( showREM ) {
// should cache a formatter
m_remText = String.format( "%d", nTilesLeft );
m_fillPaint.setTextSize( m_mediumFontHt );
m_fillPaint.getTextBounds( m_remText, 0, m_remText.length(),
m_boundsScratch );
int minWidth = m_boundsScratch.width();
if ( minWidth < 20 ) {
minWidth = 20; // it's a button; make it bigger
}
width[0] = minWidth;
height[0] = m_boundsScratch.height();
}
return showREM;
}
public void drawRemText( Rect rInner, Rect rOuter, int nTilesLeft,
boolean focussed )
{
int indx = focussed ? CommonPrefs.COLOR_FOCUS
: CommonPrefs.COLOR_TILE_BACK;
fillRectOther( rOuter, indx );
m_fillPaint.setColor( adjustColor(BLACK) );
drawCentered( m_remText, rInner, null );
}
public void measureScoreText( Rect rect, DrawScoreInfo dsi,
int[] width, int[] height )
{
String[] scoreInfo = new String[dsi.isTurn?1:2];
int indx = 0;
StringBuffer sb = new StringBuffer();
// If it's my turn I get one line. Otherwise squeeze into
// two.
if ( dsi.isTurn ) {
sb.append( dsi.name );
sb.append( ":" );
} else {
scoreInfo[indx++] = dsi.name;
}
sb.append( dsi.totalScore );
if ( dsi.nTilesLeft >= 0 ) {
sb.append( ":" );
sb.append( dsi.nTilesLeft );
}
scoreInfo[indx] = sb.toString();
m_scores[dsi.playerNum] = scoreInfo;
int rectHt = rect.height();
if ( !dsi.isTurn ) {
rectHt /= 2;
}
int textHeight = rectHt - SCORE_HT_DROP;
if ( textHeight < m_defaultFontHt ) {
textHeight = m_defaultFontHt;
}
m_fillPaint.setTextSize( textHeight );
int needWidth = 0;
for ( int ii = 0; ii < scoreInfo.length; ++ii ) {
m_fillPaint.getTextBounds( scoreInfo[ii], 0, scoreInfo[ii].length(),
m_boundsScratch );
if ( needWidth < m_boundsScratch.width() ) {
needWidth = m_boundsScratch.width();
}
}
if ( needWidth > rect.width() ) {
needWidth = rect.width();
}
width[0] = needWidth;
height[0] = rect.height();
}
public void score_drawPlayer( Rect rInner, Rect rOuter,
int gotPct, DrawScoreInfo dsi )
{
if ( 0 != (dsi.flags & CELL_ISCURSOR) ) {
fillRectOther( rOuter, CommonPrefs.COLOR_FOCUS );
} else if ( DEBUG_DRAWFRAMES && dsi.selected ) {
fillRectOther( rOuter, CommonPrefs.COLOR_FOCUS );
}
String[] texts = m_scores[dsi.playerNum];
int color = m_playerColors[dsi.playerNum];
if ( !m_prefs.allowPeek ) {
color = adjustColor( color );
}
m_fillPaint.setColor( color );
// PENDING: Shouldn't this be one once at startup?
m_bonusSummaries = new String[5];
int[] ids = { R.string.bonus_l2x_summary,
R.string.bonus_w2x_summary ,
R.string.bonus_l3x_summary,
R.string.bonus_w3x_summary };
Resources res = m_context.getResources();
for ( int ii = 0; ii < ids.length; ++ii ) {
m_bonusSummaries[ ii+1 ] = res.getString( ids[ii] );
}
int height = rOuter.height() / texts.length;
rOuter.bottom = rOuter.top + height;
for ( String text : texts ) {
drawCentered( text, rOuter, null );
rOuter.offset( 0, height );
}
if ( DEBUG_DRAWFRAMES ) {
m_strokePaint.setColor( BLACK );
drawRect( rInner, m_strokePaint );
}
}
// public boolean drawRemText( int nTilesLeft, boolean focussed, Rect rect )
// {
// boolean willDraw = 0 <= nTilesLeft;
// if ( willDraw ) {
// String remText = null;
// // should cache a formatter
// remText = String.format( "%d", nTilesLeft );
// m_fillPaint.setTextSize( m_mediumFontHt );
// m_fillPaint.getTextBounds( remText, 0, remText.length(),
// m_boundsScratch );
// int width = m_boundsScratch.width();
// if ( width < 20 ) {
// width = 20; // it's a button; make it bigger
// }
// rect.right = rect.left + width;
// Rect drawRect = new Rect( rect );
// int height = m_boundsScratch.height();
// if ( height < drawRect.height() ) {
// drawRect.inset( 0, (drawRect.height() - height) / 2 );
// }
// int indx = focussed ? CommonPrefs.COLOR_FOCUS
// : CommonPrefs.COLOR_TILE_BACK;
// fillRectOther( rect, indx );
// m_fillPaint.setColor( adjustColor(BLACK) );
// drawCentered( remText, drawRect, null );
// }
// return willDraw;
// }
// public void score_drawPlayers( Rect scoreRect, DrawScoreInfo[] playerData,
// Rect[] playerRects )
// {
// Rect tmp = new Rect();
// int nPlayers = playerRects.length;
// int width = scoreRect.width() / (nPlayers + 1);
// int left = scoreRect.left;
// int right;
// StringBuffer sb = new StringBuffer();
// String[] scoreStrings = new String[2];
// for ( int ii = 0; ii < nPlayers; ++ii ) {
// DrawScoreInfo dsi = playerData[ii];
// boolean isTurn = dsi.isTurn;
// int indx = 0;
// sb.delete( 0, sb.length() );
// if ( isTurn ) {
// sb.append( dsi.name );
// sb.append( ":" );
// } else {
// scoreStrings[indx++] = dsi.name;
// }
// sb.append( dsi.totalScore );
// if ( dsi.nTilesLeft >= 0 ) {
// sb.append( ":" );
// sb.append( dsi.nTilesLeft );
// }
// scoreStrings[indx] = sb.toString();
// int color = m_playerColors[dsi.playerNum];
// if ( !m_prefs.allowPeek ) {
// color = adjustColor( color );
// }
// m_fillPaint.setColor( color );
// right = left + (width * (isTurn? 2 : 1) );
// playerRects[ii].set( left, scoreRect.top, right, scoreRect.bottom );
// left = right;
// tmp.set( playerRects[ii] );
// tmp.inset( 2, 2 );
// int height = tmp.height() / (isTurn? 1 : 2);
// tmp.bottom = tmp.top + height;
// for ( String str : scoreStrings ) {
// drawCentered( str, tmp, null );
// if ( isTurn ) {
// break;
// }
// tmp.offset( 0, height );
// }
// if ( DEBUG_DRAWFRAMES ) {
// m_canvas.drawRect( playerRects[ii], m_strokePaint );
// }
// }
// }
public void drawTimer( Rect rect, int player, int secondsLeft )
{
if ( (m_lastSecsLeft != secondsLeft || m_lastTimerPlayer != player) ) {
m_lastSecsLeft = secondsLeft;
m_lastTimerPlayer = player;
String negSign = secondsLeft < 0? "-":"";
secondsLeft = Math.abs( secondsLeft );
String time = String.format( "%s%d:%02d", negSign, secondsLeft/60,
secondsLeft%60 );
fillRectOther( rect, CommonPrefs.COLOR_BACKGRND );
m_fillPaint.setColor( m_playerColors[player] );
Rect shorter = new Rect( rect );
shorter.inset( 0, shorter.height() / 5 );
drawCentered( time, shorter, null );
m_jniThread.handle( JNIThread.JNICmd.CMD_DRAW );
}
}
public boolean boardBegin( Rect rect, int cellWidth, int cellHeight )
{
return true;
}
public boolean drawCell( final Rect rect, String text, int tile, int value,
int owner, int bonus, int hintAtts,
final int flags )
{
boolean canDraw = figureFontDims();
if ( canDraw ) {
int backColor;
boolean empty = 0 != (flags & (CELL_DRAGSRC|CELL_ISEMPTY));
boolean pending = 0 != (flags & CELL_HIGHLIGHT);
String bonusStr = null;
if ( m_inTrade ) {
fillRectOther( rect, CommonPrefs.COLOR_BACKGRND );
}
if ( owner < 0 ) {
owner = 0;
}
int foreColor = m_playerColors[owner];
if ( 0 != (flags & CELL_ISCURSOR) ) {
backColor = m_otherColors[CommonPrefs.COLOR_FOCUS];
} else if ( empty ) {
if ( 0 == bonus ) {
backColor = m_otherColors[CommonPrefs.COLOR_NOTILE];
} else {
backColor = m_bonusColors[bonus];
bonusStr = m_bonusSummaries[bonus];
}
} else if ( pending ) {
if ( darkOnLight() ) {
foreColor = WHITE;
backColor = BLACK;
} else {
foreColor = BLACK;
backColor = WHITE;
}
} else {
backColor = m_otherColors[CommonPrefs.COLOR_TILE_BACK];
}
fillRect( rect, adjustColor( backColor ) );
if ( empty ) {
if ( (CELL_ISSTAR & flags) != 0 ) {
m_origin.setBounds( rect );
m_origin.setAlpha( m_inTrade? IN_TRADE_ALPHA >> 24 : 255 );
m_origin.draw( BoardCanvas.this );
} else if ( null != bonusStr ) {
int color = m_otherColors[CommonPrefs.COLOR_BONUSHINT];
m_fillPaint.setColor( adjustColor(color) );
Rect brect = new Rect( rect );
brect.inset( 0, brect.height()/10 );
drawCentered( bonusStr, brect, m_fontDims );
}
} else {
m_fillPaint.setColor( adjustColor(foreColor) );
drawCentered( text, rect, m_fontDims );
}
if ( (CELL_ISBLANK & flags) != 0 ) {
markBlank( rect, backColor );
}
// frame the cell
m_strokePaint.setColor( adjustColor(FRAME_GREY) );
drawRect( rect, m_strokePaint );
drawCrosshairs( rect, flags );
}
return canDraw;
} // drawCell
private boolean m_arrowHintShown = false;
public void drawBoardArrow( Rect rect, int bonus, boolean vert,
int hintAtts, int flags )
{
// figure out if the background is more dark than light
boolean useDark = darkOnLight();
if ( m_blackArrow != useDark ) {
m_blackArrow = useDark;
m_downArrow = m_rightArrow = null;
}
Drawable arrow;
if ( vert ) {
if ( null == m_downArrow ) {
m_downArrow = loadAndRecolor( R.drawable.downarrow, useDark );
}
arrow = m_downArrow;
} else {
if ( null == m_rightArrow ) {
m_rightArrow = loadAndRecolor( R.drawable.rightarrow, useDark );
}
arrow = m_rightArrow;
}
rect.inset( 2, 2 );
arrow.setBounds( rect );
arrow.draw( BoardCanvas.this );
if ( !m_arrowHintShown ) {
m_arrowHintShown = true;
// Handler handler = getHandler();
// if ( null != handler ) {
// handler.post( new Runnable() {
// public void run() {
// m_parent.
// showNotAgainDlgThen( R.string.not_again_arrow,
// R.string.
// key_notagain_arrow );
// } } );
// }
}
}
public boolean trayBegin( Rect rect, int owner, int score )
{
m_trayOwner = owner;
m_pendingScore = score;
return true;
}
public void drawTile( Rect rect, String text, int val, int flags )
{
drawTileImpl( rect, text, val, flags, true );
}
public void drawTileMidDrag( Rect rect, String text, int val, int owner,
int flags )
{
drawTileImpl( rect, text, val, flags, false );
}
public void drawTileBack( Rect rect, int flags )
{
drawTileImpl( rect, "?", -1, flags, true );
}
public void drawTrayDivider( Rect rect, int flags )
{
boolean isCursor = 0 != (flags & CELL_ISCURSOR);
boolean selected = 0 != (flags & CELL_HIGHLIGHT);
int index = isCursor? CommonPrefs.COLOR_FOCUS : CommonPrefs.COLOR_BACKGRND;
rect.inset( 0, 1 );
fillRectOther( rect, index );
rect.inset( rect.width()/4, 0 );
if ( selected ) {
drawRect( rect, m_strokePaint );
} else {
fillRect( rect, m_playerColors[m_trayOwner] );
}
}
public void score_pendingScore( Rect rect, int score, int playerNum,
int curTurn, int flags )
{
String text = score >= 0? String.format( "%d", score ) : "??";
int otherIndx = (0 == (flags & CELL_ISCURSOR))
? CommonPrefs.COLOR_BACKGRND : CommonPrefs.COLOR_FOCUS;
++rect.top;
fillRectOther( rect, otherIndx );
int playerColor = m_playerColors[playerNum];
if ( playerNum != curTurn ) {
playerColor &= NOT_TURN_ALPHA;
}
m_fillPaint.setColor( playerColor );
rect.bottom -= rect.height() / 2;
drawCentered( text, rect, null );
rect.offset( 0, rect.height() );
drawCentered( m_context.getResources().getString( R.string.pts ),
rect, null );
}
public void objFinished( /*BoardObjectType*/int typ, Rect rect )
{
if ( DrawCtx.OBJ_BOARD == typ ) {
// On squat screens, where I can't use the full width for
// the board (without scrolling), the right-most cells
// don't draw their right borders due to clipping, so draw
// for them.
m_strokePaint.setColor( adjustColor(FRAME_GREY) );
int xx = rect.left + rect.width() - 1;
drawLine( xx, rect.top, xx, rect.top + rect.height(),
m_strokePaint );
}
}
public void dictChanged( int dictPtr )
{
if ( m_dictPtr != dictPtr ) {
if ( 0 == dictPtr ) {
m_fontDims = null;
m_dictChars = null;
} else if ( m_dictPtr == 0 ||
!XwJNI.dict_tilesAreSame( m_dictPtr, dictPtr ) ) {
m_fontDims = null;
m_dictChars = XwJNI.dict_getChars( dictPtr );
}
m_dictPtr = dictPtr;
}
}
private void drawTileImpl( Rect rect, String text, int val,
int flags, boolean clearBack )
{
// boolean valHidden = (flags & CELL_VALHIDDEN) != 0;
boolean notEmpty = (flags & CELL_ISEMPTY) == 0;
boolean isCursor = (flags & CELL_ISCURSOR) != 0;
save( Canvas.CLIP_SAVE_FLAG );
rect.top += 1;
clipRect( rect );
if ( clearBack ) {
fillRectOther( rect, CommonPrefs.COLOR_BACKGRND );
}
if ( isCursor || notEmpty ) {
int color = m_otherColors[isCursor? CommonPrefs.COLOR_FOCUS
: CommonPrefs.COLOR_TILE_BACK];
if ( !clearBack ) {
color &= 0x7FFFFFFF; // translucent if being dragged.
}
fillRect( rect, color );
m_fillPaint.setColor( m_playerColors[m_trayOwner] );
if ( notEmpty ) {
positionDrawTile( rect, text, val );
drawRect( rect, m_tileStrokePaint); // frame
if ( 0 != (flags & CELL_HIGHLIGHT) ) {
rect.inset( 2, 2 );
drawRect( rect, m_tileStrokePaint ); // frame
}
}
}
restoreToCount(1); // in case new canvas....
} // drawTileImpl
private void drawCrosshairs( final Rect rect, final int flags )
{
int color = m_otherColors[CommonPrefs.COLOR_FOCUS];
if ( 0 != (flags & CELL_CROSSHOR) ) {
Rect hairRect = new Rect( rect );
hairRect.inset( 0, hairRect.height() / 3 );
fillRect( hairRect, color );
}
if ( 0 != (flags & CELL_CROSSVERT) ) {
Rect hairRect = new Rect( rect );
hairRect.inset( hairRect.width() / 3, 0 );
fillRect( hairRect, color );
}
}
private void drawCentered( String text, Rect rect, FontDims fontDims )
{
drawIn( text, rect, fontDims, Paint.Align.CENTER );
}
private void drawIn( String text, Rect rect, FontDims fontDims,
Paint.Align align )
{
int descent = -1;
int textSize;
if ( null == fontDims ) {
textSize = rect.height() - SCORE_HT_DROP;
} else {
int height = rect.height() - 4; // borders and padding, 2 each
descent = fontDims.descentFor( height );
textSize = fontDims.heightFor( height );
// DbgUtils.logf( "using descent: " + descent + " and textSize: "
// + textSize + " in height " + height );
}
m_fillPaint.setTextSize( textSize );
if ( descent == -1 ) {
descent = m_fillPaint.getFontMetricsInt().descent;
}
descent += 2;
m_fillPaint.getTextBounds( text, 0, text.length(), m_boundsScratch );
int extra = rect.width() - m_boundsScratch.width();
if ( 0 >= extra ) {
m_fillPaint.setTextAlign( Paint.Align.LEFT );
drawScaled( text, rect, m_boundsScratch, descent );
} else {
int bottom = rect.bottom - descent;
int origin = rect.left;
if ( Paint.Align.CENTER == align ) {
origin += rect.width() / 2;
} else {
origin += (extra / 5) - m_boundsScratch.left;
}
m_fillPaint.setTextAlign( align );
drawText( text, origin, bottom, m_fillPaint );
}
} // drawCentered
private void drawScaled( String text, final Rect rect,
Rect textBounds, int descent )
{
textBounds.bottom = rect.height();
Bitmap bitmap = Bitmap.createBitmap( textBounds.width(),
rect.height(),
Bitmap.Config.ARGB_8888 );
Canvas canvas = new Canvas( bitmap );
int bottom = textBounds.bottom - descent;
canvas.drawText( text, -textBounds.left, bottom, m_fillPaint );
drawBitmap( bitmap, null, rect, m_drawPaint );
}
private void positionDrawTile( final Rect rect, String text, int val )
{
if ( figureFontDims() ) {
final int offset = 2;
if ( null != text ) {
if ( null == m_letterRect ) {
m_letterRect = new Rect( 0, 0, rect.width() - offset,
rect.height() * 3 / 4 );
}
m_letterRect.offsetTo( rect.left + offset, rect.top + offset );
drawIn( text, m_letterRect, m_fontDims, Paint.Align.LEFT );
if ( FRAME_TRAY_RECTS ) {
drawRect( m_letterRect, m_strokePaint );
}
}
if ( val >= 0 ) {
int divisor = m_hasSmallScreen ? 3 : 4;
if ( null == m_valRect ) {
m_valRect = new Rect( 0, 0, rect.width() / divisor,
rect.height() / divisor );
m_valRect.inset( offset, offset );
}
m_valRect.offsetTo( rect.right - (rect.width() / divisor),
rect.bottom - (rect.height() / divisor) );
text = String.format( "%d", val );
m_fillPaint.setTextSize( m_valRect.height() );
m_fillPaint.setTextAlign( Paint.Align.RIGHT );
drawText( text, m_valRect.right, m_valRect.bottom,
m_fillPaint );
if ( FRAME_TRAY_RECTS ) {
drawRect( m_valRect, m_strokePaint );
}
}
}
}
private void fillRectOther( Rect rect, int index )
{
fillRect( rect, m_otherColors[index] );
}
private void fillRect( Rect rect, int color )
{
m_fillPaint.setColor( color );
drawRect( rect, m_fillPaint );
}
private boolean figureFontDims()
{
if ( null == m_fontDims && null != m_dictChars ) {
final int ht = 24;
final int width = 20;
Paint paint = new Paint(); // CommonPrefs.getFontFlags()??
paint.setStyle( Paint.Style.STROKE );
paint.setTextAlign( Paint.Align.LEFT );
paint.setTextSize( ht );
Bitmap bitmap = Bitmap.createBitmap( width, (ht*3)/2,
Bitmap.Config.ARGB_8888 );
Canvas canvas = new Canvas( bitmap );
// FontMetrics fmi = paint.getFontMetrics();
// DbgUtils.logf( "ascent: " + fmi.ascent );
// DbgUtils.logf( "bottom: " + fmi.bottom );
// DbgUtils.logf( "descent: " + fmi.descent );
// DbgUtils.logf( "leading: " + fmi.leading );
// DbgUtils.logf( "top : " + fmi.top );
// DbgUtils.logf( "using as baseline: " + ht );
Rect bounds = new Rect();
int maxWidth = 0;
for ( String str : m_dictChars ) {
if ( str.length() == 1 && str.charAt(0) >= 32 ) {
canvas.drawText( str, 0, ht, paint );
paint.getTextBounds( str, 0, 1, bounds );
if ( maxWidth < bounds.right ) {
maxWidth = bounds.right;
}
}
}
// for ( int row = 0; row < bitmap.getHeight(); ++row ) {
// StringBuffer sb = new StringBuffer( bitmap.getWidth() );
// for ( int col = 0; col < bitmap.getWidth(); ++col ) {
// int pixel = bitmap.getPixel( col, row );
// sb.append( pixel==0? "." : "X" );
// }
// DbgUtils.logf( sb.append(row).toString() );
// }
int topRow = 0;
findTop:
for ( int row = 0; row < bitmap.getHeight(); ++row ) {
for ( int col = 0; col < bitmap.getWidth(); ++col ) {
if ( 0 != bitmap.getPixel( col, row ) ){
topRow = row;
break findTop;
}
}
}
int bottomRow = 0;
findBottom:
for ( int row = bitmap.getHeight() - 1; row > topRow; --row ) {
for ( int col = 0; col < bitmap.getWidth(); ++col ) {
if ( 0 != bitmap.getPixel( col, row ) ){
bottomRow = row;
break findBottom;
}
}
}
m_fontDims = new FontDims( ht, topRow, bottomRow, maxWidth );
}
return null != m_fontDims;
} // figureFontDims
private int adjustColor( int color )
{
if ( m_inTrade ) {
color = color & IN_TRADE_ALPHA;
}
return color;
}
private boolean darkOnLight()
{
int background = m_otherColors[ CommonPrefs.COLOR_NOTILE ];
if ( background != m_backgroundUsed ) {
m_backgroundUsed = background;
m_darkOnLight = isLightColor( background );
}
return m_darkOnLight;
}
private void markBlank( final Rect rect, int backColor )
{
RectF oval = new RectF( rect.left, rect.top, rect.right, rect.bottom );
int curColor = 0;
boolean whiteOnBlack = !isLightColor( backColor );
if ( whiteOnBlack ) {
curColor = m_strokePaint.getColor();
m_strokePaint.setColor( WHITE );
}
drawArc( oval, 0, 360, false, m_strokePaint );
if ( whiteOnBlack ) {
m_strokePaint.setColor( curColor );
}
}
private Drawable loadAndRecolor( int resID, boolean useDark )
{
Resources res = m_context.getResources();
Drawable arrow = res.getDrawable( resID );
if ( !useDark ) {
Bitmap src = ((BitmapDrawable)arrow).getBitmap();
Bitmap bitmap = src.copy( Bitmap.Config.ARGB_8888, true );
for ( int xx = 0; xx < bitmap.getWidth(); ++xx ) {
for( int yy = 0; yy < bitmap.getHeight(); ++yy ) {
if ( BLACK == bitmap.getPixel( xx, yy ) ) {
bitmap.setPixel( xx, yy, WHITE );
}
}
}
arrow = new BitmapDrawable(bitmap);
}
return arrow;
}
private boolean isLightColor( int color )
{
int sum = 0;
for ( int ii = 0; ii < 3; ++ii ) {
sum += color & 0xFF;
color >>= 8;
}
boolean result = sum > (127*3);
return result;
}
}

View file

@ -41,45 +41,28 @@ import android.util.FloatMath;
import junit.framework.Assert;
public class BoardView extends View implements DrawCtx, BoardHandler,
SyncedDraw {
public class BoardView extends View implements BoardHandler, SyncedDraw {
private static final float MIN_FONT_DIPS = 14.0f;
private static final int MULTI_INACTIVE = -1;
private static final boolean FRAME_TRAY_RECTS = false; // for debugging
private static Bitmap s_bitmap; // the board
private static final int IN_TRADE_ALPHA = 0x3FFFFFFF;
private static final int NOT_TURN_ALPHA = 0x3FFFFFFF;
private static final int PINCH_THRESHOLD = 40;
private static final int SCORE_HT_DROP = 2;
private static final boolean DEBUG_DRAWFRAMES = false;
private Context m_context;
private Paint m_drawPaint;
private Paint m_fillPaint;
private Paint m_strokePaint;
private int m_defaultFontHt;
private int m_mediumFontHt;
private Paint m_tileStrokePaint;
private int m_jniGamePtr;
private CurGameInfo m_gi;
private CommonPrefs m_prefs;
private int m_layoutWidth;
private int m_layoutHeight;
private Canvas m_canvas; // owns the bitmap
private BoardCanvas m_canvas; // owns the bitmap
private int m_trayOwner = -1;
private Rect m_valRect;
private Rect m_letterRect;
private Drawable m_rightArrow;
private Drawable m_downArrow;
private boolean m_blackArrow;
private boolean m_inTrade = false;
private boolean m_hasSmallScreen;
// m_backgroundUsed: alpha not set ensures inequality
private int m_backgroundUsed = 0x00000000;
private boolean m_darkOnLight;
private Drawable m_origin;
private JNIThread m_jniThread;
private XWActivity m_parent;
private String[][] m_scores;
@ -96,91 +79,29 @@ public class BoardView extends View implements DrawCtx, BoardHandler,
private int m_lastSpacing = MULTI_INACTIVE;
// FontDims: exists to translate space available to the largest
// font we can draw within that space taking advantage of our use
// being limited to a known small subset of glyphs. We need two
// numbers from this: the textHeight to pass to Paint.setTextSize,
// and the descent to use when drawing. Both can be calculated
// proportionally. We know the ht we passed to Paint to get the
// height we've now measured; that gives a percent to multiply any
// future wantHt by. Ditto for the descent
private class FontDims {
FontDims( float askedHt, int topRow, int bottomRow, float width ) {
// DbgUtils.logf( "FontDims(): askedHt=" + askedHt );
// DbgUtils.logf( "FontDims(): topRow=" + topRow );
// DbgUtils.logf( "FontDims(): bottomRow=" + bottomRow );
// DbgUtils.logf( "FontDims(): width=" + width );
float gotHt = bottomRow - topRow + 1;
m_htProportion = gotHt / askedHt;
Assert.assertTrue( (bottomRow+1) >= askedHt );
float descent = (bottomRow+1) - askedHt;
// DbgUtils.logf( "descent: " + descent );
m_descentProportion = descent / askedHt;
Assert.assertTrue( m_descentProportion >= 0 );
m_widthProportion = width / askedHt;
// DbgUtils.logf( "m_htProportion: " + m_htProportion );
// DbgUtils.logf( "m_descentProportion: " + m_descentProportion );
}
private float m_htProportion;
private float m_descentProportion;
private float m_widthProportion;
int heightFor( int ht ) { return (int)(ht / m_htProportion); }
int descentFor( int ht ) { return (int)(ht * m_descentProportion); }
int widthFor( int width ) { return (int)(width / m_widthProportion); }
}
private FontDims m_fontDims;
private static final int BLACK = 0xFF000000;
private static final int WHITE = 0xFFFFFFFF;
private static final int FRAME_GREY = 0xFF101010;
private int[] m_bonusColors;
private int[] m_playerColors;
private int[] m_otherColors;
private String[] m_bonusSummaries;
// called when inflating xml
public BoardView( Context context, AttributeSet attrs )
{
super( context, attrs );
m_context = context;
m_hasSmallScreen = Utils.hasSmallScreen( context );
final float scale = getResources().getDisplayMetrics().density;
m_defaultFontHt = (int)(MIN_FONT_DIPS * scale + 0.5f);
m_mediumFontHt = m_defaultFontHt * 3 / 2;
m_drawPaint = new Paint();
m_fillPaint = new Paint( Paint.ANTI_ALIAS_FLAG );
m_strokePaint = new Paint();
m_strokePaint.setStyle( Paint.Style.STROKE );
m_tileStrokePaint = new Paint();
m_tileStrokePaint.setStyle( Paint.Style.STROKE );
float curWidth = m_tileStrokePaint.getStrokeWidth();
curWidth *= 2;
if ( curWidth < 2 ) {
curWidth = 2;
}
m_tileStrokePaint.setStrokeWidth( curWidth );
Resources res = getResources();
m_origin = res.getDrawable( R.drawable.origin );
m_boundsScratch = new Rect();
m_prefs = CommonPrefs.get(context);
m_playerColors = m_prefs.playerColors;
m_bonusColors = m_prefs.bonusColors;
m_otherColors = m_prefs.otherColors;
m_bonusSummaries = new String[5];
int[] ids = { R.string.bonus_l2x_summary,
R.string.bonus_w2x_summary ,
R.string.bonus_l3x_summary,
R.string.bonus_w3x_summary };
for ( int ii = 0; ii < ids.length; ++ii ) {
m_bonusSummaries[ ii+1 ] = getResources().getString( ids[ii] );
}
// m_bonusSummaries = new String[5];
// int[] ids = { R.string.bonus_l2x_summary,
// R.string.bonus_w2x_summary ,
// R.string.bonus_l3x_summary,
// R.string.bonus_w3x_summary };
// for ( int ii = 0; ii < ids.length; ++ii ) {
// m_bonusSummaries[ ii+1 ] = getResources().getString( ids[ii] );
// }
}
@Override
@ -417,9 +338,9 @@ public class BoardView extends View implements DrawCtx, BoardHandler,
} else {
m_layoutWidth = width;
m_layoutHeight = height;
m_fontDims = null; // force recalc of font
m_letterRect = null;
m_valRect = null;
// m_fontDims = null; // force recalc of font
// m_letterRect = null;
// m_valRect = null;
BoardDims dims = figureBoardDims( width, height );
@ -437,10 +358,8 @@ public class BoardView extends View implements DrawCtx, BoardHandler,
s_bitmap = Bitmap.createBitmap( bmWidth, bmHeight,
Bitmap.Config.ARGB_8888 );
}
m_canvas = new Canvas( s_bitmap );
// Clear it
fillRect( new Rect( 0, 0, width, height ), WHITE );
m_canvas = new BoardCanvas( m_context, s_bitmap, m_jniThread );
XwJNI.board_setDraw( m_jniGamePtr, m_canvas );
// need to synchronize??
m_jniThread.handle( JNIThread.JNICmd.CMD_LAYOUT, dims );
@ -496,745 +415,6 @@ public class BoardView extends View implements DrawCtx, BoardHandler,
return m_pendingScore;
}
// DrawCtxt interface implementation
public boolean scoreBegin( Rect rect, int numPlayers, int[] scores,
int remCount )
{
fillRectOther( rect, CommonPrefs.COLOR_BACKGRND );
m_scores = new String[numPlayers][];
return true;
}
public boolean measureRemText( Rect r, int nTilesLeft, int[] width,
int[] height )
{
boolean showREM = 0 <= nTilesLeft;
if ( showREM ) {
// should cache a formatter
m_remText = String.format( "%d", nTilesLeft );
m_fillPaint.setTextSize( m_mediumFontHt );
m_fillPaint.getTextBounds( m_remText, 0, m_remText.length(),
m_boundsScratch );
int minWidth = m_boundsScratch.width();
if ( minWidth < 20 ) {
minWidth = 20; // it's a button; make it bigger
}
width[0] = minWidth;
height[0] = m_boundsScratch.height();
}
return showREM;
}
public void drawRemText( Rect rInner, Rect rOuter, int nTilesLeft,
boolean focussed )
{
int indx = focussed ? CommonPrefs.COLOR_FOCUS
: CommonPrefs.COLOR_TILE_BACK;
fillRectOther( rOuter, indx );
m_fillPaint.setColor( adjustColor(BLACK) );
drawCentered( m_remText, rInner, null );
}
public void measureScoreText( Rect rect, DrawScoreInfo dsi,
int[] width, int[] height )
{
String[] scoreInfo = new String[dsi.isTurn?1:2];
int indx = 0;
StringBuffer sb = new StringBuffer();
// If it's my turn I get one line. Otherwise squeeze into
// two.
if ( dsi.isTurn ) {
sb.append( dsi.name );
sb.append( ":" );
} else {
scoreInfo[indx++] = dsi.name;
}
sb.append( dsi.totalScore );
if ( dsi.nTilesLeft >= 0 ) {
sb.append( ":" );
sb.append( dsi.nTilesLeft );
}
scoreInfo[indx] = sb.toString();
m_scores[dsi.playerNum] = scoreInfo;
int rectHt = rect.height();
if ( !dsi.isTurn ) {
rectHt /= 2;
}
int textHeight = rectHt - SCORE_HT_DROP;
if ( textHeight < m_defaultFontHt ) {
textHeight = m_defaultFontHt;
}
m_fillPaint.setTextSize( textHeight );
int needWidth = 0;
for ( int ii = 0; ii < scoreInfo.length; ++ii ) {
m_fillPaint.getTextBounds( scoreInfo[ii], 0, scoreInfo[ii].length(),
m_boundsScratch );
if ( needWidth < m_boundsScratch.width() ) {
needWidth = m_boundsScratch.width();
}
}
if ( needWidth > rect.width() ) {
needWidth = rect.width();
}
width[0] = needWidth;
height[0] = rect.height();
}
public void score_drawPlayer( Rect rInner, Rect rOuter,
int gotPct, DrawScoreInfo dsi )
{
if ( 0 != (dsi.flags & CELL_ISCURSOR) ) {
fillRectOther( rOuter, CommonPrefs.COLOR_FOCUS );
} else if ( DEBUG_DRAWFRAMES && dsi.selected ) {
fillRectOther( rOuter, CommonPrefs.COLOR_FOCUS );
}
String[] texts = m_scores[dsi.playerNum];
int color = m_playerColors[dsi.playerNum];
if ( !m_prefs.allowPeek ) {
color = adjustColor( color );
}
m_fillPaint.setColor( color );
int height = rOuter.height() / texts.length;
rOuter.bottom = rOuter.top + height;
for ( String text : texts ) {
drawCentered( text, rOuter, null );
rOuter.offset( 0, height );
}
if ( DEBUG_DRAWFRAMES ) {
m_strokePaint.setColor( BLACK );
m_canvas.drawRect( rInner, m_strokePaint );
}
}
// public boolean drawRemText( int nTilesLeft, boolean focussed, Rect rect )
// {
// boolean willDraw = 0 <= nTilesLeft;
// if ( willDraw ) {
// String remText = null;
// // should cache a formatter
// remText = String.format( "%d", nTilesLeft );
// m_fillPaint.setTextSize( m_mediumFontHt );
// m_fillPaint.getTextBounds( remText, 0, remText.length(),
// m_boundsScratch );
// int width = m_boundsScratch.width();
// if ( width < 20 ) {
// width = 20; // it's a button; make it bigger
// }
// rect.right = rect.left + width;
// Rect drawRect = new Rect( rect );
// int height = m_boundsScratch.height();
// if ( height < drawRect.height() ) {
// drawRect.inset( 0, (drawRect.height() - height) / 2 );
// }
// int indx = focussed ? CommonPrefs.COLOR_FOCUS
// : CommonPrefs.COLOR_TILE_BACK;
// fillRectOther( rect, indx );
// m_fillPaint.setColor( adjustColor(BLACK) );
// drawCentered( remText, drawRect, null );
// }
// return willDraw;
// }
// public void score_drawPlayers( Rect scoreRect, DrawScoreInfo[] playerData,
// Rect[] playerRects )
// {
// Rect tmp = new Rect();
// int nPlayers = playerRects.length;
// int width = scoreRect.width() / (nPlayers + 1);
// int left = scoreRect.left;
// int right;
// StringBuffer sb = new StringBuffer();
// String[] scoreStrings = new String[2];
// for ( int ii = 0; ii < nPlayers; ++ii ) {
// DrawScoreInfo dsi = playerData[ii];
// boolean isTurn = dsi.isTurn;
// int indx = 0;
// sb.delete( 0, sb.length() );
// if ( isTurn ) {
// sb.append( dsi.name );
// sb.append( ":" );
// } else {
// scoreStrings[indx++] = dsi.name;
// }
// sb.append( dsi.totalScore );
// if ( dsi.nTilesLeft >= 0 ) {
// sb.append( ":" );
// sb.append( dsi.nTilesLeft );
// }
// scoreStrings[indx] = sb.toString();
// int color = m_playerColors[dsi.playerNum];
// if ( !m_prefs.allowPeek ) {
// color = adjustColor( color );
// }
// m_fillPaint.setColor( color );
// right = left + (width * (isTurn? 2 : 1) );
// playerRects[ii].set( left, scoreRect.top, right, scoreRect.bottom );
// left = right;
// tmp.set( playerRects[ii] );
// tmp.inset( 2, 2 );
// int height = tmp.height() / (isTurn? 1 : 2);
// tmp.bottom = tmp.top + height;
// for ( String str : scoreStrings ) {
// drawCentered( str, tmp, null );
// if ( isTurn ) {
// break;
// }
// tmp.offset( 0, height );
// }
// if ( DEBUG_DRAWFRAMES ) {
// m_canvas.drawRect( playerRects[ii], m_strokePaint );
// }
// }
// }
public void drawTimer( Rect rect, int player, int secondsLeft )
{
if ( null != m_canvas && (m_lastSecsLeft != secondsLeft
|| m_lastTimerPlayer != player) ) {
m_lastSecsLeft = secondsLeft;
m_lastTimerPlayer = player;
String negSign = secondsLeft < 0? "-":"";
secondsLeft = Math.abs( secondsLeft );
String time = String.format( "%s%d:%02d", negSign, secondsLeft/60,
secondsLeft%60 );
fillRectOther( rect, CommonPrefs.COLOR_BACKGRND );
m_fillPaint.setColor( m_playerColors[player] );
Rect shorter = new Rect( rect );
shorter.inset( 0, shorter.height() / 5 );
drawCentered( time, shorter, null );
m_jniThread.handle( JNIThread.JNICmd.CMD_DRAW );
}
}
public boolean boardBegin( Rect rect, int cellWidth, int cellHeight )
{
return true;
}
public boolean drawCell( final Rect rect, String text, int tile, int value,
int owner, int bonus, int hintAtts,
final int flags )
{
boolean canDraw = figureFontDims();
if ( canDraw ) {
int backColor;
boolean empty = 0 != (flags & (CELL_DRAGSRC|CELL_ISEMPTY));
boolean pending = 0 != (flags & CELL_HIGHLIGHT);
String bonusStr = null;
if ( m_inTrade ) {
fillRectOther( rect, CommonPrefs.COLOR_BACKGRND );
}
if ( owner < 0 ) {
owner = 0;
}
int foreColor = m_playerColors[owner];
if ( 0 != (flags & CELL_ISCURSOR) ) {
backColor = m_otherColors[CommonPrefs.COLOR_FOCUS];
} else if ( empty ) {
if ( 0 == bonus ) {
backColor = m_otherColors[CommonPrefs.COLOR_NOTILE];
} else {
backColor = m_bonusColors[bonus];
bonusStr = m_bonusSummaries[bonus];
}
} else if ( pending ) {
if ( darkOnLight() ) {
foreColor = WHITE;
backColor = BLACK;
} else {
foreColor = BLACK;
backColor = WHITE;
}
} else {
backColor = m_otherColors[CommonPrefs.COLOR_TILE_BACK];
}
fillRect( rect, adjustColor( backColor ) );
if ( empty ) {
if ( (CELL_ISSTAR & flags) != 0 ) {
m_origin.setBounds( rect );
m_origin.setAlpha( m_inTrade? IN_TRADE_ALPHA >> 24 : 255 );
m_origin.draw( m_canvas );
} else if ( null != bonusStr ) {
int color = m_otherColors[CommonPrefs.COLOR_BONUSHINT];
m_fillPaint.setColor( adjustColor(color) );
Rect brect = new Rect( rect );
brect.inset( 0, brect.height()/10 );
drawCentered( bonusStr, brect, m_fontDims );
}
} else {
m_fillPaint.setColor( adjustColor(foreColor) );
drawCentered( text, rect, m_fontDims );
}
if ( (CELL_ISBLANK & flags) != 0 ) {
markBlank( rect, backColor );
}
// frame the cell
m_strokePaint.setColor( adjustColor(FRAME_GREY) );
m_canvas.drawRect( rect, m_strokePaint );
drawCrosshairs( rect, flags );
}
return canDraw;
} // drawCell
private boolean m_arrowHintShown = false;
public void drawBoardArrow( Rect rect, int bonus, boolean vert,
int hintAtts, int flags )
{
// figure out if the background is more dark than light
boolean useDark = darkOnLight();
if ( m_blackArrow != useDark ) {
m_blackArrow = useDark;
m_downArrow = m_rightArrow = null;
}
Drawable arrow;
if ( vert ) {
if ( null == m_downArrow ) {
m_downArrow = loadAndRecolor( R.drawable.downarrow, useDark );
}
arrow = m_downArrow;
} else {
if ( null == m_rightArrow ) {
m_rightArrow = loadAndRecolor( R.drawable.rightarrow, useDark );
}
arrow = m_rightArrow;
}
rect.inset( 2, 2 );
arrow.setBounds( rect );
arrow.draw( m_canvas );
if ( !m_arrowHintShown ) {
m_arrowHintShown = true;
Handler handler = getHandler();
if ( null != handler ) {
handler.post( new Runnable() {
public void run() {
m_parent.
showNotAgainDlgThen( R.string.not_again_arrow,
R.string.
key_notagain_arrow );
} } );
}
}
}
public boolean trayBegin ( Rect rect, int owner, int score )
{
m_trayOwner = owner;
m_pendingScore = score;
return true;
}
public void drawTile( Rect rect, String text, int val, int flags )
{
drawTileImpl( rect, text, val, flags, true );
}
public void drawTileMidDrag( Rect rect, String text, int val, int owner,
int flags )
{
drawTileImpl( rect, text, val, flags, false );
}
public void drawTileBack( Rect rect, int flags )
{
drawTileImpl( rect, "?", -1, flags, true );
}
public void drawTrayDivider( Rect rect, int flags )
{
boolean isCursor = 0 != (flags & CELL_ISCURSOR);
boolean selected = 0 != (flags & CELL_HIGHLIGHT);
int index = isCursor? CommonPrefs.COLOR_FOCUS : CommonPrefs.COLOR_BACKGRND;
rect.inset( 0, 1 );
fillRectOther( rect, index );
rect.inset( rect.width()/4, 0 );
if ( selected ) {
m_canvas.drawRect( rect, m_strokePaint );
} else {
fillRect( rect, m_playerColors[m_trayOwner] );
}
}
public void score_pendingScore( Rect rect, int score, int playerNum,
int curTurn, int flags )
{
String text = score >= 0? String.format( "%d", score ) : "??";
int otherIndx = (0 == (flags & CELL_ISCURSOR))
? CommonPrefs.COLOR_BACKGRND : CommonPrefs.COLOR_FOCUS;
++rect.top;
fillRectOther( rect, otherIndx );
int playerColor = m_playerColors[playerNum];
if ( playerNum != curTurn ) {
playerColor &= NOT_TURN_ALPHA;
}
m_fillPaint.setColor( playerColor );
rect.bottom -= rect.height() / 2;
drawCentered( text, rect, null );
rect.offset( 0, rect.height() );
drawCentered( getResources().getString( R.string.pts ), rect, null );
}
public void objFinished( /*BoardObjectType*/int typ, Rect rect )
{
if ( DrawCtx.OBJ_BOARD == typ ) {
// On squat screens, where I can't use the full width for
// the board (without scrolling), the right-most cells
// don't draw their right borders due to clipping, so draw
// for them.
m_strokePaint.setColor( adjustColor(FRAME_GREY) );
int xx = rect.left + rect.width() - 1;
m_canvas.drawLine( xx, rect.top, xx, rect.top + rect.height(),
m_strokePaint );
}
}
public void dictChanged( int dictPtr )
{
if ( m_dictPtr != dictPtr ) {
if ( 0 == dictPtr ) {
m_fontDims = null;
m_dictChars = null;
} else if ( m_dictPtr == 0 ||
!XwJNI.dict_tilesAreSame( m_dictPtr, dictPtr ) ) {
m_fontDims = null;
m_dictChars = XwJNI.dict_getChars( dictPtr );
}
m_dictPtr = dictPtr;
}
}
private void drawTileImpl( Rect rect, String text, int val,
int flags, boolean clearBack )
{
// boolean valHidden = (flags & CELL_VALHIDDEN) != 0;
boolean notEmpty = (flags & CELL_ISEMPTY) == 0;
boolean isCursor = (flags & CELL_ISCURSOR) != 0;
m_canvas.save( Canvas.CLIP_SAVE_FLAG );
rect.top += 1;
m_canvas.clipRect( rect );
if ( clearBack ) {
fillRectOther( rect, CommonPrefs.COLOR_BACKGRND );
}
if ( isCursor || notEmpty ) {
int color = m_otherColors[isCursor? CommonPrefs.COLOR_FOCUS
: CommonPrefs.COLOR_TILE_BACK];
if ( !clearBack ) {
color &= 0x7FFFFFFF; // translucent if being dragged.
}
fillRect( rect, color );
m_fillPaint.setColor( m_playerColors[m_trayOwner] );
if ( notEmpty ) {
positionDrawTile( rect, text, val );
m_canvas.drawRect( rect, m_tileStrokePaint); // frame
if ( 0 != (flags & CELL_HIGHLIGHT) ) {
rect.inset( 2, 2 );
m_canvas.drawRect( rect, m_tileStrokePaint ); // frame
}
}
}
m_canvas.restoreToCount(1); // in case new canvas....
} // drawTileImpl
private void drawCentered( String text, Rect rect, FontDims fontDims )
{
drawIn( text, rect, fontDims, Paint.Align.CENTER );
}
private void drawIn( String text, Rect rect, FontDims fontDims,
Paint.Align align )
{
int descent = -1;
int textSize;
if ( null == fontDims ) {
textSize = rect.height() - SCORE_HT_DROP;
} else {
int height = rect.height() - 4; // borders and padding, 2 each
descent = fontDims.descentFor( height );
textSize = fontDims.heightFor( height );
// DbgUtils.logf( "using descent: " + descent + " and textSize: "
// + textSize + " in height " + height );
}
m_fillPaint.setTextSize( textSize );
if ( descent == -1 ) {
descent = m_fillPaint.getFontMetricsInt().descent;
}
descent += 2;
m_fillPaint.getTextBounds( text, 0, text.length(), m_boundsScratch );
int extra = rect.width() - m_boundsScratch.width();
if ( 0 >= extra ) {
m_fillPaint.setTextAlign( Paint.Align.LEFT );
drawScaled( text, rect, m_boundsScratch, descent );
} else {
int bottom = rect.bottom - descent;
int origin = rect.left;
if ( Paint.Align.CENTER == align ) {
origin += rect.width() / 2;
} else {
origin += (extra / 5) - m_boundsScratch.left;
}
m_fillPaint.setTextAlign( align );
m_canvas.drawText( text, origin, bottom, m_fillPaint );
}
} // drawCentered
private void drawScaled( String text, final Rect rect,
Rect textBounds, int descent )
{
textBounds.bottom = rect.height();
Bitmap bitmap = Bitmap.createBitmap( textBounds.width(),
rect.height(),
Bitmap.Config.ARGB_8888 );
Canvas canvas = new Canvas( bitmap );
int bottom = textBounds.bottom - descent;
canvas.drawText( text, -textBounds.left, bottom, m_fillPaint );
m_canvas.drawBitmap( bitmap, null, rect, m_drawPaint );
}
private void positionDrawTile( final Rect rect, String text, int val )
{
if ( figureFontDims() ) {
final int offset = 2;
if ( null != text ) {
if ( null == m_letterRect ) {
m_letterRect = new Rect( 0, 0, rect.width() - offset,
rect.height() * 3 / 4 );
}
m_letterRect.offsetTo( rect.left + offset, rect.top + offset );
drawIn( text, m_letterRect, m_fontDims, Paint.Align.LEFT );
if ( FRAME_TRAY_RECTS ) {
m_canvas.drawRect( m_letterRect, m_strokePaint );
}
}
if ( val >= 0 ) {
int divisor = m_hasSmallScreen ? 3 : 4;
if ( null == m_valRect ) {
m_valRect = new Rect( 0, 0, rect.width() / divisor,
rect.height() / divisor );
m_valRect.inset( offset, offset );
}
m_valRect.offsetTo( rect.right - (rect.width() / divisor),
rect.bottom - (rect.height() / divisor) );
text = String.format( "%d", val );
m_fillPaint.setTextSize( m_valRect.height() );
m_fillPaint.setTextAlign( Paint.Align.RIGHT );
m_canvas.drawText( text, m_valRect.right, m_valRect.bottom,
m_fillPaint );
if ( FRAME_TRAY_RECTS ) {
m_canvas.drawRect( m_valRect, m_strokePaint );
}
}
}
}
private void drawCrosshairs( final Rect rect, final int flags )
{
int color = m_otherColors[CommonPrefs.COLOR_FOCUS];
if ( 0 != (flags & CELL_CROSSHOR) ) {
Rect hairRect = new Rect( rect );
hairRect.inset( 0, hairRect.height() / 3 );
fillRect( hairRect, color );
}
if ( 0 != (flags & CELL_CROSSVERT) ) {
Rect hairRect = new Rect( rect );
hairRect.inset( hairRect.width() / 3, 0 );
fillRect( hairRect, color );
}
}
private void fillRectOther( Rect rect, int index )
{
fillRect( rect, m_otherColors[index] );
}
private void fillRect( Rect rect, int color )
{
m_fillPaint.setColor( color );
m_canvas.drawRect( rect, m_fillPaint );
}
private boolean figureFontDims()
{
if ( null == m_fontDims && null != m_dictChars ) {
final int ht = 24;
final int width = 20;
Paint paint = new Paint(); // CommonPrefs.getFontFlags()??
paint.setStyle( Paint.Style.STROKE );
paint.setTextAlign( Paint.Align.LEFT );
paint.setTextSize( ht );
Bitmap bitmap = Bitmap.createBitmap( width, (ht*3)/2,
Bitmap.Config.ARGB_8888 );
Canvas canvas = new Canvas( bitmap );
// FontMetrics fmi = paint.getFontMetrics();
// DbgUtils.logf( "ascent: " + fmi.ascent );
// DbgUtils.logf( "bottom: " + fmi.bottom );
// DbgUtils.logf( "descent: " + fmi.descent );
// DbgUtils.logf( "leading: " + fmi.leading );
// DbgUtils.logf( "top : " + fmi.top );
// DbgUtils.logf( "using as baseline: " + ht );
Rect bounds = new Rect();
int maxWidth = 0;
for ( String str : m_dictChars ) {
if ( str.length() == 1 && str.charAt(0) >= 32 ) {
canvas.drawText( str, 0, ht, paint );
paint.getTextBounds( str, 0, 1, bounds );
if ( maxWidth < bounds.right ) {
maxWidth = bounds.right;
}
}
}
// for ( int row = 0; row < bitmap.getHeight(); ++row ) {
// StringBuffer sb = new StringBuffer( bitmap.getWidth() );
// for ( int col = 0; col < bitmap.getWidth(); ++col ) {
// int pixel = bitmap.getPixel( col, row );
// sb.append( pixel==0? "." : "X" );
// }
// DbgUtils.logf( sb.append(row).toString() );
// }
int topRow = 0;
findTop:
for ( int row = 0; row < bitmap.getHeight(); ++row ) {
for ( int col = 0; col < bitmap.getWidth(); ++col ) {
if ( 0 != bitmap.getPixel( col, row ) ){
topRow = row;
break findTop;
}
}
}
int bottomRow = 0;
findBottom:
for ( int row = bitmap.getHeight() - 1; row > topRow; --row ) {
for ( int col = 0; col < bitmap.getWidth(); ++col ) {
if ( 0 != bitmap.getPixel( col, row ) ){
bottomRow = row;
break findBottom;
}
}
}
m_fontDims = new FontDims( ht, topRow, bottomRow, maxWidth );
}
return null != m_fontDims;
} // figureFontDims
private boolean isLightColor( int color )
{
int sum = 0;
for ( int ii = 0; ii < 3; ++ii ) {
sum += color & 0xFF;
color >>= 8;
}
boolean result = sum > (127*3);
return result;
}
private void markBlank( final Rect rect, int backColor )
{
RectF oval = new RectF( rect.left, rect.top, rect.right, rect.bottom );
int curColor = 0;
boolean whiteOnBlack = !isLightColor( backColor );
if ( whiteOnBlack ) {
curColor = m_strokePaint.getColor();
m_strokePaint.setColor( WHITE );
}
m_canvas.drawArc( oval, 0, 360, false, m_strokePaint );
if ( whiteOnBlack ) {
m_strokePaint.setColor( curColor );
}
}
private boolean darkOnLight()
{
int background = m_otherColors[ CommonPrefs.COLOR_NOTILE ];
if ( background != m_backgroundUsed ) {
m_backgroundUsed = background;
m_darkOnLight = isLightColor( background );
}
return m_darkOnLight;
}
private Drawable loadAndRecolor( int resID, boolean useDark )
{
Resources res = getResources();
Drawable arrow = res.getDrawable( resID );
if ( !useDark ) {
Bitmap src = ((BitmapDrawable)arrow).getBitmap();
Bitmap bitmap = src.copy( Bitmap.Config.ARGB_8888, true );
for ( int xx = 0; xx < bitmap.getWidth(); ++xx ) {
for( int yy = 0; yy < bitmap.getHeight(); ++yy ) {
if ( BLACK == bitmap.getPixel( xx, yy ) ) {
bitmap.setPixel( xx, yy, WHITE );
}
}
}
arrow = new BitmapDrawable(bitmap);
}
return arrow;
}
private int adjustColor( int color )
{
if ( m_inTrade ) {
color = color & IN_TRADE_ALPHA;
}
return color;
}
private int getSpacing( MotionEvent event )
{
int result;