Commit 00b00812 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Add scrollback support.

Switch terminal rendering to use ListView, splitting each row into a
TerminalLineView item.  This leverages existing ListView display list
optimizations when scrolling, and gives us fling and overscroll for
free.  However, the simple case of a single line scrolling requires
an entire screen rebind.

Added locking between I/O thread and UI thread to provide consistent
view of terminal state.  Snap to current upstream libvterm, which has
updated scrollback API.  Examine full cell style when building runs.

Address terminals using "keys" instead of indicies, since ordering
can shift.  Save and restore instance state to remember scrollback
position.  Avoid crashing after closing last terminal.

Remove unused callbacks.

Bug: 8332387
Change-Id: I06468d16ae8e1ff8ac79b7115c7cb3f9434b3c0d
parent a816285f
......@@ -21,8 +21,9 @@
android:persistent="true"
android:enabled="false">
<activity android:name=".TerminalActivity"
android:windowSoftInputMode="stateAlwaysVisible|adjustResize">
<activity
android:name=".TerminalActivity"
android:windowSoftInputMode="stateAlwaysVisible|adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
......
......@@ -9,12 +9,15 @@ LOCAL_SRC_FILES := \
LOCAL_C_INCLUDES += \
external/libvterm/include \
libcore/include
libcore/include \
frameworks/base/include
LOCAL_SHARED_LIBRARIES := \
libandroidfw \
libandroid_runtime \
liblog \
libutils \
libnativehelper
libnativehelper \
libutils
LOCAL_STATIC_LIBRARIES := \
libvterm
......
This diff is collapsed.
......@@ -24,7 +24,6 @@
android:id="@+id/titles"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
/>
android:layout_gravity="top" />
</android.support.v4.view.ViewPager>
......@@ -22,7 +22,9 @@ import android.graphics.Color;
* Single terminal session backed by a pseudo terminal on the local device.
*/
public class Terminal {
private static final String TAG = "Terminal";
public static final String TAG = "Terminal";
public final int key;
private static int sNumber = 0;
......@@ -46,15 +48,17 @@ public class Terminal {
boolean strike;
int font;
int fg = Color.RED;
int bg = Color.BLUE;
int fg = Color.CYAN;
int bg = Color.DKGRAY;
}
// NOTE: clients must not call back into terminal while handling a callback,
// since native mutex isn't reentrant.
public interface TerminalClient {
public void damage(int startRow, int endRow, int startCol, int endCol);
public void moveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
public void onDamage(int startRow, int endRow, int startCol, int endCol);
public void onMoveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol);
public void bell();
public void onBell();
}
private final int mNativePtr;
......@@ -68,7 +72,7 @@ public class Terminal {
@Override
public int damage(int startRow, int endRow, int startCol, int endCol) {
if (mClient != null) {
mClient.damage(startRow, endRow, startCol, endCol);
mClient.onDamage(startRow, endRow, startCol, endCol);
}
return 1;
}
......@@ -77,7 +81,7 @@ public class Terminal {
public int moveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) {
if (mClient != null) {
mClient.moveRect(destStartRow, destEndRow, destStartCol, destEndCol, srcStartRow,
mClient.onMoveRect(destStartRow, destEndRow, destStartCol, destEndCol, srcStartRow,
srcEndRow, srcStartCol, srcEndCol);
}
return 1;
......@@ -86,7 +90,7 @@ public class Terminal {
@Override
public int bell() {
if (mClient != null) {
mClient.bell();
mClient.onBell();
}
return 1;
}
......@@ -94,8 +98,9 @@ public class Terminal {
public Terminal() {
mNativePtr = nativeInit(mCallbacks, 25, 80);
mTitle = TAG + " " + sNumber++;
mThread = new Thread(TAG) {
key = sNumber++;
mTitle = TAG + " " + key;
mThread = new Thread(mTitle) {
@Override
public void run() {
nativeRun(mNativePtr);
......@@ -110,9 +115,9 @@ public class Terminal {
mThread.start();
}
public void stop() {
if (nativeStop(mNativePtr) != 0) {
throw new IllegalStateException("stop failed");
public void destroy() {
if (nativeDestroy(mNativePtr) != 0) {
throw new IllegalStateException("destroy failed");
}
}
......@@ -120,14 +125,8 @@ public class Terminal {
mClient = client;
}
public void flushDamage() {
if (nativeFlushDamage(mNativePtr) != 0) {
throw new IllegalStateException("flushDamage failed");
}
}
public void resize(int rows, int cols) {
if (nativeResize(mNativePtr, rows, cols) != 0) {
public void resize(int rows, int cols, int scrollRows) {
if (nativeResize(mNativePtr, rows, cols, scrollRows) != 0) {
throw new IllegalStateException("resize failed");
}
}
......@@ -140,6 +139,10 @@ public class Terminal {
return nativeGetCols(mNativePtr);
}
public int getScrollRows() {
return nativeGetScrollRows(mNativePtr);
}
public void getCellRun(int row, int col, CellRun run) {
if (nativeGetCellRun(mNativePtr, row, col, run) != 0) {
throw new IllegalStateException("getCell failed");
......@@ -159,16 +162,15 @@ public class Terminal {
return nativeDispatchCharacter(mNativePtr, modifiers, character);
}
private static native int nativeInit(TerminalCallbacks callbacks, int rows, int cols);
private static native int nativeRun(int ptr);
private static native int nativeStop(int ptr);
private static native int nativeDestroy(int ptr);
private static native int nativeFlushDamage(int ptr);
private static native int nativeResize(int ptr, int rows, int cols);
private static native int nativeRun(int ptr);
private static native int nativeResize(int ptr, int rows, int cols, int scrollRows);
private static native int nativeGetCellRun(int ptr, int row, int col, CellRun run);
private static native int nativeGetRows(int ptr);
private static native int nativeGetCols(int ptr);
private static native int nativeGetScrollRows(int ptr);
private static native boolean nativeDispatchKey(int ptr, int modifiers, int key);
private static native boolean nativeDispatchCharacter(int ptr, int modifiers, int character);
......
......@@ -16,6 +16,8 @@
package com.android.terminal;
import static com.android.terminal.Terminal.TAG;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
......@@ -23,10 +25,12 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.PagerTitleStrip;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
......@@ -37,7 +41,6 @@ import android.view.ViewGroup;
* {@link TerminalService}.
*/
public class TerminalActivity extends Activity {
private static final String TAG = "Terminal";
private TerminalService mService;
......@@ -59,6 +62,7 @@ public class TerminalActivity extends Activity {
// Bind UI to known terminals
mTermAdapter.notifyDataSetChanged();
invalidateOptionsMenu();
}
@Override
......@@ -69,6 +73,9 @@ public class TerminalActivity extends Activity {
};
private final PagerAdapter mTermAdapter = new PagerAdapter() {
private SparseArray<SparseArray<Parcelable>>
mSavedState = new SparseArray<SparseArray<Parcelable>>();
@Override
public int getCount() {
if (mService != null) {
......@@ -80,9 +87,17 @@ public class TerminalActivity extends Activity {
@Override
public Object instantiateItem(ViewGroup container, int position) {
final Terminal term = mService.getTerminals().get(position);
final TerminalView view = new TerminalView(container.getContext());
view.setId(android.R.id.list);
final Terminal term = mService.getTerminals().valueAt(position);
view.setTerminal(term);
final SparseArray<Parcelable> state = mSavedState.get(term.key);
if (state != null) {
view.restoreHierarchyState(state);
}
container.addView(view);
return view;
}
......@@ -90,13 +105,24 @@ public class TerminalActivity extends Activity {
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
final TerminalView view = (TerminalView) object;
final int key = view.getTerminal().key;
SparseArray<Parcelable> state = mSavedState.get(key);
if (state == null) {
state = new SparseArray<Parcelable>();
mSavedState.put(key, state);
}
view.saveHierarchyState(state);
view.setTerminal(null);
container.removeView(view);
}
@Override
public int getItemPosition(Object object) {
final int index = mService.getTerminals().indexOf(object);
final TerminalView view = (TerminalView) object;
final int key = view.getTerminal().key;
final int index = mService.getTerminals().indexOfKey(key);
if (index == -1) {
return POSITION_NONE;
} else {
......@@ -111,7 +137,7 @@ public class TerminalActivity extends Activity {
@Override
public CharSequence getPageTitle(int position) {
return mService.getTerminals().get(position).getTitle();
return mService.getTerminals().valueAt(position).getTitle();
}
};
......@@ -146,22 +172,30 @@ public class TerminalActivity extends Activity {
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.findItem(R.id.menu_close_tab).setEnabled(mTermAdapter.getCount() > 0);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_tab: {
mService.createTerminal();
mTermAdapter.notifyDataSetChanged();
invalidateOptionsMenu();
final int index = mService.getTerminals().size() - 1;
mPager.setCurrentItem(index, true);
return true;
}
case R.id.menu_close_tab: {
final int index = mPager.getCurrentItem();
final Terminal term = mService.getTerminals().get(index);
mService.destroyTerminal(term);
// TODO: ask adamp about buggy zero item behavior
final int key = mService.getTerminals().keyAt(index);
mService.destroyTerminal(key);
mTermAdapter.notifyDataSetChanged();
invalidateOptionsMenu();
return true;
}
}
......
......@@ -21,10 +21,6 @@ public abstract class TerminalCallbacks {
return 1;
}
public int prescroll(int startRow, int endRow, int startCol, int endCol) {
return 1;
}
public int moveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) {
return 1;
......@@ -53,8 +49,4 @@ public abstract class TerminalCallbacks {
public int bell() {
return 1;
}
public int resize(int rows, int cols) {
return 1;
}
}
......@@ -21,7 +21,7 @@ import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
public class TerminalKeys implements View.OnKeyListener {
public class TerminalKeys {
private static final String TAG = "TerminalKeys";
private static final boolean DEBUG = true;
// Taken from vterm_input.h
......
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.terminal;
import static com.android.terminal.Terminal.TAG;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;
import android.view.View;
import com.android.terminal.TerminalView.TerminalMetrics;
/**
* Rendered contents of a single line of a {@link Terminal} session.
*/
public class TerminalLineView extends View {
public int pos;
public int row;
public int cols;
private final Terminal mTerm;
private final TerminalMetrics mMetrics;
public TerminalLineView(Context context, Terminal term, TerminalMetrics metrics) {
super(context);
mTerm = term;
mMetrics = metrics;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
getDefaultSize(mMetrics.charHeight, heightMeasureSpec));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mTerm == null) {
Log.w(TAG, "onDraw() without a terminal");
canvas.drawColor(Color.MAGENTA);
return;
}
final TerminalMetrics m = mMetrics;
for (int col = 0; col < cols;) {
mTerm.getCellRun(row, col, m.run);
m.bgPaint.setColor(m.run.bg);
m.textPaint.setColor(m.run.fg);
final int x = col * m.charWidth;
final int xEnd = x + (m.run.colSize * m.charWidth);
canvas.save();
canvas.translate(x, 0);
canvas.clipRect(0, 0, m.run.colSize * m.charWidth, m.charHeight);
canvas.drawPaint(m.bgPaint);
canvas.drawPosText(m.run.data, 0, m.run.dataSize, m.pos, m.textPaint);
canvas.restore();
col += m.run.colSize;
}
}
}
......@@ -20,19 +20,14 @@ import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import android.util.SparseArray;
/**
* Background service that keeps {@link Terminal} instances running and warm
* when UI isn't present.
*/
public class TerminalService extends Service {
private static final String TAG = "Terminal";
private final ArrayList<Terminal> mTerminals = new ArrayList<Terminal>();
private final SparseArray<Terminal> mTerminals = new SparseArray<Terminal>();
public class ServiceBinder extends Binder {
public TerminalService getService() {
......@@ -45,28 +40,29 @@ public class TerminalService extends Service {
return new ServiceBinder();
}
public List<Terminal> getTerminals() {
return Collections.unmodifiableList(mTerminals);
public SparseArray<Terminal> getTerminals() {
return mTerminals;
}
public Terminal createTerminal() {
public int createTerminal() {
// If our first terminal, start ourselves as long-lived service
if (mTerminals.isEmpty()) {
if (mTerminals.size() == 0) {
startService(new Intent(this, TerminalService.class));
}
final Terminal term = new Terminal();
term.start();
mTerminals.add(term);
return term;
mTerminals.put(term.key, term);
return term.key;
}
public void destroyTerminal(Terminal term) {
term.stop();
mTerminals.remove(term);
public void destroyTerminal(int key) {
final Terminal term = mTerminals.get(key);
term.destroy();
mTerminals.delete(key);
// If our last terminal, tear down long-lived service
if (mTerminals.isEmpty()) {
if (mTerminals.size() == 0) {
stopService(new Intent(this, TerminalService.class));
}
}
......
......@@ -16,20 +16,23 @@
package com.android.terminal;
import static com.android.terminal.Terminal.TAG;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.SystemClock;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.KeyEvent;
import android.view.View;
import android.widget.BaseAdapter;
import android.widget.ListView;
import com.android.terminal.Terminal.CellRun;
import com.android.terminal.Terminal.TerminalClient;
......@@ -37,219 +40,246 @@ import com.android.terminal.Terminal.TerminalClient;
/**
* Rendered contents of a {@link Terminal} session.
*/
public class TerminalView extends View {
private static final String TAG = "Terminal";
public class TerminalView extends ListView {
private static final boolean LOGD = true;
private static final int MAX_RUN_LENGTH = 128;
private static final boolean SCROLL_ON_DAMAGE = false;
private static final boolean SCROLL_ON_INPUT = true;
private final Context mContext;
private Terminal mTerm;
private final Paint mBgPaint = new Paint();
private final Paint mTextPaint = new Paint();
private boolean mScrolled;
/** Run of cells used when drawing */
private final CellRun mRun;
/** Screen coordinates to draw chars into */
private final float[] mPos;
private int mRows;
private int mCols;
private int mScrollRows;
private Terminal mTerm;
private final TerminalMetrics mMetrics = new TerminalMetrics();
private final TerminalKeys mTermKeys = new TerminalKeys();
private TerminalKeys mTermKeys;
/**
* Metrics shared between all {@link TerminalLineView} children. Locking
* provided by main thread.
*/
static class TerminalMetrics {
private static final int MAX_RUN_LENGTH = 128;
private int mCharTop;
private int mCharWidth;
private int mCharHeight;
final Paint bgPaint = new Paint();
final Paint textPaint = new Paint();
// TODO: for atomicity we might need to snapshot runs when processing
// callbacks driven by vterm thread
/** Run of cells used when drawing */
final CellRun run;
/** Screen coordinates to draw chars into */
final float[] pos;
private TerminalClient mClient = new TerminalClient() {
@Override
public void damage(int startRow, int endRow, int startCol, int endCol) {
if (LOGD) Log.d(TAG, "damage(" + startRow + ", " + endRow + ", " + startCol + ", " + endCol + ")");
// Invalidate region on screen
final int top = startRow * mCharHeight;
final int bottom = (endRow + 1) * mCharHeight;
final int left = startCol * mCharWidth;
final int right = (endCol + 1) * mCharWidth;
postInvalidate(left, top, right, bottom);
int charTop;
int charWidth;
int charHeight;
public TerminalMetrics() {
run = new Terminal.CellRun();
run.data = new char[MAX_RUN_LENGTH];
// Positions of each possible cell
// TODO: make sure this works with surrogate pairs
pos = new float[MAX_RUN_LENGTH * 2];
setTextSize(20);
}
@Override
public void moveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) {
// Treat as normal damage and perform full redraw
final int startRow = Math.min(destStartRow, srcStartRow);
final int endRow = Math.max(destEndRow, srcEndRow);
final int startCol = Math.min(destStartCol, srcStartCol);
final int endCol = Math.max(destEndCol, srcEndCol);
damage(startRow, endRow, startCol, endCol);
public void setTextSize(float textSize) {
textPaint.setTypeface(Typeface.MONOSPACE);
textPaint.setAntiAlias(true);
textPaint.setTextSize(textSize);
// Read metrics to get exact pixel dimensions
final FontMetrics fm = textPaint.getFontMetrics();
charTop = (int) Math.ceil(fm.top);
final float[] widths = new float[1];
textPaint.getTextWidths("X", widths);
charWidth = (int) Math.ceil(widths[0]);
charHeight = (int) Math.ceil(fm.descent - fm.top);
// Update drawing positions
for (int i = 0; i < MAX_RUN_LENGTH; i++) {
pos[i * 2] = i * charWidth;
pos[(i * 2) + 1] = -charTop;
}
}
}
private final Runnable mDamageRunnable = new Runnable() {
@Override
public void bell() {
Log.i(TAG, "DING!");
public void run() {
invalidateViews();
if (SCROLL_ON_DAMAGE) {
scrollToBottom(true);
}
}
};
public TerminalView(Context context) {
super(context);
mContext = context;
this(context, null);
}
mRun = new Terminal.CellRun();
mRun.data = new char[MAX_RUN_LENGTH];
public TerminalView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.listViewStyle);
}
// Positions of each possible cell
// TODO: make sure this works with surrogate pairs
mPos = new float[MAX_RUN_LENGTH * 2];
public TerminalView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setBackgroundColor(Color.BLACK);
setTextSize(20);
setBackground(null);
setDivider(null);
// TODO: remove this test code that triggers invalidates
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
v.invalidate();
v.requestFocus();
}
});
// Set view properties
setFocusable(true);
setFocusableInTouchMode(true);
setScrollContainer(true);
mTermKeys = new TerminalKeys();
setOnKeyListener(mTermKeys);
setAdapter(mAdapter);
setOnKeyListener(mKeyListener);
}
public void setTerminal(Terminal term) {
final Terminal orig = mTerm;
if (orig != null) {
orig.setClient(null);
}
mTerm = term;
if (term != null) {
term.setClient(mClient);
mTermKeys.setTerminal(term);
private final BaseAdapter mAdapter = new BaseAdapter() {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final TerminalLineView view;
if (convertView != null) {
view = (TerminalLineView) convertView;
} else {
view = new TerminalLineView(parent.getContext(), mTerm, mMetrics);
}
view.pos = position;
view.row = posToRow(position);
view.cols = mCols;
return view;
}
updateTerminalSize();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mTerm != null) {
mTerm.setClient(mClient);
@Override
public long getItemId(int position) {
return position;
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mTerm != null) {
mTerm.setClient(null);
@Override
public Object getItem(int position) {
return null;
}
}
public void setTextSize(float textSize) {
mTextPaint.setTypeface(Typeface.MONOSPACE);
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(textSize);
// Read metrics to get exact pixel dimensions
final FontMetrics fm = mTextPaint.getFontMetrics();
mCharTop = (int) Math.ceil(fm.top);
final float[] widths = new float[1];
mTextPaint.getTextWidths("X", widths);
mCharWidth = (int) Math.ceil(widths[0]);
mCharHeight = (int) Math.ceil(fm.descent - fm.top);
// Update drawing positions
for (int i = 0; i < MAX_RUN_LENGTH; i++) {
mPos[i * 2] = i * mCharWidth;
mPos[(i * 2) + 1] = -mCharTop;
@Override
public int getCount() {
if (mTerm != null) {
return mRows + mScrollRows;
} else {
return 0;
}
}
};
updateTerminalSize();
}
private TerminalClient mClient = new TerminalClient() {
@Override
public void onDamage(final int startRow, final int endRow, int startCol, int endCol) {
post(mDamageRunnable);
}
/**
* Determine terminal dimensions based on current dimensions and font size,
* and request that {@link Terminal} change to that size.
*/
public void updateTerminalSize() {
if (getWidth() > 0 && getHeight() > 0 && mTerm != null) {
final int rows = getHeight() / mCharHeight;
final int cols = getWidth() / mCharWidth;
mTerm.resize(rows, cols);
@Override
public void onMoveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) {
post(mDamageRunnable);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
updateTerminalSize();
@Override
public void onBell() {
Log.i(TAG, "DING!");
}
};
private int rowToPos(int row) {
return row + mScrollRows;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
private int posToRow(int pos) {
return pos - mScrollRows;
}
if (mTerm == null) {
Log.w(TAG, "onDraw() without a terminal");
canvas.drawColor(Color.MAGENTA);
return;
private View.OnKeyListener mKeyListener = new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
final boolean res = mTermKeys.onKey(v, keyCode, event);
if (res && SCROLL_ON_INPUT) {
scrollToBottom(true);
}
return res;
}
};
final long start = SystemClock.elapsedRealtime();
@Override
public void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
mScrolled = true;
}
// Only draw dirty region of console
final Rect dirty = canvas.getClipBounds();
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!mScrolled) {
scrollToBottom(false);
}
}
final int rows = mTerm.getRows();
final int cols = mTerm.getCols();
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
final int startRow = dirty.top / mCharHeight;
final int endRow = Math.min(dirty.bottom / mCharHeight, rows - 1);
final int startCol = dirty.left / mCharWidth;
final int endCol = Math.min(dirty.right / mCharWidth, cols - 1);
final int rows = h / mMetrics.charHeight;
final int cols = w / mMetrics.charWidth;
final int scrollRows = mScrollRows;
final CellRun run = mRun;
final float[] pos = mPos;
final boolean sizeChanged = (rows != mRows || cols != mCols || scrollRows != mScrollRows);
if (mTerm != null && sizeChanged) {
mTerm.resize(rows, cols, scrollRows);
for (int row = startRow; row <= endRow; row++) {
for (int col = startCol; col <= endCol;) {
mTerm.getCellRun(row, col, run);
mRows = rows;
mCols = cols;
mScrollRows = scrollRows;
mBgPaint.setColor(run.bg);
mTextPaint.setColor(run.fg);
mAdapter.notifyDataSetChanged();
}
}
final int y = row * mCharHeight;
final int x = col * mCharWidth;
final int xEnd = x + (run.colSize * mCharWidth);
public void scrollToBottom(boolean animate) {
final int dur = animate ? 250 : 0;
smoothScrollToPositionFromTop(getCount(), 0, dur);
mScrolled = true;
}
canvas.save(Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG);
canvas.translate(x, y);
canvas.clipRect(0, 0, run.colSize * mCharWidth, mCharHeight);
public void setTerminal(Terminal term) {
final Terminal orig = mTerm;
if (orig != null) {
orig.setClient(null);
}
mTerm = term;
mScrolled = false;
if (term != null) {
term.setClient(mClient);
mTermKeys.setTerminal(term);
canvas.drawPaint(mBgPaint);
canvas.drawPosText(run.data, 0, run.dataSize, pos, mTextPaint);
// Populate any current settings
mRows = mTerm.getRows();
mCols = mTerm.getCols();
mScrollRows = mTerm.getScrollRows();
mAdapter.notifyDataSetChanged();
}
}
canvas.restore();
public Terminal getTerminal() {
return mTerm;
}
col += run.colSize;
}
}
public void setTextSize(float textSize) {
mMetrics.setTextSize(textSize);
final long delta = SystemClock.elapsedRealtime() - start;
if (LOGD) Log.d(TAG, "onDraw() took " + delta + "ms");
// Layout will kick off terminal resize when needed
requestLayout();
}
@Override
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment