Skip to content

Commit 73d1e58

Browse files
authored
Merge pull request #50 from devmil/feature/composing_state
Adds the composing state
2 parents 420f4c9 + f346e4c commit 73d1e58

12 files changed

+169
-49
lines changed

lib/frontend/cache.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ class TextLayoutCache {
1818
return _cache[key];
1919
}
2020

21-
Paragraph performAndCacheLayout(String text, TextStyle style, int key) {
21+
Paragraph performAndCacheLayout(String text, TextStyle style, int? key) {
2222
final builder = ParagraphBuilder(style.getParagraphStyle());
2323
builder.pushStyle(style.getTextStyle());
2424
builder.addText(text);
2525

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

29-
_cache[key] = paragraph;
29+
if (key != null) {
30+
_cache[key] = paragraph;
31+
}
3032
return paragraph;
3133
}
3234

lib/frontend/input_behavior_default.dart

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import 'package:xterm/frontend/input_map.dart';
55
import 'package:xterm/xterm.dart';
66

77
class InputBehaviorDefault extends InputBehavior {
8-
const InputBehaviorDefault();
8+
InputBehaviorDefault();
99

1010
@override
1111
bool get acceptKeyStroke => true;
@@ -32,11 +32,28 @@ class InputBehaviorDefault extends InputBehavior {
3232
}
3333
}
3434

35+
String? _composingString = null;
36+
3537
@override
3638
TextEditingValue? onTextEdit(
3739
TextEditingValue value, TerminalUiInteraction terminal) {
38-
terminal.raiseOnInput(value.text);
39-
if (value == TextEditingValue.empty) {
40+
var inputText = value.text;
41+
// we just want to detect if a composing is going on and notify the terminal
42+
// about it
43+
if (value.composing.start != value.composing.end) {
44+
_composingString = inputText;
45+
terminal.updateComposingString(_composingString!);
46+
return null;
47+
}
48+
//when we reach this point the composing state is over
49+
if (_composingString != null) {
50+
_composingString = null;
51+
terminal.updateComposingString('');
52+
}
53+
54+
terminal.raiseOnInput(inputText);
55+
56+
if (value == TextEditingValue.empty || inputText == '') {
4057
return null;
4158
} else {
4259
return TextEditingValue.empty;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import 'package:xterm/frontend/input_behavior_default.dart';
22

33
class InputBehaviorDesktop extends InputBehaviorDefault {
4-
const InputBehaviorDesktop();
4+
InputBehaviorDesktop();
55
}

lib/frontend/input_behavior_mobile.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import 'package:xterm/input/keys.dart';
55
import 'package:xterm/xterm.dart';
66

77
class InputBehaviorMobile extends InputBehaviorDefault {
8-
const InputBehaviorMobile();
8+
InputBehaviorMobile();
99

1010
final acceptKeyStroke = false;
1111

lib/frontend/input_behaviors.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import 'package:xterm/frontend/input_behavior_desktop.dart';
44
import 'package:xterm/frontend/input_behavior_mobile.dart';
55

66
class InputBehaviors {
7-
static const desktop = InputBehaviorDesktop();
7+
static final desktop = InputBehaviorDesktop();
88

9-
static const mobile = InputBehaviorMobile();
9+
static final mobile = InputBehaviorMobile();
1010

1111
static InputBehavior get platform {
1212
if (Platform.I.isMobile) {

lib/frontend/input_listener.dart

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ typedef FocusHandler = void Function(bool);
1010

1111
abstract class InputListenerController {
1212
void requestKeyboard();
13+
void setCaretRect(Rect rect);
1314
}
1415

1516
class InputListener extends StatefulWidget {
@@ -123,6 +124,7 @@ class InputListenerState extends State<InputListener>
123124
);
124125
}
125126

127+
@override
126128
void requestKeyboard() {
127129
if (widget.focusNode.hasFocus) {
128130
openInputConnection();
@@ -131,6 +133,11 @@ class InputListenerState extends State<InputListener>
131133
}
132134
}
133135

136+
@override
137+
void setCaretRect(Rect rect) {
138+
_conn?.setCaretRect(rect);
139+
}
140+
134141
void onFocusChange() {
135142
if (widget.onFocus != null) {
136143
widget.onFocus?.call(widget.focusNode.hasFocus);
@@ -184,6 +191,8 @@ class InputListenerState extends State<InputListener>
184191

185192
if (newValue != null) {
186193
_conn?.setEditingState(newValue);
194+
} else {
195+
_conn?.setEditingState(TextEditingValue.empty);
187196
}
188197
}
189198

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

214-
onInput(value);
215-
216-
// if (_savedValue == null || _savedValue.text == '') {
217-
// onInput(value.text);
218-
// } else if (_savedValue.text.length < value.text.length) {
219-
// final diff = value.text.substring(_savedValue.text.length);
220-
// onInput(diff);
221-
// }
223+
if (value.text != '') {
224+
onInput(value);
225+
}
222226

223227
_savedValue = value;
224228
// print('updateEditingValue $value');

lib/frontend/oscillator.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ class Oscillator with Observable {
3636
return _value;
3737
}
3838

39+
void restart() {
40+
stop();
41+
start();
42+
}
43+
3944
void start() {
45+
_value = true;
4046
_shouldRun = true;
4147
// only start right away when anyone is listening.
4248
// the moment a listener gets registered the Oscillator will start

lib/frontend/terminal_painters.dart

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -177,35 +177,13 @@ class TerminalPainter extends CustomPainter {
177177
color = color.withOpacity(0.5);
178178
}
179179

180-
final styleToUse = (style.textStyleProvider != null)
181-
? style.textStyleProvider!(
182-
color: color,
183-
fontSize: style.fontSize,
184-
fontWeight: flags.hasFlag(CellFlags.bold)
185-
? FontWeight.bold
186-
: FontWeight.normal,
187-
fontStyle: flags.hasFlag(CellFlags.italic)
188-
? FontStyle.italic
189-
: FontStyle.normal,
190-
decoration: flags.hasFlag(CellFlags.underline)
191-
? TextDecoration.underline
192-
: TextDecoration.none,
193-
)
194-
: TextStyle(
195-
color: color,
196-
fontSize: style.fontSize,
197-
fontWeight: flags.hasFlag(CellFlags.bold)
198-
? FontWeight.bold
199-
: FontWeight.normal,
200-
fontStyle: flags.hasFlag(CellFlags.italic)
201-
? FontStyle.italic
202-
: FontStyle.normal,
203-
decoration: flags.hasFlag(CellFlags.underline)
204-
? TextDecoration.underline
205-
: TextDecoration.none,
206-
fontFamily: 'monospace',
207-
fontFamilyFallback: style.fontFamily,
208-
);
180+
final styleToUse = PaintHelper.getStyleToUse(
181+
style,
182+
color,
183+
bold: flags.hasFlag(CellFlags.bold),
184+
italic: flags.hasFlag(CellFlags.italic),
185+
underline: flags.hasFlag(CellFlags.underline),
186+
);
209187

210188
character = textLayoutCache.performAndCacheLayout(
211189
String.fromCharCode(codePoint), styleToUse, cellHash);
@@ -226,18 +204,27 @@ class CursorPainter extends CustomPainter {
226204
final bool focused;
227205
final bool blinkVisible;
228206
final int cursorColor;
207+
final int textColor;
208+
final String composingString;
209+
final TextLayoutCache textLayoutCache;
210+
final TerminalStyle style;
229211

230212
CursorPainter({
231213
required this.visible,
232214
required this.charSize,
233215
required this.focused,
234216
required this.blinkVisible,
235217
required this.cursorColor,
218+
required this.textColor,
219+
required this.composingString,
220+
required this.textLayoutCache,
221+
required this.style,
236222
});
237223

238224
@override
239225
void paint(Canvas canvas, Size size) {
240-
if (blinkVisible && visible) {
226+
bool isVisible = visible && (blinkVisible || composingString != '');
227+
if (isVisible) {
241228
_paintCursor(canvas);
242229
}
243230
}
@@ -249,7 +236,8 @@ class CursorPainter extends CustomPainter {
249236
focused != oldDelegate.focused ||
250237
visible != oldDelegate.visible ||
251238
charSize.cellWidth != oldDelegate.charSize.cellWidth ||
252-
charSize.cellHeight != oldDelegate.charSize.cellHeight;
239+
charSize.cellHeight != oldDelegate.charSize.cellHeight ||
240+
composingString != oldDelegate.composingString;
253241
}
254242
return true;
255243
}
@@ -262,5 +250,42 @@ class CursorPainter extends CustomPainter {
262250

263251
canvas.drawRect(
264252
Rect.fromLTWH(0, 0, charSize.cellWidth, charSize.cellHeight), paint);
253+
254+
if (composingString != '') {
255+
final styleToUse = PaintHelper.getStyleToUse(style, Color(textColor));
256+
final character = textLayoutCache.performAndCacheLayout(
257+
composingString, styleToUse, null);
258+
canvas.drawParagraph(character, Offset(0, 0));
259+
}
260+
}
261+
}
262+
263+
class PaintHelper {
264+
static TextStyle getStyleToUse(
265+
TerminalStyle style,
266+
Color color, {
267+
bool bold = false,
268+
bool italic = false,
269+
bool underline = false,
270+
}) {
271+
return (style.textStyleProvider != null)
272+
? style.textStyleProvider!(
273+
color: color,
274+
fontSize: style.fontSize,
275+
fontWeight: bold ? FontWeight.bold : FontWeight.normal,
276+
fontStyle: italic ? FontStyle.italic : FontStyle.normal,
277+
decoration:
278+
underline ? TextDecoration.underline : TextDecoration.none,
279+
)
280+
: TextStyle(
281+
color: color,
282+
fontSize: style.fontSize,
283+
fontWeight: bold ? FontWeight.bold : FontWeight.normal,
284+
fontStyle: italic ? FontStyle.italic : FontStyle.normal,
285+
decoration:
286+
underline ? TextDecoration.underline : TextDecoration.none,
287+
fontFamily: 'monospace',
288+
fontFamilyFallback: style.fontFamily,
289+
);
265290
}
266291
}

lib/frontend/terminal_view.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ class _TerminalViewState extends State<TerminalView> {
154154
super.dispose();
155155
}
156156

157+
GlobalKey _keyCursor = GlobalKey();
158+
157159
@override
158160
Widget build(BuildContext context) {
159161
return InputListener(
@@ -171,6 +173,22 @@ class _TerminalViewState extends State<TerminalView> {
171173
onWidgetSize(constraints.maxWidth - widget.padding * 2,
172174
constraints.maxHeight - widget.padding * 2);
173175

176+
if (_keyCursor.currentContext != null) {
177+
/// this gets set so that the accent selection menu on MacOS pops up
178+
/// at the right spot
179+
final RenderBox cursorRenderObj =
180+
_keyCursor.currentContext!.findRenderObject() as RenderBox;
181+
final offset = cursorRenderObj.localToGlobal(Offset.zero);
182+
InputListener.of(context)!.setCaretRect(
183+
Rect.fromLTWH(
184+
offset.dx,
185+
offset.dy,
186+
_cellSize.cellWidth,
187+
_cellSize.cellHeight,
188+
),
189+
);
190+
}
191+
174192
// use flutter's Scrollable to manage scrolling to better integrate
175193
// with widgets such as Scrollbar.
176194
return NotificationListener<ScrollNotification>(
@@ -282,11 +300,14 @@ class _TerminalViewState extends State<TerminalView> {
282300
),
283301
),
284302
Positioned(
303+
key: _keyCursor,
285304
child: CursorView(
286305
terminal: widget.terminal,
287306
cellSize: _cellSize,
288307
focusNode: widget.focusNode,
289308
blinkOscillator: blinkOscillator,
309+
style: widget.style,
310+
textLayoutCache: textLayoutCache,
290311
),
291312
width: _cellSize.cellWidth,
292313
height: _cellSize.cellHeight,
@@ -367,6 +388,7 @@ class _TerminalViewState extends State<TerminalView> {
367388
}
368389

369390
void onKeyStroke(RawKeyEvent event) {
391+
blinkOscillator.restart();
370392
// TODO: find a way to stop scrolling immediately after key stroke.
371393
widget.inputBehavior.onKeyStroke(event, widget.terminal);
372394
widget.terminal.setScrollOffsetFromBottom(0);
@@ -395,11 +417,16 @@ class CursorView extends StatefulWidget {
395417
final TerminalUiInteraction terminal;
396418
final FocusNode? focusNode;
397419
final Oscillator blinkOscillator;
420+
final TerminalStyle style;
421+
final TextLayoutCache textLayoutCache;
422+
398423
CursorView({
399424
required this.terminal,
400425
required this.cellSize,
401426
required this.focusNode,
402427
required this.blinkOscillator,
428+
required this.style,
429+
required this.textLayoutCache,
403430
});
404431

405432
@override
@@ -432,6 +459,10 @@ class _CursorViewState extends State<CursorView> {
432459
charSize: widget.cellSize,
433460
blinkVisible: widget.blinkOscillator.value,
434461
cursorColor: widget.terminal.cursorColor,
462+
textColor: widget.terminal.backgroundColor,
463+
style: widget.style,
464+
composingString: widget.terminal.composingString,
465+
textLayoutCache: widget.textLayoutCache,
435466
),
436467
);
437468
}

lib/terminal/terminal.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,4 +722,15 @@ class Terminal with Observable implements TerminalUiInteraction {
722722
_selection.init(Position(0, 0));
723723
_selection.update(Position(terminalWidth, bufferHeight));
724724
}
725+
726+
String _composingString = '';
727+
728+
@override
729+
String get composingString => _composingString;
730+
731+
@override
732+
void updateComposingString(String value) {
733+
_composingString = value;
734+
refresh();
735+
}
725736
}

0 commit comments

Comments
 (0)