| 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 |