OLD | NEW |
1 <!-- | 1 <!-- |
2 // Copyright 2015 The Chromium Authors. All rights reserved. | 2 // Copyright 2015 The Chromium Authors. All rights reserved. |
3 // Use of this source code is governed by a BSD-style license that can be | 3 // Use of this source code is governed by a BSD-style license that can be |
4 // found in the LICENSE file. | 4 // found in the LICENSE file. |
5 --> | 5 --> |
6 <import src="/sky/framework/elements/sky-element.sky" /> | 6 <import src="/sky/framework/elements/sky-element.sky" /> |
7 <import src="/sky/framework/elements/sky-scrollable.sky" /> | |
8 <sky-element> | 7 <sky-element> |
9 <template> | 8 <template> |
10 <style> | 9 <style> |
11 #control { | 10 #container { |
12 height: -webkit-fill-available; | 11 height: -webkit-fill-available; |
| 12 } |
| 13 #input-box { |
| 14 position: absolute; |
| 15 left: 0; |
| 16 top: 0; |
| 17 z-index: 10; |
| 18 width: 100%; |
| 19 height: 100%; |
| 20 opacity: 0; |
| 21 } |
| 22 #output-box { |
| 23 position: absolute; |
| 24 left: 0; |
| 25 top: 0; |
| 26 z-index: 0; |
| 27 width: 100%; |
| 28 height: 100%; |
13 background-color: black; | 29 background-color: black; |
14 color: rgb(255, 191, 0); | 30 color: rgb(255, 191, 0); |
15 font-family: 'Courier', 'monospace'; | 31 font-family: 'Courier', 'monospace'; |
16 font-size: small; | 32 font-size: small; |
17 } | 33 } |
18 .line { | 34 .line { |
| 35 height: 1em; |
19 white-space: nowrap; | 36 white-space: nowrap; |
20 } | 37 } |
21 </style> | 38 </style> |
22 <sky-scrollable id="control" contenteditable /> | 39 <div id="container"> |
| 40 <div id="input-box" contenteditable /> |
| 41 <div id="output-box" /> |
| 42 </div> |
| 43 </div> |
23 </template> | 44 </template> |
24 <script> | 45 <script> |
25 import 'dart:async'; | 46 import 'dart:async'; |
26 import 'dart:core'; | 47 import 'dart:core'; |
27 import 'dart:sky'; | 48 import 'dart:sky'; |
28 import 'package:mojo/services/terminal/public/interfaces/terminal_client.mojom.d
art' as terminal; | 49 import 'package:mojo/services/terminal/public/interfaces/terminal_client.mojom.d
art' as terminal; |
29 import 'package:sky/framework/embedder.dart'; | 50 import 'package:sky/framework/embedder.dart'; |
30 import 'terminal_display.dart'; | 51 import 'terminal_display.dart'; |
31 import 'terminal_file_impl.dart'; | 52 import 'terminal_file_impl.dart'; |
32 | 53 |
33 // Implements the <terminal> element, which implements a "terminal display". Has | 54 // Implements the <terminal> element, which implements a "terminal display". Has |
34 // an |url| attribute, whose value should be a Mojo app that provides the | 55 // an |url| attribute, whose value should be a Mojo app that provides the |
35 // |terminal.TerminalClient| service. | 56 // |terminal.TerminalClient| service. |
36 @Tagname('terminal') | 57 @Tagname('terminal') |
37 class TerminalDisplayImpl extends SkyElement implements TerminalDisplay { | 58 class TerminalDisplayImpl extends SkyElement implements TerminalDisplay { |
38 Element _control; | 59 Element _inputBox; |
| 60 Element _outputBox; |
| 61 int _maxLines; |
39 | 62 |
40 // Queue of unconsumed input (keystrokes), with the head at index 0. | 63 // Queue of unconsumed input (keystrokes), with the head at index 0. |
41 // Keystrokes end up here if there's no reader (i.e., |getChar()|) pending, | 64 // Keystrokes end up here if there's no reader (i.e., |getChar()|) pending, |
42 // i.e., if |_readerQueue| is empty. Invariant: At most one of |_inputQueue| | 65 // i.e., if |_readerQueue| is empty. Invariant: At most one of |_inputQueue| |
43 // and |_readerQueue| may be nonempty at any given time. | 66 // and |_readerQueue| may be nonempty at any given time. |
44 List<int> _inputQueue; | 67 List<int> _inputQueue; |
45 | 68 |
46 // Queue of things waiting for input, with the head at index 0. If a keystroke | 69 // Queue of things waiting for input, with the head at index 0. If a keystroke |
47 // is received and this is nonempty, the head is given that keystroke (and | 70 // is received and this is nonempty, the head is given that keystroke (and |
48 // dequeued). | 71 // dequeued). |
49 List<Completer<int>> _readerQueue; | 72 List<Completer<int>> _readerQueue; |
50 | 73 |
51 TerminalDisplayImpl() | 74 TerminalDisplayImpl() |
52 : _inputQueue = new List<int>(), | 75 : _inputQueue = new List<int>(), |
53 _readerQueue = new List<Completer<int>>() { | 76 _readerQueue = new List<Completer<int>>() { |
54 } | 77 } |
55 | 78 |
56 void shadowRootReady() { | 79 void shadowRootReady() { |
57 _control = shadowRoot.getElementById('control'); | 80 _inputBox = shadowRoot.getElementById('input-box'); |
58 _control.addEventListener('keydown', _handleKeyDown); | 81 _inputBox.addEventListener('keydown', _handleKeyDown); |
59 _control.addEventListener('keypress', _handleKeyPress); | 82 _inputBox.addEventListener('keypress', _handleKeyPress); |
| 83 _inputBox.addEventListener('wheel', _handleWheel); |
| 84 _outputBox = shadowRoot.getElementById('output-box'); |
| 85 |
| 86 // Hack to allow |_newLine()| to work. |
| 87 _maxLines = 100; |
60 | 88 |
61 // Initialize with the first line. | 89 // Initialize with the first line. |
62 _newLine(); | 90 _newLine(); |
63 | 91 |
| 92 // Actually compute the maximum number of lines. |
| 93 // TODO(vtl): Recompute on resize. |
| 94 _maxLines = _outputBox.clientHeight ~/ _outputBox.firstChild.offsetHeight; |
| 95 |
64 var url = getAttribute('url'); | 96 var url = getAttribute('url'); |
65 if (url != null) { | 97 if (url != null) { |
66 connect(url); | 98 connect(url); |
67 } | 99 } |
68 } | 100 } |
69 | 101 |
70 void _handleKeyDown(KeyboardEvent event) { | 102 void _handleKeyDown(KeyboardEvent event) { |
71 // TODO(vtl): In general, our key handling is a total hack (due in part to | 103 // TODO(vtl): In general, our key handling is a total hack (due in part to |
72 // sky's keyboard support being incomplete) -- e.g., we shouldn't have to | 104 // sky's keyboard support being incomplete) -- e.g., we shouldn't have to |
73 // make our div contenteditable. We have to intercept backspace (^H) here, | 105 // make our div contenteditable. We have to intercept backspace (^H) here, |
74 // since we won't actually get a keypress event for it. (Possibly, we should | 106 // since we won't actually get a keypress event for it. (Possibly, we should |
75 // only handle keydown instead of keypress, but then we'd have to handle | 107 // only handle keydown instead of keypress, but then we'd have to handle |
76 // shift, etc. ourselves.) | 108 // shift, etc. ourselves.) |
77 if (event.key == 8) { | 109 if (event.key == 8) { |
78 _enqueueChar(8); | 110 _enqueueChar(8); |
79 event.preventDefault(); | 111 event.preventDefault(); |
80 } | 112 } |
81 } | 113 } |
82 | 114 |
83 void _handleKeyPress(KeyboardEvent event) { | 115 void _handleKeyPress(KeyboardEvent event) { |
84 _enqueueChar(event.charCode); | 116 if (event.charCode != 0) { |
| 117 _enqueueChar(event.charCode); |
| 118 } |
85 event.preventDefault(); | 119 event.preventDefault(); |
86 } | 120 } |
87 | 121 |
| 122 void _handleWheel(WheelEvent event) { |
| 123 _outputBox.dispatchEvent(event); |
| 124 } |
| 125 |
88 void _enqueueChar(int charCode) { | 126 void _enqueueChar(int charCode) { |
89 // TODO(vtl): Add "echo" mode; do |putChar(event.charCode);| if echo is on. | 127 // TODO(vtl): Add "echo" mode; do |putChar(event.charCode);| if echo is on. |
90 | 128 |
91 if (_readerQueue.isEmpty) { | 129 if (_readerQueue.isEmpty) { |
92 _inputQueue.add(charCode); | 130 _inputQueue.add(charCode); |
93 } else { | 131 } else { |
94 _readerQueue.removeAt(0).complete(charCode); | 132 _readerQueue.removeAt(0).complete(charCode); |
95 } | 133 } |
96 } | 134 } |
97 | 135 |
98 void _backspace() { | 136 void _backspace() { |
99 var oldText = _control.lastChild.textContent; | 137 var oldText = _outputBox.lastChild.textContent; |
100 if (oldText.length > 0) { | 138 if (oldText.length > 0) { |
101 _control.lastChild.textContent = oldText.substring(0, oldText.length - 1); | 139 _outputBox.lastChild.textContent = oldText.substring(0, |
| 140 oldText.length - 1); |
102 } | 141 } |
103 } | 142 } |
104 | 143 |
105 void _newLine() { | 144 void _newLine() { |
106 var line = document.createElement('div'); | 145 var line = document.createElement('div'); |
107 line.setAttribute('class', 'line'); | 146 line.setAttribute('class', 'line'); |
108 _control.appendChild(line); | 147 _outputBox.appendChild(line); |
| 148 |
| 149 // Scroll if necessary. |
| 150 var children = _outputBox.getChildNodes(); |
| 151 if (children.length > _maxLines) { |
| 152 children = new List.from(children.skip(children.length - _maxLines)); |
| 153 _outputBox.setChildren(children); |
| 154 } |
109 } | 155 } |
110 | 156 |
111 void _clear() { | 157 void _clear() { |
112 while (_control.firstChild != null) { | 158 _outputBox.setChildren([]); |
113 _control.firstChild.remove(); | |
114 } | |
115 _newLine(); | 159 _newLine(); |
116 } | 160 } |
117 | 161 |
118 void connect(String url) { | 162 void connect(String url) { |
119 var terminalClient = new terminal.TerminalClientProxy.unbound(); | 163 var terminalClient = new terminal.TerminalClientProxy.unbound(); |
120 embedder.connectToService(url, terminalClient); | 164 embedder.connectToService(url, terminalClient); |
121 terminalClient.ptr.connectToTerminal(new TerminalFileImpl(this).stub); | 165 terminalClient.ptr.connectToTerminal(new TerminalFileImpl(this).stub); |
122 terminalClient.close(); | 166 terminalClient.close(); |
123 } | 167 } |
124 | 168 |
125 void putString(String s) { | 169 void putString(String s) { |
126 for (var i = 0; i < s.length; i++) { | 170 for (var i = 0; i < s.length; i++) { |
127 putChar(s.codeUnitAt(i)); | 171 putChar(s.codeUnitAt(i)); |
128 } | 172 } |
129 } | 173 } |
130 | 174 |
131 // |TerminalDisplay| implementation: | 175 // |TerminalDisplay| implementation: |
132 | 176 |
133 @override | 177 @override |
134 void putChar(int byte) { | 178 void putChar(int byte) { |
135 // Fast-path for printable chars. | 179 // Fast-path for printable chars. |
136 if (byte >= 32) { | 180 if (byte >= 32) { |
137 _control.lastChild.textContent += new String.fromCharCode(byte); | 181 _outputBox.lastChild.textContent += new String.fromCharCode(byte); |
138 return; | 182 return; |
139 } | 183 } |
140 | 184 |
141 switch (byte) { | 185 switch (byte) { |
142 case 8: // BS (^H). | 186 case 8: // BS (^H). |
143 _backspace(); | 187 _backspace(); |
144 break; | 188 break; |
145 case 10: // LF ('\n'). | 189 case 10: // LF ('\n'). |
146 _newLine(); // TODO(vtl): LF and CR should be separated. | 190 _newLine(); // TODO(vtl): LF and CR should be separated. |
147 break; | 191 break; |
(...skipping 17 matching lines...) Expand all Loading... |
165 | 209 |
166 var completer = new Completer<int>(); | 210 var completer = new Completer<int>(); |
167 _readerQueue.add(completer); | 211 _readerQueue.add(completer); |
168 return completer.future; | 212 return completer.future; |
169 } | 213 } |
170 } | 214 } |
171 | 215 |
172 _init(script) => register(script, TerminalDisplayImpl); | 216 _init(script) => register(script, TerminalDisplayImpl); |
173 </script> | 217 </script> |
174 </sky-element> | 218 </sky-element> |
OLD | NEW |