Skip to content

Instantly share code, notes, and snippets.

@roipeker
Last active February 1, 2021 19:39
Show Gist options
  • Save roipeker/8c5c69889e4b549bc6d270208730fd92 to your computer and use it in GitHub Desktop.
Save roipeker/8c5c69889e4b549bc6d270208730fd92 to your computer and use it in GitHub Desktop.
graphx dartpad test
/// ---- graphx imports.
import 'dart:collection';
import 'dart:convert';
import 'dart:developer' as dev;
import 'dart:math' as math;
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart' as painting;
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/scheduler.dart';
/// ---- end graphx imports.
class MyScene extends Sprite {
Shape shape;
@override
void addedToStage() {
stage.color = Colors.red.value;
shape = Shape();
addChild(shape);
shape.graphics.beginFill(0x0).drawCircle(0, 0, 20).beginFill(0xff0000).drawCircle(0, 0, 10).endFill();
shape.setPosition(100, 100);
Keyboard.init(stage);
stage.onMouseDown.add((event) => shape.tween(duration: .3, x: event.stagePosition.x, y: event.stagePosition.y));
stage.onEnterFrame.add((delta) {
final pressed = Keyboard.isDown;
double thrust = pressed(Keyboard.shift) ? 2 : 1;
double speed = 3 * thrust;
if (pressed(Keyboard.left)) {
shape.x -= speed;
} else if (pressed(Keyboard.right)) {
shape.x += speed;
}
if (pressed(Keyboard.up)) {
shape.y -= speed;
} else if (pressed(Keyboard.down)) {
shape.y += speed;
}
});
}
}
void main() {
runScene(MyScene());
}
void runScene(Sprite content, [String title]) {
title ??= "graphx™🌀 playground";
final controller = SceneController(back: content, config: SceneConfig.games);
final scene = SceneBuilderWidget(builder: () => controller);
runApp(MaterialApp(home: Scaffold(appBar: AppBar(title: Text(title), backgroundColor: kColorTransparent, elevation: 0), body: scene), title: title));
}
class SceneConfig {
static final SceneConfig static = SceneConfig(usePointer: false, useKeyboard: false, painterWillChange: false, autoUpdateRender: false, useTicker: false);
static final SceneConfig games = SceneConfig(autoUpdateRender: true, useTicker: true, useKeyboard: true, usePointer: true);
static final SceneConfig tools = games;
static final SceneConfig interactive = SceneConfig(autoUpdateRender: true, useTicker: true, useKeyboard: false, usePointer: true);
static final SceneConfig autoRender = SceneConfig(autoUpdateRender: true, useTicker: true, useKeyboard: false, usePointer: false);
static SceneConfig defaultConfig = interactive;
bool rebuildOnHotReload, useKeyboard, usePointer, useTicker, painterWillChange, isPersistent = false, autoUpdateRender = true;
SceneConfig({this.rebuildOnHotReload = true, this.useKeyboard = false, this.usePointer = false, this.useTicker = false, this.isPersistent = false, this.autoUpdateRender = true, this.painterWillChange}) {
if (autoUpdateRender) useTicker = true;
}
bool painterMightChange() {
if (useTicker || autoUpdateRender || usePointer || useKeyboard) return true;
return painterWillChange ?? false;
}
}
class SceneController {
Signal _onHotReload;
Signal get onHotReload => _onHotReload ??= Signal();
ScenePainter backScene, frontScene;
GxTicker get ticker {
if (_ticker == null) throw 'You need to enable ticker usage with SceneBuilderWidget( useTicker=true ) or RootScene::setup(useTicker: true), or RootScene::setup(autoUpdateAndRender: true)';
return _ticker;
}
KeyboardManager get keyboard => _keyboard;
PointerManager get pointer => _pointer;
KeyboardManager _keyboard;
PointerManager _pointer;
GxTicker _ticker;
InputConverter $inputConverter;
SceneConfig get config => _config;
final _config = SceneConfig();
GxRect Function() resolveWindowBounds;
int id = -1;
bool _isInited = false;
set config(SceneConfig sceneConfig) {
_config.rebuildOnHotReload = sceneConfig.rebuildOnHotReload ?? true;
_config.autoUpdateRender = sceneConfig.autoUpdateRender ?? true;
_config.isPersistent = sceneConfig.isPersistent ?? false;
_config.painterWillChange = sceneConfig.painterWillChange ?? true;
_config.useKeyboard = sceneConfig.useKeyboard ?? false;
_config.usePointer = sceneConfig.usePointer ?? false;
_config.useTicker = sceneConfig.useTicker ?? false;
}
SceneController({Sprite back, Sprite front, SceneConfig config}) {
assert(back != null || front != null);
if (back != null) backScene = ScenePainter(this, back);
if (front != null) frontScene = ScenePainter(this, front);
this.config = config ?? SceneConfig.defaultConfig;
}
void $init() {
if (_isInited) return;
setup();
if (_hasTicker()) {
_ticker = GxTicker();
_ticker.onFrame.add(_onTick);
if (_anySceneAutoUpdate()) _ticker.resume();
}
_initInput();
_isInited = true;
}
void setup() {
backScene?.$setup();
frontScene?.$setup();
}
void _onTick(double elapsed) {
frontScene?.tick(elapsed);
backScene?.tick(elapsed);
}
void resumeTicker() {
ticker?.resume();
}
void dispose() {
if (_config.isPersistent) return;
_onHotReload?.removeAll();
frontScene?.dispose();
backScene?.dispose();
_ticker?.dispose();
_ticker = null;
_isInited = false;
}
CustomPainter buildBackPainter() => backScene?.buildPainter();
CustomPainter buildFrontPainter() => frontScene?.buildPainter();
void _initInput() {
if (_config.useKeyboard) _keyboard ??= KeyboardManager();
if (_config.usePointer) _pointer ??= PointerManager();
if (_config.useKeyboard || _config.usePointer) $inputConverter ??= InputConverter(_pointer, _keyboard);
}
void reassembleWidget() {
_onHotReload?.dispatch();
if (_config.rebuildOnHotReload) {
dispose();
$init();
}
}
bool _sceneAutoUpdate(ScenePainter scene) => scene?.autoUpdateAndRender ?? false;
bool _anySceneAutoUpdate() => _sceneAutoUpdate(backScene) || _sceneAutoUpdate(frontScene);
bool _hasTicker() => _anySceneAutoUpdate() || _config.useTicker;
}
class ScenePainter with EventDispatcherMixin {
static ScenePainter current;
SceneController core;
Sprite root;
bool needsRepaint = false;
bool useOwnCanvas = false;
bool _ownCanvasNeedsRepaint = false;
Canvas $canvas;
Size size = Size.zero;
Stage _stage;
Stage get stage => _stage;
bool _isReady = false;
bool get isReady => _isReady;
bool autoUpdateAndRender = false;
int $currentFrameId = 0;
double $runTime = 0;
double maxFrameTime = -1;
double $currentFrameDeltaTime = 0;
double $accumulatedFrameDeltaTime = 0;
EventSignal<double> _onUpdate;
EventSignal<double> get onUpdate => _onUpdate ??= EventSignal<double>();
ui.Picture _canvasPicture;
var _mouseMoveInputDetected = false;
var _lastMouseX = -1000000.0;
var _lastMouseY = -1000000.0;
var _lastMouseOut = false;
MouseInputData _lastMouseInput;
ScenePainter(this.core, this.root) {
_stage = Stage(this);
makeCurrent();
}
CustomPainter buildPainter() => _GraphicsPainter(this);
void _paint(Canvas canvas, Size size) {
if (this.size != size) {
this.size = size;
_stage.$initFrameSize(size);
}
$canvas = canvas;
if (!_isReady) {
_isReady = true;
_initMouseInput();
_stage.addChild(root);
_stage?.$onResized?.dispatch();
}
if (useOwnCanvas) {
_pictureFromCanvas();
} else {
_stage.paint($canvas);
}
}
void $setup() {
makeCurrent();
GTween.registerCommonWraps();
autoUpdateAndRender = core.config.autoUpdateRender;
}
void _createPicture() {
final _recorder = ui.PictureRecorder();
final _canvas = Canvas(_recorder);
_stage.paint(_canvas);
_canvasPicture = _recorder.endRecording();
}
void tick(double time) {
makeCurrent();
if (autoUpdateAndRender || time != null) {
$currentFrameId++;
$runTime += time;
$update(time);
}
_onUpdate?.dispatch(time);
$render();
if (useOwnCanvas) {
_ownCanvasNeedsRepaint = true;
}
}
void $update(double deltaTime) {
if (maxFrameTime != -1 && deltaTime > maxFrameTime) {
deltaTime = maxFrameTime;
}
$currentFrameDeltaTime = deltaTime;
$accumulatedFrameDeltaTime += $currentFrameDeltaTime;
GTween.ticker.dispatch(deltaTime);
_stage.$tick(deltaTime);
if (_hasPointer) {
_detectMouseMove();
}
}
void _detectMouseMove() {
if (!_mouseMoveInputDetected && _lastMouseX != -1000000) {
final input = MouseInputData(
target: _stage,
dispatcher: _stage,
type: MouseInputType.still,
);
input.uid = ++MouseInputData.uniqueId;
input.localPosition.setTo(_lastMouseX, _lastMouseY);
input.stagePosition.setTo(_lastMouseX, _lastMouseY);
input.mouseOut = _lastMouseOut;
if (_lastMouseInput != null) {
input.buttonDown = _lastMouseInput.buttonDown;
input.rawEvent = _lastMouseInput.rawEvent;
input.buttonsFlags = _lastMouseInput.buttonsFlags;
}
_mouseInputHandler(input);
}
_mouseMoveInputDetected = false;
}
void _mouseInputHandler(MouseInputData input) {
input.time = $accumulatedFrameDeltaTime;
if (input.type == MouseInputType.move || input.type == MouseInputType.exit) {
_mouseMoveInputDetected = true;
_lastMouseX = input.stageX;
_lastMouseY = input.stageY;
_lastMouseOut = input.mouseOut;
} else if (input.type == MouseInputType.down) {
_lastMouseX = input.stageX;
_lastMouseY = input.stageY;
}
_lastMouseInput = input;
stage.captureMouseInput(input);
}
void $render() {
if (autoUpdateAndRender || needsRepaint) {
requestRender();
}
}
void requestRender() {
notify();
}
void _initMouseInput() {
core?.pointer?.onInput?.add(_onInputHandler);
}
void _onInputHandler(PointerEventData e) {
var input = MouseInputData(
target: stage,
dispatcher: stage,
type: MouseInputData.fromNativeType(e.type),
);
input.rawEvent = e;
input.buttonsFlags = e.rawEvent.buttons;
input.buttonDown = e.rawEvent.down;
input.localPosition.setTo(e.localX, e.localY);
input.scrollDelta.setTo(e.scrollDelta?.dx ?? 0, e.scrollDelta?.dy ?? 0);
input.stagePosition.setTo(e.stagePosition.x, e.stagePosition.y);
input.uid = ++MouseInputData.uniqueId;
if (input.type == MouseInputType.exit) {
input.mouseOut = true;
}
_mouseInputHandler(input);
}
void makeCurrent() {
ScenePainter.current = this;
}
void _pictureFromCanvas() {
if (_ownCanvasNeedsRepaint) {
_ownCanvasNeedsRepaint = false;
_createPicture();
}
if (_canvasPicture != null) {
$canvas.drawPicture(_canvasPicture);
}
}
@override
void dispose() {
_isReady = false;
size = Size.zero;
_stage?.dispose();
core?.pointer?.onInput?.remove(_onInputHandler);
_onUpdate?.removeAll();
_onUpdate = null;
super.dispose();
}
bool get _hasPointer => core?.pointer?.onInput != null;
bool shouldRepaint() => needsRepaint;
}
class _GraphicsPainter extends CustomPainter {
final ScenePainter scene;
_GraphicsPainter(this.scene) : super(repaint: scene);
@override
void paint(Canvas canvas, Size size) {
scene._paint(canvas, size);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => scene.shouldRepaint();
}
enum DebugBoundsMode { internal, stage }
class DisplayBoundsDebugger {
static DebugBoundsMode debugBoundsMode = DebugBoundsMode.internal;
static bool enabled = true, debugAll = false;
static final Paint _debugPaint = Paint()
..style = PaintingStyle.stroke
..color = Color(0xff00FFFF)
..strokeWidth = 1;
final DisplayObjectContainer _root;
Canvas canvas;
static final GxRect _sHelpRect = GxRect();
DisplayBoundsDebugger(DisplayObjectContainer root) : _root = root;
void render() {
if (debugBoundsMode == DebugBoundsMode.internal || !enabled) return;
_renderChildren(_root);
}
void _renderChildren(DisplayObjectContainer obj) {
if (obj.$debugBounds || debugAll) _renderBounds(obj);
for (final child in obj.children) {
if (child is DisplayObjectContainer) {
_renderChildren(child);
} else {
if (child.$debugBounds || debugAll) {
_renderBounds(child);
}
}
}
}
void _renderBounds(DisplayObject obj) {
obj.getBounds(_root, _sHelpRect);
final _paint = obj.$debugBoundsPaint ?? _debugPaint;
final linePaint = _paint.clone();
linePaint.color = linePaint.color.withOpacity(.3);
final rect = _sHelpRect.toNative();
canvas.drawLine(rect.topLeft, rect.bottomRight, linePaint);
canvas.drawLine(rect.topRight, rect.bottomLeft, linePaint);
canvas.drawRect(rect, _paint);
}
}
class Bitmap extends DisplayObject {
static final _sHelperMatrix = GxMatrix();
static final _sHelperPoint = GxPoint();
@override
String toString() => '$runtimeType (Bitmap2)${name != null ? ' {name: $name}' : ''}';
GTexture _texture;
GTexture get texture => _texture;
double $originalPivotX = 0;
double $originalPivotY = 0;
@override
set pivotX(double value) {
$originalPivotX = value;
super.pivotX = value;
}
@override
set pivotY(double value) {
$originalPivotY = value;
super.pivotY = value;
}
set texture(GTexture value) {
if (_texture == value) return;
_texture = value;
if (_texture != null) {
pivotX = -_texture.pivotX + $originalPivotX;
pivotY = -_texture.pivotY + $originalPivotY;
}
requiresRedraw();
}
Bitmap([GTexture texture]) {
this.texture = texture;
}
@override
GxRect getBounds(DisplayObject targetSpace, [GxRect out]) {
out ??= GxRect();
final matrix = _sHelperMatrix;
matrix.identity();
getTransformationMatrix(targetSpace, matrix);
if (texture != null) {
var rect = texture.getBounds();
out = MatrixUtils.getTransformedBoundsRect(
matrix,
rect,
out,
);
} else {
matrix.transformCoords(0, 0, _sHelperPoint);
out.setTo(_sHelperPoint.x, _sHelperPoint.y, 0, 0);
}
return out;
}
final _paint = Paint()..filterQuality = FilterQuality.high;
Paint get nativePaint => _paint;
@override
set alpha(double value) {
super.alpha = value;
_paint.color = _paint.color.withOpacity($alpha);
}
@override
set colorize(Color value) {
if ($colorize == value) return;
super.colorize = value;
_paint.colorFilter = $hasColorize ? PainterUtils.getColorize($colorize) : null;
}
@override
set filters(List<BaseFilter> value) {
if ($filters == value) return;
$filters = value;
if ($filters == null) {
_paint.imageFilter = null;
_paint.maskFilter = null;
}
}
@override
void paint(Canvas canvas) {
if (texture == null) return;
_hasScale9Grid = texture.scale9Grid != null;
if (_hasScale9Grid) {
_adjustScaleGrid();
}
super.paint(canvas);
}
@override
void $applyPaint(Canvas canvas) {
if (hasFilters) {
filters.forEach((filter) {
filter.update();
filter.resolvePaint(_paint);
});
}
texture?.render(canvas, _paint);
if (_hasScale9Grid) {
setScale(_buffScaleX, _buffScaleY);
}
}
bool _hasScale9Grid;
double _buffScaleX, _buffScaleY;
final GxRect _cachedBounds = GxRect();
void _adjustScaleGrid() {
_buffScaleX = scaleX;
_buffScaleY = scaleY;
super.pivotX = $originalPivotX * _buffScaleX;
super.pivotY = $originalPivotY * _buffScaleY;
_cachedBounds.x = _cachedBounds.y = 0;
_cachedBounds.width = texture.width * _buffScaleX;
_cachedBounds.height = texture.height * _buffScaleY;
texture.scale9GridDest = _cachedBounds;
setScale(1, 1);
}
}
typedef SortChildrenCallback = int Function(DisplayObject object1, DisplayObject object2);
abstract class DisplayObjectContainer extends DisplayObject {
final children = <DisplayObject>[];
DisplayObjectContainer() {
allowSaveLayer = true;
}
@override
String toString() {
final msg = name != null ? ' {name: $name}' : '';
return '$runtimeType (DisplayObjectContainer)$msg';
}
static final _sHitTestMatrix = GxMatrix();
static final _sHitTestPoint = GxPoint();
static GxMatrix $sBoundsMatrix = GxMatrix();
static GxPoint $sBoundsPoint = GxPoint();
bool mouseChildren = true;
@override
void captureMouseInput(MouseInputData input) {
if (!$hasTouchableArea) return;
if (mouseChildren) for (var i = children.length - 1; i >= 0; --i) children[i].captureMouseInput(input);
super.captureMouseInput(input);
}
@override
GxRect getBounds(DisplayObject targetSpace, [GxRect out]) {
out ??= GxRect();
final len = children.length;
if (len == 0) {
getTransformationMatrix(targetSpace, $sBoundsMatrix);
$sBoundsMatrix.transformCoords(0, 0, $sBoundsPoint);
out.setTo($sBoundsPoint.x, $sBoundsPoint.y, 0, 0);
return out;
} else if (len == 1) {
return children[0].getBounds(targetSpace, out);
} else {
var minX = 10000000.0;
var maxX = -10000000.0;
var minY = 10000000.0;
var maxY = -10000000.0;
final len = numChildren;
for (var i = 0; i < len; ++i) {
children[i].getBounds(targetSpace, out);
if (out.isEmpty) continue;
if (minX > out.x) minX = out.x;
if (maxX < out.right) maxX = out.right;
if (minY > out.y) minY = out.y;
if (maxY < out.bottom) maxY = out.bottom;
}
out.setTo(minX, minY, maxX - minX, maxY - minY);
return out;
}
}
List<DisplayObject> getObjectsUnderPoint(GxPoint localPoint) {
final result = <DisplayObject>[];
if (!$hasTouchableArea || !mouseEnabled || !hitTestMask(localPoint)) return result;
final numChild = children.length;
DisplayObject target;
for (var i = numChild - 1; i >= 0; --i) {
var child = children[i];
if (child.isMask) continue;
_sHitTestMatrix.copyFrom(child.transformationMatrix);
_sHitTestMatrix.invert();
_sHitTestMatrix.transformCoords(localPoint.x, localPoint.y, _sHitTestPoint);
if (child is DisplayObjectContainer) result.addAll(child.getObjectsUnderPoint(_sHitTestPoint));
target = child.hitTest(_sHitTestPoint);
if (target != null) result.add(child);
}
return result;
}
@override
DisplayObject hitTest(GxPoint localPoint, [bool useShape = false]) {
if (!$hasTouchableArea || !mouseEnabled || !hitTestMask(localPoint)) return null;
if (!mouseChildren) return super.hitTest(localPoint, useShape);
final numChild = children.length;
for (var i = numChild - 1; i >= 0; --i) {
var child = children[i];
if (child.isMask) continue;
_sHitTestMatrix.copyFrom(child.transformationMatrix);
_sHitTestMatrix.invert();
_sHitTestMatrix.transformCoords(
localPoint.x,
localPoint.y,
_sHitTestPoint,
);
final target = child.hitTest(_sHitTestPoint);
if (target != null) {
return mouseChildren ? this : target;
}
}
return null;
}
DisplayObject addChild(DisplayObject child) {
return addChildAt(child, children.length);
}
DisplayObject addChildAt(DisplayObject child, int index) {
if (child == null) throw "::child can't be null";
if (index < 0 || index > children.length) {
throw RangeError('Invalid child index');
}
requiresRedraw();
if (child.$parent == this) {
setChildIndex(child, index);
} else {
children.insert(index, child);
child.removeFromParent();
child.$setParent(this);
child.$onAdded?.dispatch();
if (stage != null) {
child.$onAddedToStage?.dispatch();
if (child is DisplayObjectContainer) {
_broadcastChildrenAddedToStage(child);
}
child.addedToStage();
}
}
return child;
}
int getChildIndex(DisplayObject child) => children.indexOf(child);
void setChildIndex(DisplayObject child, int index) {
final old = getChildIndex(child);
if (old == index) return;
if (old == -1) {
throw ArgumentError.value(child, null, 'Not a child of this container');
}
children.removeAt(old);
children.insert(index.clamp(0, numChildren), child);
requiresRedraw();
}
void swapChildren(DisplayObject child1, DisplayObject child2) {
final idx1 = getChildIndex(child1);
final idx2 = getChildIndex(child2);
if (idx1 == -1 || idx2 == -1) {
throw ArgumentError('Not a child of this container');
}
swapChildrenAt(idx1, idx2);
}
void swapChildrenAt(int index1, int index2) {
final child1 = getChildAt(index1);
final child2 = getChildAt(index2);
children[index1] = child2;
children[index2] = child1;
requiresRedraw();
}
void sortChildren(SortChildrenCallback compare) {
children.sort(compare);
requiresRedraw();
}
DisplayObject getChildAt(int index) {
final len = children.length;
if (index < 0) index = len + index;
if (index >= 0 && index < len) return children[index];
throw RangeError('Invalid child index');
}
DisplayObject getChildByName(String name) {
for (final child in children) {
if (child.name == name) return child;
}
return null;
}
void removeChildren([
int fromIndex = 0,
int endIndex = -1,
bool dispose = false,
]) {
if (endIndex < 0 || endIndex >= children.length) {
endIndex = children.length - 1;
}
for (var i = fromIndex; i <= endIndex; ++i) {
removeChildAt(
fromIndex,
dispose,
);
}
}
int get numChildren => children.length;
bool get hasChildren => children.isNotEmpty;
bool contains(DisplayObject child, [bool recursive = true]) {
if (!recursive) return children.contains(child);
while (child != null) {
if (child == this) return true;
child = child.$parent;
}
return false;
}
DisplayObject removeChildAt(int index, [bool dispose = false]) {
if (index >= 0 && index < children.length) {
requiresRedraw();
final child = children[index];
child.$onRemoved?.dispatch();
if (stage != null) {
child.$onRemovedFromStage?.dispatch();
child.removedFromStage();
if (child is DisplayObjectContainer) {
_broadcastChildrenRemovedFromStage(child);
}
}
child.$setParent(null);
index = children.indexOf(child);
if (index >= 0) children.removeAt(index);
if (dispose) child.dispose();
return child;
}
throw 'Invalid child index';
}
DisplayObject removeChild(DisplayObject child, [bool dispose = false]) {
if (child == null || child?.$parent != this) return null;
final index = getChildIndex(child);
if (index > -1) return removeChildAt(index, dispose);
throw 'Invalid child index';
}
@override
void update(double delta) {
for (var child in children) {
child.update(delta);
}
}
@override
void $applyPaint(Canvas canvas) {
if (!$hasVisibleArea) return;
for (var child in children) {
if (child.$hasVisibleArea) {
var mask = child.$mask;
if (mask != null) {
_drawMask(mask, child);
}
child.paint(canvas);
if (mask != null) {
_eraseMask(mask, child);
}
}
}
}
@override
@mustCallSuper
void dispose() {
for (final child in children) {
child?.dispose();
}
children.clear();
super.dispose();
}
static void _broadcastChildrenRemovedFromStage(DisplayObjectContainer child) {
child.children.forEach((e) {
e.removedFromStage();
e.$onRemovedFromStage?.dispatch();
if (e is DisplayObjectContainer) _broadcastChildrenRemovedFromStage(e);
});
}
static void _broadcastChildrenAddedToStage(DisplayObjectContainer child) {
child.children.forEach((e) {
e.addedToStage();
e.$onAddedToStage?.dispatch();
if (e is DisplayObjectContainer) _broadcastChildrenAddedToStage(e);
});
}
void _drawMask(DisplayObject mask, DisplayObject child) {}
void _eraseMask(DisplayObject mask, DisplayObject child) {}
}
abstract class DisplayObject with DisplayListSignalsMixin, RenderSignalMixin, MouseSignalsMixin, DisplayMasking {
DisplayObjectContainer $parent;
static DisplayObject $currentDrag;
static GxRect $currentDragBounds;
GxPoint _dragCenterOffset;
void startDrag([bool lockCenter = false, GxRect bounds]) {
if (!inStage || !$hasTouchableArea) {
throw 'to drag an object, it has to be visible and enabled in the stage.';
}
$currentDrag?.stopDrag();
$currentDrag = this;
$currentDragBounds = bounds;
_dragCenterOffset = GxPoint();
if (lockCenter) {
_dragCenterOffset.setTo(x - parent.mouseX, y - parent.mouseY);
}
stage.onMouseMove.add(_handleDrag);
}
void _handleDrag(MouseInputData input) {
if (this != $currentDrag) {
stage?.onMouseMove?.remove(_handleDrag);
}
if ($currentDrag == null) {
$currentDragBounds = null;
return;
}
var tx = $currentDrag.parent.mouseX + _dragCenterOffset.x;
var ty = $currentDrag.parent.mouseY + _dragCenterOffset.y;
final rect = $currentDragBounds;
if (rect != null) {
tx = tx.clamp(rect.left, rect.right);
ty = ty.clamp(rect.top, rect.bottom);
}
setPosition(tx, ty);
}
void stopDrag() {
if (this == $currentDrag) {
stage.onMouseMove.remove(_handleDrag);
$currentDrag = null;
}
}
bool $debugBounds = false;
bool mouseUseShape = false;
List<BaseFilter> $filters;
List<BaseFilter> get filters => $filters;
set filters(List<BaseFilter> value) => $filters = value;
DisplayObject $mouseDownObj;
DisplayObject $mouseOverObj;
double $lastClickTime = -1;
bool useCursor = false;
Color $colorize = Color(0x0);
bool get $hasColorize => $colorize != null && $colorize.alpha > 0;
Color get colorize => $colorize;
set colorize(Color value) {
if ($colorize == value) return;
$colorize = value;
requiresRedraw();
}
void captureMouseInput(MouseInputData input) {
if (!$hasTouchableArea) return;
if (mouseEnabled) {
if (input.captured && input.type == MouseInputType.up) {
$mouseDownObj = null;
}
var prevCaptured = input.captured;
globalToLocal(input.stagePosition, input.localPosition);
input.captured = input.captured || hitTouch(input.localPosition, mouseUseShape);
if (!prevCaptured && input.captured) {
$dispatchMouseCallback(input.type, this, input);
if ($mouseOverObj != this) {
$dispatchMouseCallback(MouseInputType.over, this, input);
}
} else if ($mouseOverObj == this) {
$dispatchMouseCallback(MouseInputType.out, this, input);
}
}
}
void $dispatchMouseCallback(
MouseInputType type,
DisplayObject object,
MouseInputData input,
) {
if (mouseEnabled) {
var mouseInput = input.clone(this, object, type);
switch (type) {
case MouseInputType.wheel:
$onMouseWheel?.dispatch(mouseInput);
break;
case MouseInputType.down:
$mouseDownObj = object;
$onMouseDown?.dispatch(mouseInput);
break;
case MouseInputType.move:
$onMouseMove?.dispatch(mouseInput);
break;
case MouseInputType.up:
if ($mouseDownObj == object && ($onMouseClick != null || $onMouseDoubleClick != null)) {
var mouseClickInput = input.clone(this, object, MouseInputType.up);
$onMouseClick?.dispatch(mouseClickInput);
if ($lastClickTime > 0 && input.time - $lastClickTime < MouseInputData.doubleClickTime) {
$onMouseDoubleClick?.dispatch(mouseClickInput);
$lastClickTime = -1;
} else {
$lastClickTime = input.time;
}
}
$mouseDownObj = null;
$onMouseUp?.dispatch(mouseInput);
break;
case MouseInputType.over:
$mouseOverObj = object;
if (useCursor && GMouse.isShowing()) {
GMouse.cursor = SystemMouseCursors.click;
}
$onMouseOver?.dispatch(mouseInput);
break;
case MouseInputType.out:
$mouseOverObj = null;
if (useCursor && GMouse.isShowing()) {
GMouse.cursor = null;
}
$onMouseOut?.dispatch(mouseInput);
break;
default:
break;
}
}
$parent?.$dispatchMouseCallback(type, object, input);
}
@override
String toString() {
final msg = name != null ? ' {name: $name}' : '';
return '$runtimeType (DisplayObject)$msg';
}
double get mouseX {
if (inStage) {
return globalToLocal(_sHelperPoint.setTo(stage.pointer.mouseX, 0)).x;
} else {
throw 'To get mouseX object needs to be a descendant of Stage.';
}
}
double get mouseY {
if (inStage) {
return globalToLocal(_sHelperPoint.setTo(0, stage.pointer.mouseY)).y;
} else {
throw 'To get mouseY object needs to be a descendant of Stage.';
}
}
GxPoint get mousePosition {
if (!inStage) {
throw 'To get mousePosition, the object needs to be in the Stage.';
}
return globalToLocal(_sHelperPoint.setTo(
stage.pointer.mouseX,
stage.pointer.mouseY,
));
}
Object userData;
String name;
double _x = 0, _y = 0, _scaleX = 1, _scaleY = 1, _rotation = 0;
double _pivotX = 0, _pivotY = 0;
double _skewX = 0, _skewY = 0;
double _z = 0, _rotationX = 0, _rotationY = 0;
double get rotationX => _rotationX;
double get rotationY => _rotationY;
double get z => _z;
double get x => _x;
double get y => _y;
double get scaleX => _scaleX;
double get scaleY => _scaleY;
double get pivotX => _pivotX;
double get pivotY => _pivotY;
double get skewX => _skewX;
double get skewY => _skewY;
double get rotation => _rotation;
double get width => getBounds($parent, _sHelperRect).width;
double get height => getBounds($parent, _sHelperRect).height;
set width(double value) {
if (value == null) throw 'width can not be null';
double actualW;
var zeroScale = _scaleX < 1e-8 && _scaleX > -1e-8;
if (zeroScale) {
scaleX = 1.0;
actualW = width;
} else {
actualW = (width / _scaleX).abs();
}
if (actualW != null) scaleX = value / actualW;
}
set height(double value) {
if (value == null) throw 'height can not be null';
double actualH;
var zeroScale = _scaleY < 1e-8 && _scaleY > -1e-8;
if (zeroScale) {
scaleY = 1.0;
actualH = height;
} else {
actualH = (height / _scaleY).abs();
}
if (actualH != null) scaleY = value / actualH;
}
set x(double value) {
if (value == null) throw 'x can not be null';
if (_x == value) return;
_x = value;
$setTransformationChanged();
}
set y(double value) {
if (value == null) throw 'y can not be null';
if (_y == value) return;
_y = value;
$setTransformationChanged();
}
set scaleX(double value) {
if (value == null) throw 'scaleX can not be null';
if (_scaleX == value) return;
_scaleX = value;
$setTransformationChanged();
}
set scaleY(double value) {
if (value == null) throw 'scaleY can not be null';
if (_scaleY == value) return;
_scaleY = value;
$setTransformationChanged();
}
set pivotX(double value) {
if (_pivotX == value) return;
_pivotX = value ?? 0.0;
$setTransformationChanged();
}
set pivotY(double value) {
if (_pivotY == value) return;
_pivotY = value ?? 0.0;
$setTransformationChanged();
}
set skewX(double value) {
if (value == null) throw 'skewX can not be null';
if (_skewX == value) return;
_skewX = value;
$setTransformationChanged();
}
set skewY(double value) {
if (value == null) throw 'skewY can not be null';
if (_skewY == value) return;
_skewY = value;
$setTransformationChanged();
}
set rotation(double value) {
if (value == null) throw 'rotation can not be null';
if (_rotation == value) return;
_rotation = value;
$setTransformationChanged();
}
set rotationX(double value) {
if (value == null) {
throw 'rotationX can not be null';
}
if (_rotationX == value) return;
_rotationX = value ?? 0.0;
if (!_isWarned3d) _warn3d();
$setTransformationChanged();
}
static bool _isWarned3d = false;
void _warn3d() {
print('Warning: 3d transformations still not properly supported');
_isWarned3d = true;
}
set rotationY(double value) {
if (value == null) {
throw 'rotationY can not be null';
}
if (_rotationY == value) return;
_rotationY = value ?? 0.0;
if (!_isWarned3d) _warn3d();
$setTransformationChanged();
}
set z(double value) {
if (value == null) {
throw 'z can not be null';
}
if (_z == value) return;
_z = value ?? 0.0;
if (!_isWarned3d) _warn3d();
$setTransformationChanged();
}
double $alpha = 1;
double get alpha => $alpha;
set alpha(double value) {
if (value == null) {
throw 'alpha can not be null';
}
if ($alpha != value) {
value ??= 1;
$alpha = value.clamp(0.0, 1.0);
requiresRedraw();
}
}
bool get isRotated => _rotation != 0 || _skewX != 0 || _skewY != 0;
bool $matrixDirty = true;
bool mouseEnabled = true;
DisplayObject $maskee;
Shape $mask;
bool $hasTouchableArea = true;
bool $hasVisibleArea = true;
bool get isMask => $maskee != null;
Shape get mask => $mask;
bool maskInverted = false;
set mask(Shape value) {
if ($mask != value) {
if ($mask != null) $mask.$maskee = null;
value?.$maskee = this;
value?.$hasVisibleArea = false;
$mask = value;
requiresRedraw();
}
}
bool _transformationChanged = false;
bool _is3D = false;
void $setTransformationChanged() {
_transformationChanged = true;
_is3D = _rotationX != 0 || _rotationY != 0 || _z != 0;
requiresRedraw();
}
static final List<DisplayObject> _sAncestors = [];
static final GxPoint _sHelperPoint = GxPoint();
static final GxRect _sHelperRect = GxRect();
static final GxMatrix _sHelperMatrix = GxMatrix();
static final GxMatrix _sHelperMatrixAlt = GxMatrix();
double get worldAlpha => alpha * ($parent?.worldAlpha ?? 1);
double get worldScaleX => scaleX * ($parent?.worldScaleX ?? 1);
double get worldScaleY => scaleX * ($parent?.worldScaleY ?? 1);
double get worldX => x - pivotX * scaleX + ($parent?.worldX ?? 0);
double get worldY => y - pivotY * scaleY + ($parent?.worldY ?? 0);
bool _visible = true;
bool get visible => _visible;
set visible(bool flag) {
if (_visible != flag) {
_visible = flag;
requiresRedraw();
}
}
DisplayObject() {
_x = _y = 0.0;
_rotation = 0.0;
alpha = 1.0;
_pivotX = _pivotY = 0.0;
_scaleX = _scaleY = 1.0;
_skewX = _skewY = 0.0;
_rotationX = _rotationY = 0.0;
mouseEnabled = true;
}
void alignPivot([Alignment alignment = Alignment.center]) {
var bounds = getBounds(this, _sHelperRect);
if (bounds.isEmpty) return;
var ax = 0.5 + alignment.x / 2;
var ay = 0.5 + alignment.y / 2;
pivotX = bounds.x + bounds.width * ax;
pivotY = bounds.y + bounds.height * ay;
}
GxRect get bounds => getBounds(this);
GxRect getBounds(DisplayObject targetSpace, [GxRect out]) {
throw 'getBounds() is abstract in DisplayObject';
}
GxPoint globalToLocal(GxPoint globalPoint, [GxPoint out]) {
getTransformationMatrix(base, _sHelperMatrixAlt);
_sHelperMatrixAlt.invert();
return _sHelperMatrixAlt.transformPoint(globalPoint, out);
}
GxPoint localToGlobal(GxPoint localPoint, [GxPoint out]) {
getTransformationMatrix(base, _sHelperMatrixAlt);
return _sHelperMatrixAlt.transformPoint(localPoint, out);
}
GxMatrix _transformationMatrix;
GxMatrix get transformationMatrix {
if (_transformationChanged || _transformationMatrix == null) {
_transformationChanged = false;
_transformationMatrix ??= GxMatrix();
$updateTransformationMatrices(
x,
y,
pivotX,
pivotY,
scaleX,
scaleY,
skewX,
skewY,
rotation,
_transformationMatrix,
);
}
return _transformationMatrix;
}
set transformationMatrix(GxMatrix matrix) {
const pi_q = math.pi / 4.0;
requiresRedraw();
_transformationChanged = false;
_transformationMatrix ??= GxMatrix();
_transformationMatrix.copyFrom(matrix);
_pivotX = _pivotY = 0;
_x = matrix.tx;
_y = matrix.ty;
_skewX = math.atan(-matrix.c / matrix.d);
_skewY = math.atan(matrix.b / matrix.a);
_scaleY = (_skewX > -pi_q && _skewX < pi_q) ? matrix.d / math.cos(_skewX) : -matrix.c / math.sin(_skewX);
_scaleX = (_skewY > -pi_q && _skewY < pi_q) ? matrix.a / math.cos(_skewY) : -matrix.b / math.sin(_skewY);
if (MathUtils.isEquivalent(_skewX, _skewY)) {
_rotation = _skewX;
_skewX = _skewY = 0;
} else {
_rotation = 0;
}
}
void $updateTransformationMatrices(
double x,
double y,
double pivotX,
double pivotY,
double scaleX,
double scaleY,
double skewX,
double skewY,
double rotation,
GxMatrix out,
) {
out.identity();
if (skewX == 0 && skewY == 0) {
if (rotation == 0) {
out.setTo(
scaleX,
0,
0,
scaleY,
x - pivotX * scaleX,
y - pivotY * scaleY,
);
} else {
final cos = math.cos(rotation);
final sin = math.sin(rotation);
final a = scaleX * cos;
final b = scaleX * sin;
final c = scaleY * -sin;
final d = scaleY * cos;
final tx = x - pivotX * a - pivotY * c;
final ty = y - pivotX * b - pivotY * d;
out.setTo(a, b, c, d, tx, ty);
}
} else {
out.identity();
out.scale(scaleX, scaleY);
out.skew(skewX, skewY);
out.rotate(rotation);
out.translate(x, y);
if (pivotX != 0 || pivotY != 0) {
out.tx = x - out.a * pivotX - out.c * pivotY;
out.ty = y - out.b * pivotX - out.d * pivotY;
}
}
}
GxMatrix getTransformationMatrix(DisplayObject targetSpace, [GxMatrix out]) {
DisplayObject commonParent, currentObj;
out?.identity();
out ??= GxMatrix();
if (targetSpace == this) {
return out;
}
if (targetSpace == $parent || (targetSpace == null && $parent == null)) {
out.copyFrom(transformationMatrix);
return out;
}
if (targetSpace == null || targetSpace == base) {
currentObj = this;
while (currentObj != targetSpace) {
out.concat(currentObj.transformationMatrix);
currentObj = currentObj.$parent;
}
return out;
}
if (targetSpace.$parent == this) {
targetSpace.getTransformationMatrix(this, out);
out.invert();
return out;
}
commonParent = DisplayObject._findCommonParent(this, targetSpace);
currentObj = this;
while (currentObj != commonParent) {
out.concat(currentObj.transformationMatrix);
currentObj = currentObj.$parent;
}
if (commonParent == targetSpace) return out;
_sHelperMatrix.identity();
currentObj = targetSpace;
while (currentObj != commonParent) {
_sHelperMatrix.concat(currentObj.transformationMatrix);
currentObj = currentObj.$parent;
}
_sHelperMatrix.invert();
out.concat(_sHelperMatrix);
return out;
}
static DisplayObject _findCommonParent(DisplayObject obj1, DisplayObject obj2) {
var current = obj1;
while (current != null) {
_sAncestors.add(current);
current = current.$parent;
}
current = obj2;
while (current != null && !_sAncestors.contains(current)) {
current = current.$parent;
}
_sAncestors.length = 0;
if (current != null) return current;
throw 'Object not connected to target';
}
bool hitTestMask(GxPoint localPoint) {
if ($mask == null && maskRect == null) {
return true;
}
if (maskRect != null) {
final isHit = maskRect.containsPoint(localPoint);
return maskRectInverted ? !isHit : isHit;
}
if ($mask.inStage) {
getTransformationMatrix($mask, _sHelperMatrixAlt);
} else {
_sHelperMatrixAlt.copyFrom($mask.transformationMatrix);
_sHelperMatrixAlt.invert();
}
var helperPoint = localPoint == _sHelperPoint ? GxPoint() : _sHelperPoint;
_sHelperMatrixAlt.transformPoint(localPoint, helperPoint);
final isHit = mask.hitTest(helperPoint) != null;
return maskInverted ? !isHit : isHit;
}
bool hitTouch(GxPoint localPoint, [bool useShape = false]) {
return hitTest(localPoint, useShape) != null;
}
DisplayObject hitTest(GxPoint localPoint, [bool useShape = false]) {
if (!$hasTouchableArea || !mouseEnabled) {
return null;
}
if (($mask != null || maskRect != null) && !hitTestMask(localPoint)) {
return null;
}
if (getBounds(this, _sHelperRect).containsPoint(localPoint)) {
return this;
}
return null;
}
DisplayObjectContainer get parent => $parent;
DisplayObject get base {
var current = this;
while (current.$parent != null) {
current = current.$parent;
}
return current;
}
bool get inStage => base is Stage;
Stage get stage => base is Stage ? base : null;
DisplayObject get root {
var current = this;
while (current.$parent != null) {
if (current.$parent is Stage) return current;
current = current.$parent;
}
return null;
}
void requiresRedraw() {
$hasTouchableArea = visible && $maskee == null && _scaleX != 0 && _scaleY != 0;
$hasVisibleArea = $alpha != 0 && visible && $maskee == null && _scaleX != 0 && _scaleY != 0;
}
void removedFromStage() {}
void addedToStage() {}
@mustCallSuper
void update(double delta) {}
bool get hasFilters => filters?.isNotEmpty ?? false;
GxRect $debugLastLayerBounds;
bool allowSaveLayer = false;
GxRect getFilterBounds([GxRect layerBounds, Paint alphaPaint]) {
layerBounds ??= getBounds($parent);
if ($filters == null || $filters.isEmpty) {
return layerBounds;
}
layerBounds = layerBounds.clone();
GxRect resultBounds;
for (var filter in $filters) {
resultBounds ??= layerBounds.clone();
if (alphaPaint != null) {
filter.update();
}
filter.expandBounds(layerBounds, resultBounds);
if (alphaPaint != null) {
filter.resolvePaint(alphaPaint);
}
}
return resultBounds;
}
Paint filterPaint = Paint();
bool $useSaveLayerBounds = true;
void paint(Canvas canvas) {
if (!$hasVisibleArea || !visible) {
return;
}
final _hasScale = _scaleX != 1 || _scaleY != 1;
final _hasTranslate = _x != 0 || _y != 0;
final _hasPivot = _pivotX != 0 || _pivotX != 0;
final _hasSkew = _skewX != 0 || _skewY != 0;
final needSave = _hasTranslate || _hasScale || rotation != 0 || _hasPivot || _hasSkew || _is3D;
final $hasFilters = hasFilters;
var _saveLayer = allowSaveLayer && $alpha != 1 || $hasColorize || $hasFilters;
final hasMask = mask != null || maskRect != null;
final showDebugBounds = DisplayBoundsDebugger.debugBoundsMode == DebugBoundsMode.internal && ($debugBounds || DisplayBoundsDebugger.debugAll);
GxRect _cacheLocalBoundsRect;
if (showDebugBounds || _saveLayer) {
_cacheLocalBoundsRect = bounds;
}
List<ComposerFilter> _composerFilters;
var filterHidesObject = false;
if (_saveLayer) {
final alphaPaint = filterPaint;
if (alpha < 1) {
alphaPaint.color = const Color(0xff000000).withOpacity(alpha);
}
alphaPaint.colorFilter = null;
alphaPaint.imageFilter = null;
alphaPaint.maskFilter = null;
if ($hasColorize) {
alphaPaint.colorFilter = ColorFilter.mode($colorize, BlendMode.srcATop);
}
Rect nativeLayerBounds;
var layerBounds = getBounds($parent);
if ($hasFilters) {
layerBounds = layerBounds.clone();
GxRect resultBounds;
for (var filter in $filters) {
resultBounds ??= layerBounds.clone();
filter.update();
filter.expandBounds(layerBounds, resultBounds);
if (filter is ComposerFilter) {
_composerFilters ??= <ComposerFilter>[];
_composerFilters.add(filter);
} else {
filter.resolvePaint(alphaPaint);
}
}
layerBounds = resultBounds;
}
$debugLastLayerBounds = layerBounds;
if ($useSaveLayerBounds) {
nativeLayerBounds = layerBounds.toNative();
}
canvas.saveLayer(nativeLayerBounds, alphaPaint);
}
if (needSave) {
canvas.save();
var m = transformationMatrix.toNative();
canvas.transform(m.storage);
if (_is3D) {
m = GxMatrix().toNative();
m.setEntry(3, 2, 0.004);
m.rotateX(_rotationX);
m.rotateY(_rotationY);
if (z != 0) {
m.translate(0.0, 0.0, z);
}
canvas.transform(m.storage);
}
}
if (hasMask) {
canvas.save();
if (maskRect != null) {
$applyMaskRect(canvas);
} else {
mask.$applyPaint(canvas);
}
}
$onPrePaint?.dispatch(canvas);
if (_composerFilters != null) {
for (var filter in _composerFilters) {
if (filter.hideObject) {
filterHidesObject = true;
}
filter.process(canvas, $applyPaint);
}
}
if (!filterHidesObject) {
$applyPaint(canvas);
}
$onPostPaint?.dispatch(canvas);
if (hasMask) {
canvas.restore();
}
if (showDebugBounds) {
final _paint = $debugBoundsPaint ?? _debugPaint;
final linePaint = _paint.clone();
linePaint.color = linePaint.color.withOpacity(.3);
final rect = _cacheLocalBoundsRect.toNative();
canvas.drawLine(rect.topLeft, rect.bottomRight, linePaint);
canvas.drawLine(rect.topRight, rect.bottomLeft, linePaint);
canvas.drawRect(rect, _paint);
}
if (needSave) {
canvas.restore();
}
if (_saveLayer) {
canvas.restore();
}
}
static final Paint _debugPaint = Paint()
..style = PaintingStyle.stroke
..color = Color(0xff00FFFF)
..strokeWidth = 1;
Paint $debugBoundsPaint = _debugPaint.clone();
void $applyPaint(Canvas canvas) {}
@mustCallSuper
void dispose() {
$parent = null;
userData = null;
name = null;
$disposePointerSignals();
$disposeDisplayListSignals();
$disposeRenderSignals();
}
void removeFromParent([bool dispose = false]) {
$parent?.removeChild(this, dispose);
}
void $setParent(DisplayObjectContainer value) {
var ancestor = value;
while (ancestor != this && ancestor != null) {
ancestor = ancestor.$parent;
}
if (ancestor == this) {
throw ArgumentError('An object cannot be added as a child to itself or one '
'of its children (or children\'s children, etc.)');
} else {
$parent = value;
}
}
double get scale => _scaleX;
set scale(double value) {
if (value == _scaleX) return;
_scaleY = _scaleX = value;
$setTransformationChanged();
}
void setPosition(double x, double y) {
_x = x;
_y = y;
$setTransformationChanged();
}
void setScale(double scaleX, [double scaleY]) {
_scaleX = scaleX;
_scaleY = scaleY ?? scaleX;
$setTransformationChanged();
}
ui.Picture createPicture([void Function(Canvas) prePaintCallback, void Function(Canvas) postPaintCallback]) {
final r = ui.PictureRecorder();
final c = Canvas(r);
prePaintCallback?.call(c);
paint(c);
postPaintCallback?.call(c);
return r.endRecording();
}
Future<GTexture> createImageTexture([
bool adjustOffset = true,
double resolution = 1,
GxRect rect,
]) async {
final img = await createImage(adjustOffset, resolution, rect);
var tx = GTexture.fromImage(img, resolution);
tx.pivotX = bounds.x;
tx.pivotY = bounds.y;
return tx;
}
Future<ui.Image> createImage([
bool adjustOffset = true,
double resolution = 1,
GxRect rect,
]) async {
rect ??= getFilterBounds();
rect = rect.clone();
if (resolution != 1) {
rect *= resolution;
}
final needsAdjust = (rect.left != 0 || rect.top != 0) && adjustOffset || resolution != 1;
ui.Picture picture;
if (needsAdjust) {
final precall = (Canvas canvas) {
if (adjustOffset) {
canvas.translate(-rect.left, -rect.top);
}
if (resolution != 1) {
canvas.scale(resolution);
}
};
final postcall = (Canvas canvas) {
if (adjustOffset) canvas.restore();
if (resolution != 1) canvas.restore();
};
picture = createPicture(precall, postcall);
} else {
picture = createPicture();
}
final width = adjustOffset ? rect.width.toInt() : rect.right.toInt();
final height = adjustOffset ? rect.height.toInt() : rect.bottom.toInt();
final output = await picture.toImage(width, height);
return output;
}
}
class Shape extends DisplayObject {
Graphics _graphics;
static final _sHelperMatrix = GxMatrix();
@override
String toString() {
final msg = name != null ? ' {name: $name}' : '';
return '$runtimeType (Shape)$msg';
}
Graphics get graphics => _graphics ??= Graphics();
@override
GxRect getBounds(DisplayObject targetSpace, [GxRect out]) {
final matrix = _sHelperMatrix;
matrix.identity();
getTransformationMatrix(targetSpace, matrix);
if (_graphics != null) {
return MatrixUtils.getTransformedBoundsRect(matrix, _graphics.getBounds(out), out);
} else {
final pos = DisplayObjectContainer.$sBoundsPoint;
matrix.transformCoords(0, 0, pos);
out.setTo(pos.x, pos.y, 0, 0);
}
return out;
}
@override
DisplayObject hitTest(GxPoint localPoint, [bool useShape = false]) {
if (!$hasTouchableArea || !mouseEnabled) {
return null;
}
return (_graphics?.hitTest(localPoint, useShape) ?? false) ? this : null;
}
static Path _inverseHugePath;
static void _initInversePath() {
if (_inverseHugePath != null) {
return;
}
_inverseHugePath = Path();
final w = 100000.0;
var r = Pool.getRect(-w / 2, -w / 2, w, w);
_inverseHugePath.addRect(r.toNative());
_inverseHugePath.close();
}
@override
void $applyPaint(Canvas canvas) {
if (isMask && _graphics != null) {
GxMatrix matrix;
var paths = _graphics.getPaths();
if (inStage && $maskee.inStage) {
matrix = getTransformationMatrix($maskee);
} else {
matrix = transformationMatrix;
}
var clipPath = paths.transform(matrix.toNative().storage);
final inverted = maskInverted || $maskee.maskInverted;
if (inverted) {
_initInversePath();
if (SystemUtils.usingSkia) {
clipPath = Path.combine(PathOperation.difference, _inverseHugePath, clipPath);
canvas.clipPath(clipPath);
} else {
trace('Shape.maskInverted is unsupported in the current platform');
}
} else {
canvas.clipPath(clipPath);
}
} else {
_graphics?.isMask = isMask;
_graphics?.alpha = worldAlpha;
_graphics?.paint(canvas);
}
}
@override
void dispose() {
_graphics?.dispose();
_graphics = null;
super.dispose();
}
}
class Sprite extends DisplayObjectContainer {
@override
String toString() {
final msg = name != null ? ' {name: $name}' : '';
return '$runtimeType (Sprite)$msg';
}
static final _sHelperMatrix = GxMatrix();
Graphics _graphics;
Graphics get graphics => _graphics ??= Graphics();
@override
GxRect getBounds(DisplayObject targetSpace, [GxRect out]) {
out = super.getBounds(targetSpace, out);
if (_graphics != null) {
_sHelperMatrix.identity();
getTransformationMatrix(targetSpace, _sHelperMatrix);
final graphicsBounds = _graphics.getBounds();
MatrixUtils.getTransformedBoundsRect(
_sHelperMatrix,
graphicsBounds,
graphicsBounds,
);
out.expandToInclude(graphicsBounds);
}
return out;
}
@override
DisplayObject hitTest(GxPoint localPoint, [bool useShape = false]) {
if (!visible || !mouseEnabled) return null;
var target = super.hitTest(localPoint);
target ??= (_graphics?.hitTest(localPoint, useShape) ?? false) ? this : null;
return target;
}
@override
void $applyPaint(Canvas canvas) {
if (!$hasVisibleArea) return;
_graphics?.alpha = worldAlpha;
_graphics?.paint(canvas);
if (hasChildren) {
super.$applyPaint(canvas);
}
}
@override
@mustCallSuper
void dispose() {
_graphics?.dispose();
_graphics = null;
super.dispose();
}
}
class Stage extends DisplayObjectContainer with ResizeSignalMixin, TickerSignalMixin, StageMouseSignalsMixin {
final ScenePainter scene;
static final _sMatrix = GxMatrix();
bool maskBounds = false;
@override
String toString() {
return '$runtimeType';
}
Size _size;
Paint _backgroundPaint;
DisplayBoundsDebugger _boundsDebugger;
SceneController get controller => scene.core;
Signal get onHotReload => controller.onHotReload;
int get color => _backgroundPaint?.color?.value;
set color(int value) {
if (value == null) {
_backgroundPaint = null;
} else {
_backgroundPaint ??= Paint();
_backgroundPaint.color = Color(value);
}
}
bool showBoundsRect = false;
double get stageWidth => _size?.width ?? 0;
double get stageHeight => _size?.height ?? 0;
Stage(this.scene) {
$parent = null;
_boundsDebugger = DisplayBoundsDebugger(this);
}
@override
void paint(Canvas canvas) {
if (maskBounds && _stageRectNative != null) {
canvas.clipRect(_stageRectNative);
}
if (_backgroundPaint != null) {
canvas.drawPaint(_backgroundPaint);
}
super.paint(canvas);
if (DisplayBoundsDebugger.debugBoundsMode == DebugBoundsMode.stage) {
_boundsDebugger.canvas = canvas;
_boundsDebugger.render();
}
if (showBoundsRect) {
canvas.drawPath(_stageBoundsRectPath, _stageBoundsRectPaint);
}
}
Path _stageBoundsRectPath = Path();
static final Paint _stageBoundsRectPaint = Paint()
..style = PaintingStyle.stroke
..color = Color(0xff000000)
..strokeWidth = 2;
final GxRect _stageRect = GxRect();
GxRect get stageRect => _stageRect;
Rect _stageRectNative;
void $initFrameSize(Size value) {
if (value != _size) {
_size = value;
_stageRectNative = _stageRect.setTo(0, 0, _size.width, _size.height).toNative();
_stageBoundsRectPath ??= Path();
_stageBoundsRectPath.reset();
_stageBoundsRectPath.addRect(_stageRectNative);
_stageBoundsRectPath.close();
Graphics.updateStageRect(_stageRect);
$onResized?.dispatch();
}
}
KeyboardManager get keyboard {
if (scene?.core?.keyboard == null) {
throw 'You need to enable keyboard capture, define useKeyboard=true '
'in your SceneController';
}
return scene?.core?.keyboard;
}
PointerManager get pointer {
if (scene?.core?.pointer == null) {
throw 'You need to enable pointer capture, define usePointer=true '
'in your SceneController';
}
return scene?.core?.pointer;
}
@override
bool hitTouch(GxPoint localPoint, [bool useShape = false]) => true;
@override
DisplayObject hitTest(GxPoint localPoint, [bool useShapes = false]) {
if (!visible || !mouseEnabled) return null;
if (localPoint.x < 0 || localPoint.x > stageWidth || localPoint.y < 0 || localPoint.y > stageHeight) return null;
return super.hitTest(localPoint) ?? this;
}
bool _isMouseInside = false;
bool get isMouseInside => _isMouseInside;
@override
void captureMouseInput(MouseInputData input) {
if (input.type == MouseInputType.exit) {
_isMouseInside = false;
var mouseInput = input.clone(this, this, input.type);
$onMouseLeave?.dispatch(mouseInput);
} else if (input.type == MouseInputType.unknown && !_isMouseInside) {
_isMouseInside = true;
$onMouseEnter?.dispatch(input.clone(this, this, MouseInputType.enter));
} else {
super.captureMouseInput(input);
}
}
GxRect getStageBounds(DisplayObject targetSpace, [GxRect out]) {
out ??= GxRect();
out.setTo(0, 0, stageWidth, stageHeight);
getTransformationMatrix(targetSpace, _sMatrix);
return out.getBounds(_sMatrix, out);
}
void $tick(double delta) {
$onEnterFrame?.dispatch(delta);
super.update(delta);
}
@override
void dispose() {
_size = null;
_backgroundPaint = null;
$disposeResizeSignals();
$disposeTickerSignals();
$disposeStagePointerSignals();
super.dispose();
}
@override
double get width => throw 'Use `stage.stageWidth` instead.';
@override
double get height => throw 'Use `stage.stageHeight` instead.';
@override
set width(double value) => throw 'Cannot set width of stage';
@override
set height(double value) => throw 'Cannot set height of stage';
@override
set x(double value) => throw 'Cannot set x-coordinate of stage';
@override
set y(double value) => throw 'Cannot set y-coordinate of stage';
@override
set scaleX(double value) => throw 'Cannot scale stage';
@override
set scaleY(double value) => throw 'Cannot scale stage';
@override
set pivotX(double value) => throw 'Cannot pivot stage';
@override
set pivotY(double value) => throw 'Cannot pivot stage';
@override
set skewX(double value) => throw 'Cannot skew stage';
@override
set skewY(double value) => throw 'Cannot skew stage';
@override
set rotation(double value) => throw 'Cannot rotate stage';
}
class StaticText extends DisplayObject {
ui.Paragraph _paragraph;
Signal _onFontLoaded;
Signal get onFontLoaded => _onFontLoaded ??= Signal();
static final _sHelperMatrix = GxMatrix();
final _alphaPaint = Paint();
@override
GxRect getBounds(DisplayObject targetSpace, [GxRect out]) {
validate();
out ??= GxRect();
out.setTo(0, 0, intrinsicWidth, textHeight);
if (targetSpace == this) {
return out;
} else {}
final matrix = _sHelperMatrix;
matrix.identity();
getTransformationMatrix(targetSpace, matrix);
MatrixUtils.getTransformedBoundsRect(matrix, out, out);
return out;
}
ui.Paragraph get paragraph => _paragraph;
ui.TextStyle _style;
double _width;
ui.ParagraphBuilder _builder;
ui.ParagraphStyle _paragraphStyle;
bool _invalidSize = true;
bool _invalidBuilder = true;
String _text;
Color backgroundColor;
String get text => _text ?? '';
set text(String value) {
if (_text == value) return;
_text = value ?? '';
_invalidBuilder = true;
}
@override
double get width => _width;
@override
set width(double value) {
if (value == null || _width == value) return;
_width = value;
_invalidSize = true;
}
double get textWidth {
return _paragraph?.maxIntrinsicWidth ?? 0;
}
double get textHeight {
return _paragraph?.height ?? 0;
}
static ui.TextStyle defaultTextStyle = ui.TextStyle(
color: Color(0xff000000),
decorationStyle: TextDecorationStyle.solid,
);
static ui.ParagraphStyle defaultParagraphStyle = ui.ParagraphStyle(
fontWeight: FontWeight.normal,
fontSize: 12,
textAlign: TextAlign.left,
);
static ui.TextStyle getStyle({
Color color,
TextDecoration decoration,
Color decorationColor,
TextDecorationStyle decorationStyle,
double decorationThickness,
FontWeight fontWeight,
FontStyle fontStyle,
TextBaseline textBaseline,
String fontFamily,
List<String> fontFamilyFallback,
double fontSize,
double letterSpacing,
double wordSpacing,
double height,
Locale locale,
Paint background,
Paint foreground,
List<Shadow> shadows,
List<ui.FontFeature> fontFeatures,
}) {
return ui.TextStyle(
color: color,
decoration: decoration,
decorationColor: decorationColor,
decorationStyle: decorationStyle,
decorationThickness: decorationThickness,
fontWeight: fontWeight,
fontStyle: fontStyle,
textBaseline: textBaseline,
fontFamily: fontFamily,
fontFamilyFallback: fontFamilyFallback,
fontSize: fontSize,
letterSpacing: letterSpacing,
wordSpacing: wordSpacing,
height: height,
locale: locale,
background: background,
foreground: foreground,
shadows: shadows,
fontFeatures: fontFeatures,
);
}
StaticText({
String text,
ui.ParagraphStyle paragraphStyle,
ui.TextStyle textStyle,
double width = double.infinity,
}) {
painting.PaintingBinding.instance.systemFonts.addListener(_fontLoaded);
this.text = text;
_width = width ?? double.infinity;
_invalidBuilder = true;
_invalidSize = true;
setTextStyle(textStyle ?? defaultTextStyle);
setParagraphStyle(paragraphStyle ?? defaultParagraphStyle);
}
void _fontLoaded() {
_invalidBuilder = true;
_invalidateBuilder();
_onFontLoaded?.dispatch();
}
@override
void dispose() {
super.dispose();
painting.PaintingBinding.instance.systemFonts.removeListener(_fontLoaded);
_onFontLoaded?.removeAll();
_onFontLoaded = null;
}
void setTextStyle(ui.TextStyle style) {
if (style == null || _style == style) return;
_style = style;
_invalidBuilder = true;
}
ui.TextStyle getTextStyle() {
return _style;
}
ui.ParagraphStyle getParagraphStyle() {
return _paragraphStyle;
}
void setParagraphStyle(ui.ParagraphStyle style) {
if (style == null || _paragraphStyle == style) return;
_paragraphStyle = style;
_invalidBuilder = true;
}
void _invalidateBuilder() {
_paragraphStyle ??= defaultParagraphStyle;
_builder = ui.ParagraphBuilder(_paragraphStyle);
if (_style != null) _builder.pushStyle(_style);
_builder.addText(_text ?? '');
_paragraph = _builder.build();
_invalidBuilder = false;
_invalidSize = true;
}
static const double _maxTextWidthForWeb = 10000;
void _layout() {
var paragraphWidth = !SystemUtils.usingSkia ? _maxTextWidthForWeb : _width;
_paragraph?.layout(ui.ParagraphConstraints(width: paragraphWidth));
_invalidSize = false;
}
@override
void $applyPaint(Canvas canvas) {
super.$applyPaint(canvas);
if (_text == '') return;
validate();
if (backgroundColor != null && backgroundColor.alpha > 0) {
canvas.drawRect(
Rect.fromLTWH(0, 0, intrinsicWidth, textHeight),
Paint()..color = backgroundColor,
);
}
if (_paragraph != null) {
if ($alpha != 1.0) {
canvas.saveLayer(null, _alphaPaint);
canvas.drawParagraph(_paragraph, Offset.zero);
canvas.restore();
} else {
canvas.drawParagraph(_paragraph, Offset.zero);
}
}
}
@override
set alpha(double value) {
final changed = value != $alpha && value != null;
super.alpha = value;
if (changed) {
_alphaPaint.color = _alphaPaint.color.withOpacity($alpha);
}
}
double get intrinsicWidth => width == double.infinity ? textWidth : width;
void validate() {
if (_invalidBuilder) {
_invalidateBuilder();
_invalidBuilder = false;
}
if (_invalidSize || _invalidBuilder) {
_layout();
_invalidSize = false;
_invalidBuilder = false;
}
}
static StaticText build({
String text,
DisplayObjectContainer doc,
Color color,
double w = double.infinity,
TextDecoration decoration,
Color decorationColor,
TextDecorationStyle decorationStyle,
double decorationThickness,
FontWeight fontWeight,
FontStyle fontStyle,
TextBaseline textBaseline,
String fontFamily,
List<String> fontFamilyFallback,
double fontSize,
double letterSpacing,
double wordSpacing,
double height,
Locale locale,
Paint background,
Paint foreground,
List<ui.Shadow> shadows,
List<ui.FontFeature> fontFeatures,
TextAlign textAlign = TextAlign.left,
TextDirection direction = TextDirection.ltr,
}) {
w ??= double.infinity;
if (w == double.infinity && textAlign != TextAlign.left) {
throw "[StaticText] To use $textAlign you need to specify the width";
}
final tf = StaticText(
text: text,
width: w,
textStyle: StaticText.getStyle(
color: color,
decoration: decoration,
decorationColor: decorationColor,
decorationStyle: decorationStyle,
decorationThickness: decorationThickness,
fontWeight: fontWeight,
fontStyle: fontStyle,
textBaseline: textBaseline,
fontFamily: fontFamily,
fontFamilyFallback: fontFamilyFallback,
fontSize: fontSize,
letterSpacing: letterSpacing,
wordSpacing: wordSpacing,
height: height,
locale: locale,
background: background,
foreground: foreground,
shadows: shadows,
fontFeatures: fontFeatures,
),
paragraphStyle: ui.ParagraphStyle(
textAlign: textAlign,
textDirection: direction,
),
);
doc?.addChild(tf);
return tf;
}
}
class CallbackParams {
List<dynamic> positional;
Map<Symbol, dynamic> named;
CallbackParams([this.positional, this.named]);
static CallbackParams parse(Object args) {
if (args == null) return null;
if (args is CallbackParams) return args;
if (args is List) {
return CallbackParams(args);
} else if (args is Map) {
return CallbackParams(null, args);
}
return CallbackParams([args]);
}
}
enum KeyEventType { down, up }
class KeyboardEventData {
final KeyEventType type;
final RawKeyEvent rawEvent;
KeyboardEventData({this.type, this.rawEvent});
bool isPressed(LogicalKeyboardKey key) {
return rawEvent.isKeyPressed(key);
}
bool isKey(LogicalKeyboardKey key) {
return rawEvent.logicalKey == key;
}
}
extension MyKeyEventExt on KeyboardEventData {
bool get arrowLeft {
return rawEvent.logicalKey == LogicalKeyboardKey.arrowLeft;
}
bool get arrowRight {
return rawEvent.logicalKey == LogicalKeyboardKey.arrowRight;
}
bool get arrowUp {
return rawEvent.logicalKey == LogicalKeyboardKey.arrowUp;
}
bool get arrowDown {
return rawEvent.logicalKey == LogicalKeyboardKey.arrowDown;
}
}
mixin EventDispatcherMixin implements Listenable {
final _updaters = HashSet<VoidCallback>();
void notify() {
for (final updater in _updaters) {
updater();
}
}
@override
void addListener(VoidCallback listener) => _updaters.add(listener);
@override
void removeListener(VoidCallback listener) => _updaters.remove(listener);
void dispose() {
_updaters.clear();
}
}
mixin TickerSignalMixin {
EventSignal<double> $onEnterFrame;
EventSignal<double> get onEnterFrame => $onEnterFrame ??= EventSignal<double>();
void $disposeTickerSignals() {
$onEnterFrame?.removeAll();
$onEnterFrame = null;
}
}
mixin ResizeSignalMixin {
Signal $onResized;
Signal get onResized => $onResized ??= Signal();
void $disposeResizeSignals() {
$onResized?.removeAll();
$onResized = null;
}
}
mixin DisplayListSignalsMixin {
Signal $onAdded;
Signal get onAdded => $onAdded ??= Signal();
Signal $onRemoved;
Signal get onRemoved => $onRemoved ??= Signal();
Signal $onRemovedFromStage;
Signal get onRemovedFromStage => $onRemovedFromStage ??= Signal();
Signal $onAddedToStage;
Signal get onAddedToStage => $onAddedToStage ??= Signal();
void $disposeDisplayListSignals() {
$onAdded?.removeAll();
$onAdded = null;
$onRemoved?.removeAll();
$onRemoved = null;
$onRemovedFromStage?.removeAll();
$onRemovedFromStage = null;
$onAddedToStage?.removeAll();
$onAddedToStage = null;
}
}
mixin RenderSignalMixin {
EventSignal<Canvas> $onPrePaint;
EventSignal<Canvas> $onPostPaint;
EventSignal<Canvas> get onPrePaint => $onPrePaint ??= EventSignal<Canvas>();
EventSignal<Canvas> get onPostPaint => $onPostPaint ??= EventSignal<Canvas>();
void $disposeRenderSignals() {
$onPrePaint?.removeAll();
$onPrePaint = null;
$onPostPaint?.removeAll();
$onPostPaint = null;
}
}
mixin StageMouseSignalsMixin<T extends MouseInputData> {
EventSignal<T> $onMouseLeave;
EventSignal<T> $onMouseEnter;
EventSignal<T> get onMouseLeave => $onMouseLeave ??= EventSignal();
EventSignal<T> get onMouseEnter => $onMouseEnter ??= EventSignal();
void $disposeStagePointerSignals() {
$onMouseLeave?.removeAll();
$onMouseLeave = null;
$onMouseEnter?.removeAll();
$onMouseEnter = null;
}
}
mixin MouseSignalsMixin<T extends MouseInputData> {
EventSignal<T> $onRightMouseDown;
EventSignal<T> $onMouseDoubleClick;
EventSignal<T> $onMouseClick;
EventSignal<T> $onMouseDown;
EventSignal<T> $onMouseUp;
EventSignal<T> $onMouseMove;
EventSignal<T> $onMouseOut;
EventSignal<T> $onMouseOver;
EventSignal<T> $onMouseWheel;
EventSignal<T> get onMouseClick => $onMouseClick ??= EventSignal();
EventSignal<T> get onMouseDoubleClick => $onMouseDoubleClick ??= EventSignal();
EventSignal<T> get onRightMouseDown => $onRightMouseDown ??= EventSignal();
EventSignal<T> get onMouseDown => $onMouseDown ??= EventSignal();
EventSignal<T> get onMouseUp => $onMouseUp ??= EventSignal();
EventSignal<T> get onMouseMove => $onMouseMove ??= EventSignal();
EventSignal<T> get onMouseOver => $onMouseOver ??= EventSignal();
EventSignal<T> get onMouseOut => $onMouseOut ??= EventSignal();
EventSignal<T> get onMouseScroll => $onMouseWheel ??= EventSignal();
void $disposePointerSignals() {
$onRightMouseDown?.removeAll();
$onRightMouseDown = null;
$onMouseClick?.removeAll();
$onMouseClick = null;
$onMouseDoubleClick?.removeAll();
$onMouseDoubleClick = null;
$onMouseDown?.removeAll();
$onMouseDown = null;
$onMouseUp?.removeAll();
$onMouseUp = null;
$onMouseMove?.removeAll();
$onMouseMove = null;
$onMouseOver?.removeAll();
$onMouseOver = null;
$onMouseOut?.removeAll();
$onMouseOut = null;
$onMouseWheel?.removeAll();
$onMouseWheel = null;
}
}
mixin PointerSignalsMixin<T extends PointerEventData> {
EventSignal<T> $onClick;
EventSignal<T> $onDown;
EventSignal<T> $onUp;
EventSignal<T> $onHover;
EventSignal<T> $onOut;
EventSignal<T> $onScroll;
EventSignal<T> get onClick => $onClick ??= EventSignal();
EventSignal<T> get onDown => $onDown ??= EventSignal();
EventSignal<T> get onUp => $onUp ??= EventSignal();
EventSignal<T> get onHover => $onHover ??= EventSignal();
EventSignal<T> get onOut => $onOut ??= EventSignal();
EventSignal<T> get onScroll => $onScroll ??= EventSignal();
void $disposePointerSignals() {
$onClick?.removeAll();
$onClick = null;
$onDown?.removeAll();
$onDown = null;
$onUp?.removeAll();
$onUp = null;
$onHover?.removeAll();
$onHover = null;
$onOut?.removeAll();
$onOut = null;
$onScroll?.removeAll();
$onScroll = null;
}
}
final mps = MPS();
class MPS {
final _cache = <String, List<Function>>{};
final _cacheOnce = <String, List<Function>>{};
void publishParams(String topic, CallbackParams args) {
void _send(Function fn) => Function.apply(fn, args?.positional, args?.named);
_cache[topic]?.forEach(_send);
_cacheOnce[topic]?.forEach(_send);
_cacheOnce.remove(topic);
}
void publish1<T>(String topic, T arg1) {
void _send(Function fn) => fn.call(arg1);
_cache[topic]?.forEach(_send);
_cacheOnce[topic]?.forEach(_send);
_cacheOnce.remove(topic);
}
void publish2<T, S>(String topic, T arg1, S arg2) {
void _send(Function fn) => fn.call(arg1, arg2);
_cache[topic]?.forEach(_send);
_cacheOnce[topic]?.forEach(_send);
_cacheOnce.remove(topic);
}
void publish(String topic) {
void _send(Function fn) => fn.call();
_cache[topic]?.forEach(_send);
_cacheOnce[topic]?.forEach(_send);
_cacheOnce.remove(topic);
}
bool once(String topic, Function callback) {
if (!_cacheOnce.containsKey(topic)) {
_cacheOnce[topic] = [];
}
if (!_cacheOnce[topic].contains(callback)) {
_cacheOnce[topic].add(callback);
return true;
}
return false;
}
bool subscribe(String topic, Function callback) {
if (!_cache.containsKey(topic)) {
_cache[topic] = [];
}
if (!_cache[topic].contains(callback)) {
_cache[topic].add(callback);
return true;
}
return false;
}
bool unsubscribe(String topic, Function callback) {
final subs = _cache[topic];
return subs.remove(callback);
}
int count(String topic) {
return _cache[topic]?.length ?? 0;
}
int countOnce(String topic) {
return _cacheOnce[topic]?.length ?? 0;
}
void off(String topic, Function callback) {
_cache[topic].remove(callback);
_cacheOnce[topic].remove(callback);
}
MPS emit(String topic) {
void _send(Function fn) => fn.call();
_cache[topic]?.forEach(_send);
_cacheOnce[topic]?.forEach(_send);
_cacheOnce.remove(topic);
return this;
}
MPS on(String topic, Function callback) {
if (!_cache.containsKey(topic)) {
_cache[topic] = [];
}
if (!_cache[topic].contains(callback)) {
_cache[topic].add(callback);
}
return this;
}
void emit1<T>(String topic, T arg1) {
void _send(Function fn) => fn.call(arg1);
_cache[topic]?.forEach(_send);
_cacheOnce[topic]?.forEach(_send);
_cacheOnce.remove(topic);
}
void emit2<T, S>(String topic, T arg1, S arg2) {
void _send(Function fn) => fn.call(arg1, arg2);
_cache[topic]?.forEach(_send);
_cacheOnce[topic]?.forEach(_send);
_cacheOnce.remove(topic);
}
void emit3<A, B, C>(String topic, A arg1, B arg2, C arg3) {
void _send(Function fn) => fn.call(arg1, arg2, arg3);
_cache[topic]?.forEach(_send);
_cacheOnce[topic]?.forEach(_send);
_cacheOnce.remove(topic);
}
void emitParams(String topic, CallbackParams args) {
void _send(Function fn) => Function.apply(fn, args?.positional, args?.named);
_cache[topic]?.forEach(_send);
_cacheOnce[topic]?.forEach(_send);
_cacheOnce.remove(topic);
}
bool offAll(String event) => _cache.remove(event) != null;
}
enum PointerEventType {
scroll,
cancel,
move,
up,
down,
enter,
exit,
hover,
}
class PointerEventData {
final double stageX;
final double stageY;
final PointerEventType type;
final PointerEvent rawEvent;
GxPoint localPosition;
double get localX => localPosition.x;
double get localY => localPosition.y;
static int doubleClickTime = 300;
PointerEventData({this.type, this.rawEvent})
: stageX = rawEvent.localPosition.dx,
stageY = rawEvent.localPosition.dy {
localPosition = GxPoint(stageX, stageY);
}
int get time => rawEvent.timeStamp.inMilliseconds;
Offset get scrollDelta {
if (rawEvent is PointerScrollEvent) {
return (rawEvent as PointerScrollEvent).scrollDelta;
}
return null;
}
GxPoint get windowPosition => GxPoint.fromNative(rawEvent.original.position);
GxPoint get stagePosition => GxPoint.fromNative(rawEvent.localPosition);
@override
String toString() {
return 'PointerEventData{'
'type: $type, '
'stageX: $stageX, '
'stageY: $stageY, '
'localX: $localX, '
'localY: $localY'
'}';
}
DisplayObject target;
DisplayObject dispatcher;
bool captured = false;
bool mouseOut = false;
PointerEventData clone(
DisplayObject target,
DisplayObject dispatcher,
PointerEventType type,
) {
var i = PointerEventData(type: type, rawEvent: rawEvent);
i.target = target;
i.dispatcher = dispatcher;
i.captured = captured;
return i;
}
}
enum MouseInputType { over, out, move, down, exit, enter, up, click, still, wheel, unknown }
class MouseInputData {
static int doubleClickTime = 250;
bool captured = false;
@override
String toString() {
return 'MouseInputData{'
'captured: $captured, '
'target: $target, '
'dispatcher: $dispatcher, '
'type: $type, '
'buttonDown: $buttonDown, '
'mouseOut: $mouseOut, '
'time: $time, '
'buttonsFlags: $buttonsFlags, '
'localPosition: $localPosition, '
'stagePosition: $_stagePosition, '
'scrollDelta: $scrollDelta}';
}
DisplayObject target;
DisplayObject dispatcher;
MouseInputType type;
bool buttonDown = false;
bool mouseOut = false;
double time = 0;
PointerEventData rawEvent;
int buttonsFlags;
bool get isSecondaryDown => buttonsFlags & kSecondaryButton == kSecondaryButton;
bool get isPrimaryDown => buttonsFlags & kPrimaryButton == kPrimaryButton;
bool get isTertiaryDown => buttonsFlags & 0x04 == 0x04;
GxPoint get stagePosition => _stagePosition;
final GxPoint _stagePosition = GxPoint();
GxPoint localPosition = GxPoint();
GxPoint scrollDelta = GxPoint();
static final GxPoint _localDelta = GxPoint();
double get localX => localPosition.x;
double get localY => localPosition.y;
double get windowX => rawEvent?.rawEvent?.original?.position?.dx ?? 0;
double get windowY => rawEvent?.rawEvent?.original?.position?.dy ?? 0;
double get stageX => _stagePosition?.x ?? 0;
double get stageY => _stagePosition?.y ?? 0;
GxPoint get localDelta {
final d = rawEvent?.rawEvent?.localDelta;
if (d == null) {
return _localDelta.setEmpty();
}
return _localDelta.setTo(d.dx, d.dy);
}
static int uniqueId = 0;
int uid;
MouseInputData({this.target, this.dispatcher, this.type});
MouseInputData clone(DisplayObject target, DisplayObject dispatcher, MouseInputType type) {
var input = MouseInputData(
target: target,
dispatcher: dispatcher,
type: type,
);
input.uid = uid;
input.buttonDown = buttonDown;
input.rawEvent = rawEvent;
input.captured = captured;
input.buttonsFlags = buttonsFlags;
input.time = time;
input._stagePosition.setTo(_stagePosition.x, _stagePosition.y);
input.scrollDelta.setTo(scrollDelta.x, scrollDelta.y);
input.localPosition.setTo(localPosition.x, localPosition.y);
input.mouseOut = mouseOut;
return input;
}
static MouseInputType fromNativeType(PointerEventType nativeType) {
if (nativeType == PointerEventType.down) {
return MouseInputType.down;
} else if (nativeType == PointerEventType.up) {
return MouseInputType.up;
} else if (nativeType == PointerEventType.hover || nativeType == PointerEventType.move) {
return MouseInputType.move;
} else if (nativeType == PointerEventType.exit) {
return MouseInputType.exit;
} else if (nativeType == PointerEventType.scroll) {
return MouseInputType.wheel;
}
return MouseInputType.unknown;
}
}
typedef EventSignalCallback<T> = void Function(T event);
class Signal {
final _listenersOnce = <Function>[];
final _listeners = <Function>[];
int _iterDispatchers = 0;
Signal();
bool has(Function callback) {
return _listeners.contains(callback) || _listenersOnce.contains(callback);
}
bool hasListeners() => _listeners.isNotEmpty || _listenersOnce.isNotEmpty;
void add(Function callback) {
if (callback == null || has(callback)) return;
_listeners.add(callback);
}
void addOnce(Function callback) {
if (callback == null || _listeners.contains(callback)) return;
_listenersOnce.add(callback);
}
void remove(Function callback) {
final idx = _listeners.indexOf(callback);
if (idx > -1) {
if (idx <= _iterDispatchers) _iterDispatchers--;
_listeners.removeAt(idx);
} else {
_listenersOnce.remove(callback);
}
}
void removeAll() {
_listeners.clear();
_listenersOnce.clear();
}
void dispatch() {
final len = _listeners.length;
for (_iterDispatchers = 0; _iterDispatchers < len; ++_iterDispatchers) {
_listeners[_iterDispatchers]?.call();
}
final lenCount = _listenersOnce.length;
for (var i = 0; i < lenCount; i++) {
_listenersOnce.removeAt(0)?.call();
}
}
void dispatchWithData(dynamic data) {
final len = _listeners.length;
for (_iterDispatchers = 0; _iterDispatchers < len; ++_iterDispatchers) {
_listeners[_iterDispatchers]?.call(data);
}
final lenCount = _listenersOnce.length;
for (var i = 0; i < lenCount; i++) {
_listenersOnce.removeAt(i)?.call(data);
}
}
}
class EventSignal<T> {
void call(EventSignalCallback<T> callback) {
add(callback);
}
final _listenersOnce = <EventSignalCallback<T>>[];
final _listeners = <EventSignalCallback<T>>[];
int _iterDispatchers = 0;
int id = -1;
bool has(EventSignalCallback<T> callback) {
return _listeners.contains(callback) || _listenersOnce.contains(callback);
}
bool hasListeners() => _listeners.isNotEmpty || _listenersOnce.isNotEmpty;
void add(EventSignalCallback<T> callback) {
if (callback == null || has(callback)) return;
_listeners.add(callback);
}
void addOnce(EventSignalCallback<T> callback) {
if (callback == null || _listeners.contains(callback)) return;
_listenersOnce.add(callback);
}
void remove(EventSignalCallback<T> callback) {
final idx = _listeners.indexOf(callback);
if (idx > -1) {
if (idx <= _iterDispatchers) _iterDispatchers--;
_listeners?.removeAt(idx);
} else {
_listenersOnce?.remove(callback);
}
}
void removeAll() {
_listeners.clear();
_listenersOnce.clear();
}
void dispatch(T data) {
for (var i = 0; i < _listeners.length; ++i) {
_listeners[i]?.call(data);
}
for (var i = 0; i < _listenersOnce.length; i++) {
_listenersOnce?.removeAt(i)?.call(data);
}
}
}
extension DisplayObjectHelpers on DisplayObject {
void centerInStage() {
if (!inStage) return;
setPosition(stage.stageWidth / 2, stage.stageHeight / 2);
}
void setProps({
Object x,
Object y,
Object scaleX,
Object scaleY,
Object scale,
Object rotation,
Object pivotX,
Object pivotY,
Object width,
Object height,
Object skewX,
Object skewY,
Object alpha,
double delay = 0,
}) {
tween(
duration: 0,
delay: delay,
x: x,
y: y,
scaleX: scaleX,
scaleY: scaleY,
scale: scale,
rotation: rotation,
pivotX: pivotX,
pivotY: pivotY,
width: width,
height: height,
skewX: skewX,
skewY: skewY,
alpha: alpha,
);
}
}
class ColorMatrix {
static const List<double> DELTA_INDEX = <double>[0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11, 0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24, 0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42, 0.44, 0.46, 0.48, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68, 0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98, 1.0, 1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54, 1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0, 2.12, 2.25, 2.37, 2.50, 2.62, 2.75, 2.87, 3.0, 3.2, 3.4, 3.6, 3.8, 4.0, 4.3, 4.7, 4.9, 5.0, 5.5, 6.0, 6.5, 6.8, 7.0, 7.3, 7.5, 7.8, 8.0, 8.4, 8.7, 9.0, 9.4, 9.6, 9.8, 10.0];
static const List<double> IDENTITY_MATRIX = <double>[
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
];
static final int LENGTH = IDENTITY_MATRIX.length;
final _storage = List<double>(25);
ColorMatrix([List<double> matrix]) {
matrix = _fixMatrix(matrix);
copyMatrix(((matrix.length == LENGTH) ? matrix : IDENTITY_MATRIX));
}
void reset() {
for (var i = 0; i < LENGTH; i++) {
_storage[i] = IDENTITY_MATRIX[i];
}
}
void adjustColor(
double pBrightness,
double pContrast,
double pSaturation,
double pHue,
) {
adjustHue(pHue);
adjustContrast(pContrast);
adjustBrightness(pBrightness);
adjustSaturation(pSaturation);
}
void adjustBrightness(double percent) {
if (percent == null || percent == 0 || percent.isNaN) return;
if (percent >= -1 && percent <= 1) {
percent *= 100;
}
percent = _cleanValue(percent, 100);
multiplyMatrix([
1,
0,
0,
0,
percent,
0,
1,
0,
0,
percent,
0,
0,
1,
0,
percent,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
]);
}
void adjustContrast(double percent) {
if (percent == null || percent == 0 || percent.isNaN) return;
if (percent >= -1 && percent <= 1) {
percent *= 100;
}
percent = _cleanValue(percent, 100);
double x;
final idx = percent.toInt();
if (percent < 0) {
x = 127 + percent / 100 * 127;
} else {
x = percent % 1;
if (x == 0) {
x = DELTA_INDEX[idx];
} else {
x = DELTA_INDEX[(idx << 0)] * (1 - x) + DELTA_INDEX[(idx << 0) + 1] * x;
}
x = x * 127 + 127;
}
multiplyMatrix([
x / 127,
0,
0,
0,
0.5 * (127 - x),
0,
x / 127,
0,
0,
0.5 * (127 - x),
0,
0,
x / 127,
0,
0.5 * (127 - x),
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
]);
}
void adjustSaturation(double percent) {
if (percent == null || percent == 0 || percent.isNaN) return;
if (percent >= -1 && percent <= 1) {
percent *= 100;
}
percent = _cleanValue(percent, 100);
var x = 1.0 + ((percent > 0) ? 3 * percent / 100 : percent / 100);
const lumR = 0.3086;
const lumG = 0.6094;
const lumB = 0.0820;
multiplyMatrix([
lumR * (1 - x) + x,
lumG * (1 - x),
lumB * (1 - x),
0,
0,
lumR * (1 - x),
lumG * (1 - x) + x,
lumB * (1 - x),
0,
0,
lumR * (1 - x),
lumG * (1 - x),
lumB * (1 - x) + x,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
]);
}
void adjustHue(double percent) {
if (percent == null || percent == 0 || percent.isNaN) return;
if (percent >= -1 && percent <= 1) {
percent *= 180;
}
percent = _cleanValue(percent, 180) / 180 * math.pi;
var cosVal = math.cos(percent);
var sinVal = math.sin(percent);
const lumR = 0.213;
const lumG = 0.715;
const lumB = 0.072;
multiplyMatrix([
lumR + cosVal * (1 - lumR) + sinVal * -lumR,
lumG + cosVal * -lumG + sinVal * -lumG,
lumB + cosVal * -lumB + sinVal * (1 - lumB),
0,
0,
lumR + cosVal * -lumR + sinVal * 0.143,
lumG + cosVal * (1 - lumG) + sinVal * 0.140,
lumB + cosVal * -lumB + sinVal * -0.283,
0,
0,
lumR + cosVal * -lumR + sinVal * -(1 - lumR),
lumG + cosVal * -lumG + sinVal * lumG,
lumB + cosVal * (1 - lumB) + sinVal * lumB,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
]);
}
void concat(List<double> pMatrix) {
pMatrix = _fixMatrix(pMatrix);
if (pMatrix.length != LENGTH) return;
multiplyMatrix(pMatrix);
}
ColorMatrix clone() => ColorMatrix(_storage);
@override
String toString() => "ColorMatrix [ ${_storage.join(" , ")} ]";
List<double> get storage => _storage.sublist(0, math.min(_storage.length, 20)).toList();
void copyMatrix(List<double> pMatrix) {
for (var i = 0; i < LENGTH; i++) {
_storage[i] = pMatrix[i];
}
}
void multiplyMatrix(List<double> pMatrix) {
var col = List<double>(25);
for (var i = 0; i < 5; i++) {
for (var j = 0; j < 5; j++) {
col[j] = _storage[j + i * 5];
}
for (var j = 0; j < 5; j++) {
var val = 0.0;
for (var k = 0; k < 5; k++) {
val += pMatrix[j + k * 5] * col[k];
}
_storage[j + i * 5] = val;
}
}
}
double _cleanValue(double pVal, double pLimit) => math.min(pLimit, math.max(-pLimit, pVal));
List<double> _fixMatrix([List<double> pMatrix]) {
if (pMatrix == null) return IDENTITY_MATRIX;
if (pMatrix.length < LENGTH) {
pMatrix = List.from(pMatrix)..addAll(IDENTITY_MATRIX.getRange(pMatrix.length, LENGTH));
} else if (pMatrix.length > LENGTH) {
pMatrix = pMatrix.sublist(0, LENGTH);
}
return pMatrix;
}
}
class GxMatrix {
double a, b, c, d, tx, ty;
Matrix4 _native;
@override
String toString() {
return 'GxMatrix {a: $a, b: $b, c: $c, d: $d, tx: $tx, ty: $ty}';
}
Matrix4 toNative() {
_native ??= Matrix4.identity();
_native.setValues(a, b, 0, 0, c, d, 0, 0, 0, 0, 1, 0, tx, ty, 0, 1);
return _native;
}
static GxMatrix fromNative(Matrix4 m) {
return GxMatrix(
m.storage[0],
m.storage[4],
m.storage[1],
m.storage[5],
m.storage[12],
m.storage[13],
);
}
GxMatrix zoomAroundPoint(GxPoint center, double zoomFactor) {
var t1 = GxMatrix();
t1.translate(-center.x, -center.y);
var sc = GxMatrix();
sc.scale(zoomFactor, zoomFactor);
var t2 = GxMatrix();
t2.translate(center.x, center.y);
var zoom = GxMatrix();
zoom.concat(t1).concat(sc).concat(t2);
return concat(zoom);
}
GxMatrix([
this.a = 1,
this.b = 0,
this.c = 0,
this.d = 1,
this.tx = 0,
this.ty = 0,
]);
GxMatrix clone() {
return GxMatrix(
a,
b,
c,
d,
tx,
ty,
);
}
GxMatrix copyFrom(GxMatrix from) {
a = from.a;
b = from.b;
c = from.c;
d = from.d;
tx = from.tx;
ty = from.ty;
return this;
}
GxMatrix setTo(double a, double b, double c, double d, double tx, double ty) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.tx = tx;
this.ty = ty;
return this;
}
GxMatrix.fromList(List<double> value) {
a = value[0];
b = value[1];
c = value[3];
d = value[4];
tx = value[2];
ty = value[5];
}
GxMatrix identity() {
a = 1;
b = 0;
c = 0;
d = 1;
tx = 0;
ty = 0;
return this;
}
GxMatrix setTransform(
double x,
double y,
double pivotX,
double pivotY,
double scaleX,
double scaleY,
double skewX,
double skewY,
double rotation,
) {
a = math.cos(rotation + skewY) * scaleX;
b = math.sin(rotation + skewY) * scaleX;
c = -math.sin(rotation - skewX) * scaleY;
d = math.cos(rotation - skewX) * scaleY;
tx = x - ((pivotX * a) + (pivotY * c));
ty = y - ((pivotX * b) + (pivotY * d));
return this;
}
GxMatrix append(GxMatrix matrix) {
final a1 = a;
final b1 = b;
final c1 = c;
final d1 = d;
a = (matrix.a * a1) + (matrix.b * c1);
b = (matrix.a * b1) + (matrix.b * d1);
c = (matrix.c * a1) + (matrix.d * c1);
d = (matrix.c * b1) + (matrix.d * d1);
tx = (matrix.tx * a1) + (matrix.ty * c1) + tx;
ty = (matrix.tx * b1) + (matrix.ty * d1) + ty;
return this;
}
GxMatrix concat(GxMatrix matrix) {
double a1, c1, tx1;
a1 = a * matrix.a + b * matrix.c;
b = a * matrix.b + b * matrix.d;
a = a1;
c1 = c * matrix.a + d * matrix.c;
d = c * matrix.b + d * matrix.d;
c = c1;
tx1 = tx * matrix.a + ty * matrix.c + matrix.tx;
ty = tx * matrix.b + ty * matrix.d + matrix.ty;
tx = tx1;
return this;
}
GxMatrix invert() {
var n = a * d - b * c;
if (n == 0) {
a = b = c = d = 0;
tx = -tx;
ty = -ty;
} else {
n = 1 / n;
var a1 = d * n;
d = a * n;
a = a1;
b *= -n;
c *= -n;
var tx1 = -a * tx - c * ty;
ty = -b * tx - d * ty;
tx = tx1;
}
return this;
}
GxMatrix scale(double scaleX, [double scaleY]) {
scaleY ??= scaleX;
a *= scaleX;
b *= scaleY;
c *= scaleX;
d *= scaleY;
tx *= scaleX;
ty *= scaleY;
return this;
}
void skew(double skewX, double skewY) {
var sinX = math.sin(skewX);
var cosX = math.cos(skewX);
var sinY = math.sin(skewY);
var cosY = math.cos(skewY);
setTo(
a * cosY - b * sinX,
a * sinY + b * cosX,
c * cosY - d * sinX,
c * sinY + d * cosX,
tx * cosY - ty * sinX,
tx * sinY + ty * cosX,
);
}
void rotate(double angle) {
final cos = math.cos(angle);
final sin = math.sin(angle);
var a1 = a * cos - b * sin;
b = a * sin + b * cos;
a = a1;
var c1 = c * cos - d * sin;
d = c * sin + d * cos;
c = c1;
var tx1 = tx * cos - ty * sin;
ty = tx * sin + ty * cos;
tx = tx1;
}
GxMatrix translate(double x, double y) {
tx += x;
ty += y;
return this;
}
GxPoint transformPoint(GxPoint point, [GxPoint out]) {
return transformCoords(point.x, point.y, out);
}
GxPoint transformInversePoint(GxPoint point, [GxPoint out]) {
return transformInverseCoords(point.x, point.y, out);
}
GxPoint transformInverseCoords(double x, double y, [GxPoint out]) {
out ??= GxPoint();
final id = 1 / ((a * d) + (c * -b));
out.x = (d * id * x) + (-c * id * y) + (((ty * c) - (tx * d)) * id);
out.y = (a * id * y) + (-b * id * x) + (((-ty * a) + (tx * b)) * id);
return out;
}
GxPoint transformCoords(double x, double y, [GxPoint out]) {
out ??= GxPoint();
out.x = a * x + c * y + tx;
out.y = d * y + b * x + ty;
return out;
}
}
class GxPoint {
static GxPoint fromNative(Offset nativeOffset) {
return GxPoint(nativeOffset.dx, nativeOffset.dy);
}
@override
String toString() {
return 'GxPoint {$x,$y}';
}
GxPoint clone() => GxPoint(x, y);
Offset toNative() => Offset(x, y);
GxPoint setEmpty() {
x = y = 0.0;
return this;
}
GxPoint setTo(double x, double y) {
this.x = x;
this.y = y;
return this;
}
double x, y;
GxPoint([this.x = 0, this.y = 0]);
static double distance(GxPoint p1, GxPoint p2) {
var dx = p2.x - p1.x;
var dy = p2.y - p1.y;
dx *= dx;
dy *= dy;
return math.sqrt(dx + dy);
}
double get length => math.sqrt(x * x + y * y);
}
class GxRect {
static GxRect fromNative(Rect nativeRect) {
return GxRect(nativeRect.left, nativeRect.top, nativeRect.width, nativeRect.height);
}
@override
String toString() {
return 'GxRect {x: $x, y: $y, w: $width, h: $height}';
}
Rect toNative() {
return Rect.fromLTWH(x, y, width, height);
}
double x, y, width, height;
double get bottom => y + height;
set bottom(double value) => height = value - y;
double get left => x;
set left(double value) {
width -= value - x;
x = value;
}
double get right => x + width;
set right(double value) {
width = value - x;
}
double get top => y;
set top(double value) {
height -= value - y;
y = value;
}
GxRect([this.x = 0.0, this.y = 0.0, this.width = 0.0, this.height = 0.0]);
GxRect setTo(double x, double y, double width, double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
return this;
}
static List<GxPoint> _sHelperPositions;
static final _sHelperPoint = GxPoint();
GxRect getBounds(GxMatrix matrix, [GxRect out]) {
out ??= GxRect();
var minX = 10000000.0;
var maxX = -10000000.0;
var minY = 10000000.0;
var maxY = -10000000.0;
final positions = getPositions(_sHelperPositions);
for (var point in positions) {
matrix.transformCoords(point.x, point.y, _sHelperPoint);
if (minX > _sHelperPoint.x) minX = _sHelperPoint.x;
if (maxX < _sHelperPoint.x) maxX = _sHelperPoint.x;
if (minY > _sHelperPoint.y) minY = _sHelperPoint.y;
if (maxY < _sHelperPoint.y) maxY = _sHelperPoint.y;
}
return out.setTo(minX, minY, maxX - minX, maxY - minY);
}
List<GxPoint> getPositions([List<GxPoint> out]) {
out ??= List.generate(4, (i) => GxPoint());
for (var i = 0; i < 4; ++i) {
out[i] ??= GxPoint();
}
out[2].x = out[0].x = left;
out[1].y = out[0].y = top;
out[3].x = out[1].x = right;
out[3].y = out[2].y = bottom;
return out;
}
GxRect clone() => GxRect(x, y, width, height);
GxRect copyFrom(GxRect other) {
return setTo(other.x, other.y, other.width, other.height);
}
GxRect intersection(GxRect rect) {
GxRect result;
var x0 = x < rect.x ? rect.x : 0.0;
var x1 = right > rect.right ? rect.right : right;
if (x1 <= 0) {
result = GxRect();
} else {
var y0 = y < rect.y ? rect.y : y;
var y1 = bottom > rect.bottom ? rect.bottom : bottom;
if (y1 <= y0) {
result = GxRect();
} else {
result = GxRect(x0, y0, x1 - x0, y1 - y0);
}
}
return result;
}
bool intersects(GxRect rect) {
var x0 = x < rect.x ? rect.x : x;
var x1 = right > rect.right ? rect.right : right;
if (x1 > x0) {
var y0 = y < rect.y ? rect.y : y;
var y1 = bottom > rect.bottom ? rect.bottom : bottom;
if (y1 > y0) return true;
}
return false;
}
GxRect expandToInclude(GxRect other) {
var r = right;
var b = bottom;
x = math.min(left, other.left);
y = math.min(top, other.top);
right = math.max(r, other.right);
bottom = math.max(b, other.bottom);
return this;
}
GxRect offset(double dx, double dy) {
x += dx;
y += dy;
return this;
}
GxRect offsetPoint(GxPoint point) {
x += point.x;
y += point.y;
return this;
}
GxRect inflate(double dx, double dy) {
x -= dx;
y -= dy;
width += dx * 2;
height += dy * 2;
return this;
}
GxRect setEmpty() {
return setTo(0, 0, 0, 0);
}
bool get isEmpty => width <= 0 || height <= 0;
bool contains(double x, double y) => x >= this.x && y >= this.y && x < right && y < bottom;
bool containsPoint(GxPoint point) => point.x >= x && point.y >= y && point.x < right && point.y < bottom;
GxRect operator *(double scale) {
x *= scale;
y *= scale;
width *= scale;
height *= scale;
return this;
}
GxRect operator /(double scale) {
x /= scale;
y /= scale;
width /= scale;
height /= scale;
return this;
}
GxRectCornerRadius _corners;
bool get hasCorners => _corners?.isNotEmpty ?? false;
GxRectCornerRadius get corners {
_corners ??= GxRectCornerRadius();
return _corners;
}
set corners(GxRectCornerRadius value) => _corners = value;
RRect toRoundNative() => corners.toNative(this);
static GxRect fromRoundNative(RRect nativeRect) {
return GxRect(
nativeRect.left,
nativeRect.top,
nativeRect.width,
nativeRect.height,
)..corners.setTo(
nativeRect.tlRadiusX,
nativeRect.trRadiusX,
nativeRect.brRadiusX,
nativeRect.blRadiusX,
);
}
}
class GxRectCornerRadius {
double topLeft, topRight, bottomRight, bottomLeft;
GxRectCornerRadius([
this.topLeft = 0,
this.topRight = 0,
this.bottomRight = 0,
this.bottomLeft = 0,
]);
void setTo([
double topLeft = 0,
double topRight = 0,
double bottomRight = 0,
double bottomLeft = 0,
]) {
this.topLeft = topLeft;
this.topRight = topRight;
this.bottomLeft = bottomLeft;
this.bottomRight = bottomRight;
}
void setTopBottom(double top, double bottom) {
topLeft = topRight = top;
bottomLeft = bottomRight = bottom;
}
void setLeftRight(double left, double right) {
topLeft = bottomLeft = left;
topRight = bottomRight = right;
}
void allTo(double radius) => topLeft = topRight = bottomLeft = bottomRight = radius;
bool get isNotEmpty => topLeft != 0 || topRight != 0 || bottomLeft != 0 || bottomRight != 0;
RRect toNative(GxRect rect) {
final tl = topLeft == 0 ? Radius.zero : Radius.circular(topLeft);
final tr = topRight == 0 ? Radius.zero : Radius.circular(topRight);
final br = bottomRight == 0 ? Radius.zero : Radius.circular(bottomRight);
final bl = bottomLeft == 0 ? Radius.zero : Radius.circular(bottomLeft);
return RRect.fromLTRBAndCorners(
rect.left,
rect.top,
rect.right,
rect.bottom,
topLeft: tl,
topRight: tr,
bottomLeft: bl,
bottomRight: br,
);
}
}
class InputConverter {
final PointerManager pointer;
final KeyboardManager keyboard;
InputConverter(this.pointer, this.keyboard);
void pointerEnter(PointerEnterEvent event) {
pointer.$process(PointerEventData(
type: PointerEventType.enter,
rawEvent: event,
));
}
void pointerExit(PointerExitEvent event) {
pointer.$process(PointerEventData(
type: PointerEventType.exit,
rawEvent: event,
));
}
void pointerHover(PointerHoverEvent event) {
pointer.$process(PointerEventData(
type: PointerEventType.hover,
rawEvent: event,
));
}
void pointerSignal(PointerSignalEvent event) {
pointer.$process(PointerEventData(
type: PointerEventType.scroll,
rawEvent: event as PointerScrollEvent,
));
}
void pointerMove(PointerMoveEvent event) {
pointer.$process(PointerEventData(
type: PointerEventType.move,
rawEvent: event,
));
}
void pointerCancel(PointerCancelEvent event) {
pointer.$process(PointerEventData(
type: PointerEventType.cancel,
rawEvent: event,
));
}
void pointerUp(PointerUpEvent event) {
pointer.$process(PointerEventData(
type: PointerEventType.up,
rawEvent: event,
));
}
void pointerDown(PointerDownEvent event) {
pointer.$process(PointerEventData(
type: PointerEventType.down,
rawEvent: event,
));
}
void handleKey(RawKeyEvent event) {
final isDown = event is RawKeyDownEvent;
keyboard.$process(KeyboardEventData(
type: isDown ? KeyEventType.down : KeyEventType.up,
rawEvent: event,
));
}
}
class KeyboardManager<T extends KeyboardEventData> {
FocusNode focusNode = FocusNode();
void dispose() {
focusNode?.dispose();
}
EventSignal<T> get onDown => _onDown ??= EventSignal<T>();
EventSignal<T> _onDown;
EventSignal<T> get onUp => _onUp ??= EventSignal<T>();
EventSignal<T> _onUp;
KeyboardEventData _lastEvent;
bool isPressed(LogicalKeyboardKey key) {
return _lastEvent?.rawEvent?.isKeyPressed(key) ?? false;
}
bool get isShiftPressed => _lastEvent?.rawEvent?.isShiftPressed ?? false;
bool get isAltPressed => _lastEvent?.rawEvent?.isAltPressed ?? false;
bool get isControlPressed => _lastEvent?.rawEvent?.isControlPressed ?? false;
bool get isMetaPressed => _lastEvent?.rawEvent?.isMetaPressed ?? false;
void $process(KeyboardEventData event) {
_lastEvent = event;
if (event.type == KeyEventType.down) {
_onDown?.dispatch(event);
} else {
_onUp?.dispatch(event);
}
}
}
abstract class GMouse {
static SystemMouseCursor _cursor;
static SystemMouseCursor _lastCursor;
static SystemMouseCursor get cursor => _cursor;
static set cursor(SystemMouseCursor value) {
value ??= SystemMouseCursors.basic;
if (_cursor == value) return;
if (_cursor != SystemMouseCursors.none) {
_lastCursor = _cursor;
}
_cursor = value;
SystemChannels.mouseCursor.invokeMethod<void>(
'activateSystemCursor',
<String, dynamic>{
'device': 1,
'kind': cursor.kind,
},
);
}
static bool isShowing() => _cursor != SystemMouseCursors.none;
static void hide() => cursor = SystemMouseCursors.none;
static void show() => cursor = _lastCursor;
}
class PointerManager<T extends PointerEventData> {
SystemMouseCursor get cursor => _cursor;
set cursor(SystemMouseCursor val) {
val ??= SystemMouseCursors.basic;
if (_cursor == val) return;
if (_cursor != SystemMouseCursors.none) {
_lastCursor = _cursor;
}
_cursor = val;
SystemChannels.mouseCursor.invokeMethod<void>(
'activateSystemCursor',
<String, dynamic>{
'device': 1,
'kind': cursor.kind,
},
);
}
void setDefaultCursor() {
cursor = null;
}
bool get showingCursor {
return _cursor != SystemMouseCursors.none;
}
set showingCursor(bool val) {
cursor = val ? _lastCursor : SystemMouseCursors.none;
}
SystemMouseCursor _cursor;
SystemMouseCursor _lastCursor;
EventSignal<T> get onInput => _onInput ??= EventSignal<T>();
EventSignal<T> _onInput;
EventSignal<T> get onDown => _onDown ??= EventSignal<T>();
EventSignal<T> _onDown;
EventSignal<T> get onUp => _onUp ??= EventSignal<T>();
EventSignal<T> _onUp;
EventSignal<T> get onCancel => _onCancel ??= EventSignal<T>();
EventSignal<T> _onCancel;
EventSignal<T> get onMove => _onMove ??= EventSignal<T>();
EventSignal<T> _onMove;
EventSignal<T> get onScroll => _onScroll ??= EventSignal<T>();
EventSignal<T> _onScroll;
EventSignal<T> get onHover => _onHover ??= EventSignal<T>();
EventSignal<T> _onHover;
EventSignal<T> get onExit => _onExit ??= EventSignal<T>();
EventSignal<T> _onExit;
EventSignal<T> get onEnter => _onEnter ??= EventSignal<T>();
EventSignal<T> _onEnter;
PointerEventData _lastEvent;
final Map _signalMapper = {};
PointerManager() {
_signalMapper[PointerEventType.down] = () => _onDown;
_signalMapper[PointerEventType.up] = () => _onUp;
_signalMapper[PointerEventType.cancel] = () => _onCancel;
_signalMapper[PointerEventType.move] = () => _onMove;
_signalMapper[PointerEventType.scroll] = () => _onScroll;
_signalMapper[PointerEventType.hover] = () => _onHover;
_signalMapper[PointerEventType.enter] = () => _onEnter;
_signalMapper[PointerEventType.exit] = () => _onExit;
}
bool get isDown => _lastEvent?.rawEvent?.down ?? false;
double get mouseX => _lastEvent?.rawEvent?.localPosition?.dx ?? 0;
double get mouseY => _lastEvent?.rawEvent?.localPosition?.dy ?? 0;
void $process(PointerEventData event) {
final signal = _signalMapper[event.type]();
_lastEvent = event;
onInput?.dispatch(event);
signal?.dispatch(event);
}
}
int _traceCount = 0;
bool _showOutsideTag = false;
bool _showFilename = false;
bool _showLinenumber = false;
bool _showClassname = false;
bool _showMethodname = false;
bool _useStack = false;
String _customTag = 'graphx™🌀';
String _separator = ', ';
int _tagPaddingCount = 0;
String _tagPaddingChar = ', ';
void traceConfig({
String customTag,
int tagPaddingCount = 0,
String tagPaddingChar = ' ',
bool showFilename = false,
bool showLinenumber = false,
bool showClassname = false,
bool showMethodname = false,
bool showOutsideTag = false,
String argsSeparator = ', ',
}) {
_tagPaddingCount = tagPaddingCount;
_tagPaddingChar = tagPaddingChar;
_customTag = customTag ?? 'graphx™🌀';
_showFilename = showFilename ?? false;
_showLinenumber = showLinenumber ?? false;
_showClassname = showClassname ?? false;
_showMethodname = showMethodname ?? false;
_showOutsideTag = showOutsideTag ?? false;
_separator = argsSeparator ?? ', ';
_useStack = _showFilename || _showClassname || _showMethodname;
}
void trace(
Object arg1, [
Object arg2,
Object arg3,
Object arg4,
Object arg5,
Object arg6,
Object arg7,
Object arg8,
Object arg9,
Object arg10,
]) {
++_traceCount;
final outputList = <String>[
'$arg1',
if (arg2 != null) '$arg2',
if (arg3 != null) '$arg3',
if (arg4 != null) '$arg4',
if (arg5 != null) '$arg5',
if (arg6 != null) '$arg6',
if (arg7 != null) '$arg7',
if (arg8 != null) '$arg8',
if (arg9 != null) '$arg9',
if (arg10 != null) '$arg10',
];
var msg = outputList.join(_separator);
var name = _customTag;
if (_useStack) {
var _stack = _getStack();
if (_tagPaddingCount > 0) {
_stack = _stack.padRight(_tagPaddingCount, _tagPaddingChar);
}
if (_showOutsideTag) {
msg = '$_stack◉ $msg';
} else {
name += ' $_stack';
}
}
if( kIsWeb ){
print( '$name : $msg');
} else {
dev.log(
msg,
name: name,
time: DateTime.now(),
sequenceNumber: _traceCount,
level: 0,
);
}
}
const _anonymousMethodTag = '<anonymous closure>';
String _getStack() {
var curr = StackTrace.current.toString();
if (curr.startsWith('#0')) {
return _stackCommon(curr);
}
return _stackWeb(curr);
}
String _stackWeb(String stack) {
return '';
}
String _stackCommon(String stack) {
stack = stack.split('\n')[2];
stack = stack.replaceAll('#2 ', '');
var elements = stack.split(' (');
var classnameMethod = elements[0];
var filenameLine = elements[1];
elements = classnameMethod.split('.');
filenameLine = filenameLine.replaceAll('package:', '');
var locationParts = filenameLine.split(':');
var filePath = locationParts[0];
var callLine = locationParts[1];
var filename = filePath.substring(filePath.lastIndexOf('/') + 1, filePath.lastIndexOf('.'));
String methodName, className = '';
var output = '';
if (_showFilename) {
output += '$filename ';
if (_showLinenumber) {
output += '↪ $callLine ';
}
}
const _sufixCall = '()';
if (elements.length == 1) {
methodName = '${elements[0]}$_sufixCall';
if (_showMethodname) {
output += '‣ $methodName ';
}
} else {
className = elements.removeAt(0);
methodName = elements.join('.');
methodName = '${methodName.replaceAll(_anonymousMethodTag, '<⁕>')}$_sufixCall';
if (_showClassname) {
output += '‣ $className ';
}
if (_showMethodname) {
output += '‣ $methodName ';
}
}
return output;
}
class BaseFilter {
void resolvePaint(Paint paint) {}
bool dirty = false;
void update() {
if (dirty) {
dirty = false;
if (isValid) {
buildFilter();
}
}
}
void buildFilter() {}
bool get isValid => true;
GxRect layerBounds;
void expandBounds(GxRect layerBounds, GxRect outputBounds) {
this.layerBounds = layerBounds;
}
}
class BlurFilter extends BaseFilter {
double _blurX = 0;
double _blurY = 0;
double maskSigma = -1;
BlurStyle style = BlurStyle.inner;
double get blurX => _blurX;
double get blurY => _blurY;
set blurX(double value) {
if (_blurX == value) return;
_blurX = Math.max(value, 0);
dirty = true;
}
set blurY(double value) {
if (_blurY == value) return;
_blurY = Math.max(value, 0);
dirty = true;
}
BlurFilter([double blurX = 0, double blurY = 0]) {
this.blurX = blurX;
this.blurY = blurY;
}
MaskFilter _maskFilter;
ui.ImageFilter _imageFilter;
final _rect = GxRect();
GxRect get filterRect => _rect;
@override
void expandBounds(GxRect layerBounds, GxRect outputBounds) {
_rect.copyFrom(layerBounds).inflate(blurX, blurY);
outputBounds.expandToInclude(_rect);
}
@override
bool get isValid => _blurX > 0 || _blurY > 0;
@override
void buildFilter() {
var maxBlur = maskSigma;
if (maxBlur == -1) {
maxBlur = Math.max(_blurX, _blurY) / 2;
if (maxBlur < 1) maxBlur = 1;
}
_maskFilter = MaskFilter.blur(style ?? BlurStyle.inner, maxBlur);
_imageFilter = ui.ImageFilter.blur(sigmaX: _blurX, sigmaY: _blurY);
}
@override
void resolvePaint(Paint paint) {
if (!isValid) return;
paint.filterQuality = FilterQuality.low;
paint.imageFilter = _imageFilter;
paint.maskFilter = _maskFilter;
}
}
class ColorFilters {
static const ColorFilter invert = ColorFilter.matrix(<double>[-1, 0, 0, 0, 255, 0, -1, 0, 0, 255, 0, 0, -1, 0, 255, 0, 0, 0, 1, 0]);
static const ColorFilter sepia = ColorFilter.matrix(<double>[0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 0, 0, 0, 0, 0, 1, 0]);
static const ColorFilter greyscale = ColorFilter.matrix(<double>[0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152, 0.0722, 0, 0, 0, 0, 0, 1, 0]);
}
class ColorMatrixFilter extends BaseFilter {
ColorFilter colorFilter;
ColorMatrixFilter(ColorFilter colorFilter) {
this.colorFilter = colorFilter;
}
final _rect = GxRect();
GxRect get filterRect => _rect;
@override
bool get isValid => colorFilter != null;
@override
void resolvePaint(Paint paint) {
if (!isValid) return;
paint.colorFilter = colorFilter;
}
}
class ComposerFilter extends BaseFilter {
final Paint paint = Paint();
bool hideObject = false;
@override
void resolvePaint(Paint paint) {}
void process(Canvas canvas, Function applyPaint, [int processCount = 1]) {}
}
class DropShadowFilter extends ComposerFilter {
double _blurX = 0, _blurY = 0, _angle = 0, _distance = 0, _strength = 0.0, maskSigma = -1;
Color _color = kColorBlack;
int iterations = 1;
BlurStyle style = BlurStyle.inner;
double get blurX => _blurX;
double get blurY => _blurY;
double get angle => _angle;
double get distance => _distance;
Color get color => _color;
double _dx = 0, _dy = 0;
set color(Color value) {
if (_color == value) return;
_color = value ?? kColorBlack;
dirty = true;
}
set angle(double value) {
if (_angle == value) return;
_angle = value % Math.PI_2;
_calculatePosition();
dirty = true;
}
set distance(double value) {
value ??= 0;
if (_distance == value) return;
_distance = value;
_calculatePosition();
dirty = true;
}
void _calculatePosition() {
_dx = Math.cos(_angle) * _distance;
_dy = Math.sin(_angle) * _distance;
}
set blurX(double value) {
if (_blurX == value) return;
_blurX = Math.max(value, 0.02);
dirty = true;
}
set blurY(double value) {
if (_blurY == value) return;
_blurY = Math.max(value, 0.02);
dirty = true;
}
DropShadowFilter([
double blurX = 0,
double blurY = 0,
double angle = 0,
double distance = 0,
Color color = kColorBlack,
bool hideObject = false,
]) {
this.blurX = blurX;
this.blurY = blurY;
this.angle = angle;
this.distance = distance;
this.color = color;
this.hideObject = hideObject;
}
ColorFilter _colorFilter;
MaskFilter _maskFilter;
ui.ImageFilter _imageFilter;
final _rect = GxRect();
GxRect get filterRect => _rect;
@override
void expandBounds(GxRect layerBounds, GxRect outputBounds) {
super.expandBounds(layerBounds, outputBounds);
_rect.copyFrom(layerBounds).inflate(blurX, blurY);
outputBounds.expandToInclude(_rect);
}
@override
bool get isValid => _blurX > 0 || _blurY > 0 && color.value != kColorTransparent.value;
static const double _minBlur = .02;
@override
void buildFilter() {
var maxBlur = maskSigma;
if (maxBlur == -1) {
maxBlur = Math.max(_blurX, _blurY) / 2;
if (maxBlur < 1) maxBlur = 1;
}
_maskFilter = MaskFilter.blur(style ?? BlurStyle.inner, maxBlur);
_imageFilter = ui.ImageFilter.blur(
sigmaX: Math.max(_blurX, _minBlur),
sigmaY: Math.max(_blurY, _minBlur),
);
_colorFilter = ColorFilter.mode(_color.withAlpha(255), BlendMode.srcATop);
paint.imageFilter = _imageFilter;
paint.maskFilter = _maskFilter;
paint.colorFilter = _colorFilter;
if (_color.alpha < 255) {
paint.color = _color;
}
}
@override
void process(Canvas canvas, Function applyPaint, [int processCount = 1]) {
canvas.saveLayer(null, paint);
canvas.translate(_dx, _dy);
applyPaint(canvas);
canvas.restore();
if (++processCount <= iterations) {
process(canvas, applyPaint, processCount);
}
}
}
class GlowFilter extends ComposerFilter {
double _blurX = 0;
double _blurY = 0;
Color _color = kColorRed;
double _strength = 0.0;
int iterations = 1;
double maskSigma = -1;
BlurStyle style = BlurStyle.inner;
double get blurX => _blurX;
double get blurY => _blurY;
Color get color => _color;
set color(Color value) {
if (_color == value) return;
_color = value ?? kColorBlack;
dirty = true;
}
set blurX(double value) {
if (_blurX == value) return;
_blurX = Math.max(value, 0);
dirty = true;
}
set blurY(double value) {
if (_blurY == value) return;
_blurY = Math.max(value, 0);
dirty = true;
}
GlowFilter([
double blurX = 0,
double blurY = 0,
Color color = kColorRed,
bool hideObject = false,
]) {
this.blurX = blurX;
this.blurY = blurY;
this.color = color;
this.hideObject = hideObject;
}
ColorFilter _colorFilter;
MaskFilter _maskFilter;
ui.ImageFilter _imageFilter;
final _rect = GxRect();
GxRect get filterRect => _rect;
@override
void expandBounds(GxRect layerBounds, GxRect outputBounds) {
super.expandBounds(layerBounds, outputBounds);
_rect.copyFrom(layerBounds).inflate(blurX * 2, blurY * 2);
outputBounds.expandToInclude(_rect);
}
@override
bool get isValid => _blurX > 0 || _blurY > 0 && color.value != kColorTransparent.value;
@override
void buildFilter() {
var maxBlur = maskSigma;
if (maxBlur == -1) {
maxBlur = Math.max(_blurX, _blurY) / 2;
if (maxBlur < 1) maxBlur = 1;
}
_maskFilter = MaskFilter.blur(style ?? BlurStyle.normal, maxBlur);
_imageFilter = ui.ImageFilter.blur(sigmaX: _blurX, sigmaY: _blurY);
_colorFilter = ColorFilter.mode(_color.withAlpha(255), BlendMode.srcATop);
paint.imageFilter = _imageFilter;
paint.maskFilter = _maskFilter;
paint.colorFilter = _colorFilter;
if (_color.alpha < 255) {
paint.color = _color;
}
}
@override
void process(Canvas canvas, Function applyPaint, [int processCount = 1]) {
canvas.saveLayer(null, paint);
applyPaint(canvas);
canvas.restore();
if (++processCount <= iterations) {
process(canvas, applyPaint, processCount);
}
}
}
enum GradientType {
linear,
radial,
sweep,
}
class Graphics with RenderUtilMixin implements GxRenderable {
final _drawingQueue = <GraphicsDrawingData>[];
GraphicsDrawingData _currentDrawing = GraphicsDrawingData(null, Path());
double alpha = 1;
static final GxMatrix _helperMatrix = GxMatrix();
Graphics mask;
bool isMask = false;
Path get _path => _currentDrawing.path;
static final Path stageRectPath = Path();
static void updateStageRect(GxRect rect) {
stageRectPath.reset();
stageRectPath.addRect(rect.toNative());
}
void dispose() {
mask = null;
_drawingQueue?.clear();
_currentDrawing = null;
}
List<GraphicsDrawingData> get drawingQueue => _drawingQueue;
void copyFrom(Graphics other, [bool deepClone = false]) {
_drawingQueue.clear();
for (final command in other._drawingQueue) {
_drawingQueue.add(command.clone(deepClone, deepClone));
}
mask = other.mask;
alpha = other.alpha;
_currentDrawing = other._currentDrawing?.clone();
}
List<GxRect> getAllBounds([List<GxRect> out]) {
out ??= <GxRect>[];
_drawingQueue.forEach((e) {
final pathRect = e?.path?.getBounds();
if (pathRect == null) return;
out.add(GxRect.fromNative(pathRect));
});
return out;
}
@override
GxRect getBounds([GxRect out]) {
Rect r;
_drawingQueue.forEach((e) {
final pathRect = e?.path?.getBounds();
if (pathRect == null) return;
if (r == null) {
r = pathRect;
} else {
r = r.expandToInclude(pathRect);
}
});
final result = r ?? Rect.zero;
if (out == null) {
out = GxRect.fromNative(result);
} else {
out.setTo(result.left, result.top, result.width, result.height);
}
return out;
}
bool hitTest(GxPoint localPoint, [bool useShape = false]) {
if (useShape) {
final point = Offset(
localPoint.x,
localPoint.y,
);
for (var e in _drawingQueue) {
if (e.path.contains(point)) return true;
}
return false;
} else {
return getBounds().contains(
localPoint.x,
localPoint.y,
);
}
}
Graphics clear() {
_drawingQueue.clear();
_holeMode = false;
_currentDrawing = GraphicsDrawingData(null, Path());
return this;
}
Graphics beginBitmapFill(
GTexture texture, [
GxMatrix matrix,
bool repeat = false,
bool smooth = false,
]) {
if (_holeMode) return this;
final fill = Paint();
fill.style = PaintingStyle.fill;
fill.isAntiAlias = smooth;
var tileMode = !repeat ? TileMode.clamp : TileMode.repeated;
matrix ??= _helperMatrix;
fill.shader = ui.ImageShader(
texture.root,
tileMode,
tileMode,
matrix.toNative().storage,
);
_addFill(fill);
_currentDrawing.shaderTexture = texture;
return this;
}
Graphics beginFill(int color, [double alpha = 1]) {
if (_holeMode) return this;
final fill = Paint();
fill.style = PaintingStyle.fill;
fill.isAntiAlias = true;
fill.color = Color(color).withOpacity(alpha.clamp(0.0, 1.0));
_addFill(fill);
return this;
}
Graphics drawPicture(ui.Picture picture) {
_drawingQueue.add(GraphicsDrawingData()..picture = picture);
return this;
}
Graphics endFill() {
if (_holeMode) {
endHole();
}
_currentDrawing = GraphicsDrawingData(null, Path());
return this;
}
Graphics lineStyle([
double thickness = 0.0,
int color = 0x0,
double alpha = 1.0,
bool pixelHinting = true,
StrokeCap caps,
StrokeJoin joints,
double miterLimit = 3.0,
]) {
alpha ??= 1.0;
alpha = alpha.clamp(0.0, 1.0);
final paint = Paint();
paint.style = PaintingStyle.stroke;
paint.color = Color(color).withOpacity(alpha);
paint.strokeWidth = thickness;
paint.isAntiAlias = pixelHinting;
paint.strokeCap = caps ?? StrokeCap.butt;
paint.strokeJoin = joints ?? StrokeJoin.miter;
if (SystemUtils.usingSkia) {
paint.strokeMiterLimit = miterLimit;
}
_addFill(paint);
return this;
}
Graphics beginGradientFill(
GradientType type,
List<int> colors, {
List<double> alphas,
List<double> ratios,
Alignment begin,
Alignment end,
double rotation = 0,
TileMode tileMode = TileMode.clamp,
Rect gradientBox,
double radius = 0.5,
double focalRadius = 0.0,
double sweepStartAngle = 0.0,
double sweepEndAngle = 6.2832,
}) {
final gradient = _createGradient(
type,
colors,
alphas,
ratios,
begin,
end,
rotation,
radius,
focalRadius,
sweepStartAngle,
sweepEndAngle,
tileMode,
);
final paint = Paint();
paint.style = PaintingStyle.fill;
_addFill(paint);
if (gradientBox != null) {
_currentDrawing.fill.shader = gradient.createShader(gradientBox);
} else {
_currentDrawing.gradient = gradient;
}
return this;
}
Gradient _createGradient(
GradientType type,
List<int> colors, [
List<double> alphas,
List<double> ratios,
Alignment begin,
Alignment end,
double rotation = 0,
double radius = 0.5,
double focalRadius = 0.0,
double sweepStartAngle = 0.0,
double sweepEndAngle = 6.2832,
TileMode tileMode = TileMode.clamp,
]) {
final _colors = _GraphUtils.colorsFromHex(colors, alphas);
final transform = GradientRotation(rotation);
if (type == GradientType.radial) {
return RadialGradient(
center: begin ?? Alignment.center,
focal: end,
radius: radius,
colors: _colors,
stops: ratios,
tileMode: tileMode,
focalRadius: focalRadius,
transform: transform,
);
} else if (type == GradientType.sweep) {
return SweepGradient(
center: begin ?? Alignment.center,
colors: _colors,
stops: ratios,
tileMode: tileMode,
transform: transform,
startAngle: sweepStartAngle,
endAngle: sweepEndAngle,
);
}
return LinearGradient(
colors: _colors,
stops: ratios,
begin: begin ?? Alignment.centerLeft,
end: end ?? Alignment.centerRight,
tileMode: tileMode,
transform: transform,
);
}
Graphics lineGradientStyle(
GradientType type,
List<int> colors, {
List<double> alphas,
List<double> ratios,
Alignment begin,
Alignment end,
double rotation = 0,
double radius = 0.5,
double focalRadius = 0.0,
double sweepStartAngle = 0.0,
double sweepEndAngle = 6.2832,
Rect gradientBox,
TileMode tileMode = TileMode.clamp,
}) {
assert(_currentDrawing.fill.style == PaintingStyle.stroke);
final gradient = _createGradient(
type,
colors,
alphas,
ratios,
begin,
end,
rotation,
radius,
focalRadius,
sweepStartAngle,
sweepEndAngle,
tileMode,
);
if (gradientBox != null) {
_currentDrawing.fill.shader = gradient.createShader(gradientBox);
} else {
_currentDrawing.gradient = gradient;
}
return this;
}
Graphics lineBitmapStyle(
GTexture texture, [
GxMatrix matrix,
bool repeat = true,
bool smooth = false,
]) {
assert(_currentDrawing.fill.style == PaintingStyle.stroke);
if (_holeMode) return this;
final fill = _currentDrawing.fill;
fill.isAntiAlias = smooth;
var tileMode = !repeat ? TileMode.clamp : TileMode.repeated;
matrix ??= _helperMatrix;
fill.shader = ui.ImageShader(
texture.root,
tileMode,
tileMode,
matrix.toNative().storage,
);
return this;
}
Graphics moveTo(double x, double y) {
_path.moveTo(x, y);
return this;
}
Graphics lineTo(double x, double y) {
_path.lineTo(x, y);
return this;
}
Graphics closePath() {
_path.close();
return this;
}
Graphics cubicCurveTo(double controlX1, double controlY1, double controlX2, double controlY2, double anchorX, double anchorY) {
_path.cubicTo(controlX1, controlY1, controlX2, controlY2, anchorX, anchorY);
return this;
}
Graphics curveTo(double controlX, double controlY, double anchorX, double anchorY) {
_path.quadraticBezierTo(controlX, controlY, anchorX, anchorY);
return this;
}
Graphics conicCurveTo(double controlX, double controlY, double anchorX, double anchorY, double weight, [bool relative = false]) {
if (!relative) {
_path.conicTo(controlX, controlY, anchorX, anchorY, weight);
} else {
_path.relativeConicTo(controlX, controlY, anchorX, anchorY, weight);
}
return this;
}
Graphics drawCircle(double x, double y, double radius) {
final pos = Offset(x, y);
final circ = Rect.fromCircle(center: pos, radius: radius);
_path.addOval(circ);
return this;
}
Graphics drawEllipse(double x, double y, double radiusX, double radiusY) {
final pos = Offset(x, y);
_path.addOval(
Rect.fromCenter(
center: pos,
width: radiusX * 2,
height: radiusY * 2,
),
);
return this;
}
Graphics drawGxRect(GxRect rect) {
_path.addRect(rect.toNative());
return this;
}
Graphics drawRect(double x, double y, double width, double height) {
final r = Rect.fromLTWH(x, y, width, height);
_path.addRect(r);
return this;
}
Graphics drawRoundRectComplex(
double x,
double y,
double width,
double height, [
double topLeftRadius = 0,
double topRightRadius = 0,
double bottomLeftRadius = 0,
double bottomRightRadius = 0,
]) {
_path.addRRect(
RRect.fromLTRBAndCorners(
x,
y,
x + width,
y + height,
topLeft: Radius.circular(topLeftRadius),
topRight: Radius.circular(topRightRadius),
bottomLeft: Radius.circular(bottomLeftRadius),
bottomRight: Radius.circular(bottomRightRadius),
),
);
return this;
}
Graphics drawRoundRect(
double x,
double y,
double width,
double height,
double ellipseWidth, [
double ellipseHeight,
]) {
final r = RRect.fromLTRBXY(x, y, x + width, y + height, ellipseWidth, ellipseHeight ?? ellipseWidth);
_path.addRRect(r);
return this;
}
Graphics drawPoly(List<GxPoint> points, [bool closePolygon = true]) {
final len = points.length;
final list = List<Offset>(len);
for (var i = 0; i < len; ++i) {
list[i] = points[i].toNative();
}
_path.addPolygon(list, true);
return this;
}
void shiftPath(double x, double y, [bool modifyPreviousPaths = false]) {
final offset = Offset(x, y);
if (modifyPreviousPaths) {
_drawingQueue.forEach((command) {
command?.path = command?.path?.shift(offset);
});
} else {
_currentDrawing?.path = _currentDrawing?.path?.shift(offset);
}
}
Graphics arcOval(
double cx,
double cy,
double radiusX,
double radiusY,
double startAngle,
double sweepAngle,
) {
_path.addArc(
Rect.fromCenter(center: Offset(cx, cy), width: radiusX * 2, height: radiusY * 2),
startAngle,
sweepAngle,
);
return this;
}
Graphics arc(double cx, double cy, double radius, double startAngle, double sweepAngle, [bool moveTo = false]) {
if (sweepAngle == 0) return this;
if (!moveTo) {
_path.arcTo(
Rect.fromCircle(center: Offset(cx, cy), radius: radius),
startAngle,
sweepAngle,
false,
);
} else {
_path.addArc(
Rect.fromCircle(center: Offset(cx, cy), radius: radius),
startAngle,
sweepAngle,
);
}
return this;
}
Graphics arcToPoint(
double endX,
double endY,
double radius, [
double rotation = 0.0,
bool largeArc = false,
bool clockwise = true,
bool relativeMoveTo = false,
]) {
if (radius == 0) return this;
if (relativeMoveTo) {
_path.arcToPoint(
Offset(endX, endY),
radius: Radius.circular(radius),
clockwise: clockwise,
largeArc: largeArc,
rotation: rotation,
);
} else {
_path.relativeArcToPoint(
Offset(endX, endY),
radius: Radius.circular(radius),
clockwise: clockwise,
largeArc: largeArc,
rotation: rotation,
);
}
return this;
}
Graphics drawPolygonFaces(
double x,
double y,
double radius,
int sides, [
double rotation = 0,
]) {
final points = List<Offset>(sides);
final rel = Math.PI_2 / sides;
for (var i = 1; i <= sides; ++i) {
final px = x + radius * Math.cos(i * rel + rotation);
final py = y + radius * Math.sin(i * rel + rotation);
points[i - 1] = Offset(px, py);
}
_path.addPolygon(points, true);
return this;
}
Graphics drawStar(
double x,
double y,
int points,
double radius, [
double innerRadius,
double rotation = 0,
]) {
innerRadius ??= radius / 2;
final startAngle = (-1 * Math.PI / 2) + rotation;
final len = points * 2;
final delta = Math.PI * 2 / len;
final polys = <Offset>[];
for (var i = 0; i < len; ++i) {
final r = i.isOdd ? innerRadius : radius;
final a = i * delta + startAngle;
polys.add(Offset(
x + (r * Math.cos(a)),
y + (r * Math.sin(a)),
));
}
_path.addPolygon(polys, true);
return this;
}
bool _holeMode = false;
Graphics beginHole() {
if (_holeMode) return this;
_holeMode = true;
_currentDrawing = GraphicsDrawingData(null, Path())..isHole = true;
return this;
}
Graphics endHole([bool applyToCurrentQueue = false]) {
_holeMode = false;
if (!_currentDrawing.isHole) {
throw "Can't endHole() without starting a beginHole() command.";
}
final _holePath = _path;
_holePath.close();
_currentDrawing = _drawingQueue.last;
if (!applyToCurrentQueue) {
_currentDrawing.path = Path.combine(
PathOperation.difference,
_path,
_holePath,
);
} else {
for (final cmd in _drawingQueue) {
cmd.path = Path.combine(
PathOperation.difference,
cmd.path,
_holePath,
);
}
}
return this;
}
void paintWithFill(Canvas canvas, Paint fill) {
if (!_isVisible) return;
_drawingQueue.forEach((graph) {
if (graph.hasPicture) {
canvas.drawPicture(graph.picture);
return;
}
canvas.drawPath(graph.path, fill);
});
}
Path getPaths() {
var output = Path();
if (SystemUtils.usingSkia) {
_drawingQueue.forEach((graph) {
output = Path.combine(PathOperation.union, output, graph.path);
});
} else {
trace('Graphics.getPaths() is unsupported in the current platform.');
}
return output;
}
@override
void paint(Canvas canvas) {
if (isMask) {
_drawingQueue.forEach((graph) {
canvas.clipPath(graph.path, doAntiAlias: false);
});
return;
}
_constrainAlpha();
if (!_isVisible) return;
_drawingQueue.forEach((graph) {
if (graph.hasPicture) {
canvas.drawPicture(graph.picture);
return;
}
final fill = graph.fill;
final baseColor = fill.color;
if (baseColor.alpha == 0) return;
if (graph.hasGradient) {
Rect _bounds;
if (graph.hasVertices) {
_bounds = graph.vertices.getBounds();
} else {
_bounds = graph.path.getBounds();
}
fill.shader = graph.gradient.createShader(_bounds);
} else {
if (alpha != 1) {
fill.color = baseColor.withOpacity(baseColor.opacity * alpha);
}
}
if (graph.hasVertices) {
if (graph.vertices.uvtData != null && graph.shaderTexture != null) {
graph.vertices.calculateUvt(graph.shaderTexture);
}
var myPaint = Paint();
myPaint.strokeWidth = 2;
myPaint.color = Color(0xffff00ff);
myPaint.style = PaintingStyle.stroke;
if (fill.style == PaintingStyle.stroke) {
canvas.drawRawPoints(ui.PointMode.lines, graph.vertices.rawPoints, fill);
} else {
canvas.drawVertices(
graph.vertices.rawData,
graph.vertices.blendMode ?? BlendMode.src,
fill,
);
}
} else {
canvas.drawPath(graph.path, fill);
}
fill.color = baseColor;
});
}
void _addFill(Paint fill) {
Path path;
if (_currentDrawing.isSameType(fill)) {
path = Path();
} else {
path = _currentDrawing?.path;
}
_currentDrawing = GraphicsDrawingData(fill, path);
_drawingQueue.add(_currentDrawing);
}
bool get _isVisible => alpha > 0.0 || _drawingQueue.isEmpty;
void pushData(
GraphicsDrawingData data, [
bool asCurrent = false,
double x,
double y,
]) {
if (x != null && y != null && data.path != null) {
data.path = data.path.shift(Offset(x, y));
}
_drawingQueue.add(data);
if (asCurrent) _currentDrawing = data;
}
GraphicsDrawingData popData() {
return _drawingQueue.removeLast();
}
void removeData(GraphicsDrawingData data) {
_drawingQueue.remove(data);
}
void _constrainAlpha() {
alpha = alpha.clamp(0.0, 1.0);
}
Graphics drawPath(Path path, [double x = 0, double y = 0, GxMatrix transform]) {
_path.addPath(
path,
Offset(x, y),
matrix4: transform?.toNative()?.storage,
);
return this;
}
Graphics drawTriangles(
List<double> vertices, [
List<int> indices,
List<double> uvtData,
List<int> colors,
BlendMode blendMode = BlendMode.src,
Culling culling = Culling.positive,
]) {
assert(_currentDrawing != null);
assert(_currentDrawing.fill != null);
_currentDrawing.vertices = _GraphVertices(VertexMode.triangles, vertices, indices, uvtData, colors, blendMode, culling);
return this;
}
}
class GraphicsDrawingData {
Path path;
Paint fill;
Gradient gradient;
ui.Picture picture;
bool isHole = false;
BlendMode blendMode = BlendMode.src;
_GraphVertices vertices;
GTexture shaderTexture;
bool get hasVertices => vertices != null;
GraphicsDrawingData([this.fill, this.path]);
bool get hasPicture => picture != null;
bool get hasGradient => gradient != null;
GraphicsDrawingData clone([
bool cloneFill = false,
bool clonePath = false,
]) {
final _fill = cloneFill ? fill?.clone() : fill;
final _path = clonePath ? (path != null ? Path.from(path) : null) : path;
final _vertices = vertices;
return GraphicsDrawingData(_fill, _path)
..gradient = gradient
..picture = picture
..blendMode = blendMode
..vertices = _vertices;
}
bool isSameType(Paint otherFill) {
return fill?.style == otherFill?.style ?? false;
}
}
extension ExtSkiaPaintCustom on Paint {
Paint clone([Paint out]) {
out ??= Paint();
out.maskFilter = maskFilter;
out.blendMode = blendMode;
out.color = color;
out.style = style;
out.colorFilter = colorFilter;
out.filterQuality = filterQuality;
out.imageFilter = imageFilter;
out.invertColors = invertColors;
out.isAntiAlias = isAntiAlias;
out.shader = shader;
out.strokeCap = strokeCap;
out.strokeJoin = strokeJoin;
if (SystemUtils.usingSkia) {
out.strokeMiterLimit = strokeMiterLimit;
}
out.strokeWidth = strokeWidth;
return out;
}
}
class _GraphVertices {
List<double> vertices, uvtData;
List<double> adjustedUvtData;
List<int> colors, indices;
BlendMode blendMode;
VertexMode mode;
Path _path;
Rect _bounds;
bool _normalizedUvt;
Float32List _rawPoints;
Float32List get rawPoints {
if (_rawPoints != null) return _rawPoints;
var points = _GraphUtils.getTrianglePoints(this);
_rawPoints = Float32List.fromList(points);
return _rawPoints;
}
Culling culling;
_GraphVertices(
this.mode,
this.vertices, [
this.indices,
this.uvtData,
this.colors,
this.blendMode = BlendMode.src,
this.culling = Culling.positive,
]) {
_normalizedUvt = false;
if (uvtData != null && uvtData.length > 6) {
for (var i = 0; i < 6; ++i) {
if (uvtData[i] <= 2.0) {
_normalizedUvt = true;
}
}
if (uvtData[uvtData.length - 2] <= 2.0 || uvtData[uvtData.length - 1] <= 2.0) {
_normalizedUvt = true;
}
}
}
void reset() {
_path?.reset();
_rawData = null;
_bounds = null;
}
Rect getBounds() {
if (_bounds != null) return _bounds;
_bounds = computePath().getBounds();
return _bounds;
}
Path computePath() {
_path ??= _GraphUtils.getPathFromVertices(this);
return _path;
}
ui.Vertices _rawData;
ui.Vertices get rawData {
if (_rawData != null) {
return _rawData;
}
Float32List _textureCoordinates;
Int32List _colors;
Uint16List _indices;
if (uvtData != null && adjustedUvtData != null) {
_textureCoordinates = Float32List.fromList(adjustedUvtData);
}
if (colors != null) _colors = Int32List.fromList(colors);
if (indices != null) _indices = Uint16List.fromList(indices);
_rawData = ui.Vertices.raw(
VertexMode.triangles,
Float32List.fromList(vertices),
textureCoordinates: _textureCoordinates,
colors: _colors,
indices: _indices,
);
return _rawData;
}
void calculateUvt(GTexture shaderTexture) {
if (uvtData == null) return;
if (!_normalizedUvt) {
adjustedUvtData = uvtData;
} else {
var imgW = shaderTexture.width;
var imgH = shaderTexture.height;
adjustedUvtData = List<double>(uvtData.length);
for (var i = 0; i < uvtData.length; i += 2) {
adjustedUvtData[i] = uvtData[i] * imgW;
adjustedUvtData[i + 1] = uvtData[i + 1] * imgH;
}
}
}
void calculateCulling() {
var i = 0;
var offsetX = 0.0, offsetY = 0.0;
var ind = indices;
var v = vertices;
var l = indices.length;
while (i < l) {
var _a = i;
var _b = i + 1;
var _c = i + 2;
var iax = ind[_a] * 2;
var iay = ind[_a] * 2 + 1;
var ibx = ind[_b] * 2;
var iby = ind[_b] * 2 + 1;
var icx = ind[_c] * 2;
var icy = ind[_c] * 2 + 1;
var x1 = v[iax] - offsetX;
var y1 = v[iay] - offsetY;
var x2 = v[ibx] - offsetX;
var y2 = v[iby] - offsetY;
var x3 = v[icx] - offsetX;
var y3 = v[icy] - offsetY;
switch (culling) {
case Culling.positive:
if (!_GraphUtils.isCCW(x1, y1, x2, y2, x3, y3)) {
i += 3;
continue;
}
break;
case Culling.negative:
if (_GraphUtils.isCCW(x1, y1, x2, y2, x3, y3)) {
i += 3;
continue;
}
break;
default:
break;
}
i += 3;
}
}
}
class _GraphUtils {
static bool isCCW(
double x1,
double y1,
double x2,
double y2,
double x3,
double y3,
) =>
((x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1)) < 0;
static List<Color> colorsFromHex(List<int> colors, List<double> alphas) {
final _colors = List<Color>(colors.length);
for (var i = 0; i < colors.length; ++i) {
final a = (alphas != null && i < alphas.length ? alphas[i] : 1.0);
_colors[i] = Color(colors[i]).withOpacity(a);
}
return _colors;
}
static final Path _helperPath = Path();
static Path getPathFromVertices(_GraphVertices v) {
var path = _helperPath;
path.reset();
var pos = v.vertices;
var len = pos.length;
final points = <Offset>[];
for (var i = 0; i < len; i += 2) {
points.add(Offset(pos[i], pos[i + 1]));
}
path.addPolygon(points, true);
return path;
}
static List<double> getTrianglePoints(_GraphVertices v) {
var ver = v.vertices;
var ind = v.indices;
if (ind == null) {
var len = ver.length;
var out = List<double>(len * 2);
var j = 0;
for (var i = 0; i < len; i += 6) {
out[j++] = ver[i + 0];
out[j++] = ver[i + 1];
out[j++] = ver[i + 2];
out[j++] = ver[i + 3];
out[j++] = ver[i + 2];
out[j++] = ver[i + 3];
out[j++] = ver[i + 4];
out[j++] = ver[i + 5];
out[j++] = ver[i + 4];
out[j++] = ver[i + 5];
out[j++] = ver[i + 0];
out[j++] = ver[i + 1];
}
return out;
} else {
var len = ind.length;
var out = List<double>(len * 4);
var j = 0;
for (var i = 0; i < len; i += 3) {
var i0 = ind[i + 0];
var i1 = ind[i + 1];
var i2 = ind[i + 2];
var v0 = i0 * 2;
var v1 = i1 * 2;
var v2 = i2 * 2;
out[j++] = ver[v0];
out[j++] = ver[v0 + 1];
out[j++] = ver[v1];
out[j++] = ver[v1 + 1];
out[j++] = ver[v1];
out[j++] = ver[v1 + 1];
out[j++] = ver[v2];
out[j++] = ver[v2 + 1];
out[j++] = ver[v2];
out[j++] = ver[v2 + 1];
out[j++] = ver[v0];
out[j++] = ver[v0 + 1];
}
return out;
}
}
}
enum Culling { negative, positive }
class GxIcon extends DisplayObject {
static final _sHelperMatrix = GxMatrix();
final _localBounds = GxRect();
@override
GxRect getBounds(DisplayObject targetSpace, [GxRect out]) {
_sHelperMatrix.identity();
getTransformationMatrix(targetSpace, _sHelperMatrix);
var r = MatrixUtils.getTransformedBoundsRect(
_sHelperMatrix,
_localBounds,
out,
);
return r;
}
@override
DisplayObject hitTest(GxPoint localPoint, [bool useShape = false]) {
if (!visible || !mouseEnabled) return null;
return _localBounds.containsPoint(localPoint) ? this : null;
}
IconData _data;
double _size;
int _color;
bool _invalidStyle = false;
int get color => _color;
set color(int value) {
if (value == _color) return;
_color = value;
_invalidStyle = true;
requiresRedraw();
}
double get size => _size;
set size(double value) {
if (value == _size) return;
_size = value;
_localBounds?.setTo(0, 0, size, size);
_invalidStyle = true;
requiresRedraw();
}
IconData get data => _data;
set data(IconData value) {
if (value == _data) return;
_data = value;
_invalidStyle = true;
requiresRedraw();
}
GxIcon(IconData data, [
int color = 0xffffff,
double size = 24.0,
]) {
_data = data;
_color = color;
this.size = size;
_setup();
}
@override
set alpha(double value) {
if ($alpha == value) return;
super.alpha = value;
_invalidStyle = true;
requiresRedraw();
}
ui.Paragraph _paragraph;
ui.ParagraphBuilder _builder;
ui.TextStyle _style;
Paint _paint;
Shadow _shadow;
void setPaint(Paint value) {
_paint = value;
_invalidStyle = true;
requiresRedraw();
}
void setShadow(Shadow value) {
_shadow = value;
_invalidStyle = true;
requiresRedraw();
}
void _updateStyle() {
if (_data == null) return;
_style = ui.TextStyle(
color: _paint == null ? Color(color).withOpacity(alpha) : null,
fontSize: _size,
fontFamily: _resolveFontFamily(),
foreground: _paint,
shadows: _shadow != null ? [_shadow] : null,
);
_builder = ui.ParagraphBuilder(ui.ParagraphStyle());
_builder.pushStyle(_style);
final charCode = String.fromCharCode(_data.codePoint);
_builder.addText(charCode);
_paragraph = _builder.build();
_paragraph.layout(ui.ParagraphConstraints(width: double.infinity));
_invalidStyle = false;
}
String _resolveFontFamily() {
if (data == null) return null;
if (data.fontPackage == null) {
return data.fontFamily;
} else {
return 'packages/${data.fontPackage}/${data.fontFamily}';
}
}
void _setup() {
_updateStyle();
}
@override
void $applyPaint(Canvas canvas) {
if (data == null) return;
if (_invalidStyle) {
_invalidStyle = false;
_updateStyle();
}
if (_paragraph != null) {
canvas.drawParagraph(_paragraph, Offset.zero);
}
}
}
class GifFrame {
Duration duration;
GTexture texture;
GifFrame(this.duration, this.texture);
}
class GifAtlas extends GTexture {
final List<GifFrame> _frames = [];
List<GTexture> get textureFrames {
return _frames.map((e) => e.texture).toList();
}
void addFrame(GifFrame frame) {
_frames.add(frame);
if (_frames.length == 1) {
_refresh();
}
}
int numFrames = 0;
GifAtlas();
int _frame = 0;
void _refresh() {
root = _frames[_frame].texture.root;
}
bool prevFrame() {
--_frame;
if (_frame < 0) _frame = numFrames - 1;
_refresh();
return true;
}
bool nextFrame() {
++_frame;
_frame %= numFrames;
_refresh();
return true;
}
}
class GSubTexture extends GTexture {
GTexture _parent;
bool _ownsParent;
GxRect _region;
GxRect _sourceRegion;
bool _rotated;
double _w;
double _h;
double _scale;
Rect _sourceRegionRect;
Rect _destRect;
GSubTexture(GTexture parent, {GxRect region, bool ownsParent = false, GxRect frame, bool rotated, double scaleModifier = 1}) {
$setTo(
parent,
region: region,
ownsParent: ownsParent,
frame: frame,
rotated: rotated,
scaleModifier: scaleModifier,
);
}
void $setTo(GTexture parent, {GxRect region, bool ownsParent = false, GxRect frame, bool rotated, double scaleModifier = 1}) {
_region ??= GxRect();
if (region != null) {
_region.copyFrom(region);
} else {
_region.setTo(0, 0, parent.nativeWidth, parent.nativeHeight);
}
if (frame != null) {
if (this.frame != null) {
this.frame.copyFrom(frame);
} else {
this.frame = frame.clone();
}
} else {
this.frame = null;
}
_parent = parent;
_ownsParent = ownsParent;
_rotated = rotated;
_w = (rotated ? _region.height : _region.width) / scaleModifier;
_h = (rotated ? _region.width : _region.height) / scaleModifier;
_scale = parent.scale * scaleModifier;
_sourceRegion = _region.clone() * scale;
_sourceRegionRect = _sourceRegion.toNative();
_destRect = Rect.fromLTWH(0, 0, nativeWidth, nativeHeight);
sourceRect = GxRect(0, 0, nativeWidth, nativeHeight);
}
@override
void dispose() {
if (_ownsParent) {
_parent.dispose();
}
super.dispose();
}
GxRect get region => _region;
bool get rotated => _rotated;
bool get ownsParent => _ownsParent;
GTexture get parent => _parent;
@override
ui.Image get root => _parent.root;
@override
double get width => _w;
@override
double get height => _h;
@override
double get nativeWidth => _w * _scale;
@override
double get nativeHeight => _h * _scale;
@override
double get scale => _scale;
void updateMatrices() {}
@override
void render(Canvas canvas, [Paint paint]) {
paint ??= GTexture.sDefaultPaint;
paint.isAntiAlias = true;
canvas.drawImageRect(root, _sourceRegionRect, _destRect, paint);
}
}
class GTexture {
GxRect frame;
GxRect scale9Grid;
GxRect scale9GridDest;
double get width => nativeWidth;
double get height => nativeHeight;
double get nativeWidth => root?.width?.toDouble() ?? 0;
double get nativeHeight => root?.height?.toDouble() ?? 0;
Color color;
double get frameWidth => frame?.width ?? nativeWidth;
double get frameHeight => frame?.height ?? nativeHeight;
double actualWidth, actualHeight;
double pivotX = 0, pivotY = 0;
GxRect sourceRect;
double scale = 1;
ui.Image root;
void copyFrom(GTexture other) {
root = other.root;
color = other.color;
actualWidth = other.actualWidth;
actualHeight = other.actualHeight;
sourceRect = other.sourceRect;
pivotX = other.pivotX;
pivotY = other.pivotY;
scale = other.scale;
}
GxRect getBounds() {
return sourceRect;
}
static GTexture fromColor(double w, double h, Color color, [double scale = 1]) {
var texture = GTexture.empty(w, h, scale);
texture.color = color;
return texture;
}
static GTexture fromImage(
ui.Image data, [
double scale = 1,
]) {
var texture = GTexture.empty(data.width / scale, data.height / scale, scale);
texture.root = data;
texture.actualWidth = data.width.toDouble();
texture.actualHeight = data.height.toDouble();
texture.sourceRect = GxRect(0, 0, data.width / scale, data.height / scale);
return texture;
}
static double contentScaleFactor = 1;
static GTexture empty(double width, double height, [double scale = -1]) {
if (scale <= 0) {
scale = contentScaleFactor;
}
var oriWidth = width * scale;
var oriHeight = height * scale;
var actualW = oriWidth;
var actualH = oriHeight;
final t = GTexture();
t.actualWidth = actualW;
t.actualHeight = actualH;
t.scale = scale;
return t;
}
static Paint sDefaultPaint = Paint();
void render(Canvas canvas, [Paint paint]) {
paint ??= sDefaultPaint;
if (scale != 1) {
canvas.save();
canvas.scale(1 / scale);
_drawImage(canvas, paint);
canvas.restore();
} else {
_drawImage(canvas, paint);
}
}
void _drawImage(Canvas canvas, Paint paint) {
if (scale9Grid != null) {
canvas.drawImageNine(
root,
scale9Grid.toNative(),
scale9GridDest.toNative(),
paint,
);
} else {
canvas.drawImage(root, Offset.zero, paint);
}
}
void dispose() {
root?.dispose();
root = null;
color = null;
frame = null;
}
}
class GxTicker {
GxTicker();
Ticker _ticker;
EventSignal<double> _onFrame;
EventSignal<double> get onFrame => _onFrame ??= EventSignal();
VoidCallback _nextFrameCallback;
void callNextFrame(VoidCallback callback) {
_nextFrameCallback = callback;
}
void _createTicker() {
if (_ticker != null) return;
_ticker = Ticker(_onTick);
_ticker.start();
_ticker.muted = true;
}
bool get isTicking => _ticker?.isTicking ?? false;
bool get isActive => _ticker?.isActive ?? false;
double get currentDeltaTime => _currentDeltaTime;
double get currentDeltaRatio => _currentDeltaRatio;
void resume() {
if (isTicking) return;
_createTicker();
_ticker?.muted = false;
_expectedDelta = 1.0 / frameRate;
}
void pause() {
if (!isTicking) return;
_ticker?.muted = true;
}
double _currentTime = 0;
double _currentDeltaTime = 0;
double _currentDeltaRatio = 0.0;
double frameRate = 60.0;
double _expectedDelta;
void _onTick(Duration elapsed) {
var now = elapsed.inMilliseconds.toDouble() * .001;
_currentDeltaTime = (now - _currentTime);
_currentTime = now;
_currentDeltaTime = _currentDeltaTime.clamp(1.0 / frameRate, 1.0);
_currentDeltaRatio = _currentDeltaTime / _expectedDelta;
if (_nextFrameCallback != null) {
var callback = _nextFrameCallback;
_nextFrameCallback = null;
callback?.call();
}
_onFrame?.dispatch(_currentDeltaTime);
}
void dispose() {
_onFrame?.removeAll();
_onFrame = null;
_ticker?.stop(canceled: true);
_ticker?.dispose();
_ticker = null;
}
}
Stopwatch _stopwatch;
void _initTimer() {
if (_stopwatch != null) return;
_stopwatch = Stopwatch();
_stopwatch.start();
}
int getTimer() {
if (_stopwatch == null) {
_initTimer();
}
return _stopwatch.elapsedMilliseconds;
}
class PropTween {
GTweenable t;
Object p;
double s;
double c;
Object cObj;
bool f;
int pr;
bool pg;
String n;
bool r;
PropTween _next, _prev;
PropTween({
GTweenable target,
Object property,
double start,
double change,
String name,
PropTween next,
int priority = 0,
}) {
t = target;
p = property;
s = start;
c = change;
n = name;
if (next != null) {
next._prev = this;
_next = next;
}
pr = priority;
}
}
typedef EaseDef = double Function(double e);
typedef EaseFunction = double Function(double e);
abstract class GEase {
static EaseFunction defaultEasing = decelerate;
static EaseFunction ease = Curves.ease.transform;
static EaseFunction easeOut = Curves.easeOut.transform;
static EaseFunction easeIn = Curves.easeIn.transform;
static EaseFunction easeInOut = Curves.easeInOut.transform;
static EaseFunction elasticIn = Curves.elasticIn.transform;
static EaseFunction elasticOut = Curves.elasticOut.transform;
static EaseFunction elasticInOut = Curves.elasticInOut.transform;
static EaseFunction bounceOut = Curves.bounceOut.transform;
static EaseFunction bounceIn = Curves.bounceIn.transform;
static EaseFunction bounceInOut = Curves.bounceInOut.transform;
static EaseFunction easeOutBack = Curves.easeOutBack.transform;
static EaseFunction easeInBack = Curves.easeInBack.transform;
static EaseFunction easeInOutBack = Curves.easeInOutBack.transform;
static EaseFunction easeOutSine = Curves.easeOutSine.transform;
static EaseFunction easeInSine = Curves.easeInSine.transform;
static EaseFunction easeInOutSine = Curves.easeInOutSine.transform;
static EaseFunction easeOutQuad = Curves.easeOutQuad.transform;
static EaseFunction easeInQuad = Curves.easeInQuad.transform;
static EaseFunction easeInOutQuad = Curves.easeInOutQuad.transform;
static EaseFunction easeOutCubic = Curves.easeOutCubic.transform;
static EaseFunction easeInCubic = Curves.easeInCubic.transform;
static EaseFunction easeInOutCubic = Curves.easeInOutCubic.transform;
static EaseFunction easeOutQuart = Curves.easeOutQuart.transform;
static EaseFunction easeInQuart = Curves.easeInQuart.transform;
static EaseFunction easeInOutQuart = Curves.easeInOutQuart.transform;
static EaseFunction easeOutQuint = Curves.easeOutQuint.transform;
static EaseFunction easeInQuint = Curves.easeInQuint.transform;
static EaseFunction easeInOutQuint = Curves.easeInOutQuint.transform;
static EaseFunction easeOutCirc = Curves.easeOutCirc.transform;
static EaseFunction easeInCirc = Curves.easeInCirc.transform;
static EaseFunction easeInOutCirc = Curves.easeInOutCirc.transform;
static EaseFunction easeOutExpo = Curves.easeOutCirc.transform;
static EaseFunction easeInExpo = Curves.easeInExpo.transform;
static EaseFunction easeInOutExpo = Curves.easeInOutExpo.transform;
static EaseFunction decelerate = Curves.decelerate.transform;
static EaseFunction easeInToLinear = Curves.easeInToLinear.transform;
static EaseFunction fastLinearToSlowEaseIn = Curves.fastLinearToSlowEaseIn.transform;
static EaseFunction fastOutSlowIn = Curves.fastOutSlowIn.transform;
static EaseFunction linear = Curves.linear.transform;
static EaseFunction linearToEaseOut = Curves.linearToEaseOut.transform;
static EaseFunction slowMiddle = Curves.slowMiddle.transform;
}
extension GTweenNumExt on num {
GTweenableDouble get twn {
return this is double ? GTweenableDouble(this) : GTweenableInt(this);
}
}
extension GTweenDoubleExt on double {
GTweenableDouble get twn => GTweenableDouble(this);
}
extension GTweenIntExt on int {
GTweenableInt get twn => GTweenableInt(this);
}
extension GTweenMapExt on Map<String, dynamic> {
GTweenableMap get twn => GTweenableMap(this);
}
extension GTweenMap2Ext on Map {
GTweenableMap get twn => GTweenableMap(this);
}
extension GTweenListExt on List {
GTweenableList get twn => GTweenableList(this);
}
extension GTweenDiplayObjectExt on DisplayObject {
GTweenableDisplayObject get twn => GTweenableDisplayObject(this);
GTween tween({
@required double duration,
Object x,
Object y,
Object scaleX,
Object scaleY,
Object scale,
Object rotation,
Object rotationX,
Object rotationY,
Object pivotX,
Object pivotY,
Object width,
Object height,
Object skewX,
Object skewY,
Color colorize,
Object alpha,
EaseFunction ease,
double delay,
bool useFrames,
int overwrite = 1,
Function onStart,
Object onStartParams,
Function onComplete,
Object onCompleteParams,
Function onUpdate,
Object onUpdateParams,
bool runBackwards,
bool immediateRender,
Map startAt,
}) {
final targetValues = {
if (x != null) 'x': x,
if (y != null) 'y': y,
if (scaleX != null) 'scaleX': scaleX,
if (scaleY != null) 'scaleY': scaleY,
if (scale != null) 'scale': scale,
if (rotation != null) 'rotation': rotation,
if (rotationX != null) 'rotationX': rotationX,
if (rotationY != null) 'rotationY': rotationY,
if (pivotX != null) 'pivotX': pivotX,
if (pivotY != null) 'pivotY': pivotY,
if (width != null) 'width': width,
if (height != null) 'height': height,
if (skewX != null) 'skewX': skewX,
if (skewY != null) 'skewY': skewY,
if (alpha != null) 'alpha': alpha,
if (colorize != null) 'colorize': colorize,
};
return GTween.to(
this,
duration,
targetValues,
GVars(
ease: ease,
delay: delay,
useFrames: useFrames,
overwrite: overwrite,
onStart: onStart,
onStartParams: onStartParams,
onComplete: onComplete,
onCompleteParams: onCompleteParams,
onUpdate: onUpdate,
onUpdateParams: onUpdateParams,
runBackwards: runBackwards,
immediateRender: immediateRender,
startAt: startAt,
),
);
}
void setProps({
Object x,
Object y,
Object scaleX,
Object scaleY,
Object scale,
Object rotation,
Object pivotX,
Object pivotY,
Object width,
Object height,
Object skewX,
Object skewY,
Object rotationX,
Object rotationY,
Object alpha,
Color colorize,
double delay = 0,
bool visible,
bool immediateRender = true,
}) {
if (visible != null) {
this.visible = visible;
}
tween(
duration: 0,
delay: delay,
immediateRender: immediateRender ?? true,
x: x,
y: y,
scaleX: scaleX,
scaleY: scaleY,
scale: scale,
rotation: rotation,
pivotX: pivotX,
pivotY: pivotY,
width: width,
colorize: colorize,
height: height,
skewX: skewX,
skewY: skewY,
alpha: alpha,
rotationX: rotationX,
rotationY: rotationY,
);
}
}
extension GTweenBlurFilterExt on BlurFilter {
GTweenableBlur get twn => GTweenableBlur(this);
GTween tween({
@required double duration,
Object blurX,
Object blurY,
EaseFunction ease,
double delay,
bool useFrames,
int overwrite = 1,
VoidCallback onStart,
Object onStartParams,
VoidCallback onComplete,
Object onCompleteParams,
VoidCallback onUpdate,
Object onUpdateParams,
bool runBackwards,
bool immediateRender,
Map startAt,
}) =>
twn.tween(
duration: duration,
blurX: blurX,
blurY: blurY,
ease: ease,
delay: delay,
useFrames: useFrames,
overwrite: overwrite,
onStart: onStart,
onStartParams: onStartParams,
onComplete: onComplete,
onCompleteParams: onCompleteParams,
onUpdate: onUpdate,
onUpdateParams: onUpdateParams,
runBackwards: runBackwards,
immediateRender: immediateRender,
startAt: startAt,
);
}
extension GTweenDropShadowFilterExt on DropShadowFilter {
GTweenableDropShadowFilter get twn => GTweenableDropShadowFilter(this);
GTween tween({
@required double duration,
Object blurX,
Object blurY,
Object angle,
Object distance,
Color color,
EaseFunction ease,
double delay,
bool useFrames,
int overwrite = 1,
VoidCallback onStart,
Object onStartParams,
VoidCallback onComplete,
Object onCompleteParams,
VoidCallback onUpdate,
Object onUpdateParams,
bool runBackwards,
bool immediateRender,
Map startAt,
}) {
return twn.tween(
duration: duration,
blurX: blurX,
blurY: blurY,
angle: angle,
distance: distance,
color: color,
ease: ease,
delay: delay,
useFrames: useFrames,
overwrite: overwrite,
onStart: onStart,
onStartParams: onStartParams,
onComplete: onComplete,
onCompleteParams: onCompleteParams,
onUpdate: onUpdate,
onUpdateParams: onUpdateParams,
runBackwards: runBackwards,
immediateRender: immediateRender,
startAt: startAt,
);
}
}
extension GTweenGlowFilterExt on GlowFilter {
GTweenableGlowFilter get twn => GTweenableGlowFilter(this);
GTween tween({
@required double duration,
Object blurX,
Object blurY,
Color color,
EaseFunction ease,
double delay,
bool useFrames,
int overwrite = 1,
VoidCallback onStart,
Object onStartParams,
VoidCallback onComplete,
Object onCompleteParams,
VoidCallback onUpdate,
Object onUpdateParams,
bool runBackwards,
bool immediateRender,
Map startAt,
}) {
return twn.tween(
duration: duration,
blurX: blurX,
blurY: blurY,
color: color,
ease: ease,
delay: delay,
useFrames: useFrames,
overwrite: overwrite,
onStart: onStart,
onStartParams: onStartParams,
onComplete: onComplete,
onCompleteParams: onCompleteParams,
onUpdate: onUpdate,
onUpdateParams: onUpdateParams,
runBackwards: runBackwards,
immediateRender: immediateRender,
startAt: startAt,
);
}
}
extension GTweenPointExt on GxPoint {
GTweenablePoint get twn => GTweenablePoint(this);
}
extension GTweenRectExt on GxRect {
GTweenableRect get twn => GTweenableRect(this);
}
extension GTweenColorExt on Color {
GTweenableColor get twn => GTweenableColor(this);
}
class GTweenLerpColor extends GTweenLerpProp<Color> {
GTweenLerpColor({
GTweenSetProp<Color> setProp,
GTweenGetProp<Color> getProp,
}) : super(
setProp: setProp,
getProp: getProp,
);
@override
Color resolve(double ratio) {
final value = Color.lerp(from, to, ratio);
setProp?.call(value);
return value;
}
}
typedef GTweenSetProp<T> = void Function(T targetValue);
typedef GTweenGetProp<T> = T Function();
class GTweenLerpProp<T> {
T from;
T to;
String name;
GTweenSetProp<T> setProp;
GTweenGetProp<T> getProp;
GTweenLerpProp({this.setProp, this.getProp});
T resolve(double ratio) => null;
}
mixin GTweenable {
Object target;
void setTweenProp(PropTween tweenProp) {
final lerpObj = _lerps[tweenProp.p];
if (lerpObj == null) return;
lerpObj.to = tweenProp.cObj;
tweenProp.c = 1.0;
}
final Map<String, GTweenLerpProp> _lerps = {};
@override
String toString() => '[GTweenable] $target';
Map<Object, List<Function>> _accessors;
void _addLerp(String prop, GTweenLerpProp lerp) {
_lerps[prop] = lerp;
}
void initProps() => _accessors = getTweenableAccessors();
Map<String, List<Function>> getTweenableAccessors() => null;
void setProperty(Object prop, double value) {
if (_lerps[prop] != null) {
_lerps[prop].resolve(value);
} else {
if (_accessors == null) initProps();
_accessors[prop][1](value);
}
}
double getProperty(Object prop) {
if (_lerps.containsKey(prop)) {
_lerps[prop]?.from = _lerps[prop]?.getProp();
return 0.0;
}
if (_accessors == null) initProps();
return _accessors[prop][0]();
}
void dispose() {
_lerps?.clear();
}
double operator [](String key) => getProperty(key);
void operator []=(String key, double value) => setProperty(key, value);
}
abstract class CommonTweenWraps {
static Map<String, List<Function>> displayObject(DisplayObject o) {
return {
'x': [() => o.x, (v) => o.x = v],
'y': [() => o.y, (v) => o.y = v],
'scaleX': [() => o.scaleX, (v) => o.scaleX = v],
'scaleY': [() => o.scaleY, (v) => o.scaleY = v],
'scale': [() => o.scale, (v) => o.scale = v],
'rotation': [() => o.rotation, (v) => o.rotation = v],
'rotationX': [() => o.rotationX, (v) => o.rotationX = v],
'rotationY': [() => o.rotationY, (v) => o.rotationY = v],
'pivotX': [() => o.pivotX, (v) => o.pivotX = v],
'pivotY': [() => o.pivotY, (v) => o.pivotY = v],
'width': [() => o.width, (v) => o.width = v],
'height': [() => o.height, (v) => o.height = v],
'skewX': [() => o.skewX, (v) => o.skewX = v],
'skewY': [() => o.skewY, (v) => o.skewY = v],
'alpha': [() => o.alpha, (v) => o.alpha = v],
};
}
}
class GTweenableDouble with GTweenable, SingleValueTweenMixin {
static GTweenable wrap(Object target) => target is double ? GTweenableDouble(target) : null;
double value;
GTweenableDouble(double target) {
value = this.target = target;
}
@override
Map<String, List<Function>> getTweenableAccessors() => {
'value': [() => value, (v) => value = v],
};
@override
String toString() => '[GTweenableDouble] $value';
}
class GTweenableInt with GTweenable, SingleValueTweenMixin {
static GTweenable wrap(Object target) => target is int ? GTweenableInt(target) : null;
int value;
GTweenableInt(int target) {
value = this.target = target;
}
@override
Map<String, List<Function>> getTweenableAccessors() => {
'value': [
() => value + .0,
(v) => value = v.round(),
],
};
@override
String toString() => '[GTweenableInt] $value';
}
class GTweenableMap with GTweenable {
static GTweenable wrap(Object target) => target is Map<String, dynamic> ? GTweenableMap(target) : null;
Map value;
GTweenableMap(Map target) {
value = this.target = target;
}
@override
void setProperty(Object prop, double val) {
value[prop] = convertFromDouble(value[prop], val);
}
@override
double getProperty(Object prop) {
return convertToDouble(value[prop]);
}
GTween tween(
Map targetMap, {
@required double duration,
EaseFunction ease,
double delay,
bool useFrames,
int overwrite,
VoidCallback onStart,
Object onStartParams,
VoidCallback onComplete,
Object onCompleteParams,
VoidCallback onUpdate,
Object onUpdateParams,
bool runBackwards,
bool immediateRender,
Map startAt,
}) {
assert(targetMap != null);
targetMap.removeWhere((k, v) => !value.containsKey(k));
if (targetMap.isEmpty) {
throw '''
tween(targetMap) Map can't be empty. Or there are no matching keys with the tweenable target.''';
}
return GTween.to(
this,
duration,
targetMap,
GVars(
ease: ease,
delay: delay,
useFrames: useFrames,
overwrite: overwrite,
onStart: onStart,
onStartParams: onStartParams,
onComplete: onComplete,
onCompleteParams: onCompleteParams,
onUpdate: onUpdate,
onUpdateParams: onUpdateParams,
runBackwards: runBackwards,
immediateRender: immediateRender,
startAt: startAt,
));
}
}
class GTweenableList with GTweenable {
static GTweenable wrap(Object target) {
if (target is! List) return null;
return GTweenableList(target);
}
List value;
GTweenableList(List target) {
value = this.target = target;
}
@override
void setProperty(Object prop, double val) {
final index = int.tryParse('$prop');
value[index] = convertFromDouble(value[index], val);
}
@override
double getProperty(Object prop) {
return convertToDouble(value[int.parse('$prop')]);
}
GTween tween(
List targetList, {
@required double duration,
EaseFunction ease,
double delay,
bool useFrames,
int overwrite,
VoidCallback onStart,
Object onStartParams,
VoidCallback onComplete,
Object onCompleteParams,
VoidCallback onUpdate,
Object onUpdateParams,
bool runBackwards,
bool immediateRender,
Map startAt,
}) {
assert(targetList != null);
targetList.removeWhere((element) => element is! num);
if (targetList.isEmpty) {
throw '''
tween(targetList) List can't be empty. Or values inside of it where not a number type''';
}
final targetMap = {};
for (var i = 0; i < targetList.length; ++i) {
targetMap[i] = targetList[i];
}
return GTween.to(
this,
duration,
targetMap,
GVars(
ease: ease,
delay: delay,
useFrames: useFrames,
overwrite: overwrite,
onStart: onStart,
onStartParams: onStartParams,
onComplete: onComplete,
onCompleteParams: onCompleteParams,
onUpdate: onUpdate,
onUpdateParams: onUpdateParams,
runBackwards: runBackwards,
immediateRender: immediateRender,
startAt: startAt,
));
}
}
Object convertFromDouble(Object originalValue, double val) {
if (originalValue is int) {
return val.toInt();
} else if (originalValue is String) {
return '$val';
} else {
return val;
}
}
double convertToDouble(Object val) {
if (val is int) {
return val + .0;
} else if (val is String) {
return double.tryParse(val);
}
return val;
}
mixin SingleValueTweenMixin {
Object getValue;
GTween tween(
Object value, {
@required double duration,
EaseFunction ease,
double delay,
bool useFrames,
int overwrite,
Function onStart,
Object onStartParams,
Function onComplete,
Object onCompleteParams,
Function onUpdate,
Object onUpdateParams,
bool runBackwards,
bool immediateRender,
Map startAt,
}) {
assert(value != null);
return GTween.to(
this,
duration,
{'value': value},
GVars(
ease: ease,
delay: delay,
useFrames: useFrames,
overwrite: overwrite,
onStart: onStart,
onStartParams: onStartParams,
onComplete: onComplete,
onCompleteParams: onCompleteParams,
onUpdate: onUpdate,
onUpdateParams: onUpdateParams,
runBackwards: runBackwards,
immediateRender: immediateRender,
startAt: startAt,
));
}
}
class GTweenableDisplayObject with GTweenable {
static GTweenable wrap(Object target) {
if (target is! DisplayObject) return null;
return GTweenableDisplayObject(target);
}
GTweenableDisplayObject(DisplayObject target) {
this.target = target;
_addLerp(
'colorize',
GTweenLerpColor(
setProp: (value) => target.colorize = value,
getProp: () => target.colorize,
),
);
}
@override
Map<String, List<Function>> getTweenableAccessors() => CommonTweenWraps.displayObject(target);
}
class GTweenableBlur with GTweenable {
static GTweenable wrap(Object target) => (target is! BlurFilter) ? null : GTweenableBlur(target);
BlurFilter value;
GTweenableBlur(BlurFilter target) {
value = this.target = target;
}
@override
Map<String, List<Function>> getTweenableAccessors() => {
'blurX': [() => value.blurX, (v) => value.blurX = v],
'blurY': [() => value.blurY, (v) => value.blurY = v]
};
GTween tween({@required double duration, Object blurX, Object blurY, EaseFunction ease, double delay, bool useFrames, int overwrite = 1, VoidCallback onStart, Object onStartParams, VoidCallback onComplete, Object onCompleteParams, VoidCallback onUpdate, Object onUpdateParams, bool runBackwards, bool immediateRender, Map startAt}) {
final targetMap = {if (blurX != null) 'blurX': blurX, if (blurY != null) 'blurY': blurY};
return GTween.to(this, duration, targetMap, GVars(ease: ease, delay: delay, useFrames: useFrames, overwrite: overwrite, onStart: onStart, onStartParams: onStartParams, onComplete: onComplete, onCompleteParams: onCompleteParams, onUpdate: onUpdate, onUpdateParams: onUpdateParams, runBackwards: runBackwards, immediateRender: immediateRender, startAt: startAt));
}
}
class GTweenableDropShadowFilter with GTweenable {
static GTweenable wrap(Object target) => (target is! DropShadowFilter) ? null : GTweenableDropShadowFilter(target);
DropShadowFilter value;
GTweenableDropShadowFilter(DropShadowFilter target) {
value = this.target = target;
_addLerp(
'color',
GTweenLerpColor(setProp: (value) => target.color = value, getProp: () => target.color),
);
}
@override
Map<String, List<Function>> getTweenableAccessors() => {
'blurX': [() => value.blurX, (v) => value.blurX = v],
'blurY': [() => value.blurY, (v) => value.blurY = v],
'angle': [() => value.angle, (v) => value.angle = v],
'distance': [() => value.distance, (v) => value.distance = v],
'color': [() => value.color, (v) => value.color = v]
};
GTween tween({@required double duration, Object blurX, Object blurY, Object angle, Object distance, Color color, EaseFunction ease, double delay, bool useFrames, int overwrite = 1, VoidCallback onStart, Object onStartParams, VoidCallback onComplete, Object onCompleteParams, VoidCallback onUpdate, Object onUpdateParams, bool runBackwards, bool immediateRender, Map startAt}) {
final targetMap = {if (blurX != null) 'blurX': blurX, if (blurY != null) 'blurY': blurY, if (angle != null) 'angle': angle, if (distance != null) 'distance': distance, if (color != null) 'color': color};
return GTween.to(this, duration, targetMap, GVars(ease: ease, delay: delay, useFrames: useFrames, overwrite: overwrite, onStart: onStart, onStartParams: onStartParams, onComplete: onComplete, onCompleteParams: onCompleteParams, onUpdate: onUpdate, onUpdateParams: onUpdateParams, runBackwards: runBackwards, immediateRender: immediateRender, startAt: startAt));
}
}
class GTweenableGlowFilter with GTweenable {
static GTweenable wrap(Object target) => (target is! GlowFilter) ? null : GTweenableGlowFilter(target);
GlowFilter value;
GTweenableGlowFilter(GlowFilter target) {
value = this.target = target;
_addLerp('color', GTweenLerpColor(setProp: (value) => target.color = value, getProp: () => target.color));
}
@override
Map<String, List<Function>> getTweenableAccessors() => {
'blurX': [() => value.blurX, (v) => value.blurX = v],
'blurY': [() => value.blurY, (v) => value.blurY = v],
'color': [() => value.color, (v) => value.color = v]
};
GTween tween({@required double duration, Object blurX, Object blurY, Color color, EaseFunction ease, double delay, bool useFrames, int overwrite = 1, VoidCallback onStart, Object onStartParams, VoidCallback onComplete, Object onCompleteParams, VoidCallback onUpdate, Object onUpdateParams, bool runBackwards, bool immediateRender, Map startAt}) {
final targetMap = {if (blurX != null) 'blurX': blurX, if (blurY != null) 'blurY': blurY, if (color != null) 'color': color};
return GTween.to(this, duration, targetMap, GVars(ease: ease, delay: delay, useFrames: useFrames, overwrite: overwrite, onStart: onStart, onStartParams: onStartParams, onComplete: onComplete, onCompleteParams: onCompleteParams, onUpdate: onUpdate, onUpdateParams: onUpdateParams, runBackwards: runBackwards, immediateRender: immediateRender, startAt: startAt));
}
}
class GTweenablePoint with GTweenable {
static GTweenable wrap(Object target) => (target is! GxPoint) ? null : GTweenablePoint(target);
GxPoint value;
GTweenablePoint(GxPoint target) {
value = this.target = target;
}
@override
Map<String, List<Function>> getTweenableAccessors() => {
'x': [() => value.x, (v) => value.x = v],
'y': [() => value.y, (v) => value.y = v]
};
GTween tween({@required double duration, Object x, Object y, GxPoint to, EaseFunction ease, double delay, bool useFrames, int overwrite, VoidCallback onStart, Object onStartParams, VoidCallback onComplete, Object onCompleteParams, VoidCallback onUpdate, Object onUpdateParams, bool runBackwards, bool immediateRender, Map startAt}) {
if ((x != null || y != null) && to != null) throw '''GTween Can't use 'x, y' AND 'to' arguments for GxPoint tween. Choose one''';
x = to?.x ?? x;
y = to?.y ?? y;
final targetMap = {if (x != null) 'x': x, if (y != null) 'y': y};
return GTween.to(this, duration, targetMap, GVars(ease: ease, delay: delay, useFrames: useFrames, overwrite: overwrite, onStart: onStart, onStartParams: onStartParams, onComplete: onComplete, onCompleteParams: onCompleteParams, onUpdate: onUpdate, onUpdateParams: onUpdateParams, runBackwards: runBackwards, immediateRender: immediateRender, startAt: startAt));
}
}
class GTweenableRect with GTweenable {
static GTweenable wrap(Object target) => (target is! GxRect) ? null : GTweenableRect(target);
GxRect value;
GTweenableRect(GxRect target) {
value = this.target = target;
}
@override
Map<String, List<Function>> getTweenableAccessors() => {
'x': [() => value.x, (v) => value.x = v],
'y': [() => value.y, (v) => value.y = v],
'width': [() => value.width, (v) => value.width = v],
'height': [() => value.height, (v) => value.height = v]
};
GTween tween({@required double duration, Object x, Object y, Object width, Object height, GxRect to, EaseFunction ease, double delay, bool useFrames, int overwrite, VoidCallback onStart, Object onStartParams, VoidCallback onComplete, Object onCompleteParams, VoidCallback onUpdate, Object onUpdateParams, bool runBackwards, bool immediateRender, Map startAt}) {
if ((x != null || y != null || width != null || height != null) && to != null) throw "GTween Can't use 'x, y, width, height' AND 'to' arguments to tween a [GxRect]. Choose one";
x = to?.x ?? x;
y = to?.y ?? y;
width = to?.width ?? width;
height = to?.height ?? height;
final targetMap = {if (x != null) 'x': x, if (y != null) 'y': y, if (width != null) 'width': width, if (height != null) 'height': height};
return GTween.to(this, duration, targetMap, GVars(ease: ease, delay: delay, useFrames: useFrames, overwrite: overwrite, onStart: onStart, onStartParams: onStartParams, onComplete: onComplete, onCompleteParams: onCompleteParams, onUpdate: onUpdate, onUpdateParams: onUpdateParams, runBackwards: runBackwards, immediateRender: immediateRender, startAt: startAt));
}
}
class GTweenableColor with GTweenable {
static GTweenable wrap(Object target) {
if (target is! Color) return null;
return GTweenableColor(target);
}
Color value;
PropTween _propTween;
Color _targetColor;
@override
void setTweenProp(PropTween tweenProp) {
_propTween = tweenProp;
_propTween.c = 1.0;
}
GTweenableColor(Color target) {
value = this.target = target;
}
@override
void setProperty(Object prop, double val) {
value = Color.lerp(target, _targetColor, val);
}
@override
double getProperty(Object prop) => 0.0;
GTween tween(Color color, {@required double duration, EaseFunction ease, double delay, bool useFrames, int overwrite, VoidCallback onStart, Object onStartParams, VoidCallback onComplete, Object onCompleteParams, VoidCallback onUpdate, Object onUpdateParams, bool runBackwards, bool immediateRender, Map startAt}) {
assert(color != null);
_targetColor = color;
return GTween.to(
this,
duration,
{'value': 1},
GVars(
ease: ease,
delay: delay,
useFrames: useFrames,
overwrite: overwrite,
onStart: onStart,
onStartParams: onStartParams,
onComplete: onComplete,
onCompleteParams: onCompleteParams,
onUpdate: onUpdate,
onUpdateParams: onUpdateParams,
runBackwards: runBackwards,
immediateRender: immediateRender,
startAt: startAt,
));
}
}
class GVars {
EaseFunction ease;
double delay;
bool useFrames;
int overwrite;
Function onStart;
CallbackParams onStartParams;
Function onComplete;
CallbackParams onCompleteParams;
Function onUpdate;
CallbackParams onUpdateParams;
bool runBackwards;
bool immediateRender;
Map startAt;
GVars({
this.ease,
this.delay,
this.useFrames,
this.overwrite,
this.onStart,
this.onComplete,
this.onUpdate,
Object onStartParams,
Object onCompleteParams,
Object onUpdateParams,
this.runBackwards,
this.immediateRender,
this.startAt,
}) {
this.onStartParams = CallbackParams.parse(onStartParams);
this.onCompleteParams = CallbackParams.parse(onCompleteParams);
this.onUpdateParams = CallbackParams.parse(onUpdateParams);
}
void defaults() {
ease ??= GTween.defaultEase;
immediateRender ??= false;
useFrames ??= false;
runBackwards ??= false;
}
void _setTween(GTween gTween) {
if (onStartParams != null) {
_setCallbackParams(gTween, onStartParams);
}
if (onCompleteParams != null) {
_setCallbackParams(gTween, onCompleteParams);
}
if (onUpdateParams != null) {
_setCallbackParams(gTween, onUpdateParams);
}
}
static const String selfTweenKey = '{self}';
void _setCallbackParams(GTween twn, CallbackParams params) {
final named = params.named;
final positional = params.positional;
if (named != null) {
if (named.containsValue(selfTweenKey)) {
for (var e in named.entries) {
if (e.value == selfTweenKey) {
named[e.key] = twn;
}
}
}
}
if (positional != null) {
if (positional.contains(selfTweenKey)) {
params.positional = positional.map((e) {
if (e == selfTweenKey) {
return twn;
}
return e;
}).toList();
}
}
}
}
class GTween {
static void registerCommonWraps([List<GxAnimatableBuilder> otherWraps]) {
GTween.registerWrap(GTweenableDisplayObject.wrap);
GTween.registerWrap(GTweenableMap.wrap);
GTween.registerWrap(GTweenableDouble.wrap);
GTween.registerWrap(GTweenableInt.wrap);
GTween.registerWrap(GTweenableMap.wrap);
GTween.registerWrap(GTweenableList.wrap);
otherWraps?.forEach(GTween.registerWrap);
}
static double _time = 0;
static double _frame = 0;
static EventSignal<double> ticker = EventSignal<double>();
static EaseFunction defaultEase = GEase.easeOut;
static final Set<GxAnimatableBuilder> _tweenableBuilders = {};
static void registerWrap(GxAnimatableBuilder builder) => _tweenableBuilders.add(builder);
static Map _reservedProps;
static GTween _first;
static GTween _last;
double _duration;
Map vars;
GVars nanoVars;
double _startTime;
Object target;
Object _animatableTarget;
bool _useFrames;
double ratio = 0;
Function _ease;
bool _inited = false;
PropTween _firstPT;
GTween _next;
GTween _prev;
List _targets;
bool _gc = false;
GTween(Object target, double duration, Map vars, [GVars myVars]) {
if (_reservedProps == null) {
_reservedProps = {
'delay': 1,
'ease': 1,
'usedFrames': 1,
'overwrite': 1,
'onComplete': 1,
'runBackwards': 1,
'immediateRender': 1,
'onUpdate': 1,
'startAt': 1,
};
_time = getTimer() / 1000;
_frame = 0;
ticker.add(_updateRoot);
}
nanoVars = myVars ?? GVars();
nanoVars.defaults();
nanoVars._setTween(this);
this.vars = vars;
_duration = duration;
this.target = target;
if (target is List) {
var targetList = target;
if (targetList.first is Map<String, dynamic> || targetList.first is GTweenable) {
_targets = List.of(target);
}
} else if (target is GTweenable) {
_animatableTarget = target.target;
} else {
if (target is Function || target is Map) {
} else {
GTweenable result;
for (final builder in _tweenableBuilders) {
result = builder(target);
if (result != null) {
break;
}
}
target = this.target = result;
_animatableTarget = result?.target;
}
}
_ease = nanoVars.ease;
_useFrames = nanoVars.useFrames ?? false;
_startTime = (_useFrames ? _frame : _time) + (nanoVars.delay ?? 0);
if (nanoVars.overwrite == 1) {
if (_animatableTarget != null) {
killTweensOf(_animatableTarget);
} else {
killTweensOf(this.target);
}
}
_prev = _last;
if (_last != null) {
_last._next = this;
} else {
_first = this;
}
_last = this;
if (nanoVars.immediateRender || (duration == 0 && nanoVars.delay == 0 && nanoVars.immediateRender != false)) {
_render(0);
}
}
void _init() {
if (nanoVars.startAt != null) {
var newVars = GVars()..immediateRender = true;
GTween.to(target, 0, nanoVars.startAt, newVars);
}
if (_targets != null) {
var i = _targets.length;
while (--i > -1) {
_initProps(_targets[i]);
}
} else {
_initProps(target);
}
if (nanoVars.runBackwards) {
var pt = _firstPT;
while (pt != null) {
pt.s += pt.c;
pt.c = -pt.c;
pt = pt._next;
}
}
_inited = true;
}
void _initProps(Object target) {
if (target == null) return;
for (final key in vars.keys) {
final prop = '$key';
if (!_reservedProps.containsKey(prop)) {
_firstPT = PropTween(target: target, property: key, next: _firstPT);
var startVal = _getStartValue(target, key);
_firstPT.s = startVal;
var endValue = _getEndValue(vars, key, _firstPT.s);
_firstPT.cObj = vars[key];
_firstPT.c = endValue;
_firstPT.t.setTweenProp(_firstPT);
if (_firstPT._next != null) {
_firstPT._next._prev = _firstPT;
}
}
}
}
double _getEndValue(Map pvars, dynamic prop, double start) {
dynamic val = pvars[prop];
if (val is num) {
double v = val + 0.0;
return v - start;
} else if (val is String) {
if (val.length > 2 && val[1] == '=') {
var multiplier = double.tryParse('${val[0]}1') ?? 1;
var factor = double.tryParse(val.substring(2));
return multiplier * factor;
} else {
return double.tryParse(val);
}
}
return 0;
}
void _setCurrentValue(PropTween pt, double ratio) {
var value = pt.c * ratio + pt.s;
if (pt.t is GTweenable) {
pt.t.setProperty(pt.p, value);
} else {
pt.t[pt.p] = value;
}
}
double _getStartValue(Object t, dynamic prop) {
if (t is GTweenable) {
return t.getProperty(prop);
} else if (t is Map) {
return t[prop];
}
throw 'error';
}
void _render(double time) {
if (!_inited) {
_init();
time = 0;
}
var prevTime = time;
if (time >= _duration) {
time = _duration;
ratio = 1;
} else if (time <= 0) {
if (prevTime == 0) {
_signal(nanoVars.onStart, nanoVars.onStartParams);
}
ratio = 0;
} else {
ratio = _ease(time / _duration);
}
var pt = _firstPT;
while (pt != null) {
_setCurrentValue(pt, ratio);
pt = pt._next;
}
_signal(nanoVars.onUpdate, nanoVars.onUpdateParams);
if (time == _duration) {
kill();
if (nanoVars.onCompleteParams == null) {
nanoVars.onComplete?.call();
} else {
_signal(nanoVars.onComplete, nanoVars.onCompleteParams);
}
}
}
void _signal(Function callback, CallbackParams params) {
if (callback != null) {
Function.apply(callback, params?.positional, params?.named);
}
}
Object _getAnimatable(Object searchTarget) {
if (_animatableTarget == searchTarget) return target;
if (_targets != null) {
for (var t in _targets) {
if (t is GTweenable) {
if (t.target == searchTarget) return t;
}
}
}
return null;
}
void kill([Object tg]) {
tg ??= _targets ?? target;
var pt = _firstPT;
if (tg is List) {
var targetList = tg;
if (targetList.first is Map<String, dynamic> || targetList.first is GTweenable) {
var i = targetList.length;
while (--i > -1) {
kill(targetList[i]);
}
return;
}
} else if (_targets != null) {
var i = _targets.length;
if (tg is! GTweenable) {
tg = _getAnimatable(tg);
}
while (--i > -1) {
if (tg == _targets[i]) {
_targets.removeAt(i);
}
}
while (pt != null) {
if (pt.t == tg) {
if (pt._next != null) {
pt._next._prev = pt._prev;
}
if (pt._prev != null) {
pt._prev._next = pt._next;
} else {
_firstPT = pt._next;
}
}
pt = pt._next;
}
}
if (_targets == null || _targets.isEmpty) {
_gc = true;
if (_prev != null) {
_prev._next = _next;
} else if (this == _first) {
_first = _next;
}
if (_next != null) {
_next._prev = _prev;
} else if (this == _last) {
_last = _prev;
}
if (target is GTweenable) {
(target as GTweenable).dispose();
}
_next = _prev = null;
}
}
static void killAll() {
var t = _first;
while (t != null) {
var next = t._next;
t.kill();
t = next;
}
}
static GTween to(Object target, double duration, Map vars, [GVars nanoVars]) {
nanoVars ??= GVars();
return GTween(target, duration, vars, nanoVars);
}
static GTween from(Object target, double duration, Map vars, [GVars nanoVars]) {
nanoVars ??= GVars();
nanoVars.runBackwards = true;
nanoVars.immediateRender ??= true;
return GTween(target, duration, vars, nanoVars);
}
static GTween delayedCall(
double delay,
Function callback, {
Object params,
bool useFrames = false,
}) {
var props = GVars()
..delay = delay
..useFrames = useFrames
..onComplete = callback
..onCompleteParams = CallbackParams.parse(params);
return GTween(
callback,
0,
{},
props,
);
}
static double timeScale = 1;
static void _updateRoot(double delta) {
_frame += 1;
if (delta <= 0) delta = .016;
_time += delta * timeScale;
var tween = _first;
while (tween != null) {
var next = tween._next;
var t = tween._useFrames ? _frame : _time;
if (t >= tween._startTime && !tween._gc) {
tween._render(t - tween._startTime);
}
tween = next;
}
}
static void killTweensOf(Object target) {
var t = _first;
while (t != null) {
var next = t._next;
if (t.target == target || t._animatableTarget == target) {
t.kill();
} else if (t._targets != null) {
t.kill(target);
}
t = next;
}
}
}
typedef GxAnimatableBuilder = GTweenable Function(Object target);
const kColorTransparent = Color(0);
const kColorBlack = Color(0xff000000);
const kColorRed = Color(0xff00ff00);
abstract class ContextUtils {
static GxRect getRenderObjectBounds(BuildContext context) {
final box = context.findRenderObject() as RenderBox;
return GxRect.fromNative(box.localToGlobal(Offset.zero) & box.size);
}
}
abstract class GxRenderable {
void paint(Canvas canvas);
GxRect getBounds();
}
class Keyboard {
final LogicalKeyboardKey value;
const Keyboard._(this.value);
static const up = Keyboard._(LogicalKeyboardKey.arrowUp);
static const left = Keyboard._(LogicalKeyboardKey.arrowLeft);
static const right = Keyboard._(LogicalKeyboardKey.arrowRight);
static const down = Keyboard._(LogicalKeyboardKey.arrowDown);
static const none = Keyboard._(LogicalKeyboardKey.none);
static const hyper = Keyboard._(LogicalKeyboardKey.hyper);
static const superKey = Keyboard._(LogicalKeyboardKey.superKey);
static const fnLock = Keyboard._(LogicalKeyboardKey.fnLock);
static const suspend = Keyboard._(LogicalKeyboardKey.suspend);
static const resume = Keyboard._(LogicalKeyboardKey.resume);
static const turbo = Keyboard._(LogicalKeyboardKey.turbo);
static const privacyScreenToggle = Keyboard._(LogicalKeyboardKey.privacyScreenToggle);
static const sleep = Keyboard._(LogicalKeyboardKey.sleep);
static const wakeUp = Keyboard._(LogicalKeyboardKey.wakeUp);
static const displayToggleIntExt = Keyboard._(LogicalKeyboardKey.displayToggleIntExt);
static const usbReserved = Keyboard._(LogicalKeyboardKey.usbReserved);
static const usbErrorRollOver = Keyboard._(LogicalKeyboardKey.usbErrorRollOver);
static const usbPostFail = Keyboard._(LogicalKeyboardKey.usbPostFail);
static const usbErrorUndefined = Keyboard._(LogicalKeyboardKey.usbErrorUndefined);
static const keyA = Keyboard._(LogicalKeyboardKey.keyA);
static const keyB = Keyboard._(LogicalKeyboardKey.keyB);
static const keyC = Keyboard._(LogicalKeyboardKey.keyC);
static const keyD = Keyboard._(LogicalKeyboardKey.keyD);
static const keyE = Keyboard._(LogicalKeyboardKey.keyE);
static const keyF = Keyboard._(LogicalKeyboardKey.keyF);
static const keyG = Keyboard._(LogicalKeyboardKey.keyG);
static const keyH = Keyboard._(LogicalKeyboardKey.keyH);
static const keyI = Keyboard._(LogicalKeyboardKey.keyI);
static const keyJ = Keyboard._(LogicalKeyboardKey.keyJ);
static const keyK = Keyboard._(LogicalKeyboardKey.keyK);
static const keyL = Keyboard._(LogicalKeyboardKey.keyL);
static const keyM = Keyboard._(LogicalKeyboardKey.keyM);
static const keyN = Keyboard._(LogicalKeyboardKey.keyN);
static const keyO = Keyboard._(LogicalKeyboardKey.keyO);
static const keyP = Keyboard._(LogicalKeyboardKey.keyP);
static const keyQ = Keyboard._(LogicalKeyboardKey.keyQ);
static const keyR = Keyboard._(LogicalKeyboardKey.keyR);
static const keyS = Keyboard._(LogicalKeyboardKey.keyS);
static const keyT = Keyboard._(LogicalKeyboardKey.keyT);
static const keyU = Keyboard._(LogicalKeyboardKey.keyU);
static const keyV = Keyboard._(LogicalKeyboardKey.keyV);
static const keyW = Keyboard._(LogicalKeyboardKey.keyW);
static const keyX = Keyboard._(LogicalKeyboardKey.keyX);
static const keyY = Keyboard._(LogicalKeyboardKey.keyY);
static const keyZ = Keyboard._(LogicalKeyboardKey.keyZ);
static const digit1 = Keyboard._(LogicalKeyboardKey.digit1);
static const digit2 = Keyboard._(LogicalKeyboardKey.digit2);
static const digit3 = Keyboard._(LogicalKeyboardKey.digit3);
static const digit4 = Keyboard._(LogicalKeyboardKey.digit4);
static const digit5 = Keyboard._(LogicalKeyboardKey.digit5);
static const digit6 = Keyboard._(LogicalKeyboardKey.digit6);
static const digit7 = Keyboard._(LogicalKeyboardKey.digit7);
static const digit8 = Keyboard._(LogicalKeyboardKey.digit8);
static const digit9 = Keyboard._(LogicalKeyboardKey.digit9);
static const digit0 = Keyboard._(LogicalKeyboardKey.digit0);
static const enter = Keyboard._(LogicalKeyboardKey.enter);
static const escape = Keyboard._(LogicalKeyboardKey.escape);
static const backspace = Keyboard._(LogicalKeyboardKey.backspace);
static const tab = Keyboard._(LogicalKeyboardKey.tab);
static const space = Keyboard._(LogicalKeyboardKey.space);
static const minus = Keyboard._(LogicalKeyboardKey.minus);
static const equal = Keyboard._(LogicalKeyboardKey.equal);
static const bracketLeft = Keyboard._(LogicalKeyboardKey.bracketLeft);
static const bracketRight = Keyboard._(LogicalKeyboardKey.bracketRight);
static const backslash = Keyboard._(LogicalKeyboardKey.backslash);
static const semicolon = Keyboard._(LogicalKeyboardKey.semicolon);
static const quote = Keyboard._(LogicalKeyboardKey.quote);
static const backquote = Keyboard._(LogicalKeyboardKey.backquote);
static const comma = Keyboard._(LogicalKeyboardKey.comma);
static const period = Keyboard._(LogicalKeyboardKey.period);
static const slash = Keyboard._(LogicalKeyboardKey.slash);
static const capsLock = Keyboard._(LogicalKeyboardKey.capsLock);
static const f1 = Keyboard._(LogicalKeyboardKey.f1);
static const f2 = Keyboard._(LogicalKeyboardKey.f2);
static const f3 = Keyboard._(LogicalKeyboardKey.f3);
static const f4 = Keyboard._(LogicalKeyboardKey.f4);
static const f5 = Keyboard._(LogicalKeyboardKey.f5);
static const f6 = Keyboard._(LogicalKeyboardKey.f6);
static const f7 = Keyboard._(LogicalKeyboardKey.f7);
static const f8 = Keyboard._(LogicalKeyboardKey.f8);
static const f9 = Keyboard._(LogicalKeyboardKey.f9);
static const f10 = Keyboard._(LogicalKeyboardKey.f10);
static const f11 = Keyboard._(LogicalKeyboardKey.f11);
static const f12 = Keyboard._(LogicalKeyboardKey.f12);
static const printScreen = Keyboard._(LogicalKeyboardKey.printScreen);
static const scrollLock = Keyboard._(LogicalKeyboardKey.scrollLock);
static const pause = Keyboard._(LogicalKeyboardKey.pause);
static const insert = Keyboard._(LogicalKeyboardKey.insert);
static const home = Keyboard._(LogicalKeyboardKey.home);
static const pageUp = Keyboard._(LogicalKeyboardKey.pageUp);
static const delete = Keyboard._(LogicalKeyboardKey.delete);
static const end = Keyboard._(LogicalKeyboardKey.end);
static const pageDown = Keyboard._(LogicalKeyboardKey.pageDown);
static const arrowRight = Keyboard._(LogicalKeyboardKey.arrowRight);
static const arrowLeft = Keyboard._(LogicalKeyboardKey.arrowLeft);
static const arrowDown = Keyboard._(LogicalKeyboardKey.arrowDown);
static const arrowUp = Keyboard._(LogicalKeyboardKey.arrowUp);
static const numLock = Keyboard._(LogicalKeyboardKey.numLock);
static const numpadDivide = Keyboard._(LogicalKeyboardKey.numpadDivide);
static const numpadMultiply = Keyboard._(LogicalKeyboardKey.numpadMultiply);
static const numpadSubtract = Keyboard._(LogicalKeyboardKey.numpadSubtract);
static const numpadAdd = Keyboard._(LogicalKeyboardKey.numpadAdd);
static const numpadEnter = Keyboard._(LogicalKeyboardKey.numpadEnter);
static const numpad1 = Keyboard._(LogicalKeyboardKey.numpad1);
static const numpad2 = Keyboard._(LogicalKeyboardKey.numpad2);
static const numpad3 = Keyboard._(LogicalKeyboardKey.numpad3);
static const numpad4 = Keyboard._(LogicalKeyboardKey.numpad4);
static const numpad5 = Keyboard._(LogicalKeyboardKey.numpad5);
static const numpad6 = Keyboard._(LogicalKeyboardKey.numpad6);
static const numpad7 = Keyboard._(LogicalKeyboardKey.numpad7);
static const numpad8 = Keyboard._(LogicalKeyboardKey.numpad8);
static const numpad9 = Keyboard._(LogicalKeyboardKey.numpad9);
static const numpad0 = Keyboard._(LogicalKeyboardKey.numpad0);
static const numpadDecimal = Keyboard._(LogicalKeyboardKey.numpadDecimal);
static const intlBackslash = Keyboard._(LogicalKeyboardKey.intlBackslash);
static const contextMenu = Keyboard._(LogicalKeyboardKey.contextMenu);
static const power = Keyboard._(LogicalKeyboardKey.power);
static const numpadEqual = Keyboard._(LogicalKeyboardKey.numpadEqual);
static const f13 = Keyboard._(LogicalKeyboardKey.f13);
static const f14 = Keyboard._(LogicalKeyboardKey.f14);
static const f15 = Keyboard._(LogicalKeyboardKey.f15);
static const f16 = Keyboard._(LogicalKeyboardKey.f16);
static const f17 = Keyboard._(LogicalKeyboardKey.f17);
static const f18 = Keyboard._(LogicalKeyboardKey.f18);
static const f19 = Keyboard._(LogicalKeyboardKey.f19);
static const f20 = Keyboard._(LogicalKeyboardKey.f20);
static const f21 = Keyboard._(LogicalKeyboardKey.f21);
static const f22 = Keyboard._(LogicalKeyboardKey.f22);
static const f23 = Keyboard._(LogicalKeyboardKey.f23);
static const f24 = Keyboard._(LogicalKeyboardKey.f24);
static const open = Keyboard._(LogicalKeyboardKey.open);
static const help = Keyboard._(LogicalKeyboardKey.help);
static const select = Keyboard._(LogicalKeyboardKey.select);
static const again = Keyboard._(LogicalKeyboardKey.again);
static const undo = Keyboard._(LogicalKeyboardKey.undo);
static const cut = Keyboard._(LogicalKeyboardKey.cut);
static const copy = Keyboard._(LogicalKeyboardKey.copy);
static const paste = Keyboard._(LogicalKeyboardKey.paste);
static const find = Keyboard._(LogicalKeyboardKey.find);
static const audioVolumeMute = Keyboard._(LogicalKeyboardKey.audioVolumeMute);
static const audioVolumeUp = Keyboard._(LogicalKeyboardKey.audioVolumeUp);
static const audioVolumeDown = Keyboard._(LogicalKeyboardKey.audioVolumeDown);
static const numpadComma = Keyboard._(LogicalKeyboardKey.numpadComma);
static const intlRo = Keyboard._(LogicalKeyboardKey.intlRo);
static const kanaMode = Keyboard._(LogicalKeyboardKey.kanaMode);
static const intlYen = Keyboard._(LogicalKeyboardKey.intlYen);
static const convert = Keyboard._(LogicalKeyboardKey.convert);
static const nonConvert = Keyboard._(LogicalKeyboardKey.nonConvert);
static const lang1 = Keyboard._(LogicalKeyboardKey.lang1);
static const lang2 = Keyboard._(LogicalKeyboardKey.lang2);
static const lang3 = Keyboard._(LogicalKeyboardKey.lang3);
static const lang4 = Keyboard._(LogicalKeyboardKey.lang4);
static const lang5 = Keyboard._(LogicalKeyboardKey.lang5);
static const abort = Keyboard._(LogicalKeyboardKey.abort);
static const props = Keyboard._(LogicalKeyboardKey.props);
static const numpadParenLeft = Keyboard._(LogicalKeyboardKey.numpadParenLeft);
static const numpadParenRight = Keyboard._(LogicalKeyboardKey.numpadParenRight);
static const numpadBackspace = Keyboard._(LogicalKeyboardKey.numpadBackspace);
static const numpadMemoryStore = Keyboard._(LogicalKeyboardKey.numpadMemoryStore);
static const numpadMemoryRecall = Keyboard._(LogicalKeyboardKey.numpadMemoryRecall);
static const numpadMemoryClear = Keyboard._(LogicalKeyboardKey.numpadMemoryClear);
static const numpadMemoryAdd = Keyboard._(LogicalKeyboardKey.numpadMemoryAdd);
static const numpadMemorySubtract = Keyboard._(LogicalKeyboardKey.numpadMemorySubtract);
static const numpadSignChange = Keyboard._(LogicalKeyboardKey.numpadSignChange);
static const numpadClear = Keyboard._(LogicalKeyboardKey.numpadClear);
static const numpadClearEntry = Keyboard._(LogicalKeyboardKey.numpadClearEntry);
static const controlLeft = Keyboard._(LogicalKeyboardKey.controlLeft);
static const shiftLeft = Keyboard._(LogicalKeyboardKey.shiftLeft);
static const altLeft = Keyboard._(LogicalKeyboardKey.altLeft);
static const metaLeft = Keyboard._(LogicalKeyboardKey.metaLeft);
static const controlRight = Keyboard._(LogicalKeyboardKey.controlRight);
static const shiftRight = Keyboard._(LogicalKeyboardKey.shiftRight);
static const altRight = Keyboard._(LogicalKeyboardKey.altRight);
static const metaRight = Keyboard._(LogicalKeyboardKey.metaRight);
static const info = Keyboard._(LogicalKeyboardKey.info);
static const closedCaptionToggle = Keyboard._(LogicalKeyboardKey.closedCaptionToggle);
static const brightnessUp = Keyboard._(LogicalKeyboardKey.brightnessUp);
static const brightnessDown = Keyboard._(LogicalKeyboardKey.brightnessDown);
static const brightnessToggle = Keyboard._(LogicalKeyboardKey.brightnessToggle);
static const brightnessMinimum = Keyboard._(LogicalKeyboardKey.brightnessMinimum);
static const brightnessMaximum = Keyboard._(LogicalKeyboardKey.brightnessMaximum);
static const brightnessAuto = Keyboard._(LogicalKeyboardKey.brightnessAuto);
static const kbdIllumUp = Keyboard._(LogicalKeyboardKey.kbdIllumUp);
static const kbdIllumDown = Keyboard._(LogicalKeyboardKey.kbdIllumDown);
static const mediaLast = Keyboard._(LogicalKeyboardKey.mediaLast);
static const launchPhone = Keyboard._(LogicalKeyboardKey.launchPhone);
static const programGuide = Keyboard._(LogicalKeyboardKey.programGuide);
static const exit = Keyboard._(LogicalKeyboardKey.exit);
static const channelUp = Keyboard._(LogicalKeyboardKey.channelUp);
static const channelDown = Keyboard._(LogicalKeyboardKey.channelDown);
static const mediaPlay = Keyboard._(LogicalKeyboardKey.mediaPlay);
static const mediaPause = Keyboard._(LogicalKeyboardKey.mediaPause);
static const mediaRecord = Keyboard._(LogicalKeyboardKey.mediaRecord);
static const mediaFastForward = Keyboard._(LogicalKeyboardKey.mediaFastForward);
static const mediaRewind = Keyboard._(LogicalKeyboardKey.mediaRewind);
static const mediaTrackNext = Keyboard._(LogicalKeyboardKey.mediaTrackNext);
static const mediaTrackPrevious = Keyboard._(LogicalKeyboardKey.mediaTrackPrevious);
static const mediaStop = Keyboard._(LogicalKeyboardKey.mediaStop);
static const eject = Keyboard._(LogicalKeyboardKey.eject);
static const mediaPlayPause = Keyboard._(LogicalKeyboardKey.mediaPlayPause);
static const speechInputToggle = Keyboard._(LogicalKeyboardKey.speechInputToggle);
static const bassBoost = Keyboard._(LogicalKeyboardKey.bassBoost);
static const mediaSelect = Keyboard._(LogicalKeyboardKey.mediaSelect);
static const launchWordProcessor = Keyboard._(LogicalKeyboardKey.launchWordProcessor);
static const launchSpreadsheet = Keyboard._(LogicalKeyboardKey.launchSpreadsheet);
static const launchMail = Keyboard._(LogicalKeyboardKey.launchMail);
static const launchContacts = Keyboard._(LogicalKeyboardKey.launchContacts);
static const launchCalendar = Keyboard._(LogicalKeyboardKey.launchCalendar);
static const launchApp2 = Keyboard._(LogicalKeyboardKey.launchApp2);
static const launchApp1 = Keyboard._(LogicalKeyboardKey.launchApp1);
static const launchInternetBrowser = Keyboard._(LogicalKeyboardKey.launchInternetBrowser);
static const logOff = Keyboard._(LogicalKeyboardKey.logOff);
static const lockScreen = Keyboard._(LogicalKeyboardKey.lockScreen);
static const launchControlPanel = Keyboard._(LogicalKeyboardKey.launchControlPanel);
static const selectTask = Keyboard._(LogicalKeyboardKey.selectTask);
static const launchDocuments = Keyboard._(LogicalKeyboardKey.launchDocuments);
static const spellCheck = Keyboard._(LogicalKeyboardKey.spellCheck);
static const launchKeyboardLayout = Keyboard._(LogicalKeyboardKey.launchKeyboardLayout);
static const launchScreenSaver = Keyboard._(LogicalKeyboardKey.launchScreenSaver);
static const launchAssistant = Keyboard._(LogicalKeyboardKey.launchAssistant);
static const launchAudioBrowser = Keyboard._(LogicalKeyboardKey.launchAudioBrowser);
static const newKey = Keyboard._(LogicalKeyboardKey.newKey);
static const close = Keyboard._(LogicalKeyboardKey.close);
static const save = Keyboard._(LogicalKeyboardKey.save);
static const print = Keyboard._(LogicalKeyboardKey.print);
static const browserSearch = Keyboard._(LogicalKeyboardKey.browserSearch);
static const browserHome = Keyboard._(LogicalKeyboardKey.browserHome);
static const browserBack = Keyboard._(LogicalKeyboardKey.browserBack);
static const browserForward = Keyboard._(LogicalKeyboardKey.browserForward);
static const browserStop = Keyboard._(LogicalKeyboardKey.browserStop);
static const browserRefresh = Keyboard._(LogicalKeyboardKey.browserRefresh);
static const browserFavorites = Keyboard._(LogicalKeyboardKey.browserFavorites);
static const zoomIn = Keyboard._(LogicalKeyboardKey.zoomIn);
static const zoomOut = Keyboard._(LogicalKeyboardKey.zoomOut);
static const zoomToggle = Keyboard._(LogicalKeyboardKey.zoomToggle);
static const redo = Keyboard._(LogicalKeyboardKey.redo);
static const mailReply = Keyboard._(LogicalKeyboardKey.mailReply);
static const mailForward = Keyboard._(LogicalKeyboardKey.mailForward);
static const mailSend = Keyboard._(LogicalKeyboardKey.mailSend);
static const keyboardLayoutSelect = Keyboard._(LogicalKeyboardKey.keyboardLayoutSelect);
static const showAllWindows = Keyboard._(LogicalKeyboardKey.showAllWindows);
static const gameButton1 = Keyboard._(LogicalKeyboardKey.gameButton1);
static const gameButton2 = Keyboard._(LogicalKeyboardKey.gameButton2);
static const gameButton3 = Keyboard._(LogicalKeyboardKey.gameButton3);
static const gameButton4 = Keyboard._(LogicalKeyboardKey.gameButton4);
static const gameButton5 = Keyboard._(LogicalKeyboardKey.gameButton5);
static const gameButton6 = Keyboard._(LogicalKeyboardKey.gameButton6);
static const gameButton7 = Keyboard._(LogicalKeyboardKey.gameButton7);
static const gameButton8 = Keyboard._(LogicalKeyboardKey.gameButton8);
static const gameButton9 = Keyboard._(LogicalKeyboardKey.gameButton9);
static const gameButton10 = Keyboard._(LogicalKeyboardKey.gameButton10);
static const gameButton11 = Keyboard._(LogicalKeyboardKey.gameButton11);
static const gameButton12 = Keyboard._(LogicalKeyboardKey.gameButton12);
static const gameButton13 = Keyboard._(LogicalKeyboardKey.gameButton13);
static const gameButton14 = Keyboard._(LogicalKeyboardKey.gameButton14);
static const gameButton15 = Keyboard._(LogicalKeyboardKey.gameButton15);
static const gameButton16 = Keyboard._(LogicalKeyboardKey.gameButton16);
static const gameButtonA = Keyboard._(LogicalKeyboardKey.gameButtonA);
static const gameButtonB = Keyboard._(LogicalKeyboardKey.gameButtonB);
static const gameButtonC = Keyboard._(LogicalKeyboardKey.gameButtonC);
static const gameButtonLeft1 = Keyboard._(LogicalKeyboardKey.gameButtonLeft1);
static const gameButtonLeft2 = Keyboard._(LogicalKeyboardKey.gameButtonLeft2);
static const gameButtonMode = Keyboard._(LogicalKeyboardKey.gameButtonMode);
static const gameButtonRight1 = Keyboard._(LogicalKeyboardKey.gameButtonRight1);
static const gameButtonRight2 = Keyboard._(LogicalKeyboardKey.gameButtonRight2);
static const gameButtonSelect = Keyboard._(LogicalKeyboardKey.gameButtonSelect);
static const gameButtonStart = Keyboard._(LogicalKeyboardKey.gameButtonStart);
static const gameButtonThumbLeft = Keyboard._(LogicalKeyboardKey.gameButtonThumbLeft);
static const gameButtonThumbRight = Keyboard._(LogicalKeyboardKey.gameButtonThumbRight);
static const gameButtonX = Keyboard._(LogicalKeyboardKey.gameButtonX);
static const gameButtonY = Keyboard._(LogicalKeyboardKey.gameButtonY);
static const gameButtonZ = Keyboard._(LogicalKeyboardKey.gameButtonZ);
static const fn = Keyboard._(LogicalKeyboardKey.fn);
static const shift = Keyboard._(LogicalKeyboardKey.shift);
static const meta = Keyboard._(LogicalKeyboardKey.meta);
static const alt = Keyboard._(LogicalKeyboardKey.alt);
static const control = Keyboard._(LogicalKeyboardKey.control);
static final Map<LogicalKeyboardKey, bool> _justReleased = {};
static final Map<LogicalKeyboardKey, bool> _pressed = {};
static final Map<Keyboard, bool Function()> _metaKeys = {
shift: () => isDown(shiftLeft) || isDown(shiftRight),
meta: () => isDown(metaLeft) || isDown(metaRight),
control: () => isDown(controlLeft) || isDown(controlRight),
alt: () => isDown(altLeft) || isDown(altRight),
};
static Stage _stage;
static bool justReleased(Keyboard key) => _justReleased[key] != null;
static bool isDown(Keyboard key) => _metaKeys.containsKey(key) ? _metaKeys[key]() : (_pressed[key.value] ?? false);
static void init(Stage stage) {
_stage = stage;
_stage.keyboard.onDown.add(_onKey);
_stage.keyboard.onUp.add(_onKey);
}
static void dispose() {
_stage?.keyboard?.onDown?.remove(_onKey);
_stage?.keyboard?.onUp?.remove(_onKey);
}
static void _onKey(KeyboardEventData input) {
final k = input.rawEvent.logicalKey;
if (input.type == KeyEventType.down) {
_pressed[k] = true;
} else {
_justReleased[k] = true;
_pressed.remove(k);
}
}
}
abstract class ListUtils {
static void mergeSort(List<DisplayObject> input, SortChildrenCallback compare, int startIndex, int len, List<DisplayObject> buffer) {
if (len > 1) {
int i, endIndex = startIndex + len, halfLen = len ~/ 2, l = startIndex, r = startIndex + halfLen;
mergeSort(input, compare, startIndex, halfLen, buffer);
mergeSort(input, compare, startIndex + halfLen, len - halfLen, buffer);
for (i = 0; i < len; i++) {
if (l < startIndex + halfLen && (r == endIndex || compare(input[l], input[r]) <= 0)) {
buffer[i] = input[l];
l++;
} else {
buffer[i] = input[r];
r++;
}
}
for (i = startIndex; i < endIndex; ++i) input[i] = buffer[i - startIndex];
}
}
}
double deg2rad(double deg) => deg / 180.0 * math.pi;
double rad2deg(double rad) => rad / math.pi * 180.0;
abstract class MathUtils {
static const double halfPi = math.pi / 2, pi2 = math.pi * 2;
static int getNextPowerOfTwo(num value) {
if (value is int && value > 0 && (value.toInt() & (value.toInt() - 1)) == 0) {
return value;
} else {
var result = 1;
value -= 0.000000001;
while (result < value) result <<= 1;
return result;
}
}
static double normalizeAngle(double angle) {
angle = angle % pi2;
if (angle < -math.pi) angle += pi2;
if (angle > math.pi) angle -= pi2;
return angle;
}
static bool isPointInTriangle(GxPoint p, GxPoint a, GxPoint b, GxPoint c) {
var v0x = c.x - a.x;
var v0y = c.y - a.y;
var v1x = b.x - a.x;
var v1y = b.y - a.y;
var v2x = p.x - a.x;
var v2y = p.y - a.y;
var dot00 = v0x * v0x + v0y * v0y;
var dot01 = v0x * v1x + v0y * v1y;
var dot02 = v0x * v2x + v0y * v2y;
var dot11 = v1x * v1x + v1y * v1y;
var dot12 = v1x * v2x + v1y * v2y;
final invDen = 1.0 / (dot00 * dot11 - dot01 * dot01);
final u = (dot11 * dot02 - dot01 * dot12) * invDen;
final v = (dot00 * dot12 - dot01 * dot02) * invDen;
return (u >= 0) && (v >= 0) && (u + v < 1);
}
static bool isEquivalent(double a, double b, [double epsilon = .0001]) => (a - epsilon < b) && (a + epsilon > b);
static double shortRotation(double rotation) {
if (rotation < -math.pi) {
rotation += pi2;
} else if (rotation > math.pi) {
rotation -= pi2;
}
return rotation;
}
}
abstract class MatrixUtils {
static void skew(GxMatrix matrix, double skewX, double skewY) {
var sinX = math.sin(skewX);
var cosX = math.cos(skewX);
var sinY = math.sin(skewY);
var cosY = math.cos(skewY);
matrix.setTo(matrix.a * cosY - matrix.b * sinX, matrix.a * sinY + matrix.b * cosX, matrix.c * cosY - matrix.d * sinX, matrix.c * sinY + matrix.d * cosX, matrix.tx * cosY - matrix.ty * sinX, matrix.tx * sinY + matrix.ty * cosX);
}
static GxRect getTransformedBoundsRect(GxMatrix matrix, GxRect rect, [GxRect out]) {
out ??= GxRect();
var minX = 10000000.0;
var maxX = -10000000.0;
var minY = 10000000.0;
var maxY = -10000000.0;
var tx1 = matrix.a * rect.x + matrix.c * rect.y + matrix.tx;
var ty1 = matrix.d * rect.y + matrix.b * rect.x + matrix.ty;
var tx2 = matrix.a * rect.x + matrix.c * rect.bottom + matrix.tx;
var ty2 = matrix.d * rect.bottom + matrix.b * rect.x + matrix.ty;
var tx3 = matrix.a * rect.right + matrix.c * rect.y + matrix.tx;
var ty3 = matrix.d * rect.y + matrix.b * rect.right + matrix.ty;
var tx4 = matrix.a * rect.right + matrix.c * rect.bottom + matrix.tx;
var ty4 = matrix.d * rect.bottom + matrix.b * rect.right + matrix.ty;
if (minX > tx1) minX = tx1;
if (minX > tx2) minX = tx2;
if (minX > tx3) minX = tx3;
if (minX > tx4) minX = tx4;
if (minY > ty1) minY = ty1;
if (minY > ty2) minY = ty2;
if (minY > ty3) minY = ty3;
if (minY > ty4) minY = ty4;
if (maxX < tx1) maxX = tx1;
if (maxX < tx2) maxX = tx2;
if (maxX < tx3) maxX = tx3;
if (maxX < tx4) maxX = tx4;
if (maxY < ty1) maxY = ty1;
if (maxY < ty2) maxY = ty2;
if (maxY < ty3) maxY = ty3;
if (maxY < ty4) maxY = ty4;
out.setTo(minX, minY, maxX - minX, maxY - minY);
return out;
}
}
mixin RenderUtilMixin {
ui.Picture createPicture([void Function(Canvas) prePaintCallback]) {
final r = ui.PictureRecorder();
final c = Canvas(r);
prePaintCallback?.call(c);
return r.endRecording();
}
void paint(Canvas canvas);
GxRect getBounds();
Future<GTexture> createImageTexture([bool adjustOffset = true, double resolution = 1]) async => GTexture.fromImage(await createImage(adjustOffset, resolution), resolution);
Future<ui.Image> createImage([bool adjustOffset = true, double resolution = 1]) async {
var rect = getBounds();
if (resolution != 1) rect *= resolution;
final needsAdjust = (rect.x != 0 || rect.y != 0) && adjustOffset || resolution != 1;
final picture = createPicture(
!needsAdjust
? null
: (c) {
if (adjustOffset) c.translate(-rect.left, -rect.top);
if (resolution != 1) c.scale(resolution);
},
);
final width = adjustOffset ? rect.width.toInt() : rect.right.toInt();
final height = adjustOffset ? rect.height.toInt() : rect.bottom.toInt();
final output = await picture.toImage(width, height);
picture?.dispose();
return output;
}
}
mixin DisplayMasking {
GxRect maskRect;
double maskRectCornerRadius;
bool maskRectInverted = false;
void $applyMaskRect(Canvas c) {
if (maskRect.hasCorners) {
c.clipRRect(maskRect.toRoundNative(), doAntiAlias: true);
} else {
c.clipRect(maskRect.toNative(), clipOp: maskRectInverted ? ui.ClipOp.difference : ui.ClipOp.intersect, doAntiAlias: true);
}
}
}
abstract class PainterUtils {
static Paint emptyPaint = Paint();
static Paint alphaPaint = Paint()
..color = Color(0xff000000)
..blendMode = BlendMode.srcATop;
static Paint getAlphaPaint(double alpha) => alphaPaint..color = alphaPaint.color.withOpacity(alpha);
static ColorFilter getColorize(Color color) => ColorFilter.mode(color, BlendMode.srcATop);
}
mixin Pool {
static final _points = <GxPoint>[];
static final _rectangles = <GxRect>[];
static final _matrices = <GxMatrix>[];
static GxPoint getPoint([double x = 0, double y = 0]) => _points.isEmpty ? GxPoint(x, y) : _points.removeLast()
..x = x
..y = y;
static void putPoint(GxPoint point) {
if (point != null) _points.add(point);
}
static GxMatrix getMatrix([double a = 1, double b = 0, double c = 0, double d = 1, double tx = 0, double ty = 0]) {
if (_matrices.isEmpty) return GxMatrix(a, b, c, d, tx, ty);
return _matrices.removeLast()..setTo(a, b, c, d, tx, ty);
}
static void putMatrix(GxMatrix matrix) {
if (matrix != null) _matrices.add(matrix);
}
static GxRect getRect([double x = 0, double y = 0, double w = 0, double h = 0]) {
if (_rectangles.isEmpty) return GxRect(x, y, w, h);
return _rectangles.removeLast()..setTo(x, y, w, h);
}
static void putRect(GxRect rect) {
if (rect != null) _rectangles.add(rect);
}
}
class StringUtils {
static bool parseBoolean(String value) {
if (value == null) return false;
return value == 'true' || value == 'TRUE' || value == 'True' || value == '1';
}
}
abstract class SystemUtils {
static bool get usingSkia {
if (!kIsWeb) return true;
return const bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: false);
}
}
mixin TextureUtils {
static final Shape _helperShape = Shape();
static Graphics get _g => _helperShape.graphics;
static double resolution = 1.0;
static void scale9Rect(GTexture tx, double x, {double y, double w, double h, bool adjustScale = false}) {
y ??= x;
w ??= -x;
h ??= -y;
if (adjustScale) {
x *= tx.scale;
y *= tx.scale;
w *= tx.scale;
h *= tx.scale;
}
if (w < 0) {
w = tx.width + w * 2;
}
if (h < 0) {
h = tx.height + h * 2;
}
var out = GxRect(x, y, w, h);
tx.scale9Grid = out;
}
static Future<GTexture> createCircle({
int color = 0xff00ff,
double alpha = 1,
double radius = 20,
double x = 0,
double y = 0,
String id,
}) async {
_g.clear()..beginFill(color, alpha).drawCircle(x, y, radius);
return await _drawShape(id);
}
static Future<GTexture> createRect({
int color = 0xff00ff,
double alpha = 1,
double x = 0,
double y = 0,
double w = 20,
double h = 20,
String id,
}) async {
_g.clear()..beginFill(color, alpha).drawRect(x, y, w, h);
return (await _drawShape(id));
}
static Future<GTexture> createRoundRect({
int color = 0xff00ff,
double alpha = 1,
double x = 0,
double y = 0,
double w = 20,
double h = 20,
double r = 8,
String id,
}) async {
_g.clear()..beginFill(color, alpha).drawRoundRect(x, y, w, h, r);
return (await _drawShape(id));
}
static Future<GTexture> createTriangle({
int color = 0xff00ff,
double alpha = 1,
double w = 20,
double h = 20,
double rotation = 0,
String id,
}) async {
_g.clear().beginFill(color, alpha).drawPolygonFaces(0, 0, w / 2, 3, rotation).endFill();
var heightScale = h / w;
_helperShape.scaleY = heightScale;
var tx = await _drawShape(id);
_helperShape.scaleY = 1;
return tx;
}
static Future<GTexture> _drawShape([String id]) async {
final tx = await _helperShape.createImageTexture(true, TextureUtils.resolution);
if (id != null) {
ResourceLoader.textures[id] = tx;
}
return tx;
}
static List<GTexture> getRectAtlasFromGTexture(GTexture base, int w, {int h, int padding = 0, double scale = 1}) {
h ??= w;
var cols = base.sourceRect.width / w;
var rows = base.sourceRect.height / h;
var total = cols * rows;
var output = <GTexture>[];
final _w = w.toDouble();
final _h = h.toDouble();
for (var i = 0; i < total; ++i) {
final px = (i % cols) * _w;
final py = (i ~/ cols) * _h;
var subRect = GxRect(px, py, _w, _h);
var texture = GSubTexture(base, region: subRect, scaleModifier: scale, rotated: false);
output.add(texture);
}
return output;
}
static bool isValidTextureSize(int size) => getNextValidTextureSize(size) == size;
static int getNextValidTextureSize(int size) {
var _size = 1;
while (size > _size) _size *= 2;
return _size;
}
static int getPreviousValidTextureSize(int size) {
return getNextValidTextureSize(size) >> 1;
}
static int getNearestValidTextureSize(int size) {
final prev = getPreviousValidTextureSize(size);
final next = getNextValidTextureSize(size);
return size - prev < next - size ? prev : next;
}
}
abstract class GraphicsClipper extends CustomClipper<Path> {
bool reClip = false;
double x = 0, y = 0, rotation = 0, scaleX = 1, scaleY = 1, pivotX = 0, pivotY = 0, skewX = 0, skewY = 0;
@override
Path getClip(Size size) {
final g = Graphics();
g.beginFill(0x0);
draw(g, size);
return applyTransform(g.getPaths());
}
Path applyTransform(Path p) {
var hasTransform = x != 0 || y != 0 || scaleX != 1 || scaleY != 1 || skewX != 1 || skewY != 1 || pivotX != 1 || pivotY != 1;
if (!hasTransform) return p;
final m = Pool.getMatrix();
m.setTransform(x, y, pivotX, pivotY, scaleX, scaleY, skewX, skewY, rotation);
final result = p.transform(m.toNative().storage);
Pool.putMatrix(m);
return result;
}
void draw(Graphics g, Size size);
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) => reClip;
}
class SceneBuilderWidget extends StatefulWidget {
final Widget child;
final SceneController Function() builder;
final bool painterIsComplex, mouseOpaque;
final HitTestBehavior pointerBehaviour;
const SceneBuilderWidget({Key key, this.builder, this.child, this.painterIsComplex = true, this.mouseOpaque = true, this.pointerBehaviour = HitTestBehavior.translucent}) : super(key: key);
@override
_SceneBuilderWidgetState createState() => _SceneBuilderWidgetState();
}
class _SceneBuilderWidgetState extends State<SceneBuilderWidget> {
SceneController _controller;
@override
void initState() {
super.initState();
_controller = widget.builder();
_controller.resolveWindowBounds = _getRenderObjectWindowBounds;
_controller.$init();
}
GxRect _getRenderObjectWindowBounds() {
if (!mounted) return null;
return ContextUtils.getRenderObjectBounds(context);
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
@override
void reassemble() {
super.reassemble();
_controller.reassembleWidget();
}
@override
Widget build(BuildContext context) {
Widget child = CustomPaint(painter: _controller.buildBackPainter(), foregroundPainter: _controller.buildFrontPainter(), isComplex: widget.painterIsComplex, willChange: _controller.config.painterMightChange(), child: widget.child ?? Container());
var converter = _controller.$inputConverter;
if (_controller.config.usePointer) {
child = MouseRegion(
onEnter: converter.pointerEnter,
onExit: converter.pointerExit,
onHover: converter.pointerHover,
cursor: MouseCursor.defer,
opaque: widget.mouseOpaque,
child: Listener(child: child, behavior: widget.pointerBehaviour, onPointerDown: converter.pointerDown, onPointerUp: converter.pointerUp, onPointerCancel: converter.pointerCancel, onPointerMove: converter.pointerMove, onPointerSignal: converter.pointerSignal),
);
}
if (_controller.config.useKeyboard) {
child = RawKeyboardListener(onKey: converter.handleKey, autofocus: true, includeSemantics: false, focusNode: converter.keyboard.focusNode, child: child);
}
return child;
}
}
typedef MPSFunctionBuilder<T> = Widget Function(BuildContext context, MPSEvent<T> event, Widget child);
class MPSBuilder<T> extends StatefulWidget {
final Widget child;
final MPS mps;
final MPSFunctionBuilder<T> builder;
final List<String> topics;
const MPSBuilder({Key key, @required this.builder, @required this.topics, @required this.mps, this.child}) : super(key: key);
@override
_MPSBuilderState<T> createState() => _MPSBuilderState<T>();
}
class _MPSBuilderState<T> extends State<MPSBuilder<T>> {
MPSEvent<T> _data = MPSEvent.empty();
final _maps = <String, Function>{};
@override
void initState() {
super.initState();
for (var t in widget.topics) {
_maps[t] = ([p1, p2, p3]) {
if (p2 != null || p3 != null) {
var paramStrings = '';
if (p2 != null) paramStrings = ', "$p2" ';
if (p3 != null) paramStrings = ', "$p3" ';
trace('''WARNING:
[MPSBuilder] doesnt support more than 1 argument. $paramStrings will not be reachable in the `builder`''');
}
setState(() => _data = MPSEvent(t, p1));
};
widget.mps.on(t, _maps[t]);
}
}
@override
Widget build(BuildContext context) => widget.builder(context, _data, widget.child);
@override
void dispose() {
for (var t in widget.topics) widget.mps.off(t, _maps[t]);
super.dispose();
}
}
class MPSEvent<T> {
final String type;
final T data;
factory MPSEvent.empty() => const MPSEvent('');
const MPSEvent(this.type, [this.data]);
@override
String toString() {
if (type == '') return 'MPSEvent [empty]';
var str = 'MPSEvent "$type"';
if (data != null) str += ' data=$data';
return str;
}
}
abstract class Math {
static final math.Random _rnd = math.Random();
static const PI = math.pi;
static const PI_2 = math.pi * 2;
static const PI1_2 = math.pi / 2;
static const E = math.e;
static const LN10 = math.ln10;
static const LN2 = math.ln2;
static const LOG10E = math.log10e;
static const LOG2E = math.log2e;
static const SQRT1_2 = math.sqrt1_2;
static const SQRT2 = math.sqrt2;
static final cos = math.cos;
static final acos = math.acos;
static final sin = math.sin;
static final asin = math.asin;
static final tan = math.tan;
static final atan = math.atan;
static final atan2 = math.atan2;
static final sqrt = math.sqrt;
static final exp = math.exp;
static final log = math.log;
static final max = math.max;
static final min = math.min;
static final pow = math.pow;
static num ceil(double value, [bool keepDouble = true]) {
return keepDouble ? value?.ceilToDouble() : value?.ceil();
}
static num floor(double value, [bool keepDouble = true]) {
return keepDouble ? value?.floorToDouble() : value?.floor();
}
static num round(double value, [bool keepDouble = true]) {
return keepDouble ? value?.roundToDouble() : value?.round();
}
static num abs(num value) => value.abs();
static bool randomBool() => _rnd.nextBool();
static double random() => _rnd.nextDouble();
static E randomList<E>(List<E> list) => list[randomRangeInt(0, list.length - 1)];
static double randomRange(num min, num max) => min.toDouble() + random() * (max.toDouble() - min.toDouble());
static int randomRangeInt(num min, num max) => min.toInt() + _rnd.nextInt(max.toInt() - min.toInt());
static double randomRangeClamp(num min, num max, num clamp) => (randomRange(min.toDouble(), max.toDouble()) / clamp.toDouble()).roundToDouble() * clamp.toDouble();
static int randomRangeIntClamp(num min, num max, num clamp) => (randomRangeInt(min.toInt(), max.toInt()) / clamp.toInt()).round() * clamp.toInt();
}
abstract class ResourceLoader {
static Map<String, GTexture> textures = <String, GTexture>{};
static Map<String, GifAtlas> gifs = <String, GifAtlas>{};
static GTexture getTexture(String cacheId) => textures[cacheId];
static GifAtlas getGif(String cacheId) => gifs[cacheId];
static Future<GifAtlas> loadGif(String path, [double resolution = 1.0, String cacheId]) async {
cacheId ??= path;
final data = await rootBundle.load(path);
final bytes = Uint8List.view(data.buffer);
final codec = await ui.instantiateImageCodec(bytes, allowUpscaling: false);
resolution ??= TextureUtils.resolution;
final atlas = GifAtlas();
atlas.scale = resolution;
atlas.numFrames = codec.frameCount;
for (var i = 0; i < atlas.numFrames; ++i) {
final frame = await codec.getNextFrame();
final texture = GTexture.fromImage(frame.image, resolution);
var gifFrame = GifFrame(frame.duration, texture);
atlas.addFrame(gifFrame);
}
gifs[cacheId] = textures[cacheId] = atlas;
return atlas;
}
static Future<GTexture> loadTexture(String path, [double resolution = 1.0, String cacheId]) async {
cacheId ??= path;
textures[cacheId] = GTexture.fromImage(await loadImage(path), resolution);
return textures[cacheId];
}
static Future<ByteData> loadBinary(String path) async => await rootBundle.load(path);
static Future<ui.Image> loadImage(String path, {int targetWidth, int targetHeight}) async {
final data = await rootBundle.load(path);
final bytes = Uint8List.view(data.buffer);
final codec = await ui.instantiateImageCodec(
bytes,
allowUpscaling: false,
targetWidth: targetWidth,
targetHeight: targetHeight,
);
return (await codec.getNextFrame()).image;
}
static Future<String> loadString(String path) async => await rootBundle.loadString(path);
static Future<dynamic> loadJson(String path) async => jsonDecode(await loadString(path));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment