Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds the composing state #50

Merged
merged 1 commit into from
Aug 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions lib/frontend/cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ class TextLayoutCache {
return _cache[key];
}

Paragraph performAndCacheLayout(String text, TextStyle style, int key) {
Paragraph performAndCacheLayout(String text, TextStyle style, int? key) {
final builder = ParagraphBuilder(style.getParagraphStyle());
builder.pushStyle(style.getTextStyle());
builder.addText(text);

final paragraph = builder.build();
paragraph.layout(ParagraphConstraints(width: double.infinity));

_cache[key] = paragraph;
if (key != null) {
_cache[key] = paragraph;
}
return paragraph;
}

Expand Down
23 changes: 20 additions & 3 deletions lib/frontend/input_behavior_default.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:xterm/frontend/input_map.dart';
import 'package:xterm/xterm.dart';

class InputBehaviorDefault extends InputBehavior {
const InputBehaviorDefault();
InputBehaviorDefault();

@override
bool get acceptKeyStroke => true;
Expand All @@ -32,11 +32,28 @@ class InputBehaviorDefault extends InputBehavior {
}
}

String? _composingString = null;

@override
TextEditingValue? onTextEdit(
TextEditingValue value, TerminalUiInteraction terminal) {
terminal.raiseOnInput(value.text);
if (value == TextEditingValue.empty) {
var inputText = value.text;
// we just want to detect if a composing is going on and notify the terminal
// about it
if (value.composing.start != value.composing.end) {
_composingString = inputText;
terminal.updateComposingString(_composingString!);
return null;
}
//when we reach this point the composing state is over
if (_composingString != null) {
_composingString = null;
terminal.updateComposingString('');
}

terminal.raiseOnInput(inputText);

if (value == TextEditingValue.empty || inputText == '') {
return null;
} else {
return TextEditingValue.empty;
Expand Down
2 changes: 1 addition & 1 deletion lib/frontend/input_behavior_desktop.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:xterm/frontend/input_behavior_default.dart';

class InputBehaviorDesktop extends InputBehaviorDefault {
const InputBehaviorDesktop();
InputBehaviorDesktop();
}
2 changes: 1 addition & 1 deletion lib/frontend/input_behavior_mobile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:xterm/input/keys.dart';
import 'package:xterm/xterm.dart';

class InputBehaviorMobile extends InputBehaviorDefault {
const InputBehaviorMobile();
InputBehaviorMobile();

final acceptKeyStroke = false;

Expand Down
4 changes: 2 additions & 2 deletions lib/frontend/input_behaviors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import 'package:xterm/frontend/input_behavior_desktop.dart';
import 'package:xterm/frontend/input_behavior_mobile.dart';

class InputBehaviors {
static const desktop = InputBehaviorDesktop();
static final desktop = InputBehaviorDesktop();

static const mobile = InputBehaviorMobile();
static final mobile = InputBehaviorMobile();

static InputBehavior get platform {
if (Platform.I.isMobile) {
Expand Down
20 changes: 12 additions & 8 deletions lib/frontend/input_listener.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ typedef FocusHandler = void Function(bool);

abstract class InputListenerController {
void requestKeyboard();
void setCaretRect(Rect rect);
}

class InputListener extends StatefulWidget {
Expand Down Expand Up @@ -123,6 +124,7 @@ class InputListenerState extends State<InputListener>
);
}

@override
void requestKeyboard() {
if (widget.focusNode.hasFocus) {
openInputConnection();
Expand All @@ -131,6 +133,11 @@ class InputListenerState extends State<InputListener>
}
}

@override
void setCaretRect(Rect rect) {
_conn?.setCaretRect(rect);
}

void onFocusChange() {
if (widget.onFocus != null) {
widget.onFocus?.call(widget.focusNode.hasFocus);
Expand Down Expand Up @@ -184,6 +191,8 @@ class InputListenerState extends State<InputListener>

if (newValue != null) {
_conn?.setEditingState(newValue);
} else {
_conn?.setEditingState(TextEditingValue.empty);
}
}

Expand Down Expand Up @@ -211,14 +220,9 @@ class TerminalTextInputClient extends TextInputClient {
void updateEditingValue(TextEditingValue value) {
// print('updateEditingValue $value');

onInput(value);

// if (_savedValue == null || _savedValue.text == '') {
// onInput(value.text);
// } else if (_savedValue.text.length < value.text.length) {
// final diff = value.text.substring(_savedValue.text.length);
// onInput(diff);
// }
if (value.text != '') {
onInput(value);
}

_savedValue = value;
// print('updateEditingValue $value');
Expand Down
6 changes: 6 additions & 0 deletions lib/frontend/oscillator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ class Oscillator with Observable {
return _value;
}

void restart() {
stop();
start();
}

void start() {
_value = true;
_shouldRun = true;
// only start right away when anyone is listening.
// the moment a listener gets registered the Oscillator will start
Expand Down
87 changes: 56 additions & 31 deletions lib/frontend/terminal_painters.dart
Original file line number Diff line number Diff line change
Expand Up @@ -177,35 +177,13 @@ class TerminalPainter extends CustomPainter {
color = color.withOpacity(0.5);
}

final styleToUse = (style.textStyleProvider != null)
? style.textStyleProvider!(
color: color,
fontSize: style.fontSize,
fontWeight: flags.hasFlag(CellFlags.bold)
? FontWeight.bold
: FontWeight.normal,
fontStyle: flags.hasFlag(CellFlags.italic)
? FontStyle.italic
: FontStyle.normal,
decoration: flags.hasFlag(CellFlags.underline)
? TextDecoration.underline
: TextDecoration.none,
)
: TextStyle(
color: color,
fontSize: style.fontSize,
fontWeight: flags.hasFlag(CellFlags.bold)
? FontWeight.bold
: FontWeight.normal,
fontStyle: flags.hasFlag(CellFlags.italic)
? FontStyle.italic
: FontStyle.normal,
decoration: flags.hasFlag(CellFlags.underline)
? TextDecoration.underline
: TextDecoration.none,
fontFamily: 'monospace',
fontFamilyFallback: style.fontFamily,
);
final styleToUse = PaintHelper.getStyleToUse(
style,
color,
bold: flags.hasFlag(CellFlags.bold),
italic: flags.hasFlag(CellFlags.italic),
underline: flags.hasFlag(CellFlags.underline),
);

character = textLayoutCache.performAndCacheLayout(
String.fromCharCode(codePoint), styleToUse, cellHash);
Expand All @@ -226,18 +204,27 @@ class CursorPainter extends CustomPainter {
final bool focused;
final bool blinkVisible;
final int cursorColor;
final int textColor;
final String composingString;
final TextLayoutCache textLayoutCache;
final TerminalStyle style;

CursorPainter({
required this.visible,
required this.charSize,
required this.focused,
required this.blinkVisible,
required this.cursorColor,
required this.textColor,
required this.composingString,
required this.textLayoutCache,
required this.style,
});

@override
void paint(Canvas canvas, Size size) {
if (blinkVisible && visible) {
bool isVisible = visible && (blinkVisible || composingString != '');
if (isVisible) {
_paintCursor(canvas);
}
}
Expand All @@ -249,7 +236,8 @@ class CursorPainter extends CustomPainter {
focused != oldDelegate.focused ||
visible != oldDelegate.visible ||
charSize.cellWidth != oldDelegate.charSize.cellWidth ||
charSize.cellHeight != oldDelegate.charSize.cellHeight;
charSize.cellHeight != oldDelegate.charSize.cellHeight ||
composingString != oldDelegate.composingString;
}
return true;
}
Expand All @@ -262,5 +250,42 @@ class CursorPainter extends CustomPainter {

canvas.drawRect(
Rect.fromLTWH(0, 0, charSize.cellWidth, charSize.cellHeight), paint);

if (composingString != '') {
final styleToUse = PaintHelper.getStyleToUse(style, Color(textColor));
final character = textLayoutCache.performAndCacheLayout(
composingString, styleToUse, null);
canvas.drawParagraph(character, Offset(0, 0));
}
}
}

class PaintHelper {
static TextStyle getStyleToUse(
TerminalStyle style,
Color color, {
bool bold = false,
bool italic = false,
bool underline = false,
}) {
return (style.textStyleProvider != null)
? style.textStyleProvider!(
color: color,
fontSize: style.fontSize,
fontWeight: bold ? FontWeight.bold : FontWeight.normal,
fontStyle: italic ? FontStyle.italic : FontStyle.normal,
decoration:
underline ? TextDecoration.underline : TextDecoration.none,
)
: TextStyle(
color: color,
fontSize: style.fontSize,
fontWeight: bold ? FontWeight.bold : FontWeight.normal,
fontStyle: italic ? FontStyle.italic : FontStyle.normal,
decoration:
underline ? TextDecoration.underline : TextDecoration.none,
fontFamily: 'monospace',
fontFamilyFallback: style.fontFamily,
);
}
}
31 changes: 31 additions & 0 deletions lib/frontend/terminal_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ class _TerminalViewState extends State<TerminalView> {
super.dispose();
}

GlobalKey _keyCursor = GlobalKey();

@override
Widget build(BuildContext context) {
return InputListener(
Expand All @@ -171,6 +173,22 @@ class _TerminalViewState extends State<TerminalView> {
onWidgetSize(constraints.maxWidth - widget.padding * 2,
constraints.maxHeight - widget.padding * 2);

if (_keyCursor.currentContext != null) {
/// this gets set so that the accent selection menu on MacOS pops up
/// at the right spot
final RenderBox cursorRenderObj =
_keyCursor.currentContext!.findRenderObject() as RenderBox;
final offset = cursorRenderObj.localToGlobal(Offset.zero);
InputListener.of(context)!.setCaretRect(
Rect.fromLTWH(
offset.dx,
offset.dy,
_cellSize.cellWidth,
_cellSize.cellHeight,
),
);
}

// use flutter's Scrollable to manage scrolling to better integrate
// with widgets such as Scrollbar.
return NotificationListener<ScrollNotification>(
Expand Down Expand Up @@ -282,11 +300,14 @@ class _TerminalViewState extends State<TerminalView> {
),
),
Positioned(
key: _keyCursor,
child: CursorView(
terminal: widget.terminal,
cellSize: _cellSize,
focusNode: widget.focusNode,
blinkOscillator: blinkOscillator,
style: widget.style,
textLayoutCache: textLayoutCache,
),
width: _cellSize.cellWidth,
height: _cellSize.cellHeight,
Expand Down Expand Up @@ -367,6 +388,7 @@ class _TerminalViewState extends State<TerminalView> {
}

void onKeyStroke(RawKeyEvent event) {
blinkOscillator.restart();
// TODO: find a way to stop scrolling immediately after key stroke.
widget.inputBehavior.onKeyStroke(event, widget.terminal);
widget.terminal.setScrollOffsetFromBottom(0);
Expand Down Expand Up @@ -395,11 +417,16 @@ class CursorView extends StatefulWidget {
final TerminalUiInteraction terminal;
final FocusNode? focusNode;
final Oscillator blinkOscillator;
final TerminalStyle style;
final TextLayoutCache textLayoutCache;

CursorView({
required this.terminal,
required this.cellSize,
required this.focusNode,
required this.blinkOscillator,
required this.style,
required this.textLayoutCache,
});

@override
Expand Down Expand Up @@ -432,6 +459,10 @@ class _CursorViewState extends State<CursorView> {
charSize: widget.cellSize,
blinkVisible: widget.blinkOscillator.value,
cursorColor: widget.terminal.cursorColor,
textColor: widget.terminal.backgroundColor,
style: widget.style,
composingString: widget.terminal.composingString,
textLayoutCache: widget.textLayoutCache,
),
);
}
Expand Down
11 changes: 11 additions & 0 deletions lib/terminal/terminal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -722,4 +722,15 @@ class Terminal with Observable implements TerminalUiInteraction {
_selection.init(Position(0, 0));
_selection.update(Position(terminalWidth, bufferHeight));
}

String _composingString = '';

@override
String get composingString => _composingString;

@override
void updateComposingString(String value) {
_composingString = value;
refresh();
}
}
Loading