Ver Fonte

Adds blinking Cursor support

devmil há 4 anos atrás
pai
commit
c2a23d3547
3 ficheiros alterados com 406 adições e 239 exclusões
  1. 39 0
      lib/frontend/oscillator.dart
  2. 266 0
      lib/frontend/terminal_painters.dart
  3. 101 239
      lib/frontend/terminal_view.dart

+ 39 - 0
lib/frontend/oscillator.dart

@@ -11,6 +11,21 @@ class Oscillator with Observable {
 
   var _value = true;
   Timer? _timer;
+  var _shouldRun = false;
+
+  @override
+  void addListener(listener) {
+    super.addListener(listener);
+    resume();
+  }
+
+  @override
+  void removeListener(listener) {
+    super.removeListener(listener);
+    if (listeners.isEmpty) {
+      pause();
+    }
+  }
 
   void _onOscillation(_) {
     _value = !_value;
@@ -22,11 +37,35 @@ class Oscillator with Observable {
   }
 
   void start() {
+    _shouldRun = true;
+    // only start right away when anyone is listening.
+    // the moment a listener gets registered the Oscillator will start
+    if (listeners.isNotEmpty) {
+      _startInternal();
+    }
+  }
+
+  void _startInternal() {
     if (_timer != null) return;
     _timer = Timer.periodic(duration, _onOscillation);
   }
 
+  void pause() {
+    _stopInternal();
+  }
+
+  void resume() {
+    if (_shouldRun) {
+      _startInternal();
+    }
+  }
+
   void stop() {
+    _shouldRun = false;
+    _stopInternal();
+  }
+
+  void _stopInternal() {
     _timer?.cancel();
     _timer = null;
   }

+ 266 - 0
lib/frontend/terminal_painters.dart

@@ -0,0 +1,266 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:xterm/buffer/cell_flags.dart';
+import 'package:xterm/buffer/line/line.dart';
+import 'package:xterm/mouse/position.dart';
+import 'package:xterm/terminal/terminal_ui_interaction.dart';
+import 'package:xterm/theme/terminal_style.dart';
+import 'package:xterm/util/bit_flags.dart';
+
+import 'char_size.dart';
+import 'cache.dart';
+
+class TerminalPainter extends CustomPainter {
+  TerminalPainter({
+    required this.terminal,
+    required this.style,
+    required this.charSize,
+    required this.textLayoutCache,
+  });
+
+  final TerminalUiInteraction terminal;
+  final TerminalStyle style;
+  final CellSize charSize;
+  final TextLayoutCache textLayoutCache;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    _paintBackground(canvas);
+
+    // if (oscillator.value) {
+    // }
+
+    _paintText(canvas);
+
+    _paintSelection(canvas);
+  }
+
+  void _paintBackground(Canvas canvas) {
+    final lines = terminal.getVisibleLines();
+
+    for (var row = 0; row < lines.length; row++) {
+      final line = lines[row];
+      final offsetY = row * charSize.cellHeight;
+      // final cellCount = math.min(terminal.viewWidth, line.length);
+      final cellCount = terminal.terminalWidth;
+
+      for (var col = 0; col < cellCount; col++) {
+        final cellWidth = line.cellGetWidth(col);
+        if (cellWidth == 0) {
+          continue;
+        }
+
+        final cellFgColor = line.cellGetFgColor(col);
+        final cellBgColor = line.cellGetBgColor(col);
+        final effectBgColor = line.cellHasFlag(col, CellFlags.inverse)
+            ? cellFgColor
+            : cellBgColor;
+
+        if (effectBgColor == 0x00) {
+          continue;
+        }
+
+        // when a program reports black as background then it "really" means transparent
+        if (effectBgColor == 0xFF000000) {
+          continue;
+        }
+
+        // final cellFlags = line.cellGetFlags(i);
+        // final cell = line.getCell(i);
+        // final attr = cell.attr;
+
+        final offsetX = col * charSize.cellWidth;
+        final effectWidth = charSize.cellWidth * cellWidth + 1;
+        final effectHeight = charSize.cellHeight + 1;
+
+        // background color is already painted with opacity by the Container of
+        // TerminalPainter so wo don't need to fallback to
+        // terminal.theme.background here.
+
+        final paint = Paint()..color = Color(effectBgColor);
+        canvas.drawRect(
+          Rect.fromLTWH(offsetX, offsetY, effectWidth, effectHeight),
+          paint,
+        );
+      }
+    }
+  }
+
+  void _paintSelection(Canvas canvas) {
+    final paint = Paint()..color = Colors.white.withOpacity(0.3);
+
+    for (var y = 0; y < terminal.terminalHeight; y++) {
+      final offsetY = y * charSize.cellHeight;
+      final absoluteY = terminal.convertViewLineToRawLine(y) -
+          terminal.scrollOffsetFromBottom;
+
+      for (var x = 0; x < terminal.terminalWidth; x++) {
+        var cellCount = 0;
+
+        while (
+            (terminal.selection?.contains(Position(x + cellCount, absoluteY)) ??
+                    false) &&
+                x + cellCount < terminal.terminalWidth) {
+          cellCount++;
+        }
+
+        if (cellCount == 0) {
+          continue;
+        }
+
+        final offsetX = x * charSize.cellWidth;
+        final effectWidth = cellCount * charSize.cellWidth;
+        final effectHeight = charSize.cellHeight;
+
+        canvas.drawRect(
+          Rect.fromLTWH(offsetX, offsetY, effectWidth, effectHeight),
+          paint,
+        );
+
+        x += cellCount;
+      }
+    }
+  }
+
+  void _paintText(Canvas canvas) {
+    final lines = terminal.getVisibleLines();
+
+    for (var row = 0; row < lines.length; row++) {
+      final line = lines[row];
+      final offsetY = row * charSize.cellHeight;
+      // final cellCount = math.min(terminal.viewWidth, line.length);
+      final cellCount = terminal.terminalWidth;
+
+      for (var col = 0; col < cellCount; col++) {
+        final width = line.cellGetWidth(col);
+
+        if (width == 0) {
+          continue;
+        }
+
+        final offsetX = col * charSize.cellWidth;
+        _paintCell(canvas, line, col, offsetX, offsetY);
+      }
+    }
+  }
+
+  void _paintCell(
+    Canvas canvas,
+    BufferLine line,
+    int cell,
+    double offsetX,
+    double offsetY,
+  ) {
+    final codePoint = line.cellGetContent(cell);
+    final fgColor = line.cellGetFgColor(cell);
+    final bgColor = line.cellGetBgColor(cell);
+    final flags = line.cellGetFlags(cell);
+
+    if (codePoint == 0 || flags.hasFlag(CellFlags.invisible)) {
+      return;
+    }
+
+    // final cellHash = line.cellGetHash(cell);
+    final cellHash = hashValues(codePoint, fgColor, bgColor, flags);
+
+    var character = textLayoutCache.getLayoutFromCache(cellHash);
+    if (character != null) {
+      canvas.drawParagraph(character, Offset(offsetX, offsetY));
+      return;
+    }
+
+    final cellColor = flags.hasFlag(CellFlags.inverse) ? bgColor : fgColor;
+
+    var color = Color(cellColor);
+
+    if (flags & CellFlags.faint != 0) {
+      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,
+          );
+
+    character = textLayoutCache.performAndCacheLayout(
+        String.fromCharCode(codePoint), styleToUse, cellHash);
+
+    canvas.drawParagraph(character, Offset(offsetX, offsetY));
+  }
+
+  @override
+  bool shouldRepaint(CustomPainter oldDelegate) {
+    /// paint only when the terminal has changed since last paint.
+    return terminal.dirty;
+  }
+}
+
+class CursorPainter extends CustomPainter {
+  final bool visible;
+  final CellSize charSize;
+  final bool focused;
+  final bool blinkVisible;
+  final int cursorColor;
+
+  CursorPainter({
+    required this.visible,
+    required this.charSize,
+    required this.focused,
+    required this.blinkVisible,
+    required this.cursorColor,
+  });
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    if (blinkVisible && visible) {
+      _paintCursor(canvas);
+    }
+  }
+
+  @override
+  bool shouldRepaint(covariant CustomPainter oldDelegate) {
+    if (oldDelegate is CursorPainter) {
+      return blinkVisible != oldDelegate.blinkVisible ||
+          focused != oldDelegate.focused ||
+          visible != oldDelegate.visible ||
+          charSize.cellWidth != oldDelegate.charSize.cellWidth ||
+          charSize.cellHeight != oldDelegate.charSize.cellHeight;
+    }
+    return true;
+  }
+
+  void _paintCursor(Canvas canvas) {
+    final paint = Paint()
+      ..color = Color(cursorColor)
+      ..strokeWidth = focused ? 0.0 : 1.0
+      ..style = focused ? PaintingStyle.fill : PaintingStyle.stroke;
+
+    canvas.drawRect(
+        Rect.fromLTWH(0, 0, charSize.cellWidth, charSize.cellHeight), paint);
+  }
+}

+ 101 - 239
lib/frontend/terminal_view.dart

@@ -1,13 +1,12 @@
 import 'dart:math' as math;
 import 'dart:ui';
 
+import 'package:flutter/cupertino.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter/scheduler.dart';
 import 'package:flutter/services.dart';
-import 'package:xterm/buffer/cell_flags.dart';
-import 'package:xterm/buffer/line/line.dart';
 import 'package:xterm/frontend/cache.dart';
 import 'package:xterm/frontend/char_size.dart';
 import 'package:xterm/frontend/helpers.dart';
@@ -15,11 +14,10 @@ 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/oscillator.dart';
+import 'package:xterm/frontend/terminal_painters.dart';
 import 'package:xterm/mouse/position.dart';
 import 'package:xterm/terminal/terminal_ui_interaction.dart';
 import 'package:xterm/theme/terminal_style.dart';
-import 'package:xterm/util/bit_flags.dart';
-import 'package:xterm/util/hash_values.dart';
 
 class TerminalView extends StatefulWidget {
   TerminalView({
@@ -88,7 +86,7 @@ class TerminalView extends StatefulWidget {
 
 class _TerminalViewState extends State<TerminalView> {
   /// blinking cursor and blinking character
-  final oscillator = Oscillator.ms(600);
+  final blinkOscillator = Oscillator.ms(600);
 
   final textLayoutCache = TextLayoutCache(TextDirection.ltr, 10240);
 
@@ -101,10 +99,10 @@ class _TerminalViewState extends State<TerminalView> {
 
   /// Scroll position from the terminal. Not null if terminal scroll extent has
   /// been updated and needs to be syncronized to flutter side.
-  double? _terminalScrollExtent;
+  double? _pendingTerminalScrollExtent;
 
   void onTerminalChange() {
-    _terminalScrollExtent =
+    _pendingTerminalScrollExtent =
         _cellSize.cellHeight * widget.terminal.scrollOffsetFromTop;
 
     if (mounted) {
@@ -119,7 +117,7 @@ class _TerminalViewState extends State<TerminalView> {
 
   @override
   void initState() {
-    // oscillator.start();
+    blinkOscillator.start();
     // oscillator.addListener(onTick);
 
     // measureCellSize is expensive so we cache the result.
@@ -146,7 +144,7 @@ class _TerminalViewState extends State<TerminalView> {
 
   @override
   void dispose() {
-    // oscillator.stop();
+    blinkOscillator.stop();
     // oscillator.removeListener(onTick);
 
     widget.terminal.removeListener(onTerminalChange);
@@ -203,10 +201,10 @@ class _TerminalViewState extends State<TerminalView> {
                   offset.applyContentDimensions(
                       minScrollExtent, maxScrollExtent);
 
-                  // syncronize pending terminal scroll extent to ScrollController
-                  if (_terminalScrollExtent != null) {
-                    position.correctPixels(_terminalScrollExtent!);
-                    _terminalScrollExtent = null;
+                  // synchronize pending terminal scroll extent to ScrollController
+                  if (_pendingTerminalScrollExtent != null) {
+                    position.correctPixels(_pendingTerminalScrollExtent!);
+                    _pendingTerminalScrollExtent = null;
                   }
                 }
 
@@ -262,15 +260,29 @@ class _TerminalViewState extends State<TerminalView> {
       },
       child: Container(
         constraints: BoxConstraints.expand(),
-        child: CustomPaint(
-          painter: TerminalPainter(
-            terminal: widget.terminal,
-            view: widget,
-            oscillator: oscillator,
-            focused: focused,
-            charSize: _cellSize,
-            textLayoutCache: textLayoutCache,
-          ),
+        child: Stack(
+          children: <Widget>[
+            CustomPaint(
+              painter: TerminalPainter(
+                terminal: widget.terminal,
+                style: widget.style,
+                charSize: _cellSize,
+                textLayoutCache: textLayoutCache,
+              ),
+            ),
+            Positioned(
+              child: CursorView(
+                terminal: widget.terminal,
+                cellSize: _cellSize,
+                focusNode: widget.focusNode,
+                blinkOscillator: blinkOscillator,
+              ),
+              width: _cellSize.cellWidth,
+              height: _cellSize.cellHeight,
+              left: _getCursorOffset().dx,
+              top: _getCursorOffset().dy,
+            ),
+          ],
         ),
         color: Color(widget.terminal.backgroundColor).withOpacity(
           widget.opacity,
@@ -279,6 +291,14 @@ class _TerminalViewState extends State<TerminalView> {
     );
   }
 
+  Offset _getCursorOffset() {
+    final screenCursorY = widget.terminal.cursorY;
+    final offsetX = _cellSize.cellWidth * widget.terminal.cursorX;
+    final offsetY = _cellSize.cellHeight * screenCursorY;
+
+    return Offset(offsetX, offsetY);
+  }
+
   /// Get global cell position from mouse position.
   Position getMouseOffset(double px, double py) {
     final col = (px / _cellSize.cellWidth).floor();
@@ -358,248 +378,90 @@ class _TerminalViewState extends State<TerminalView> {
   }
 }
 
-class TerminalPainter extends CustomPainter {
-  TerminalPainter({
+class CursorView extends StatefulWidget {
+  final CellSize cellSize;
+  final TerminalUiInteraction terminal;
+  final FocusNode? focusNode;
+  final Oscillator blinkOscillator;
+  CursorView({
     required this.terminal,
-    required this.view,
-    required this.oscillator,
-    required this.focused,
-    required this.charSize,
-    required this.textLayoutCache,
+    required this.cellSize,
+    required this.focusNode,
+    required this.blinkOscillator,
   });
 
-  final TerminalUiInteraction terminal;
-  final TerminalView view;
-  final Oscillator oscillator;
-  final bool focused;
-  final CellSize charSize;
-  final TextLayoutCache textLayoutCache;
-
   @override
-  void paint(Canvas canvas, Size size) {
-    if (!terminal.isReady) {
-      return;
-    }
-    _paintBackground(canvas);
-
-    // if (oscillator.value) {
-    // }
-
-    if (terminal.showCursor) {
-      _paintCursor(canvas);
-    }
-
-    _paintText(canvas);
+  State<StatefulWidget> createState() => _CursorViewState();
+}
 
-    _paintSelection(canvas);
+class _CursorViewState extends State<CursorView> {
+  bool get focused {
+    return widget.focusNode?.hasFocus ?? false;
   }
 
-  void _paintBackground(Canvas canvas) {
-    final lines = terminal.getVisibleLines();
-
-    for (var row = 0; row < lines.length; row++) {
-      final line = lines[row];
-      final offsetY = row * charSize.cellHeight;
-      // final cellCount = math.min(terminal.viewWidth, line.length);
-      final cellCount = terminal.terminalWidth;
-
-      for (var col = 0; col < cellCount; col++) {
-        final cellWidth = line.cellGetWidth(col);
-        if (cellWidth == 0) {
-          continue;
-        }
-
-        final cellFgColor = line.cellGetFgColor(col);
-        final cellBgColor = line.cellGetBgColor(col);
-        final effectBgColor = line.cellHasFlag(col, CellFlags.inverse)
-            ? cellFgColor
-            : cellBgColor;
+  var _isOscillatorCallbackRegistered = false;
 
-        if (effectBgColor == 0x00) {
-          continue;
-        }
-
-        // when a program reports black as background then it "really" means transparent
-        if (effectBgColor == 0xFF000000) {
-          continue;
-        }
-
-        // final cellFlags = line.cellGetFlags(i);
-        // final cell = line.getCell(i);
-        // final attr = cell.attr;
-
-        final offsetX = col * charSize.cellWidth;
-        final effectWidth = charSize.cellWidth * cellWidth + 1;
-        final effectHeight = charSize.cellHeight + 1;
+  @override
+  void initState() {
+    _isOscillatorCallbackRegistered = true;
+    widget.blinkOscillator.addListener(onOscillatorTick);
 
-        // background color is already painted with opacity by the Container of
-        // TerminalPainter so wo don't need to fallback to
-        // terminal.theme.background here.
+    widget.terminal.addListener(onTerminalChange);
 
-        final paint = Paint()..color = Color(effectBgColor);
-        canvas.drawRect(
-          Rect.fromLTWH(offsetX, offsetY, effectWidth, effectHeight),
-          paint,
-        );
-      }
-    }
+    super.initState();
   }
 
-  void _paintSelection(Canvas canvas) {
-    final selection = terminal.selection;
-    if (selection == null) {
-      return;
-    }
-    final paint = Paint()..color = Colors.white.withOpacity(0.3);
-
-    for (var y = 0; y < terminal.terminalHeight; y++) {
-      final offsetY = y * charSize.cellHeight;
-      final absoluteY = terminal.convertViewLineToRawLine(y) -
-          terminal.scrollOffsetFromBottom;
-
-      for (var x = 0; x < terminal.terminalWidth; x++) {
-        var cellCount = 0;
-
-        while (selection.contains(Position(x + cellCount, absoluteY)) &&
-            x + cellCount < terminal.terminalWidth) {
-          cellCount++;
-        }
-
-        if (cellCount == 0) {
-          continue;
-        }
-
-        final offsetX = x * charSize.cellWidth;
-        final effectWidth = cellCount * charSize.cellWidth;
-        final effectHeight = charSize.cellHeight;
-
-        canvas.drawRect(
-          Rect.fromLTWH(offsetX, offsetY, effectWidth, effectHeight),
-          paint,
-        );
-
-        x += cellCount;
-      }
-    }
+  @override
+  Widget build(BuildContext context) {
+    return CustomPaint(
+      painter: CursorPainter(
+        visible: _isCursorVisible(),
+        focused: focused,
+        charSize: widget.cellSize,
+        blinkVisible: widget.blinkOscillator.value,
+        cursorColor: widget.terminal.cursorColor,
+      ),
+    );
   }
 
-  void _paintText(Canvas canvas) {
-    final lines = terminal.getVisibleLines();
-
-    for (var row = 0; row < lines.length; row++) {
-      final line = lines[row];
-      final offsetY = row * charSize.cellHeight;
-      // final cellCount = math.min(terminal.viewWidth, line.length);
-      final cellCount = terminal.terminalWidth;
-
-      for (var col = 0; col < cellCount; col++) {
-        final width = line.cellGetWidth(col);
-
-        if (width == 0) {
-          continue;
-        }
-
-        final offsetX = col * charSize.cellWidth;
-        _paintCell(canvas, line, col, offsetX, offsetY);
-      }
+  bool _isCursorVisible() {
+    final screenCursorY = widget.terminal.cursorY;
+    if (screenCursorY < 0 || screenCursorY >= widget.terminal.terminalHeight) {
+      return false;
     }
+    return widget.terminal.showCursor;
   }
 
-  void _paintCell(
-    Canvas canvas,
-    BufferLine line,
-    int cell,
-    double offsetX,
-    double offsetY,
-  ) {
-    final codePoint = line.cellGetContent(cell);
-    final fgColor = line.cellGetFgColor(cell);
-    final bgColor = line.cellGetBgColor(cell);
-    final flags = line.cellGetFlags(cell);
-
-    if (codePoint == 0 || flags.hasFlag(CellFlags.invisible)) {
-      return;
-    }
-
-    // final cellHash = line.cellGetHash(cell);
-    final fontSize = view.style.fontSize;
-    final cellHash = hashValues(codePoint, fgColor, bgColor, flags);
-
-    var character = textLayoutCache.getLayoutFromCache(cellHash);
-    if (character != null) {
-      canvas.drawParagraph(character, Offset(offsetX, offsetY));
-      return;
-    }
-
-    final cellColor = flags.hasFlag(CellFlags.inverse) ? bgColor : fgColor;
-
-    var color = Color(cellColor);
-
-    if (flags & CellFlags.faint != 0) {
-      color = color.withOpacity(0.5);
-    }
-
-    final style = (view.style.textStyleProvider != null)
-        ? view.style.textStyleProvider!(
-            color: color,
-            fontSize: 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: 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: view.style.fontFamily,
-          );
-
-    // final tp = textLayoutCache.getOrPerformLayout(span);
-    character = textLayoutCache.performAndCacheLayout(
-        String.fromCharCode(codePoint), style, cellHash);
+  @override
+  void dispose() {
+    widget.terminal.removeListener(onTerminalChange);
+    widget.blinkOscillator.removeListener(onOscillatorTick);
 
-    canvas.drawParagraph(character, Offset(offsetX, offsetY));
+    super.dispose();
   }
 
-  void _paintCursor(Canvas canvas) {
-    final screenCursorY = terminal.cursorY + terminal.scrollOffsetFromBottom;
-    if (screenCursorY < 0 || screenCursorY >= terminal.terminalHeight) {
+  void onTerminalChange() {
+    if (!mounted) {
       return;
     }
 
-    final width = charSize.cellWidth *
-        (terminal.currentLine?.cellGetWidth(terminal.cursorX).clamp(1, 2) ?? 1);
-
-    final offsetX = charSize.cellWidth * terminal.cursorX;
-    final offsetY = charSize.cellHeight * screenCursorY;
-    final paint = Paint()
-      ..color = Color(terminal.cursorColor)
-      ..strokeWidth = focused ? 0.0 : 1.0
-      ..style = focused ? PaintingStyle.fill : PaintingStyle.stroke;
-
-    canvas.drawRect(
-        Rect.fromLTWH(offsetX, offsetY, width, charSize.cellHeight), paint);
+    setState(() {
+      if (_isCursorVisible() /*&& widget.terminal.blinkingCursor*/ && focused) {
+        if (!_isOscillatorCallbackRegistered) {
+          _isOscillatorCallbackRegistered = true;
+          widget.blinkOscillator.addListener(onOscillatorTick);
+        }
+      } else {
+        if (_isOscillatorCallbackRegistered) {
+          _isOscillatorCallbackRegistered = false;
+          widget.blinkOscillator.removeListener(onOscillatorTick);
+        }
+      }
+    });
   }
 
-  @override
-  bool shouldRepaint(CustomPainter oldDelegate) {
-    /// paint only when the terminal has changed since last paint.
-    return terminal.dirty;
+  void onOscillatorTick() {
+    setState(() {});
   }
 }