TerminalView.java revision 9cae0a9616b1b71eac7e762d198fe1da47fea901
1410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey/*
2410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey * Copyright (C) 2013 The Android Open Source Project
3410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey *
4410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
5410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey * you may not use this file except in compliance with the License.
6410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey * You may obtain a copy of the License at
7410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey *
8410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
9410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey *
10410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey * Unless required by applicable law or agreed to in writing, software
11410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
12410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey * See the License for the specific language governing permissions and
14410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey * limitations under the License.
15410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey */
16410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
17410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeypackage com.android.terminal;
18410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
19410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeyimport android.content.Context;
20410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeyimport android.graphics.Canvas;
21410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeyimport android.graphics.Color;
22410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeyimport android.graphics.Paint;
239cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkeyimport android.graphics.Typeface;
24410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeyimport android.graphics.Paint.FontMetrics;
25410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeyimport android.graphics.Rect;
26410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeyimport android.view.View;
27410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
28410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeyimport android.os.SystemClock;
29410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeyimport android.util.Log;
30410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
31410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeyimport com.android.terminal.Terminal.TerminalClient;
32410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
33410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey/**
34410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey * Rendered contents of a {@link Terminal} session.
35410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey */
36410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeypublic class TerminalView extends View {
37410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private static final String TAG = "Terminal";
38410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
39410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private final Context mContext;
40410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private final Terminal mTerm;
41410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
42410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private final Paint mBgPaint = new Paint();
43410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private final Paint mTextPaint = new Paint();
44410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
45410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private int mCharTop;
46410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private int mCharWidth;
47410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private int mCharHeight;
48410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
49410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private TerminalClient mClient = new TerminalClient() {
50410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        @Override
51410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        public void damage(int startRow, int endRow, int startCol, int endCol) {
52410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            final int top = startRow * mCharHeight;
53410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            final int bottom = (endRow + 1) * mCharHeight;
54410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            final int left = startCol * mCharWidth;
55410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            final int right = (endCol + 1) * mCharWidth;
56410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
57410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            // Invalidate region on screen
58410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            postInvalidate(left, top, right, bottom);
59410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        }
60410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
61410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        @Override
62410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        public void bell() {
63410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            Log.i(TAG, "DING!");
64410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        }
65410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    };
66410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
67410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    public TerminalView(Context context, Terminal term) {
68410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        super(context);
69410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mContext = context;
70410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mTerm = term;
71410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
72410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        setBackgroundColor(Color.BLACK);
73410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        setTextSize(20);
74410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
75410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        // TODO: remove this test code that triggers invalidates
76410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        setOnClickListener(new OnClickListener() {
77410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            @Override
78410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            public void onClick(View v) {
79410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey                v.invalidate();
80410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            }
81410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        });
82410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    }
83410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
84410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    @Override
85410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    protected void onAttachedToWindow() {
86410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        super.onAttachedToWindow();
87410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mTerm.setClient(mClient);
88410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    }
89410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
90410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    @Override
91410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    protected void onDetachedFromWindow() {
92410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        super.onDetachedFromWindow();
93410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mTerm.setClient(null);
94410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    }
95410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
96410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    public void setTextSize(float textSize) {
979cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        mTextPaint.setTypeface(Typeface.MONOSPACE);
98410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mTextPaint.setTextSize(textSize);
99410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
100410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        // Read metrics to get exact pixel dimensions
101410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        final FontMetrics fm = mTextPaint.getFontMetrics();
102410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mCharTop = (int) Math.ceil(fm.top);
103410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
104410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        final float[] widths = new float[1];
105410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mTextPaint.getTextWidths("X", widths);
106410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mCharWidth = (int) Math.ceil(widths[0]);
107410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mCharHeight = (int) Math.ceil(fm.descent - fm.top);
108410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
109410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        updateTerminalSize();
110410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    }
111410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
112410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    /**
113410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey     * Determine terminal dimensions based on current dimensions and font size,
114410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey     * and request that {@link Terminal} change to that size.
115410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey     */
116410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    public void updateTerminalSize() {
117410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        if (getWidth() > 0 && getHeight() > 0) {
118410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            final int rows = getHeight() / mCharHeight;
119410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            final int cols = getWidth() / mCharWidth;
120410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            mTerm.resize(rows, cols);
121410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        }
122410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    }
123410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
124410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    @Override
125410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
126410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        super.onLayout(changed, left, top, right, bottom);
127410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        if (changed) {
128410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            updateTerminalSize();
129410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        }
130410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    }
1319cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey
1329cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey    private static final int MAX_RUN_LENGTH = 128;
133410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
134410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    @Override
135410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    protected void onDraw(Canvas canvas) {
136410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        super.onDraw(canvas);
137410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
138410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        final long start = SystemClock.elapsedRealtime();
139410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
140410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        // Only draw dirty region of console
141410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        final Rect dirty = canvas.getClipBounds();
142410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
1439cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        final int rows = mTerm.getRows();
1449cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        final int cols = mTerm.getCols();
1459cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey
146410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        final int startRow = dirty.top / mCharHeight;
1479cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        final int endRow = Math.min(dirty.bottom / mCharHeight, rows - 1);
148410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        final int startCol = dirty.left / mCharWidth;
1499cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        final int endCol = Math.min(dirty.right / mCharWidth, cols - 1);
1509cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey
1519cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        final Terminal.CellRun run = new Terminal.CellRun();
1529cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        run.data = new char[MAX_RUN_LENGTH];
153410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
1549cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        // Positions of each possible cell
1559cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        // TODO: make sure this works with surrogate pairs
1569cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        final float[] pos = new float[MAX_RUN_LENGTH * 2];
1579cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        for (int i = 0; i < MAX_RUN_LENGTH; i++) {
1589cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey            pos[i * 2] = i * mCharWidth;
1599cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey            pos[(i * 2) + 1] = -mCharTop;
1609cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        }
161410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
162410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        for (int row = startRow; row <= endRow; row++) {
163410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            for (int col = startCol; col <= endCol;) {
1649cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                mTerm.getCellRun(row, col, run);
165410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
1669cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                mBgPaint.setColor(run.bgColor);
1679cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                mTextPaint.setColor(run.fgColor);
168410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
169410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey                final int y = row * mCharHeight;
170410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey                final int x = col * mCharWidth;
1719cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                final int xEnd = x + (run.colSize * mCharWidth);
1729cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey
1739cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                canvas.save(Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG);
1749cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                canvas.translate(x, y);
1759cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                canvas.clipRect(0, 0, run.colSize * mCharWidth, mCharHeight);
1769cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey
1779cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                canvas.drawPaint(mBgPaint);
1789cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                canvas.drawPosText(run.data, 0, run.dataSize, pos, mTextPaint);
179410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
1809cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                canvas.restore();
181410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
1829cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                col += run.colSize;
183410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            }
184410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        }
185410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
186410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        final long delta = SystemClock.elapsedRealtime() - start;
187410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        Log.d(TAG, "onDraw() took " + delta + "ms");
188410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    }
189410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey}
190