Prechádzať zdrojové kódy

add classes copy from C# US Project

melon.yin 2 rokov pred
rodič
commit
a83da0ad1c

+ 0 - 0
lib/base_models/visuals/adorner.dart → lib/base_models/adorners/adorner_interface.dart


+ 33 - 0
lib/base_models/adorners/sychro_adorner_interface.dart

@@ -0,0 +1,33 @@
+import 'package:fis_common/event/event_type.dart';
+
+import '../update_obj_base.dart';
+import 'adorner_interface.dart';
+
+abstract class ISychroAdorner implements IAdorner, IUpdateTransaction {
+  /// Gets or sets a value that indicates whether this accessory is in the active state.
+  ///
+  /// The default value is [false].
+  ///
+  /// Remarks: true indicates the accessory is currently active in the parent(visual/visual area).
+  ///
+  /// false indicates the accessory has been "deleted"(use cache mode,not delete really).
+  bool get isVisible;
+  set isVisible(bool value);
+
+  /// Occurs when the value of the IsVisible property changes on this accessory.
+  late FEventHandler isVisibleChanged;
+
+  /// Occurs when the value of the properties changes on this accessory.
+  late FEventHandler updated;
+
+  /// Determines whether a provided native modification need raise synchronize operations in next process cycle.
+  ///
+  /// [modificationType] native modification id which use to check is match to the synchFilters
+  void checkModify(int modificationType);
+  // void CheckModify(UpdateIdEnum modificationType);
+
+  /// Synchronize interesting data from native and refreshes the property values.
+  void reload();
+
+  IDisposable lockOnUpdating();
+}

+ 0 - 0
lib/base_models/physical_coordinates/linear_tissue.dart → lib/base_models/physical_coordinates/linear_tissue_physical_coordinate.dart


+ 148 - 0
lib/base_models/update_obj_base.dart

@@ -0,0 +1,148 @@
+import 'dart:collection';
+
+import 'package:fis_common/event/event_type.dart';
+import 'package:fis_common/logger/logger.dart';
+import 'package:flutter/foundation.dart';
+
+abstract class IUpdateTransaction {
+  bool get updateLocked;
+
+  late FEventHandler<List<IDisposable>> lockedOnUpdating;
+}
+
+abstract class UpdateObjectBase implements IUpdateTransaction {
+  static bool debugCoordinate = false;
+
+  late final UpdateLocker _locker;
+
+  final updated = FEventHandler<void>();
+
+  @override
+  final lockedOnUpdating = FEventHandler<List<IDisposable>>();
+
+  @override
+  bool get updateLocked => _locker.paused;
+
+  UpdateObjectBase() {
+    _locker = UpdateLocker(_onProcess);
+  }
+
+  @protected
+  void onUpdated();
+
+  void tryUpdate() {
+    if (_locker.paused) {
+      _locker.cacheUpdate();
+      return;
+    }
+    _raiseUpdate();
+  }
+
+  IDisposable lockOnUpdating() {
+    bool locked = updateLocked;
+    IDisposable breaker = _locker.pauseProcessing();
+    if (!locked && updateLocked) {
+      final eventArgs = <IDisposable>[];
+      _onLockedOnUpdating(eventArgs);
+      for (final childBreaker in eventArgs) {
+        _locker.cacheChildBreaker(childBreaker);
+      }
+    }
+    return breaker;
+  }
+
+  void _onProcess(bool isModified) {
+    if (isModified) {
+      _raiseUpdate();
+    }
+  }
+
+  void _raiseUpdate() {
+    try {
+      onUpdated();
+      updated.emit(this, null);
+    } on Exception catch (ex) {
+      logger.e("UpdateObjectBase.RaiseUpdate failed, type:$runtimeType.", ex);
+    }
+  }
+
+  void _onLockedOnUpdating(List<IDisposable> e) {
+    lockedOnUpdating.emit(this, e);
+  }
+}
+
+class UpdateLocker {
+  bool _isModified = false;
+  late final void Function(bool)? _processor;
+  late final List<IDisposable> _selfBreakers;
+  late final Queue<IDisposable> _childBreakers;
+  late final Object? tag;
+
+  bool get paused => _selfBreakers.isNotEmpty;
+
+  UpdateLocker(void Function(bool)? onEndTransaction) {
+    _selfBreakers = <IDisposable>[];
+    _childBreakers = Queue<IDisposable>();
+    _processor = onEndTransaction;
+  }
+
+  IDisposable pauseProcessing() {
+    return _Breaker(this);
+  }
+
+  void cacheChildBreaker(IDisposable? breaker) {
+    if (breaker != null && !_childBreakers.contains(breaker)) {
+      _childBreakers.add(breaker);
+    }
+  }
+
+  void cacheUpdate() {
+    _isModified = true;
+  }
+
+  void _process() {
+    try {
+      if (_processor != null) {
+        _processor!.call(_isModified);
+      }
+      tag = null;
+      while (_childBreakers.isNotEmpty) {
+        final breaker = _childBreakers.removeLast();
+        breaker.dispose();
+      }
+    } on Exception catch (ex, _) {
+      logger.e("Failed execute UpdateLocker process.ex:{0}", ex);
+    } finally {
+      _isModified = false;
+    }
+  }
+}
+
+class _Breaker implements IDisposable {
+  late UpdateLocker _owner;
+
+  _Breaker(UpdateLocker owner) {
+    _owner = owner;
+    _owner._selfBreakers.add(this);
+  }
+
+  @override
+  void dispose() {
+    _owner._selfBreakers.remove(this);
+    if (!_owner.paused) {
+      _owner._process();
+    }
+  }
+}
+
+abstract class IDisposable {
+  void dispose();
+}
+
+extension IDisposableExt on IDisposable {
+  T using<T>(T Function() action) {
+    final result = action();
+    dispose();
+    return result;
+  }
+}

+ 0 - 1
lib/base_models/view_port/rect_region.dart

@@ -1 +0,0 @@
-class RectRegion {}

+ 0 - 0
lib/base_models/view_port/image_boundary_interface.dart → lib/base_models/viewports/image_boundary_interface.dart


+ 8 - 8
lib/base_models/view_port/logical_coordinate_interface.dart → lib/base_models/viewports/logical_coordinate_interface.dart

@@ -9,8 +9,8 @@ abstract class ILogicalCoordinate {
   /// Gets a Boundary that specifies the desired region of valid image.
   ///
   /// Image boundary may outside the Region
-  IImageBoundary get imageBoundary;
-  set imageBoundary(IImageBoundary value);
+  IImageBoundary? get imageBoundary;
+  set imageBoundary(IImageBoundary? value);
 
   /// Gets or sets a value indicating whether to flip the image horizontally.
   bool get isFlipHorizontal;
@@ -25,8 +25,8 @@ abstract class ILogicalCoordinate {
   /// Region of rendered area is relevant to LayoutSchema.
   ///
   /// Region is match to the NormalizationCoordinate.Region
-  RectRegion get region;
-  set region(RectRegion value);
+  RectRegion? get region;
+  set region(RectRegion? value);
 
   /// Gets the unit of X-axis(Horizontal),
   ///
@@ -35,8 +35,8 @@ abstract class ILogicalCoordinate {
   /// TissueTimeMotion:XUnit=Units.s,YUnit=Units.cm
   ///
   /// DopplerTimeMotion:XUnit=Units.s,YUnit=Unit.cms
-  Unit get xUnit;
-  set xUnit(Unit value);
+  Unit? get xUnit;
+  set xUnit(Unit? value);
 
   /// Gets the unit of Y-axis(Vertical),
   ///
@@ -45,6 +45,6 @@ abstract class ILogicalCoordinate {
   /// TissueTimeMotion:XUnit=Units.s,YUnit=Units.cm
   ///
   /// DopplerTimeMotion:XUnit=Units.s,YUnit=Unit.cms
-  Unit get yUnit;
-  set yUnit(Unit value);
+  Unit? get yUnit;
+  set yUnit(Unit? value);
 }

+ 1 - 1
lib/base_models/view_port/physical_coordinate_interface.dart → lib/base_models/viewports/physical_coordinate_interface.dart

@@ -1,4 +1,4 @@
-import 'package:fis_measure/base_models/visuals/visual_area.dart';
+import 'package:fis_measure/base_models/visuals/visual_area_interface.dart';
 import 'package:fis_measure/interfaces/date_types/point.dart';
 
 import 'image_boundary_interface.dart';

+ 70 - 0
lib/base_models/viewports/rect_region.dart

@@ -0,0 +1,70 @@
+import 'package:fis_common/logger/logger.dart';
+import 'package:fis_measure/vid.dart';
+
+class RectRegion {
+  double _left = 0;
+  double _top = 0;
+  double _right = 0;
+  double _bottom = 0;
+
+  RectRegion();
+
+  factory RectRegion.fill(
+    double x,
+    double y,
+    double width,
+    double height,
+  ) {
+    if (width < 0.0 || height < 0.0) {
+      logger.e("Size_WidthAndHeightCannotBeNegative($x,$y,$width,$height)");
+      width = height = 0;
+    }
+    final rect = RectRegion()
+      .._left = x
+      .._top = y;
+    rect._right = rect.left + width;
+    rect._bottom = rect.top + height;
+    return rect;
+  }
+
+  factory RectRegion.pointTLBR(
+    DPoint topLeft,
+    DPoint bottomRight,
+  ) {
+    final rect = RectRegion()
+      .._left = topLeft.x
+      .._top = topLeft.y
+      .._right = bottomRight.x
+      .._bottom = bottomRight.y;
+    return rect;
+  }
+
+  double get left => _left;
+  double get right => _right;
+  double get top => _top;
+  double get bottom => _bottom;
+  double get width => (_right - _left).abs();
+  double get height => (_bottom - _top).abs();
+  DPoint get topLeft => DPoint(left, top);
+  DPoint get topRight => DPoint(right, top);
+  DPoint get bottomLeft => DPoint(left, bottom);
+  DPoint get bottomRight => DPoint(right, bottom);
+
+  @override
+  bool operator ==(Object other) {
+    return other is RectRegion &&
+        _left == other._left &&
+        _top == other._top &&
+        _right == other._right &&
+        _bottom == other._bottom;
+  }
+
+  @override
+  int get hashCode =>
+      top.hashCode ^ bottom.hashCode ^ left.hashCode ^ right.hashCode;
+
+  @override
+  String toString() {
+    return 'left:$left,right:$right,top:$top,bottom:$bottom';
+  }
+}

+ 23 - 0
lib/base_models/viewports/tissue_viewport.dart

@@ -0,0 +1,23 @@
+import 'package:fis_measure/base_models/update_obj_base.dart';
+
+import 'package:fis_common/event/event_type.dart';
+import 'package:fis_measure/interfaces/enums/uint.dart';
+
+import '../visual_areas/tissue_area.dart';
+import 'physical_coordinate_interface.dart';
+import 'viewport.dart';
+
+// class TissueViewPort extends  ViewPort<TissueArea, ITissuePhysicalCoordinate>{
+//   TissueViewPort(TissueArea visualArea, Unit xUnit, Unit yUnit) : super(visualArea, xUnit, yUnit);
+
+//   @override
+//   set lockedOnUpdating(FEventHandler<List<IDisposable>> _lockedOnUpdating) {
+//     // TODO: implement lockedOnUpdating
+//   }
+
+//   @override
+//   set updated(FEventHandler _updated) {
+//     // TODO: implement updated
+//   }
+  
+// }

+ 308 - 0
lib/base_models/viewports/viewport.dart

@@ -0,0 +1,308 @@
+// ignore_for_file: constant_identifier_names
+
+import 'package:fis_common/event/event_type.dart';
+import 'package:fis_common/logger/logger.dart';
+import 'package:fis_measure/interfaces/base/version.dart';
+import 'package:fis_measure/interfaces/date_types/matrix.dart';
+import 'package:fis_measure/interfaces/date_types/point.dart';
+import 'package:fis_measure/interfaces/date_types/sr_segment_infos.dart';
+import 'package:fis_measure/interfaces/enums/uint.dart';
+import 'package:flutter/foundation.dart';
+
+import '../visuals/visual_area_interface.dart';
+import '../update_obj_base.dart';
+import '../visuals/normalization_coordinate.dart';
+import 'viewport_interface.dart';
+import 'image_boundary_interface.dart';
+import 'logical_coordinate_interface.dart';
+import 'physical_coordinate_interface.dart';
+import 'rect_region.dart';
+
+part 'viewport.ext.dart';
+
+abstract class ViewPort<TV extends IVisualArea, T extends IPhysicalCoordinate>
+    extends UpdateObjectBase implements IViewPort, ILogicalCoordinate {
+  static const int _RetryCountOnFailed = 5;
+  // ignore: non_constant_identifier_names
+  static final Version _CurrentVersion = Version(1, 0);
+
+  final physicalCoordinateChanged = FEventHandler<void>();
+
+  /* Fields */
+
+  int _retryOnDownloadFailed = 0;
+
+  @protected
+  final List<T> _availablePhysicals = <T>[];
+
+  ///a DMatrix defines functionality that enables transform a point in normalization to logical.
+  DMatrix? _normalizationToLogicalConvert;
+
+  /// a DMatrix defines functionality that enables transform a point in logical to normalization.
+  DMatrix? _logicalToNormalizationConvert;
+
+  bool _isLeftRight = false;
+  bool _isUpDown = false;
+  RectRegion? _region;
+  IImageBoundary? _imageBoundary;
+  T? _physical;
+  bool _physicalCoordinateReady = false;
+  bool _isDirty = false;
+
+  late final TV _visualArea;
+  late Unit? _xUnit;
+  late Unit? _yUnit;
+  late NormalizationCoordinate _normalization;
+
+  /* Properties */
+
+  @override
+  bool get physicalCoordinateReady => _physicalCoordinateReady;
+  NormalizationCoordinate get normalization => _normalization;
+
+  @override
+  IImageBoundary? get imageBoundary => _imageBoundary;
+  @override
+  set imageBoundary(IImageBoundary? value) {
+    if (_imageBoundary != value) {
+      _imageBoundary = value;
+      tryUpdate();
+    }
+  }
+
+  @override
+  T? get physical => _physical;
+  set physical(T? value) {
+    if (_physical == value) return;
+
+    var updateItem = _physical as UpdateObjectBase?;
+    if (updateItem != null) {
+      updateItem.updated.removeListener(_onPhysicalUpdated);
+    }
+    _physical = value;
+    updateItem = _physical as UpdateObjectBase?;
+    if (updateItem != null) {
+      // Physical coordinate is not ready, don't raise Updated event.
+      // subscribe PhysicalCoordinateChanged event if necessary.
+      updateItem.updated.addListener(_onPhysicalUpdated);
+    }
+    logger.i(
+      "Working coordinate for view port on visual area:${_visualArea.visualAreaType.name} changed to ${_physical == null ? "null" : _physical.runtimeType}",
+    );
+    _onPhysicalCoordinateChanged();
+  }
+
+  bool get isDirty => _isDirty;
+
+  @override
+  bool get isFlipHorizontal => _isLeftRight;
+  @override
+  set isFlipHorizontal(bool value) {
+    if (_isLeftRight != value) {
+      _isLeftRight = value;
+      tryUpdate();
+    }
+  }
+
+  @override
+  bool get isFlipVertical => _isUpDown;
+  @override
+  set isFlipVertical(bool value) {
+    if (_isUpDown != value) {
+      _isUpDown = value;
+      tryUpdate();
+    }
+  }
+
+  @override
+  RectRegion? get region => _region;
+  @override
+  set region(RectRegion? value) {
+    if (_region != value) {
+      _region = value;
+      tryUpdate();
+    }
+  }
+
+  TV get visualArea => _visualArea;
+
+  @override
+  Unit? get xUnit => _xUnit;
+  @override
+  set xUnit(Unit? value) {
+    if (_xUnit != value) {
+      _xUnit = value;
+      tryUpdate();
+    }
+  }
+
+  @override
+  Unit? get yUnit => _yUnit;
+  @override
+  set yUnit(Unit? value) {
+    if (_yUnit != value) {
+      _yUnit = value;
+      tryUpdate();
+    }
+  }
+
+  /* Contruction */
+
+  ViewPort(TV visualArea, Unit xUnit, Unit yUnit) {
+    _retryOnDownloadFailed = _RetryCountOnFailed;
+    _visualArea = visualArea;
+    _xUnit = xUnit;
+    _yUnit = yUnit;
+    _normalization = NormalizationCoordinate();
+  }
+
+  /* Public methods */
+
+  void checkModify(int modificationType) {
+    bool isNeedSynchronize = true;
+    if (isNeedSynchronize) {
+      _isDirty = true;
+    }
+  }
+
+  bool download() {
+    if (!isDirty) return true;
+
+    bool isValid = physical != null;
+    if (!isValid) return true;
+
+    IDisposable? locker;
+    try {
+      locker = lockOnUpdating();
+
+      updateNormalizationCoordinate();
+
+      bool successful = physical!.download(_visualArea, this);
+      _physicalCoordinateReady = successful;
+
+      _isDirty = false;
+      if (!successful) {
+        logger.w(
+          "ViewPort on visual area:${_visualArea.visualAreaType.name} download physicalcoordinate data from native failed!",
+        );
+        return false;
+      }
+    } catch (ex) {
+      _physicalCoordinateReady = false;
+      _retryOnDownloadFailed--;
+      if (_retryOnDownloadFailed == 0) {
+        _retryOnDownloadFailed = _RetryCountOnFailed;
+        _isDirty = false;
+      }
+
+      // if (!(ex is InvalidOperationException))
+      // {
+      //     logger.e("ViewPort.Download failed, ex:{0}.", ex);
+      // }
+    } finally {
+      if (locker != null) {
+        locker.dispose();
+      }
+    }
+
+    return true;
+  }
+
+  void updateNormalizationCoordinate() {
+    bool isModified = normalization.download(_visualArea);
+    if (isModified) {
+      tryUpdate();
+    }
+  }
+
+  DPoint convert(DPoint pointInNormalization) {
+    return _normalizationToLogicalConvert!
+        .transformWidthPoint(pointInNormalization);
+  }
+
+  VecPoint convertBackVec(VecPoint pointInLogical) {
+    return _logicalToNormalizationConvert!
+        .transformWithVecPoint(pointInLogical);
+  }
+
+  DPoint convertBack(DPoint pointInLogical) {
+    return _logicalToNormalizationConvert!.transformWidthPoint(pointInLogical);
+  }
+
+  DMatrix getConvertMatrix() {
+    return _normalizationToLogicalConvert!;
+  }
+
+  bool checkBoundary() {
+    if (physical != null && imageBoundary != null) {
+      return physical!.checkBoundary(imageBoundary!);
+    }
+    return false;
+  }
+
+  void registerPhysicalCoordinate(T? physicalCoordinate, bool isRegister) {
+    if (physicalCoordinate == null) {
+      logger
+          .e("Can't register/unregister a null instance physical coordinate!");
+      return;
+    }
+    if (_availablePhysicals.contains(physicalCoordinate)) {
+      _availablePhysicals.remove(physicalCoordinate);
+    }
+    if (isRegister) {
+      _availablePhysicals.add(physicalCoordinate);
+      logger.i(
+        "Reg coordinate ${physicalCoordinate.runtimeType}. visual area:${_visualArea.visualAreaType.name}",
+      );
+      physical = physicalCoordinate;
+    } else {
+      logger.i(
+        "UnReg coordinate ${physicalCoordinate.runtimeType}. visual area:${_visualArea.visualAreaType.name}",
+      );
+      if (physical == physicalCoordinate) {
+        physical =
+            _availablePhysicals.isNotEmpty ? _availablePhysicals.last : null;
+      }
+    }
+  }
+
+  @protected
+  void resetPhysicalCoordinates() {
+    _availablePhysicals.clear();
+    physical = null;
+  }
+
+  @override
+  void onUpdated() {
+    _logicalToNormalizationConvert = buildConvertMatrix();
+    _normalizationToLogicalConvert = _logicalToNormalizationConvert;
+    if (_normalizationToLogicalConvert!.hasInverse) {
+      _normalizationToLogicalConvert!.invert();
+    }
+  }
+
+  @protected
+  DMatrix buildConvertMatrix() {
+    var logicalToNormalization = DMatrix();
+    double horizontalOffset =
+        -1 * (isFlipHorizontal ? region!.right : region!.left);
+    double verticalOffset =
+        -1 * (isFlipVertical ? region!.bottom : region!.top);
+    double xScale =
+        (isFlipHorizontal ? -1 : 1) / (region!.right - region!.left);
+    double yScale = (isFlipVertical ? -1 : 1) / (region!.bottom - region!.top);
+    logicalToNormalization.translate(horizontalOffset, verticalOffset);
+    logicalToNormalization.scale(xScale, yScale);
+    return logicalToNormalization;
+  }
+
+  /* Private methods */
+
+  void _onPhysicalUpdated(Object sender, dynamic eventArgs) {
+    tryUpdate();
+  }
+
+  void _onPhysicalCoordinateChanged() {
+    physicalCoordinateChanged.emit(this, null);
+  }
+}

+ 31 - 0
lib/base_models/viewports/viewport.ext.dart

@@ -0,0 +1,31 @@
+part of 'viewport.dart';
+
+// class _ViewPortLogicalCoordinatePart implements ILogicalCoordinate {
+//   _ViewPortLogicalCoordinatePart(this.viewPort);
+
+//   final ViewPort viewPort;
+
+//   @override
+//   IImageBoundary? get imageBoundary => viewPort.imageBoundary;
+//   @override
+//   set imageBoundary(IImageBoundary? value) => viewPort.imageBoundary = value;
+
+//   @override
+//   bool get isFlipHorizontal => viewPort.isFlipHorizontal;
+//   @override
+//   set isFlipHorizontal(bool value) => viewPort.isFlipHorizontal = value;
+
+//   @override
+//   bool get isFlipVertical => viewPort.isFlipVertical;
+//   @override
+//   set isFlipVertical(bool value) => viewPort.isFlipVertical = value;
+
+//   RectRegion? get region;
+//   set region(RectRegion? value);
+
+//   Unit? get xUnit;
+//   set xUnit(Unit? value);
+
+//   Unit? get yUnit;
+//   set yUnit(Unit? value);
+// }

+ 5 - 5
lib/base_models/view_port/view_port_interface.dart → lib/base_models/viewports/viewport_interface.dart

@@ -9,12 +9,12 @@ abstract class IViewPort {
   bool get physicalCoordinateReady;
 
   /// Gets a coordinate system in Physical  which contains physical relative data for a visual area.
-  IPhysicalCoordinate get physical;
+  IPhysicalCoordinate? get physical;
 
   /// Gets a Boundary that specifies the desired region of valid image.
   ///
   /// remarks: Image boundary may outside the Region </remarks>
-  IImageBoundary get imageBoundary;
+  IImageBoundary? get imageBoundary;
 
   /// Gets a value indicating whether to flip the image horizontally.
   bool get isFlipHorizontal;
@@ -29,7 +29,7 @@ abstract class IViewPort {
   /// Region of rendered area is relevant to LayoutSchema.
   ///
   /// Region is match to the NormalizationCoordinate.Region
-  RectRegion get region;
+  RectRegion? get region;
 
   /// Gets the unit of X-axis(Horizontal),
   ///
@@ -38,7 +38,7 @@ abstract class IViewPort {
   /// TissueTimeMotion:XUnit=Units.s,YUnit=Units.cm
   ///
   /// DopplerTimeMotion:XUnit=Units.s,YUnit=Unit.cms
-  Unit get xUnit;
+  Unit? get xUnit;
 
   /// Gets the unit of Y-axis(Vertical),
   ///
@@ -47,7 +47,7 @@ abstract class IViewPort {
   /// TissueTimeMotion:XUnit=Units.s,YUnit=Units.cm
   ///
   /// DopplerTimeMotion:XUnit=Units.s,YUnit=Unit.cms
-  Unit get yUnit;
+  Unit? get yUnit;
 
   /// Occurs when the value of the properties changes on this accessory.
   late final FEventHandler updated;

+ 0 - 0
lib/base_models/visual_areas/flow_area.dart


+ 3 - 0
lib/base_models/visual_areas/tissue_area.dart

@@ -0,0 +1,3 @@
+import 'package:fis_measure/base_models/visuals/visual_area_interface.dart';
+
+class TissueArea {}//implements IVisualArea{}

+ 61 - 0
lib/base_models/visual_areas/visual_area.dart

@@ -0,0 +1,61 @@
+import 'package:fis_common/event/event_type.dart';
+import 'package:fis_measure/interfaces/collections/changed_args.dart';
+import 'package:flutter/foundation.dart';
+
+import '../adorners/adorner_interface.dart';
+import '../adorners/sychro_adorner_interface.dart';
+import '../modes/mode.dart';
+import '../viewports/viewport_interface.dart';
+import '../visuals/visual_area_interface.dart';
+
+abstract class VisualArea<T extends IViewPort>
+    implements IVisualArea, ISychroAdornerContainer {
+  final accessoryCollectionChanged =
+      FEventHandler<CollectionChangedArgs<IAdorner>>();
+
+  /// Occurs when adorner in AdornerCollection is either added, removed.
+  @override
+  final adornerCollectionChanged =
+      FEventHandler<CollectionChangedArgs<IAdorner>>();
+
+  /// Occurs when the value of the IsVisible property changes on this visual area.
+  final isVisibleChanged = FEventHandler<void>();
+
+  /// Occurs when the correspondent mode of this visual area changes.
+  final modeChanged = FEventHandler<void>();
+
+  @override
+  final viewModeChanged = FEventHandler<void>();
+
+  IMode? _mode;
+  bool _detailView = false;
+  T? _viewPort;
+  // IVisual? _originalVisual;
+
+  // TODO: set List to NotificationList
+  late List<IAdorner> _adorners;
+  late List<IAdorner> _accessories;
+
+  @override
+  List<IAdorner> get adorners => _adorners;
+  List<IAdorner> get accessories => _accessories;
+  @override
+  bool get isVisible => _mode != null;
+  @override
+  T? get viewPort => _viewPort;
+  @override
+  IMode? get mode => _mode;
+
+  @protected
+  void onViewModeChanged() {
+    viewModeChanged.emit(this, null);
+  }
+}
+
+abstract class ISychroAdornerContainer {
+  void addSychroAdorner(ISychroAdorner sychroAdorners);
+
+  void addSychroAdorners(List<ISychroAdorner> sychroAdorners);
+
+  void removeSychroAdorners(List<ISychroAdorner> sychroAdorners);
+}

+ 18 - 0
lib/base_models/visuals/normalization_coordinate.dart

@@ -1,4 +1,7 @@
 import 'package:fis_measure/interfaces/date_types/matrix.dart';
+import 'package:fis_measure/interfaces/date_types/rect.dart';
+
+import 'visual_area_interface.dart';
 
 class NormalizationCoordinate {
   // DMatrix _regionToExtendedRegionConvert;
@@ -6,4 +9,19 @@ class NormalizationCoordinate {
   // DMatrix _visualToExtendedRegionConvert;
   // DMatrix _visualToExtendedRegionConvertBack;
   // static bool? _canEcgAttachToTissue;
+
+  DRect? _region;
+  DRect? get region => _region;
+  // set region(DRect? value) => _region = region;
+
+  bool download(IVisualArea area) {
+    // var regions = GetViewPortRegions(area);
+    // if (region != regions[0] || ExtendedRegion != regions[1]) {
+    //   Region = regions[0];
+    //   ExtendedRegion = regions[1];
+    //   BuildConvertMatrix();
+    //   return true;
+    // }
+    return false;
+  }
 }

+ 4 - 4
lib/base_models/visuals/visual_area.dart → lib/base_models/visuals/visual_area_interface.dart

@@ -1,10 +1,10 @@
 import 'package:fis_common/event/event_type.dart';
 import 'package:fis_measure/base_models/modes/mode.dart';
-import 'package:fis_measure/base_models/view_port/view_port_interface.dart';
+import 'package:fis_measure/base_models/viewports/viewport_interface.dart';
 import 'package:fis_measure/interfaces/collections/changed_args.dart';
 import 'package:fis_measure/interfaces/enums/visual_area_type.dart';
 
-import 'adorner.dart';
+import '../adorners/adorner_interface.dart';
 
 abstract class IVisualArea {
   bool get detailView;
@@ -21,14 +21,14 @@ abstract class IVisualArea {
 
   VisualAreaTypeEnum get visualAreaType;
 
-  IMode get mode;
+  IMode? get mode;
 
   /// Gets a value indicating whether correspondent mode is not null.
   /// If a visual area is visible, the Mode is not null, otherwise Mode is null.
   bool get isVisible;
 
   /// Gets a ViewPort object that represents the view port info of this area.
-  IViewPort get viewPort;
+  IViewPort? get viewPort;
 
   bool get isZoomArea;
 

+ 1 - 0
lib/base_models/visuals/visual_interface.dart

@@ -0,0 +1 @@
+abstract class IVisual {}

+ 118 - 0
lib/interfaces/base/version.dart

@@ -0,0 +1,118 @@
+class Version implements Comparable<Version> {
+  final int major;
+  final int minor;
+  final int? build;
+  final int? revision;
+
+  Version(
+    this.major,
+    this.minor, [
+    this.build,
+    this.revision,
+  ]);
+
+  Version incrementMajor() => Version(major + 1, 0);
+
+  Version incrementMinor() => Version(major, minor + 1);
+
+  Version incrementBuild() => Version(major, minor, (build ?? 0) + 1);
+
+  Version incrementRevision() =>
+      Version(major, minor, build, (revision ?? 0) + 1);
+
+  static Version parse(String input) {
+    final str = input.trim();
+    if (str.isEmpty) {
+      throw const FormatException("Cannot parse empty string into version.");
+    }
+    final segments = str.split('.');
+    if (segments.length < 2) {
+      throw const FormatException(
+          "Format error: major and minor are required.");
+    }
+    if (segments.any((e) => !_isIntger(e))) {
+      throw const FormatException("Format error: segment can only be intger.");
+    }
+    final len = segments.length;
+    int _major = int.parse(segments[0]);
+    int _minor = int.parse(segments[1]);
+    int? _build;
+    int? _revison;
+    if (len > 2) _build = int.parse(segments[2]);
+    if (len > 3) _revison = int.parse(segments[3]);
+    return Version(_major, _minor, _build, _revison);
+  }
+
+  @override
+  int compareTo(Version? other) {
+    if (other == null) {
+      throw ArgumentError.notNull("other");
+    }
+
+    return _compare(this, other);
+  }
+
+  @override
+  String toString() {
+    final _build = build ?? (revision == null ? null : 0);
+    return <int?>[major, minor, _build, revision]
+        .where((e) => e != null)
+        .join('.');
+  }
+
+  @override
+  bool operator ==(Object other) =>
+      other is Version && _compare(this, other) == 0;
+  bool operator <(dynamic o) => o is Version && _compare(this, o) < 0;
+  bool operator <=(dynamic o) => o is Version && _compare(this, o) <= 0;
+  bool operator >(dynamic o) => o is Version && _compare(this, o) > 0;
+  bool operator >=(dynamic o) => o is Version && _compare(this, o) >= 0;
+
+  @override
+  int get hashCode => toString().hashCode;
+
+  static int _compare(Version? a, Version? b) {
+    if (a == null) {
+      throw ArgumentError.notNull("a");
+    }
+
+    if (b == null) {
+      throw ArgumentError.notNull("b");
+    }
+
+    if (a.major > b.major) return 1;
+    if (a.major < b.major) return -1;
+
+    if (a.minor > b.minor) return 1;
+    if (a.minor < b.minor) return -1;
+
+    if (b.build != null) {
+      if (a.build != null) {
+        if (a.build! > b.build!) return 1;
+        if (a.build! < b.build!) return -1;
+      } else {
+        if (b.build! > 0 && b.revision != null && b.revision! > 0) {
+          return -1;
+        }
+      }
+    }
+    if (b.revision != null) {
+      if (a.revision != null) {
+        if (a.revision! > b.revision!) return 1;
+        if (a.revision! < b.revision!) return -1;
+      } else {
+        if (b.revision! > 0) {
+          return -1;
+        }
+      }
+    }
+    return 0;
+  }
+
+  static bool _isIntger(String? s) {
+    if (s == null) {
+      return false;
+    }
+    return int.tryParse(s) != null;
+  }
+}

+ 6 - 1
lib/interfaces/date_types/int_rect.dart

@@ -12,7 +12,12 @@ class IntRect {
 
   IntRect();
 
-  factory IntRect.fill(int left, int top, int width, int height) {
+  factory IntRect.fill(
+    int left,
+    int top,
+    int width,
+    int height,
+  ) {
     final rect = IntRect()
       .._left = left
       .._top = top

+ 33 - 1
test/widget_test.dart

@@ -4,7 +4,9 @@
 // utility that Flutter provides. For example, you can send tap and scroll
 // gestures. You can also use WidgetTester to find child widgets in the widget
 // tree, read text, and verify that the values of widget properties are correct.
-
+import 'dart:math' as math;
+import 'package:fis_measure/interfaces/date_types/point.dart';
+import 'package:fis_measure/interfaces/date_types/skew_transform.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
 
@@ -12,4 +14,34 @@ void main() {
   testWidgets('Counter increments smoke test', (WidgetTester tester) async {
     // Build our app and trigger a frame.
   });
+
+  test("linear_tissue", () {
+    const steer = 3.0;
+    const depthStart = 1.0;
+    const depthEnd = 3.99999991059303;
+    final back = DSkewTransform(steer, 0, 0, (depthEnd - depthStart) / 2);
+    final inverse = back.inverse;
+    final p1 = inverse.transform(DPoint(0.84, 0.24907692307692308));
+    final p2 = inverse.transform(DPoint(0.84, 0.4989230769230769));
+    final d = (p2 - p1).lengthSquared;
+    print(p1);
+    print(p2);
+    print(d);
+  });
+
+  test("convex_tissue", () {
+    const zeroRadius = 5.1225;
+    final logicalPoint = DPoint(1, 1);
+    double depth = math.sqrt(math.pow(logicalPoint.x, 2) +
+            math.pow(logicalPoint.y + zeroRadius, 2)) -
+        zeroRadius;
+    double tilt =
+        toDegrees(math.atan2(logicalPoint.x, logicalPoint.y + zeroRadius));
+    final d = DPoint(tilt, depth);
+    print(d);
+  });
+}
+
+double toDegrees(double angle) {
+  return angle / math.pi * 180;
 }