Sfoglia il codice sorgente

➕support input behavior customization

xuty 5 anni fa
parent
commit
19198d2f5c

+ 14 - 5
lib/buffer/buffer.dart

@@ -128,7 +128,7 @@ class Buffer {
     final result = <BufferLine>[];
 
     for (var i = height - terminal.viewHeight; i < height; i++) {
-      final y = i - scrollOffset;
+      final y = i - scrollOffsetFromBottom;
       if (y >= 0 && y < height) {
         result.add(lines[y]);
       }
@@ -310,23 +310,32 @@ class Buffer {
     setPosition(cursorX, cursorY);
   }
 
-  int get scrollOffset {
+  int get scrollOffsetFromBottom {
     return _scrollLinesFromBottom;
   }
 
-  void setScrollOffset(int offset) {
+  int get scrollOffsetFromTop {
+    return terminal.invisibleHeight - scrollOffsetFromBottom;
+  }
+
+  void setScrollOffsetFromBottom(int offset) {
     if (height < terminal.viewHeight) return;
     final maxOffset = height - terminal.viewHeight;
     _scrollLinesFromBottom = offset.clamp(0, maxOffset);
     terminal.refresh();
   }
 
+  void setScrollOffsetFromTop(int offset) {
+    final bottomOffset = terminal.invisibleHeight - offset;
+    setScrollOffsetFromBottom(bottomOffset);
+  }
+
   void screenScrollUp(int lines) {
-    setScrollOffset(scrollOffset + lines);
+    setScrollOffsetFromBottom(scrollOffsetFromBottom + lines);
   }
 
   void screenScrollDown(int lines) {
-    setScrollOffset(scrollOffset - lines);
+    setScrollOffsetFromBottom(scrollOffsetFromBottom - lines);
   }
 
   void saveCursor() {

+ 15 - 0
lib/frontend/input_behavior.dart

@@ -0,0 +1,15 @@
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:xterm/xterm.dart';
+
+abstract class InputBehavior {
+  const InputBehavior();
+
+  bool get acceptKeyStroke;
+
+  void onKeyStroke(RawKeyEvent event, Terminal terminal);
+
+  TextEditingValue onTextEdit(TextEditingValue value, Terminal terminal);
+
+  void onAction(TextInputAction action, Terminal terminal);
+}

+ 42 - 0
lib/frontend/input_behavior_default.dart

@@ -0,0 +1,42 @@
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:xterm/frontend/input_behavior.dart';
+import 'package:xterm/frontend/input_map.dart';
+import 'package:xterm/xterm.dart';
+
+class InputBehaviorDefault extends InputBehavior {
+  const InputBehaviorDefault();
+
+  @override
+  bool get acceptKeyStroke => true;
+
+  @override
+  void onKeyStroke(RawKeyEvent event, Terminal terminal) {
+    if (event is! RawKeyDownEvent) {
+      return;
+    }
+
+    final key = inputMap(event.logicalKey);
+
+    if (key != null) {
+      terminal.keyInput(
+        key,
+        ctrl: event.isControlPressed,
+        alt: event.isAltPressed,
+        shift: event.isShiftPressed,
+      );
+    }
+  }
+
+  @override
+  TextEditingValue onTextEdit(TextEditingValue value, Terminal terminal) {
+    terminal.onInput(value.text);
+    print(value);
+    return TextEditingValue.empty;
+  }
+
+  @override
+  void onAction(TextInputAction action, Terminal terminal) {
+    //
+  }
+}

+ 5 - 0
lib/frontend/input_behavior_desktop.dart

@@ -0,0 +1,5 @@
+import 'package:xterm/frontend/input_behavior_default.dart';
+
+class InputBehaviorDesktop extends InputBehaviorDefault {
+  const InputBehaviorDesktop();
+}

+ 45 - 0
lib/frontend/input_behavior_mobile.dart

@@ -0,0 +1,45 @@
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:xterm/frontend/input_behavior_default.dart';
+import 'package:xterm/input/keys.dart';
+import 'package:xterm/xterm.dart';
+
+class InputBehaviorMobile extends InputBehaviorDefault {
+  const InputBehaviorMobile();
+
+  static const _placeholder = '  ';
+
+  bool get acceptKeyStroke => false;
+
+  TextEditingValue onTextEdit(TextEditingValue value, Terminal terminal) {
+    if (value.text.length > _placeholder.length) {
+      terminal.onInput(value.text.substring(_placeholder.length));
+    } else if (value.text.length < _placeholder.length) {
+      terminal.keyInput(TerminalKey.backspace);
+    } else {
+      if (value.selection.baseOffset < 1) {
+        terminal.keyInput(TerminalKey.arrowLeft);
+      } else if (value.selection.baseOffset > 1) {
+        terminal.keyInput(TerminalKey.arrowRight);
+      }
+    }
+
+    print(value);
+    // return TextEditingValue(text: '  ', composing: TextRange());
+    return TextEditingValue(
+      text: _placeholder,
+      selection: TextSelection.collapsed(offset: 2),
+    );
+  }
+
+  void onAction(TextInputAction action, Terminal terminal) {
+    print('action $action');
+    switch (action) {
+      case TextInputAction.done:
+        terminal.keyInput(TerminalKey.enter);
+        break;
+      default:
+        print('unknown action $action');
+    }
+  }
+}

+ 19 - 0
lib/frontend/input_behaviors.dart

@@ -0,0 +1,19 @@
+import 'dart:io';
+
+import 'package:xterm/frontend/input_behavior.dart';
+import 'package:xterm/frontend/input_behavior_desktop.dart';
+import 'package:xterm/frontend/input_behavior_mobile.dart';
+
+class InputBehaviors {
+  static const desktop = InputBehaviorDesktop();
+
+  static const mobile = InputBehaviorMobile();
+
+  static InputBehavior get platform {
+    if (Platform.isAndroid || Platform.isIOS) {
+      return mobile;
+    }
+
+    return desktop;
+  }
+}

+ 28 - 11
lib/frontend/input_listener.dart

@@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
 import 'package:meta/meta.dart';
 
 typedef KeyStrokeHandler = void Function(RawKeyEvent);
-typedef InputHandler = void Function(String);
+typedef InputHandler = TextEditingValue Function(TextEditingValue);
 typedef ActionHandler = void Function(TextInputAction);
 typedef FocusHandler = void Function(bool);
 
@@ -15,18 +15,22 @@ class InputListener extends StatefulWidget {
   InputListener({
     @required this.child,
     @required this.onKeyStroke,
-    @required this.onInput,
+    @required this.onTextInput,
+    @required this.onAction,
     @required this.focusNode,
     this.onFocus,
     this.autofocus = false,
+    this.listenKeyStroke = true,
   });
 
   final Widget child;
-  final InputHandler onInput;
+  final InputHandler onTextInput;
   final KeyStrokeHandler onKeyStroke;
+  final ActionHandler onAction;
   final FocusHandler onFocus;
   final bool autofocus;
   final FocusNode focusNode;
+  final bool listenKeyStroke;
 
   @override
   InputListenerState createState() => InputListenerState();
@@ -60,10 +64,19 @@ class InputListenerState extends State<InputListener>
 
   @override
   Widget build(BuildContext context) {
-    return RawKeyboardListener(
+    if (widget.listenKeyStroke) {
+      return RawKeyboardListener(
+        focusNode: widget.focusNode,
+        onKey: widget.onKeyStroke,
+        autofocus: widget.autofocus,
+        child: widget.child,
+      );
+    }
+
+    return Focus(
       focusNode: widget.focusNode,
-      onKey: widget.onKeyStroke,
       autofocus: widget.autofocus,
+      includeSemantics: false,
       child: widget.child,
     );
   }
@@ -124,20 +137,23 @@ class InputListenerState extends State<InputListener>
     }
   }
 
-  void onInput(String text) {
-    widget.onInput(text);
-    conn?.setEditingState(TextEditingValue.empty);
+  void onInput(TextEditingValue value) {
+    final newValue = widget.onTextInput(value);
+
+    if (newValue != null) {
+      conn?.setEditingState(newValue);
+    }
   }
 
   void onAction(TextInputAction action) {
-    //
+    widget?.onAction(action);
   }
 }
 
 class TerminalTextInputClient extends TextInputClient {
   TerminalTextInputClient(this.onInput, this.onAction);
 
-  final InputHandler onInput;
+  final void Function(TextEditingValue) onInput;
   final ActionHandler onAction;
 
   TextEditingValue _savedValue;
@@ -153,7 +169,7 @@ class TerminalTextInputClient extends TextInputClient {
   void updateEditingValue(TextEditingValue value) {
     print('updateEditingValue $value');
 
-    onInput(value.text);
+    onInput(value);
 
     // if (_savedValue == null || _savedValue.text == '') {
     //   onInput(value.text);
@@ -168,6 +184,7 @@ class TerminalTextInputClient extends TextInputClient {
 
   void performAction(TextInputAction action) {
     // print('performAction $action');
+    onAction(action);
   }
 
   void updateFloatingCursor(RawFloatingCursorPoint point) {

+ 39 - 23
lib/frontend/terminal_view.dart

@@ -10,8 +10,9 @@ import 'package:meta/meta.dart';
 import 'package:xterm/buffer/cell.dart';
 import 'package:xterm/frontend/char_size.dart';
 import 'package:xterm/frontend/helpers.dart';
+import 'package:xterm/frontend/input_behavior.dart';
+import 'package:xterm/frontend/input_behaviors.dart';
 import 'package:xterm/frontend/input_listener.dart';
-import 'package:xterm/frontend/input_map.dart';
 import 'package:xterm/frontend/oscillator.dart';
 import 'package:xterm/frontend/cache.dart';
 import 'package:xterm/mouse/position.dart';
@@ -54,9 +55,11 @@ class TerminalView extends StatefulWidget {
     this.fontHeightScaleFactor = 1.1,
     FocusNode focusNode,
     ScrollController scrollController,
+    InputBehavior inputBehavior,
   })  : assert(terminal != null),
         focusNode = focusNode ?? FocusNode(),
         scrollController = scrollController ?? ScrollController(),
+        inputBehavior = inputBehavior ?? InputBehaviors.platform,
         super(key: key ?? ValueKey(terminal));
 
   final Terminal terminal;
@@ -69,6 +72,8 @@ class TerminalView extends StatefulWidget {
   final double fontHeightScaleFactor;
   final List<String> fontFamily;
 
+  final InputBehavior inputBehavior;
+
   CellSize measureCellSize() {
     final testString = 'xxxxxxxxxx' * 1000;
 
@@ -118,6 +123,15 @@ class _TerminalViewState extends State<TerminalView> {
   var _maxScrollExtent = 0.0;
 
   void onTerminalChange() {
+    // if (_offset != null) {
+    //   final currentScrollExtent =
+    //       _cellSize.cellHeight * widget.terminal.buffer.scrollOffsetFromTop;
+
+    //   if (_offset.pixels != currentScrollExtent) {
+    //     _offset.correctBy(currentScrollExtent - _offset.pixels - 1);
+    //   }
+    // }
+
     if (mounted) {
       setState(() {});
     }
@@ -155,8 +169,10 @@ class _TerminalViewState extends State<TerminalView> {
   @override
   Widget build(BuildContext context) {
     return InputListener(
+      listenKeyStroke: widget.inputBehavior.acceptKeyStroke,
       onKeyStroke: onKeyStroke,
-      onInput: onInput,
+      onTextInput: onInput,
+      onAction: onAction,
       onFocus: onFocus,
       focusNode: widget.focusNode,
       autofocus: true,
@@ -169,11 +185,17 @@ class _TerminalViewState extends State<TerminalView> {
               offset.applyViewportDimension(constraints.maxHeight);
 
               _minScrollExtent = 0.0;
+
               _maxScrollExtent = math.max(
                   0.0,
                   _cellSize.cellHeight * widget.terminal.buffer.height -
                       constraints.maxHeight);
 
+              // final currentScrollExtent = _cellSize.cellHeight *
+              //     widget.terminal.buffer.scrollOffsetFromTop;
+
+              // offset.correctBy(currentScrollExtent - offset.pixels - 1);
+
               offset.applyContentDimensions(_minScrollExtent, _maxScrollExtent);
 
               _offset = offset;
@@ -192,7 +214,11 @@ class _TerminalViewState extends State<TerminalView> {
       behavior: HitTestBehavior.deferToChild,
       dragStartBehavior: DragStartBehavior.down,
       onTapDown: (detail) {
-        InputListener.of(context).requestKeyboard();
+        if (widget.terminal.selection.isEmpty) {
+          InputListener.of(context).requestKeyboard();
+        } else {
+          widget.terminal.selection.clear();
+        }
         final pos = detail.localPosition;
         final offset = getMouseOffset(pos.dx, pos.dy);
         widget.terminal.mouseMode.onTap(widget.terminal, offset);
@@ -232,7 +258,7 @@ class _TerminalViewState extends State<TerminalView> {
 
     final x = col;
     final y = widget.terminal.buffer.convertViewLineToRawLine(row) -
-        widget.terminal.buffer.scrollOffset;
+        widget.terminal.buffer.scrollOffsetFromBottom;
 
     return Position(x, y);
   }
@@ -261,26 +287,12 @@ class _TerminalViewState extends State<TerminalView> {
     }
   }
 
-  void onInput(String input) {
-    widget.terminal.onInput(input);
+  TextEditingValue onInput(TextEditingValue value) {
+    return widget.inputBehavior.onTextEdit(value, widget.terminal);
   }
 
   void onKeyStroke(RawKeyEvent event) {
-    if (event is! RawKeyDownEvent) {
-      return;
-    }
-
-    final key = inputMap(event.logicalKey);
-    widget.terminal.debug.onMsg(key);
-    if (key != null) {
-      widget.terminal.input(
-        key,
-        ctrl: event.isControlPressed,
-        alt: event.isAltPressed,
-        shift: event.isShiftPressed,
-      );
-    }
-
+    widget.inputBehavior.onKeyStroke(event, widget.terminal);
     _offset.moveTo(_maxScrollExtent);
   }
 
@@ -290,10 +302,14 @@ class _TerminalViewState extends State<TerminalView> {
     });
   }
 
+  void onAction(TextInputAction action) {
+    widget.inputBehavior.onAction(action, widget.terminal);
+  }
+
   void onScroll() {
     final charOffset = (_offset.pixels / _cellSize.cellHeight).ceil();
     final offset = widget.terminal.invisibleHeight - charOffset;
-    widget.terminal.buffer.setScrollOffset(offset);
+    widget.terminal.buffer.setScrollOffsetFromBottom(offset);
   }
 }
 
@@ -369,7 +385,7 @@ class TerminalPainter extends CustomPainter {
     for (var y = 0; y < terminal.viewHeight; y++) {
       final offsetY = y * charSize.cellHeight;
       final absoluteY = terminal.buffer.convertViewLineToRawLine(y) -
-          terminal.buffer.scrollOffset;
+          terminal.buffer.scrollOffsetFromBottom;
 
       for (var x = 0; x < terminal.viewWidth; x++) {
         var cellCount = 0;

+ 0 - 1
lib/mouse/mouse_mode.dart

@@ -20,7 +20,6 @@ class MouseModeNone extends MouseMode {
   @override
   void onTap(Terminal terminal, Position offset) {
     terminal.debug.onMsg('tap: $offset');
-    terminal.selection.clear();
   }
 
   @override

+ 7 - 5
lib/terminal/terminal.dart

@@ -131,7 +131,7 @@ class Terminal with Observable {
 
   int get cursorX => buffer.cursorX;
   int get cursorY => buffer.cursorY;
-  int get scrollOffset => buffer.scrollOffset;
+  int get scrollOffset => buffer.scrollOffsetFromBottom;
 
   void write(String text) async {
     for (var char in text.runes) {
@@ -269,13 +269,15 @@ class Terminal with Observable {
     }
   }
 
-  void input(
+  void keyInput(
     TerminalKey key, {
-    bool ctrl,
-    bool alt,
-    bool shift,
+    bool ctrl = false,
+    bool alt = false,
+    bool shift = false,
     // bool meta,
   }) {
+    debug.onMsg(key);
+
     if (onInput == null) {
       return;
     }