TerminalView.java revision 6a142b6d4831c3841b6be1705fc97c9b75a7c9d1
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;
23410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeyimport android.graphics.Paint.FontMetrics;
24410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeyimport android.graphics.Rect;
25479bd643981271fb0edf756ae5915e44a7352c4dJeff Sharkeyimport android.graphics.Typeface;
26410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeyimport android.os.SystemClock;
27410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkeyimport android.util.Log;
28479bd643981271fb0edf756ae5915e44a7352c4dJeff Sharkeyimport android.view.View;
29410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
30a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkeyimport com.android.terminal.Terminal.CellRun;
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
39a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey    private static final int MAX_RUN_LENGTH = 128;
40a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey
41410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private final Context mContext;
42410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private final Terminal mTerm;
43410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
44410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private final Paint mBgPaint = new Paint();
45410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private final Paint mTextPaint = new Paint();
46410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
47a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey    /** Run of cells used when drawing */
48a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey    private final CellRun mRun;
49a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey    /** Screen coordinates to draw chars into */
50a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey    private final float[] mPos;
51a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey
52410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private int mCharTop;
53410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private int mCharWidth;
54410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private int mCharHeight;
55410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
56a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey    // TODO: for atomicity we might need to snapshot runs when processing
57a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey    // callbacks driven by vterm thread
58a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey
59410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    private TerminalClient mClient = new TerminalClient() {
60410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        @Override
61410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        public void damage(int startRow, int endRow, int startCol, int endCol) {
626a142b6d4831c3841b6be1705fc97c9b75a7c9d1Jeff Sharkey            Log.d(TAG, "damage(" + startRow + ", " + endRow + ", " + startCol + ", " + endCol + ")");
636a142b6d4831c3841b6be1705fc97c9b75a7c9d1Jeff Sharkey
646a142b6d4831c3841b6be1705fc97c9b75a7c9d1Jeff Sharkey            // Invalidate region on screen
65410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            final int top = startRow * mCharHeight;
66410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            final int bottom = (endRow + 1) * mCharHeight;
67410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            final int left = startCol * mCharWidth;
68410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            final int right = (endCol + 1) * mCharWidth;
69410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            postInvalidate(left, top, right, bottom);
70410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        }
71410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
72410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        @Override
736a142b6d4831c3841b6be1705fc97c9b75a7c9d1Jeff Sharkey        public void moveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol,
746a142b6d4831c3841b6be1705fc97c9b75a7c9d1Jeff Sharkey                int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) {
756a142b6d4831c3841b6be1705fc97c9b75a7c9d1Jeff Sharkey            // Treat as normal damage and perform full redraw
766a142b6d4831c3841b6be1705fc97c9b75a7c9d1Jeff Sharkey            final int startRow = Math.min(destStartRow, srcStartRow);
776a142b6d4831c3841b6be1705fc97c9b75a7c9d1Jeff Sharkey            final int endRow = Math.max(destEndRow, srcEndRow);
786a142b6d4831c3841b6be1705fc97c9b75a7c9d1Jeff Sharkey            final int startCol = Math.min(destStartCol, srcStartCol);
796a142b6d4831c3841b6be1705fc97c9b75a7c9d1Jeff Sharkey            final int endCol = Math.max(destEndCol, srcEndCol);
806a142b6d4831c3841b6be1705fc97c9b75a7c9d1Jeff Sharkey            damage(startRow, endRow, startCol, endCol);
816a142b6d4831c3841b6be1705fc97c9b75a7c9d1Jeff Sharkey        }
826a142b6d4831c3841b6be1705fc97c9b75a7c9d1Jeff Sharkey
836a142b6d4831c3841b6be1705fc97c9b75a7c9d1Jeff Sharkey        @Override
84410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        public void bell() {
85410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            Log.i(TAG, "DING!");
86410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        }
87410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    };
88410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
89410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    public TerminalView(Context context, Terminal term) {
90410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        super(context);
91410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mContext = context;
92410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mTerm = term;
93410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
94a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey        mRun = new Terminal.CellRun();
95a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey        mRun.data = new char[MAX_RUN_LENGTH];
96a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey
97a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey        // Positions of each possible cell
98a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey        // TODO: make sure this works with surrogate pairs
99a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey        mPos = new float[MAX_RUN_LENGTH * 2];
100a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey
101410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        setBackgroundColor(Color.BLACK);
102410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        setTextSize(20);
103410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
104410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        // TODO: remove this test code that triggers invalidates
105410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        setOnClickListener(new OnClickListener() {
106410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            @Override
107410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            public void onClick(View v) {
108410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey                v.invalidate();
109410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            }
110410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        });
111410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    }
112410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
113410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    @Override
114410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    protected void onAttachedToWindow() {
115410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        super.onAttachedToWindow();
116410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mTerm.setClient(mClient);
117410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    }
118410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
119410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    @Override
120410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    protected void onDetachedFromWindow() {
121410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        super.onDetachedFromWindow();
122410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mTerm.setClient(null);
123410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    }
124410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
125410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    public void setTextSize(float textSize) {
1269cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        mTextPaint.setTypeface(Typeface.MONOSPACE);
127410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mTextPaint.setTextSize(textSize);
128410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
129410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        // Read metrics to get exact pixel dimensions
130410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        final FontMetrics fm = mTextPaint.getFontMetrics();
131410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mCharTop = (int) Math.ceil(fm.top);
132410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
133410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        final float[] widths = new float[1];
134410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mTextPaint.getTextWidths("X", widths);
135410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mCharWidth = (int) Math.ceil(widths[0]);
136410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        mCharHeight = (int) Math.ceil(fm.descent - fm.top);
137410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
138a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey        // Update drawing positions
139a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey        for (int i = 0; i < MAX_RUN_LENGTH; i++) {
140a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey            mPos[i * 2] = i * mCharWidth;
141a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey            mPos[(i * 2) + 1] = -mCharTop;
142a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey        }
143a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey
144410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        updateTerminalSize();
145410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    }
146410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
147410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    /**
148410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey     * Determine terminal dimensions based on current dimensions and font size,
149410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey     * and request that {@link Terminal} change to that size.
150410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey     */
151410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    public void updateTerminalSize() {
152410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        if (getWidth() > 0 && getHeight() > 0) {
153410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            final int rows = getHeight() / mCharHeight;
154410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            final int cols = getWidth() / mCharWidth;
155410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            mTerm.resize(rows, cols);
156a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey            mTerm.flushDamage();
157410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        }
158410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    }
159410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
160410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    @Override
161410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
162410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        super.onLayout(changed, left, top, right, bottom);
163410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        if (changed) {
164410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            updateTerminalSize();
165410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        }
166410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    }
167410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
168410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    @Override
169410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    protected void onDraw(Canvas canvas) {
170410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        super.onDraw(canvas);
171410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
172410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        final long start = SystemClock.elapsedRealtime();
173410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
174410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        // Only draw dirty region of console
175410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        final Rect dirty = canvas.getClipBounds();
176410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
1779cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        final int rows = mTerm.getRows();
1789cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        final int cols = mTerm.getCols();
1799cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey
180410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        final int startRow = dirty.top / mCharHeight;
1819cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        final int endRow = Math.min(dirty.bottom / mCharHeight, rows - 1);
182410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        final int startCol = dirty.left / mCharWidth;
1839cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey        final int endCol = Math.min(dirty.right / mCharWidth, cols - 1);
1849cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey
185a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey        final CellRun run = mRun;
186a76e33884c55bbd5db7e512b7687210cc3f635cfJeff Sharkey        final float[] pos = mPos;
187410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
188410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        for (int row = startRow; row <= endRow; row++) {
189410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            for (int col = startCol; col <= endCol;) {
1909cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                mTerm.getCellRun(row, col, run);
191410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
1929cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                mBgPaint.setColor(run.bgColor);
1939cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                mTextPaint.setColor(run.fgColor);
194410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
195410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey                final int y = row * mCharHeight;
196410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey                final int x = col * mCharWidth;
1979cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                final int xEnd = x + (run.colSize * mCharWidth);
1989cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey
1999cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                canvas.save(Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG);
2009cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                canvas.translate(x, y);
2019cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                canvas.clipRect(0, 0, run.colSize * mCharWidth, mCharHeight);
2029cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey
2039cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                canvas.drawPaint(mBgPaint);
2049cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                canvas.drawPosText(run.data, 0, run.dataSize, pos, mTextPaint);
205410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
2069cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                canvas.restore();
207410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
2089cae0a9616b1b71eac7e762d198fe1da47fea901Jeff Sharkey                col += run.colSize;
209410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey            }
210410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        }
211410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey
212410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        final long delta = SystemClock.elapsedRealtime() - start;
213410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey        Log.d(TAG, "onDraw() took " + delta + "ms");
214410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey    }
215410e0da343fd581f3112037deb475db9fb0da850Jeff Sharkey}
216