mirror of
https://github.com/flutter/flutter
synced 2024-09-19 08:11:56 +00:00
Make a material design Input component
We don't yet have a focus controller, which means once this control becomes focused there's no way for it to lose focus. R=eseidel@chromium.org Review URL: https://codereview.chromium.org/999553002
This commit is contained in:
parent
7e9bbbec21
commit
a04c6b09ef
|
@ -4,7 +4,7 @@
|
|||
|
||||
import 'package:sky/services/keyboard/keyboard.mojom.dart';
|
||||
|
||||
typedef void StringChangedCallback(EditableString updated);
|
||||
typedef void StringUpdated();
|
||||
|
||||
class TextRange {
|
||||
final int start;
|
||||
|
@ -23,11 +23,11 @@ class EditableString implements KeyboardClient {
|
|||
TextRange composing = const TextRange.empty();
|
||||
TextRange selection = const TextRange.empty();
|
||||
|
||||
final StringChangedCallback onChanged;
|
||||
final StringUpdated onUpdated;
|
||||
|
||||
KeyboardClientStub stub;
|
||||
|
||||
EditableString({this.text: '', this.onChanged}) {
|
||||
EditableString({this.text: '', this.onUpdated}) {
|
||||
stub = new KeyboardClientStub.unbound()..impl = this;
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ class EditableString implements KeyboardClient {
|
|||
TextRange committedRange = _replaceOrAppend(composing, text);
|
||||
selection = new TextRange.collapsed(committedRange.end);
|
||||
composing = const TextRange.empty();
|
||||
onChanged(this);
|
||||
onUpdated();
|
||||
}
|
||||
|
||||
void deleteSurroundingText(int beforeLength, int afterLength) {
|
||||
|
@ -97,23 +97,23 @@ class EditableString implements KeyboardClient {
|
|||
_delete(beforeRange);
|
||||
selection = new TextRange(start: selection.start - beforeLength,
|
||||
end: selection.end - beforeLength);
|
||||
onChanged(this);
|
||||
onUpdated();
|
||||
}
|
||||
|
||||
void setComposingRegion(int start, int end) {
|
||||
composing = new TextRange(start: start, end: end);
|
||||
onChanged(this);
|
||||
onUpdated();
|
||||
}
|
||||
|
||||
void setComposingText(String text, int newCursorPosition) {
|
||||
// TODO(abarth): Why is |newCursorPosition| always 1?
|
||||
composing = _replaceOrAppend(composing, text);
|
||||
selection = new TextRange.collapsed(composing.end);
|
||||
onChanged(this);
|
||||
onUpdated();
|
||||
}
|
||||
|
||||
void setSelection(int start, int end) {
|
||||
selection = new TextRange(start: start, end: end);
|
||||
onChanged(this);
|
||||
onUpdated();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,73 +3,35 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
import '../../framework/fn.dart';
|
||||
import '../../framework/shell.dart' as shell;
|
||||
import '../../framework/theme/colors.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'editable_string.dart';
|
||||
import 'package:sky/services/keyboard/keyboard.mojom.dart';
|
||||
|
||||
class EditableText extends Component {
|
||||
static Style _style = new Style('''
|
||||
display: paragraph;
|
||||
white-space: pre-wrap;
|
||||
padding: 10px;
|
||||
height: 200px;
|
||||
background-color: lightblue;'''
|
||||
static final Style _style = new Style('''
|
||||
display: inline;'''
|
||||
);
|
||||
|
||||
static Style _cusorStyle = new Style('''
|
||||
static final Style _cusorStyle = new Style('''
|
||||
display: inline-block;
|
||||
width: 2px;
|
||||
height: 1.2em;
|
||||
vertical-align: top;
|
||||
background-color: blue;'''
|
||||
background-color: ${Blue[500]};'''
|
||||
);
|
||||
|
||||
static Style _composingStyle = new Style('''
|
||||
static final Style _composingStyle = new Style('''
|
||||
display: inline;
|
||||
text-decoration: underline;'''
|
||||
);
|
||||
|
||||
KeyboardServiceProxy _service;
|
||||
|
||||
EditableString _string;
|
||||
EditableString value;
|
||||
bool focused;
|
||||
Timer _cursorTimer;
|
||||
bool _showCursor = false;
|
||||
|
||||
EditableText({Object key}) : super(key: key, stateful: true) {
|
||||
_string = new EditableString(text: '', onChanged: _handleTextChanged);
|
||||
events.listen('click', _handleClick);
|
||||
events.listen('focus', _handleFocus);
|
||||
events.listen('blur', _handleBlur);
|
||||
}
|
||||
|
||||
void _handleTextChanged(EditableString string) {
|
||||
setState(() {
|
||||
_string = string;
|
||||
_showCursor = true;
|
||||
_restartCursorTimer();
|
||||
});
|
||||
}
|
||||
|
||||
void _handleClick(_) {
|
||||
if (_service != null)
|
||||
return;
|
||||
_service = new KeyboardServiceProxy.unbound();
|
||||
shell.requestService(_service);
|
||||
_service.ptr.show(_string.stub);
|
||||
_restartCursorTimer();
|
||||
setState(() {
|
||||
_showCursor = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _handleFocus(_) {
|
||||
print("_handleFocus");
|
||||
}
|
||||
|
||||
void _handleBlur(_) {
|
||||
print("_handleBlur");
|
||||
EditableText({Object key, this.value, this.focused})
|
||||
: super(key: key, stateful: true) {
|
||||
}
|
||||
|
||||
void _cursorTick(Timer timer) {
|
||||
|
@ -78,28 +40,39 @@ class EditableText extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
void _restartCursorTimer() {
|
||||
if (_cursorTimer != null)
|
||||
_cursorTimer.cancel();
|
||||
void _startCursorTimer() {
|
||||
_showCursor = true;
|
||||
_cursorTimer = new Timer.periodic(
|
||||
new Duration(milliseconds: 500), _cursorTick);
|
||||
}
|
||||
|
||||
void didUnmount() {
|
||||
void _stopCursorTimer() {
|
||||
_cursorTimer.cancel();
|
||||
_cursorTimer = null;
|
||||
_showCursor = false;
|
||||
}
|
||||
|
||||
void didUnmount() {
|
||||
if (_cursorTimer != null)
|
||||
_stopCursorTimer();
|
||||
}
|
||||
|
||||
Node build() {
|
||||
if (focused && _cursorTimer == null)
|
||||
_startCursorTimer();
|
||||
else if (!focused && _cursorTimer != null)
|
||||
_stopCursorTimer();
|
||||
|
||||
List<Node> children = new List<Node>();
|
||||
|
||||
if (!_string.composing.isValid) {
|
||||
children.add(new Text(_string.text));
|
||||
if (!value.composing.isValid) {
|
||||
children.add(new Text(value.text));
|
||||
} else {
|
||||
String beforeComposing = _string.textBefore(_string.composing);
|
||||
String beforeComposing = value.textBefore(value.composing);
|
||||
if (!beforeComposing.isEmpty)
|
||||
children.add(new Text(beforeComposing));
|
||||
|
||||
String composing = _string.textInside(_string.composing);
|
||||
String composing = value.textInside(value.composing);
|
||||
if (!composing.isEmpty) {
|
||||
children.add(new Container(
|
||||
key: 'composing',
|
||||
|
@ -108,7 +81,7 @@ class EditableText extends Component {
|
|||
));
|
||||
}
|
||||
|
||||
String afterComposing = _string.textAfter(_string.composing);
|
||||
String afterComposing = value.textAfter(value.composing);
|
||||
if (!afterComposing.isEmpty)
|
||||
children.add(new Text(afterComposing));
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
import '../../framework/fn.dart';
|
||||
import 'editable_text.dart';
|
||||
import 'input.dart';
|
||||
|
||||
class EditorApp extends App {
|
||||
Node build() {
|
||||
return new EditableText();
|
||||
return new Input();
|
||||
}
|
||||
}
|
||||
|
|
66
examples/editor/input.dart
Normal file
66
examples/editor/input.dart
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../framework/fn.dart';
|
||||
import '../../framework/theme/colors.dart';
|
||||
import 'editable_string.dart';
|
||||
import 'editable_text.dart';
|
||||
import 'keyboard.dart';
|
||||
|
||||
typedef void ValueChanged(value);
|
||||
|
||||
class Input extends Component {
|
||||
static final Style _style = new Style('''
|
||||
display: paragraph;
|
||||
margin: 8px;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid ${Grey[200]};
|
||||
align-self: center;
|
||||
height: 1.2em;
|
||||
white-space: pre;
|
||||
overflow: hidden;'''
|
||||
);
|
||||
|
||||
static final String _focusedInlineStyle = '''
|
||||
padding: 7px;
|
||||
border-bottom: 2px solid ${Blue[500]};''';
|
||||
|
||||
ValueChanged onChanged;
|
||||
String value;
|
||||
|
||||
bool _focused = false;
|
||||
EditableString _editableValue;
|
||||
|
||||
Input({Object key, this.value: ''}) : super(key: key, stateful: true) {
|
||||
_editableValue = new EditableString(text: value,
|
||||
onUpdated: _handleTextUpdated);
|
||||
events.listen('click', _handleClick);
|
||||
}
|
||||
|
||||
void _handleClick(_) {
|
||||
keyboard.show(_editableValue.stub);
|
||||
setState(() {
|
||||
_focused = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _handleTextUpdated() {
|
||||
setState(() {});
|
||||
if (value != _editableValue.text) {
|
||||
value = _editableValue.text;
|
||||
if (onChanged != null)
|
||||
onChanged(value);
|
||||
}
|
||||
}
|
||||
|
||||
Node build() {
|
||||
return new Container(
|
||||
style: _style,
|
||||
inlineStyle: _focused ? _focusedInlineStyle : null,
|
||||
children: [
|
||||
new EditableText(value: _editableValue, focused: _focused),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
20
examples/editor/keyboard.dart
Normal file
20
examples/editor/keyboard.dart
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../framework/shell.dart' as shell;
|
||||
import 'package:sky/services/keyboard/keyboard.mojom.dart';
|
||||
|
||||
class _KeyboardConnection {
|
||||
KeyboardServiceProxy proxy;
|
||||
|
||||
_KeyboardConnection() {
|
||||
proxy = new KeyboardServiceProxy.unbound();
|
||||
shell.requestService(proxy);
|
||||
}
|
||||
|
||||
KeyboardService get keyboard => proxy.ptr;
|
||||
}
|
||||
|
||||
final _KeyboardConnection _connection = new _KeyboardConnection();
|
||||
final KeyboardService keyboard = _connection.keyboard;
|
Loading…
Reference in a new issue