TerminalView.java revision 6a142b6d4831c3841b6be1705fc97c9b75a7c9d1
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.terminal;
18
19import android.content.Context;
20import android.graphics.Canvas;
21import android.graphics.Color;
22import android.graphics.Paint;
23import android.graphics.Paint.FontMetrics;
24import android.graphics.Rect;
25import android.graphics.Typeface;
26import android.os.SystemClock;
27import android.util.Log;
28import android.view.View;
29
30import com.android.terminal.Terminal.CellRun;
31import com.android.terminal.Terminal.TerminalClient;
32
33/**
34 * Rendered contents of a {@link Terminal} session.
35 */
36public class TerminalView extends View {
37    private static final String TAG = "Terminal";
38
39    private static final int MAX_RUN_LENGTH = 128;
40
41    private final Context mContext;
42    private final Terminal mTerm;
43
44    private final Paint mBgPaint = new Paint();
45    private final Paint mTextPaint = new Paint();
46
47    /** Run of cells used when drawing */
48    private final CellRun mRun;
49    /** Screen coordinates to draw chars into */
50    private final float[] mPos;
51
52    private int mCharTop;
53    private int mCharWidth;
54    private int mCharHeight;
55
56    // TODO: for atomicity we might need to snapshot runs when processing
57    // callbacks driven by vterm thread
58
59    private TerminalClient mClient = new TerminalClient() {
60        @Override
61        public void damage(int startRow, int endRow, int startCol, int endCol) {
62            Log.d(TAG, "damage(" + startRow + ", " + endRow + ", " + startCol + ", " + endCol + ")");
63
64            // Invalidate region on screen
65            final int top = startRow * mCharHeight;
66            final int bottom = (endRow + 1) * mCharHeight;
67            final int left = startCol * mCharWidth;
68            final int right = (endCol + 1) * mCharWidth;
69            postInvalidate(left, top, right, bottom);
70        }
71
72        @Override
73        public void moveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
74                int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) {
75            // Treat as normal damage and perform full redraw
76            final int startRow = Math.min(destStartRow, srcStartRow);
77            final int endRow = Math.max(destEndRow, srcEndRow);
78            final int startCol = Math.min(destStartCol, srcStartCol);
79            final int endCol = Math.max(destEndCol, srcEndCol);
80            damage(startRow, endRow, startCol, endCol);
81        }
82
83        @Override
84        public void bell() {
85            Log.i(TAG, "DING!");
86        }
87    };
88
89    public TerminalView(Context context, Terminal term) {
90        super(context);
91        mContext = context;
92        mTerm = term;
93
94        mRun = new Terminal.CellRun();
95        mRun.data = new char[MAX_RUN_LENGTH];
96
97        // Positions of each possible cell
98        // TODO: make sure this works with surrogate pairs
99        mPos = new float[MAX_RUN_LENGTH * 2];
100
101        setBackgroundColor(Color.BLACK);
102        setTextSize(20);
103
104        // TODO: remove this test code that triggers invalidates
105        setOnClickListener(new OnClickListener() {
106            @Override
107            public void onClick(View v) {
108                v.invalidate();
109            }
110        });
111    }
112
113    @Override
114    protected void onAttachedToWindow() {
115        super.onAttachedToWindow();
116        mTerm.setClient(mClient);
117    }
118
119    @Override
120    protected void onDetachedFromWindow() {
121        super.onDetachedFromWindow();
122        mTerm.setClient(null);
123    }
124
125    public void setTextSize(float textSize) {
126        mTextPaint.setTypeface(Typeface.MONOSPACE);
127        mTextPaint.setTextSize(textSize);
128
129        // Read metrics to get exact pixel dimensions
130        final FontMetrics fm = mTextPaint.getFontMetrics();
131        mCharTop = (int) Math.ceil(fm.top);
132
133        final float[] widths = new float[1];
134        mTextPaint.getTextWidths("X", widths);
135        mCharWidth = (int) Math.ceil(widths[0]);
136        mCharHeight = (int) Math.ceil(fm.descent - fm.top);
137
138        // Update drawing positions
139        for (int i = 0; i < MAX_RUN_LENGTH; i++) {
140            mPos[i * 2] = i * mCharWidth;
141            mPos[(i * 2) + 1] = -mCharTop;
142        }
143
144        updateTerminalSize();
145    }
146
147    /**
148     * Determine terminal dimensions based on current dimensions and font size,
149     * and request that {@link Terminal} change to that size.
150     */
151    public void updateTerminalSize() {
152        if (getWidth() > 0 && getHeight() > 0) {
153            final int rows = getHeight() / mCharHeight;
154            final int cols = getWidth() / mCharWidth;
155            mTerm.resize(rows, cols);
156            mTerm.flushDamage();
157        }
158    }
159
160    @Override
161    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
162        super.onLayout(changed, left, top, right, bottom);
163        if (changed) {
164            updateTerminalSize();
165        }
166    }
167
168    @Override
169    protected void onDraw(Canvas canvas) {
170        super.onDraw(canvas);
171
172        final long start = SystemClock.elapsedRealtime();
173
174        // Only draw dirty region of console
175        final Rect dirty = canvas.getClipBounds();
176
177        final int rows = mTerm.getRows();
178        final int cols = mTerm.getCols();
179
180        final int startRow = dirty.top / mCharHeight;
181        final int endRow = Math.min(dirty.bottom / mCharHeight, rows - 1);
182        final int startCol = dirty.left / mCharWidth;
183        final int endCol = Math.min(dirty.right / mCharWidth, cols - 1);
184
185        final CellRun run = mRun;
186        final float[] pos = mPos;
187
188        for (int row = startRow; row <= endRow; row++) {
189            for (int col = startCol; col <= endCol;) {
190                mTerm.getCellRun(row, col, run);
191
192                mBgPaint.setColor(run.bgColor);
193                mTextPaint.setColor(run.fgColor);
194
195                final int y = row * mCharHeight;
196                final int x = col * mCharWidth;
197                final int xEnd = x + (run.colSize * mCharWidth);
198
199                canvas.save(Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG);
200                canvas.translate(x, y);
201                canvas.clipRect(0, 0, run.colSize * mCharWidth, mCharHeight);
202
203                canvas.drawPaint(mBgPaint);
204                canvas.drawPosText(run.data, 0, run.dataSize, pos, mTextPaint);
205
206                canvas.restore();
207
208                col += run.colSize;
209            }
210        }
211
212        final long delta = SystemClock.elapsedRealtime() - start;
213        Log.d(TAG, "onDraw() took " + delta + "ms");
214    }
215}
216