| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- import 'package:flutter/cupertino.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/rendering.dart';
- import 'package:flutter/services.dart';
- import 'package:xterm/src/core/buffer/cell_offset.dart';
- import 'package:xterm/src/core/input/keys.dart';
- import 'package:xterm/src/terminal.dart';
- import 'package:xterm/src/ui/controller.dart';
- import 'package:xterm/src/ui/cursor_type.dart';
- import 'package:xterm/src/ui/custom_text_edit.dart';
- import 'package:xterm/src/ui/gesture/gesture_handler.dart';
- import 'package:xterm/src/ui/input_map.dart';
- import 'package:xterm/src/ui/keyboard_visibility.dart';
- import 'package:xterm/src/ui/render.dart';
- import 'package:xterm/src/ui/shortcut/actions.dart';
- import 'package:xterm/src/ui/shortcut/shortcuts.dart';
- import 'package:xterm/src/ui/terminal_text_style.dart';
- import 'package:xterm/src/ui/terminal_theme.dart';
- import 'package:xterm/src/ui/themes.dart';
- class TerminalView extends StatefulWidget {
- const TerminalView(
- this.terminal, {
- Key? key,
- this.controller,
- this.theme = TerminalThemes.defaultTheme,
- this.textStyle = const TerminalStyle(),
- this.padding,
- this.scrollController,
- this.autoResize = true,
- this.backgroundOpacity = 1,
- this.focusNode,
- this.autofocus = false,
- this.onTapUp,
- this.onSecondaryTapDown,
- this.onSecondaryTapUp,
- this.mouseCursor = SystemMouseCursors.text,
- this.keyboardType = TextInputType.emailAddress,
- this.keyboardAppearance = Brightness.dark,
- this.cursorType = TerminalCursorType.block,
- this.alwaysShowCursor = false,
- this.deleteDetection = false,
- this.shortcuts,
- }) : super(key: key);
- /// The underlying terminal that this widget renders.
- final Terminal terminal;
- final TerminalController? controller;
- /// The theme to use for this terminal.
- final TerminalTheme theme;
- /// The style to use for painting characters.
- final TerminalStyle textStyle;
- /// Padding around the inner [Scrollable] widget.
- final EdgeInsets? padding;
- /// Scroll controller for the inner [Scrollable] widget.
- final ScrollController? scrollController;
- /// Should this widget automatically notify the underlying terminal when its
- /// size changes. [true] by default.
- final bool autoResize;
- /// Opacity of the terminal background. Set to 0 to make the terminal
- /// background transparent.
- final double backgroundOpacity;
- /// An optional focus node to use as the focus node for this widget.
- final FocusNode? focusNode;
- /// True if this widget will be selected as the initial focus when no other
- /// node in its scope is currently focused.
- final bool autofocus;
- /// Callback for when the user taps on the terminal.
- final void Function(TapUpDetails, CellOffset)? onTapUp;
- /// Function called when the user taps on the terminal with a secondary
- /// button.
- final void Function(TapDownDetails, CellOffset)? onSecondaryTapDown;
- /// Function called when the user stops holding down a secondary button.
- final void Function(TapUpDetails, CellOffset)? onSecondaryTapUp;
- /// The mouse cursor for mouse pointers that are hovering over the terminal.
- /// [SystemMouseCursors.text] by default.
- final MouseCursor mouseCursor;
- /// The type of information for which to optimize the text input control.
- /// [TextInputType.emailAddress] by default.
- final TextInputType keyboardType;
- /// The appearance of the keyboard. [Brightness.dark] by default.
- ///
- /// This setting is only honored on iOS devices.
- final Brightness keyboardAppearance;
- /// The type of cursor to use. [TerminalCursorType.block] by default.
- final TerminalCursorType cursorType;
- /// Whether to always show the cursor. This is useful for debugging.
- /// [false] by default.
- final bool alwaysShowCursor;
- /// Workaround to detect delete key for platforms and IMEs that does not
- /// emit hardware delete event. Prefered on mobile platforms. [false] by
- /// default.
- final bool deleteDetection;
- /// Shortcuts for this terminal. This has higher priority than input handler
- /// of the terminal If not provided, [defaultTerminalShortcuts] will be used.
- final Map<ShortcutActivator, Intent>? shortcuts;
- @override
- State<TerminalView> createState() => TerminalViewState();
- }
- class TerminalViewState extends State<TerminalView> {
- late final FocusNode _focusNode;
- late final ShortcutManager _shortcutManager;
- final _customTextEditKey = GlobalKey<CustomTextEditState>();
- final _scrollableKey = GlobalKey<ScrollableState>();
- final _viewportKey = GlobalKey();
- String? _composingText;
- late TerminalController _controller;
- late ScrollController _scrollController;
- RenderTerminal get renderTerminal =>
- _viewportKey.currentContext!.findRenderObject() as RenderTerminal;
- @override
- void initState() {
- _focusNode = widget.focusNode ?? FocusNode();
- _controller = widget.controller ?? TerminalController();
- _scrollController = widget.scrollController ?? ScrollController();
- _shortcutManager = ShortcutManager(
- shortcuts: widget.shortcuts ?? defaultTerminalShortcuts,
- );
- super.initState();
- }
- @override
- void didUpdateWidget(TerminalView oldWidget) {
- if (oldWidget.focusNode != widget.focusNode) {
- _focusNode = widget.focusNode ?? FocusNode();
- }
- if (oldWidget.controller != widget.controller) {
- _controller = widget.controller ?? TerminalController();
- }
- if (oldWidget.scrollController != widget.scrollController) {
- _scrollController = widget.scrollController ?? ScrollController();
- }
- super.didUpdateWidget(oldWidget);
- }
- @override
- void dispose() {
- _focusNode.dispose();
- _shortcutManager.dispose();
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- Widget child = Scrollable(
- key: _scrollableKey,
- controller: _scrollController,
- viewportBuilder: (context, offset) {
- return _TerminalView(
- key: _viewportKey,
- terminal: widget.terminal,
- controller: _controller,
- offset: offset,
- padding: MediaQuery.of(context).padding,
- autoResize: widget.autoResize,
- textStyle: widget.textStyle,
- theme: widget.theme,
- focusNode: _focusNode,
- cursorType: widget.cursorType,
- alwaysShowCursor: widget.alwaysShowCursor,
- onEditableRect: _onEditableRect,
- composingText: _composingText,
- );
- },
- );
- child = CustomTextEdit(
- key: _customTextEditKey,
- focusNode: _focusNode,
- inputType: widget.keyboardType,
- keyboardAppearance: widget.keyboardAppearance,
- deleteDetection: widget.deleteDetection,
- onInsert: (text) {
- _scrollToBottom();
- widget.terminal.textInput(text);
- },
- onDelete: () {
- _scrollToBottom();
- widget.terminal.keyInput(TerminalKey.backspace);
- },
- onComposing: (text) {
- setState(() => _composingText = text);
- },
- onAction: (action) {
- _scrollToBottom();
- if (action == TextInputAction.done) {
- widget.terminal.keyInput(TerminalKey.enter);
- }
- },
- onKey: _onKeyEvent,
- child: child,
- );
- child = TerminalActions(
- terminal: widget.terminal,
- controller: _controller,
- child: child,
- );
- child = KeyboardVisibilty(
- onKeyboardShow: _onKeyboardShow,
- child: child,
- );
- child = TerminalGestureHandler(
- terminalView: this,
- onTapUp: _onTapUp,
- onTapDown: _onTapDown,
- onSecondaryTapDown:
- widget.onSecondaryTapDown != null ? _onSecondaryTapDown : null,
- onSecondaryTapUp:
- widget.onSecondaryTapUp != null ? _onSecondaryTapUp : null,
- child: child,
- );
- child = MouseRegion(
- cursor: widget.mouseCursor,
- child: child,
- );
- child = Container(
- color: widget.theme.background.withOpacity(widget.backgroundOpacity),
- padding: widget.padding,
- child: child,
- );
- return child;
- }
- void requestKeyboard() {
- _customTextEditKey.currentState?.requestKeyboard();
- }
- void closeKeyboard() {
- _customTextEditKey.currentState?.closeKeyboard();
- }
- void _onTapUp(TapUpDetails details) {
- final offset = renderTerminal.getCellOffset(details.localPosition);
- widget.onTapUp?.call(details, offset);
- }
- void _onTapDown(_) {
- if (_controller.selection != null) {
- _controller.clearSelection();
- } else {
- _customTextEditKey.currentState?.requestKeyboard();
- }
- }
- void _onSecondaryTapDown(TapDownDetails details) {
- final offset = renderTerminal.getCellOffset(details.localPosition);
- widget.onSecondaryTapDown?.call(details, offset);
- }
- void _onSecondaryTapUp(TapUpDetails details) {
- final offset = renderTerminal.getCellOffset(details.localPosition);
- widget.onSecondaryTapUp?.call(details, offset);
- }
- bool get hasInputConnection {
- return _customTextEditKey.currentState?.hasInputConnection == true;
- }
- KeyEventResult _onKeyEvent(FocusNode focusNode, RawKeyEvent event) {
- // ignore: invalid_use_of_protected_member
- final shortcutResult = _shortcutManager.handleKeypress(
- focusNode.context!,
- event,
- );
- if (shortcutResult != KeyEventResult.ignored) {
- return shortcutResult;
- }
- if (event is! RawKeyDownEvent) {
- return KeyEventResult.ignored;
- }
- final key = inputMap(event.logicalKey);
- if (key == null) {
- return KeyEventResult.ignored;
- }
- final handled = widget.terminal.keyInput(
- key,
- ctrl: event.isControlPressed,
- alt: event.isAltPressed,
- shift: event.isShiftPressed,
- );
- if (handled) {
- _scrollToBottom();
- }
- return handled ? KeyEventResult.handled : KeyEventResult.ignored;
- }
- void _onKeyboardShow() {
- if (_focusNode.hasFocus) {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- _scrollToBottom();
- });
- }
- }
- void _onEditableRect(Rect rect, Rect caretRect) {
- _customTextEditKey.currentState?.setEditableRect(rect, caretRect);
- }
- void _scrollToBottom() {
- final position = _scrollableKey.currentState?.position;
- if (position != null) {
- position.jumpTo(position.maxScrollExtent);
- }
- }
- }
- class _TerminalView extends LeafRenderObjectWidget {
- const _TerminalView({
- Key? key,
- required this.terminal,
- required this.controller,
- required this.offset,
- required this.padding,
- required this.autoResize,
- required this.textStyle,
- required this.theme,
- required this.focusNode,
- required this.cursorType,
- required this.alwaysShowCursor,
- this.onEditableRect,
- this.composingText,
- }) : super(key: key);
- final Terminal terminal;
- final TerminalController controller;
- final ViewportOffset offset;
- final EdgeInsets padding;
- final bool autoResize;
- final TerminalStyle textStyle;
- final TerminalTheme theme;
- final FocusNode focusNode;
- final TerminalCursorType cursorType;
- final bool alwaysShowCursor;
- final EditableRectCallback? onEditableRect;
- final String? composingText;
- @override
- RenderTerminal createRenderObject(BuildContext context) {
- return RenderTerminal(
- terminal: terminal,
- controller: controller,
- offset: offset,
- padding: padding,
- autoResize: autoResize,
- textStyle: textStyle,
- theme: theme,
- focusNode: focusNode,
- cursorType: cursorType,
- alwaysShowCursor: alwaysShowCursor,
- onEditableRect: onEditableRect,
- composingText: composingText,
- );
- }
- @override
- void updateRenderObject(BuildContext context, RenderTerminal renderObject) {
- renderObject
- ..terminal = terminal
- ..controller = controller
- ..offset = offset
- ..padding = padding
- ..autoResize = autoResize
- ..textStyle = textStyle
- ..theme = theme
- ..focusNode = focusNode
- ..cursorType = cursorType
- ..alwaysShowCursor = alwaysShowCursor
- ..onEditableRect = onEditableRect
- ..composingText = composingText;
- }
- }
|