[hw] rename haxework package to hw
This commit is contained in:
64
src/main/hw/animate/Animate.hx
Normal file
64
src/main/hw/animate/Animate.hx
Normal file
@@ -0,0 +1,64 @@
|
||||
package hw.animate;
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.display.Stage;
|
||||
import flash.events.Event;
|
||||
import hw.view.IView;
|
||||
|
||||
class Animate implements IAnimate {
|
||||
public static var defaultDuraion = 300;
|
||||
|
||||
private static var running:Array<IAnimate> = new Array<IAnimate>();
|
||||
|
||||
public static function bind(stage:Stage):Void {
|
||||
stage.addEventListener(Event.ENTER_FRAME, function(_) {
|
||||
Animate.updateAll();
|
||||
});
|
||||
}
|
||||
|
||||
public static function updateAll():Void {
|
||||
if (running.length > 0) {
|
||||
var time = Date.now().getTime();
|
||||
for (animate in running) animate.update(time);
|
||||
}
|
||||
}
|
||||
|
||||
private var callback:Animate -> Void;
|
||||
private var view:IView<Dynamic>;
|
||||
private var duration:Int;
|
||||
private var startTime:Float;
|
||||
private var progress:Float;
|
||||
|
||||
private var object(get, null):DisplayObject;
|
||||
|
||||
public function new(view:IView<Dynamic>, duration:Int = -1) {
|
||||
this.view = view;
|
||||
this.duration = duration > -1 ? duration : defaultDuraion;
|
||||
}
|
||||
|
||||
private inline function get_object():DisplayObject {
|
||||
return cast view.content;
|
||||
}
|
||||
|
||||
public function start(callback:IAnimate -> Void, custom:Bool = false):Void {
|
||||
startTime = Date.now().getTime();
|
||||
this.callback = callback;
|
||||
if (!custom) running.push(this);
|
||||
update(startTime);
|
||||
}
|
||||
|
||||
private function update(time:Float):Void {
|
||||
progress = (time - startTime) / duration;
|
||||
if (progress >= 1) {
|
||||
running.remove(this);
|
||||
if (callback != null) {
|
||||
callback(this);
|
||||
callback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function cancel():Void {
|
||||
if (!Math.isNaN(startTime)) update(startTime + duration);
|
||||
}
|
||||
}
|
||||
62
src/main/hw/animate/CircleMaskAnimate.hx
Executable file
62
src/main/hw/animate/CircleMaskAnimate.hx
Executable file
@@ -0,0 +1,62 @@
|
||||
package hw.animate;
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.display.Sprite;
|
||||
import hw.animate.Animate;
|
||||
import hw.animate.IAnimate;
|
||||
import hw.view.IView;
|
||||
|
||||
class CircleMaskAnimate extends Animate {
|
||||
|
||||
private var mask:Sprite;
|
||||
private var cyrcle:Sprite;
|
||||
private var size:Float;
|
||||
|
||||
public function new(view:IView<DisplayObject>, duration:Int = -1) {
|
||||
super(view, duration);
|
||||
this.view = view;
|
||||
this.mask = new Sprite();
|
||||
this.cyrcle = new Sprite();
|
||||
}
|
||||
|
||||
override public function start(callback:IAnimate -> Void, custom:Bool = false):Void {
|
||||
var width = view.parent.width;
|
||||
var height = view.parent.height;
|
||||
size = Math.sqrt(width * width + height * height);
|
||||
//size = Math.max(width, height);
|
||||
cyrcle.x = mask.x = -(size - width) / 2 - size;
|
||||
cyrcle.y = mask.y = -(size - height) / 2 - size;
|
||||
|
||||
redraw(size, size);
|
||||
|
||||
view.parent.container.addChild(mask);
|
||||
view.content.mask = mask;
|
||||
view.parent.container.addChild(cyrcle);
|
||||
|
||||
super.start(callback, custom);
|
||||
}
|
||||
|
||||
private function redraw(size:Float, r:Float):Void {
|
||||
mask.graphics.clear();
|
||||
mask.graphics.beginFill(0xffffff);
|
||||
mask.graphics.drawCircle(size + size / 2, size + size / 2, r / 2);
|
||||
mask.graphics.endFill();
|
||||
|
||||
cyrcle.graphics.clear();
|
||||
cyrcle.graphics.lineStyle(4, 0xffffff);
|
||||
cyrcle.graphics.drawCircle(size + size / 2, size + size / 2, r / 2);
|
||||
cyrcle.graphics.lineStyle();
|
||||
}
|
||||
|
||||
override private function update(time:Float):Void {
|
||||
super.update(time);
|
||||
|
||||
redraw(size, size * progress);
|
||||
|
||||
if (progress >= 1 && view.content.parent != null) {
|
||||
if (view.content.parent.contains(mask)) view.content.parent.removeChild(mask);
|
||||
view.content.mask = null;
|
||||
if (view.content.parent.contains(cyrcle)) view.parent.container.removeChild(cyrcle);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/main/hw/animate/FadeAnimate.hx
Executable file
20
src/main/hw/animate/FadeAnimate.hx
Executable file
@@ -0,0 +1,20 @@
|
||||
package hw.animate;
|
||||
|
||||
import hw.animate.Animate;
|
||||
import hw.animate.IAnimate;
|
||||
|
||||
class FadeAnimate extends Animate {
|
||||
|
||||
override public function start(callback:IAnimate -> Void, custom:Bool = false):Void {
|
||||
object.alpha = 1.0;
|
||||
super.start(callback, custom);
|
||||
}
|
||||
|
||||
override private function update(time:Float):Void {
|
||||
super.update(time);
|
||||
object.alpha = 1 - (progress * 1.0);
|
||||
if (progress >= 1) {
|
||||
object.alpha = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/main/hw/animate/IAnimate.hx
Normal file
10
src/main/hw/animate/IAnimate.hx
Normal file
@@ -0,0 +1,10 @@
|
||||
package hw.animate;
|
||||
|
||||
interface IAnimate {
|
||||
|
||||
public function start(callback:IAnimate -> Void, custom:Bool = false):Void;
|
||||
|
||||
public function cancel():Void;
|
||||
|
||||
private function update(time:Float):Void;
|
||||
}
|
||||
14
src/main/hw/animate/SlideAnimate.hx
Normal file
14
src/main/hw/animate/SlideAnimate.hx
Normal file
@@ -0,0 +1,14 @@
|
||||
package hw.animate;
|
||||
|
||||
class SlideAnimate extends Animate {
|
||||
|
||||
override public function start(callback:IAnimate -> Void, custom:Bool = false):Void {
|
||||
object.x = view.x - this.view.width + this.view.width / progress;
|
||||
super.start(callback, custom);
|
||||
}
|
||||
|
||||
override private function update(time:Float):Void {
|
||||
super.update(time);
|
||||
object.x = view.x - this.view.width + this.view.width / Math.min(1, progress);
|
||||
}
|
||||
}
|
||||
19
src/main/hw/animate/UnFadeAnimate.hx
Executable file
19
src/main/hw/animate/UnFadeAnimate.hx
Executable file
@@ -0,0 +1,19 @@
|
||||
package hw.animate;
|
||||
|
||||
import hw.animate.Animate;
|
||||
|
||||
class UnFadeAnimate extends Animate {
|
||||
|
||||
override public function start(callback:IAnimate -> Void, custom:Bool = false):Void {
|
||||
object.alpha = 0.0;
|
||||
super.start(callback, custom);
|
||||
}
|
||||
|
||||
override private function update(time:Float):Void {
|
||||
super.update(time);
|
||||
object.alpha = progress * 1.0;
|
||||
if (progress >= 1) {
|
||||
object.alpha = 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/main/hw/app/App.hx
Normal file
86
src/main/hw/app/App.hx
Normal file
@@ -0,0 +1,86 @@
|
||||
package hw.app;
|
||||
|
||||
import flash.display.BitmapData;
|
||||
import flash.display.StageDisplayState;
|
||||
import flash.events.FullScreenEvent;
|
||||
import flash.Lib;
|
||||
import hw.animate.Animate;
|
||||
import hw.animate.FadeAnimate;
|
||||
import hw.animate.IAnimate;
|
||||
import hw.animate.UnFadeAnimate;
|
||||
import hw.resources.IResources;
|
||||
import hw.signal.Signal;
|
||||
import hw.view.IView;
|
||||
import hw.view.popup.PopupManager;
|
||||
import hw.view.Root;
|
||||
import hw.view.theme.ITheme;
|
||||
|
||||
class App {
|
||||
|
||||
public var view(default, set):IView<Dynamic>;
|
||||
|
||||
private function set_view(value:IView<Dynamic>):IView<Dynamic> {
|
||||
view = value;
|
||||
Root.bind(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
public var icon(default, set):BitmapData;
|
||||
|
||||
private function set_icon(value:BitmapData):BitmapData {
|
||||
icon = value;
|
||||
#if linux
|
||||
if (icon != null) {
|
||||
hw.app.LinuxIcon.value = icon;
|
||||
}
|
||||
#end
|
||||
return icon;
|
||||
}
|
||||
|
||||
public var fullScreenSupport(get, never):Bool;
|
||||
|
||||
public function get_fullScreenSupport():Bool {
|
||||
return Lib.current.stage.allowsFullScreen;
|
||||
}
|
||||
|
||||
public var fullScreen(get, set):Bool;
|
||||
|
||||
private function get_fullScreen():Bool {
|
||||
return Lib.current.stage.displayState != StageDisplayState.NORMAL;
|
||||
}
|
||||
|
||||
private function set_fullScreen(value:Bool):Bool {
|
||||
Lib.current.stage.displayState = value ? StageDisplayState.FULL_SCREEN : StageDisplayState.NORMAL;
|
||||
return get_fullScreen();
|
||||
}
|
||||
|
||||
public var fullScreenSignal(default, null):Signal<Bool> = new Signal();
|
||||
|
||||
@:provide static var app:App;
|
||||
@:provide static var resources:IResources;
|
||||
@:provide static var popupManager:PopupManager;
|
||||
|
||||
@:provide public var theme:ITheme;
|
||||
|
||||
public function new() {
|
||||
Lib.current.stage.stageFocusRect = false;
|
||||
Lib.current.stage.addEventListener(FullScreenEvent.FULL_SCREEN, event -> fullScreenSignal.emit(event.fullScreen));
|
||||
Animate.bind(Lib.current.stage);
|
||||
|
||||
popupManager.showAnimateFactory = createShowAnimate;
|
||||
popupManager.closeAnimateFactory = createCloseAnimate;
|
||||
|
||||
resources.text.put("app.version", Const.instance.VERSION);
|
||||
resources.text.put("app.name", Const.instance.NAME);
|
||||
|
||||
app = this;
|
||||
}
|
||||
|
||||
private function createShowAnimate(view:IView<Dynamic>):IAnimate {
|
||||
return new UnFadeAnimate(view);
|
||||
}
|
||||
|
||||
private function createCloseAnimate(view:IView<Dynamic>):IAnimate {
|
||||
return new FadeAnimate(view);
|
||||
}
|
||||
}
|
||||
22
src/main/hw/app/Const.hx
Executable file
22
src/main/hw/app/Const.hx
Executable file
@@ -0,0 +1,22 @@
|
||||
package hw.app;
|
||||
|
||||
import flash.Lib;
|
||||
import flash.system.Capabilities;
|
||||
|
||||
@:singleton class Const {
|
||||
public var FPS(default, null):Int;
|
||||
public var BUILD(default, null):String;
|
||||
public var VERSION(default, null):String;
|
||||
public var NAME(default, null):String;
|
||||
public var DEBUG(default, null):Bool;
|
||||
|
||||
public function new():Void {
|
||||
BUILD = CompilationOption.get("build");
|
||||
#if lime
|
||||
FPS = Std.parseInt(Lib.current.stage.application.meta.get("fps"));
|
||||
VERSION = Lib.current.stage.application.meta.get("version");
|
||||
NAME = Lib.current.stage.application.meta.get("name");
|
||||
#end
|
||||
DEBUG = Capabilities.isDebugger;
|
||||
}
|
||||
}
|
||||
29
src/main/hw/app/LinuxIcon.hx
Normal file
29
src/main/hw/app/LinuxIcon.hx
Normal file
@@ -0,0 +1,29 @@
|
||||
package hw.app;
|
||||
|
||||
import flash.display.BitmapData;
|
||||
import flash.filters.ColorMatrixFilter;
|
||||
import flash.geom.Point;
|
||||
import flash.Lib;
|
||||
|
||||
class LinuxIcon {
|
||||
|
||||
public static var value(default, set):BitmapData;
|
||||
|
||||
private static function set_value(value:BitmapData):BitmapData {
|
||||
LinuxIcon.value = value;
|
||||
Lib.current.stage.window.setIcon(prepareIcon(value).image);
|
||||
return LinuxIcon.value;
|
||||
}
|
||||
|
||||
private static function prepareIcon(bitmap:BitmapData):BitmapData {
|
||||
var matrix:Array<Float> = [];
|
||||
matrix = matrix.concat([0, 0, 1, 0, 0]);
|
||||
matrix = matrix.concat([0, 1, 0, 0, 0]);
|
||||
matrix = matrix.concat([1, 0, 0, 0, 0]);
|
||||
matrix = matrix.concat([0, 0, 0, 1, 0]);
|
||||
var cmf:ColorMatrixFilter = new ColorMatrixFilter(matrix);
|
||||
var bitmap:BitmapData = bitmap.clone();
|
||||
bitmap.applyFilter(bitmap, bitmap.rect, new Point(0, 0), cmf);
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
71
src/main/hw/color/Color.hx
Normal file
71
src/main/hw/color/Color.hx
Normal file
@@ -0,0 +1,71 @@
|
||||
package hw.color;
|
||||
|
||||
abstract Color(Int) {
|
||||
public var alpha(get, never):Int;
|
||||
public var red(get, never):Int;
|
||||
public var green(get, never):Int;
|
||||
public var blue(get, never):Int;
|
||||
public var zero(get, never):Bool;
|
||||
|
||||
public inline function new(value:Int) {
|
||||
this = value;
|
||||
}
|
||||
|
||||
private inline function get_alpha():Int {
|
||||
return (this >> 24) & 255;
|
||||
}
|
||||
|
||||
private inline function get_red():Int {
|
||||
return (this >> 16) & 255;
|
||||
}
|
||||
|
||||
private inline function get_green():Int {
|
||||
return (this >> 8) & 255;
|
||||
}
|
||||
|
||||
private inline function get_blue():Int {
|
||||
return this & 255;
|
||||
}
|
||||
|
||||
private inline function get_zero():Bool {
|
||||
return green == 0 && red == 0 && blue == 0;
|
||||
}
|
||||
|
||||
@:from static public inline function fromArray(value:Array<Int>):Color {
|
||||
return new Color((value[0] << 24) + (value[1] << 16) + (value[2] << 8) + value[3]);
|
||||
}
|
||||
|
||||
@:from static public inline function fromInt(value:Int):Color {
|
||||
return new Color(value);
|
||||
}
|
||||
|
||||
@:to public inline function toInt():Int {
|
||||
return this;
|
||||
}
|
||||
|
||||
@:from static public inline function fromString(value:String):Color {
|
||||
return new Color(switch value {
|
||||
case "white": 0xFFFFFF;
|
||||
case "silver": 0xC0C0C0;
|
||||
case "gray": 0x808080;
|
||||
case "black": 0x000000;
|
||||
case "red": 0xFF0000;
|
||||
case "maroon": 0x800000;
|
||||
case "yellow": 0xFFFF00;
|
||||
case "olive": 0x808000;
|
||||
case "lime": 0x00FF00;
|
||||
case "green": 0x008000;
|
||||
case "aqua": 0x00FFFF;
|
||||
case "teal": 0x008080;
|
||||
case "blue": 0x0000FF;
|
||||
case "navy": 0x000080;
|
||||
case "fuchsia": 0xFF00FF;
|
||||
case "purple": 0x800080;
|
||||
case x: Std.parseInt('0x${x.split('#').pop()}');
|
||||
});
|
||||
}
|
||||
|
||||
@:to public inline function toString():String {
|
||||
return StringTools.hex(this);
|
||||
}
|
||||
}
|
||||
36
src/main/hw/color/ColorUtil.hx
Normal file
36
src/main/hw/color/ColorUtil.hx
Normal file
@@ -0,0 +1,36 @@
|
||||
package hw.color;
|
||||
|
||||
class ColorUtil {
|
||||
|
||||
public static function floor(colorPart:Float):Int {
|
||||
return Std.int(Math.max(0, Math.min(255, colorPart)));
|
||||
}
|
||||
|
||||
public static function multiply(color:Color, m:Float):Color {
|
||||
return [
|
||||
color.alpha,
|
||||
floor(color.red * m),
|
||||
floor(color.green * m),
|
||||
floor(color.blue * m),
|
||||
];
|
||||
}
|
||||
|
||||
public static function diff(color:Color, d:Int):Color {
|
||||
return [
|
||||
color.alpha,
|
||||
floor(color.red + d),
|
||||
floor(color.green + d),
|
||||
floor(color.blue + d),
|
||||
];
|
||||
}
|
||||
|
||||
public static function grey(color:Color):Color {
|
||||
var m = (color.red + color.green + color.blue) / 3;
|
||||
return [
|
||||
color.alpha,
|
||||
floor(m),
|
||||
floor(m),
|
||||
floor(m),
|
||||
];
|
||||
}
|
||||
}
|
||||
35
src/main/hw/geom/IntPoint.hx
Normal file
35
src/main/hw/geom/IntPoint.hx
Normal file
@@ -0,0 +1,35 @@
|
||||
package hw.geom;
|
||||
|
||||
abstract IntPoint(Array<Int>) {
|
||||
public var x(get, set):Int;
|
||||
public var y(get, set):Int;
|
||||
|
||||
public function new(x:Int, y:Int) {
|
||||
this = [x, y];
|
||||
}
|
||||
|
||||
private inline function get_x() return this[0];
|
||||
|
||||
private inline function set_x(value) return this[0] = value;
|
||||
|
||||
private inline function get_y() return this[1];
|
||||
|
||||
private inline function set_y(value) return this[1] = value;
|
||||
|
||||
public function clone():IntPoint {
|
||||
return new IntPoint(x, y);
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return 'IntPoint{x=$x,y=$y}';
|
||||
}
|
||||
|
||||
@:to inline public function toInt():Int {
|
||||
return (x << 16) + y;
|
||||
}
|
||||
|
||||
@:to inline public function toPoint():Point {
|
||||
return new Point(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
40
src/main/hw/geom/Point.hx
Normal file
40
src/main/hw/geom/Point.hx
Normal file
@@ -0,0 +1,40 @@
|
||||
package hw.geom;
|
||||
|
||||
import flash.geom.Point as FlashPoint;
|
||||
|
||||
abstract Point(Array<Float>) {
|
||||
public var x(get, set):Float;
|
||||
public var y(get, set):Float;
|
||||
|
||||
public function new(x:Float, y:Float) {
|
||||
this = [x, y];
|
||||
}
|
||||
|
||||
private inline function get_x() return this[0];
|
||||
|
||||
private inline function set_x(value) return this[0] = value;
|
||||
|
||||
private inline function get_y() return this[1];
|
||||
|
||||
private inline function set_y(value) return this[1] = value;
|
||||
|
||||
public function add(point:Point):Point {
|
||||
return new Point(x + point.x, y + point.y);
|
||||
}
|
||||
|
||||
public function subtract(point:Point):Point {
|
||||
return new Point(x - point.x, y - point.y);
|
||||
}
|
||||
|
||||
public function clone():Point {
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return 'Point{x=$x,y=$y}';
|
||||
}
|
||||
|
||||
@:from public static function fromFlashPoint(value:FlashPoint):Point {
|
||||
return new Point(value.x, value.y);
|
||||
}
|
||||
}
|
||||
102
src/main/hw/geom/Rectangle.hx
Normal file
102
src/main/hw/geom/Rectangle.hx
Normal file
@@ -0,0 +1,102 @@
|
||||
package hw.geom;
|
||||
|
||||
abstract Rectangle(Array<Float>) {
|
||||
public var x(get, set):Float;
|
||||
public var y(get, set):Float;
|
||||
public var width(get, set):Float;
|
||||
public var height(get, set):Float;
|
||||
public var center(get, set):Point;
|
||||
public var size(get, set):Point;
|
||||
public var left(get, never):Float;
|
||||
public var right(get, never):Float;
|
||||
public var top(get, never):Float;
|
||||
public var bottom(get, never):Float;
|
||||
public var position(get, set):Point;
|
||||
|
||||
public function new(x:Float = 0, y:Float = 0, width:Float = 0, height:Float = 0) {
|
||||
this = [x, y, width, height];
|
||||
}
|
||||
|
||||
private inline function get_x() return this[0];
|
||||
|
||||
private inline function set_x(value) return this[0] = value;
|
||||
|
||||
private inline function get_y() return this[1];
|
||||
|
||||
private inline function set_y(value) return this[1] = value;
|
||||
|
||||
private inline function get_width() return this[2];
|
||||
|
||||
private inline function set_width(value) return this[2] = value;
|
||||
|
||||
private inline function get_height() return this[3];
|
||||
|
||||
private inline function set_height(value) return this[3] = value;
|
||||
|
||||
private function get_center():Point {
|
||||
return new Point(x + width / 2, y + height / 2);
|
||||
}
|
||||
|
||||
private function set_center(value:Point):Point {
|
||||
x = value.x - width / 2;
|
||||
y = value.y - height / 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
private function get_size():Point {
|
||||
return new Point(width, height);
|
||||
}
|
||||
|
||||
private function set_size(value:Point):Point {
|
||||
width = value.x;
|
||||
height = value.y;
|
||||
return value;
|
||||
}
|
||||
|
||||
public function contain(point:Point):Bool {
|
||||
return point.x >= left && point.y >= top && point.x <= right && point.y <= bottom;
|
||||
}
|
||||
|
||||
public function intersection(rect:Rectangle):Bool {
|
||||
return !(
|
||||
rect.left > right ||
|
||||
rect.right < left ||
|
||||
rect.top > bottom ||
|
||||
rect.bottom < top
|
||||
);
|
||||
}
|
||||
|
||||
public function clone():Rectangle {
|
||||
return new Rectangle(x, y, width, height);
|
||||
}
|
||||
|
||||
public function toString():String {
|
||||
return 'Rectangle{x=$x,y=$y,width=$width,height=$height}';
|
||||
}
|
||||
|
||||
private function get_left():Float {
|
||||
return x;
|
||||
}
|
||||
|
||||
private function get_right():Float {
|
||||
return x + width;
|
||||
}
|
||||
|
||||
private function get_top():Float {
|
||||
return y;
|
||||
}
|
||||
|
||||
private function get_bottom():Float {
|
||||
return y + height;
|
||||
}
|
||||
|
||||
private function get_position():Point {
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
private function set_position(position:Point):Point {
|
||||
x = position.x;
|
||||
y = position.y;
|
||||
return position;
|
||||
}
|
||||
}
|
||||
15
src/main/hw/log/AndroidLog.hx
Normal file
15
src/main/hw/log/AndroidLog.hx
Normal file
@@ -0,0 +1,15 @@
|
||||
package hw.log;
|
||||
|
||||
import cpp.VarArg;
|
||||
import haxe.extern.Rest;
|
||||
import cpp.ConstCharStar;
|
||||
|
||||
@:include("android/log.h")
|
||||
extern class AndroidLog {
|
||||
|
||||
@:native("__android_log_print")
|
||||
public static function print(prio:Int, tag:ConstCharStar, fmt:ConstCharStar, rest:Rest<VarArg>):Void;
|
||||
|
||||
@:native("__android_log_write")
|
||||
public static function write(prio:Int, tag:ConstCharStar, message:ConstCharStar):Void;
|
||||
}
|
||||
58
src/main/hw/log/BaseLogger.hx
Executable file
58
src/main/hw/log/BaseLogger.hx
Executable file
@@ -0,0 +1,58 @@
|
||||
package hw.log;
|
||||
|
||||
import hw.log.ILogger.LogLevel;
|
||||
import haxe.CallStack;
|
||||
|
||||
|
||||
class LoggerUtil {
|
||||
|
||||
public static function printPos(pos:haxe.PosInfos):String {
|
||||
return '${pos.fileName}:${pos.lineNumber}:';
|
||||
}
|
||||
|
||||
public static function printStackItem(item:StackItem):String {
|
||||
return switch item {
|
||||
case CFunction: 'CFunction';
|
||||
case Module(m): m;
|
||||
case FilePos(s, file, line): '${file}:${line}';
|
||||
case Method(classname, method): '${classname}::${method}}';
|
||||
case LocalFunction(v): 'LocalFunction(${v})';
|
||||
}
|
||||
}
|
||||
|
||||
public static function printError(error:Dynamic):String {
|
||||
return error == null ? '' : Std.string('${error}\n\t${CallStack.exceptionStack().map(printStackItem).join('\n\t')}');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class BaseLogger implements ILogger {
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function log(level:LogLevel, tag:String, message:String, ?error:Dynamic, ?p:haxe.PosInfos):Void {
|
||||
write(buildString(level, tag, message, error, p));
|
||||
}
|
||||
|
||||
private function buildString(level:LogLevel, tag:String, message:String, ?error:Dynamic, ?p:haxe.PosInfos):String {
|
||||
return '${Date.now()} | ${LoggerUtil.printPos(p)} [${level}] ${tag} - ${message}${LoggerUtil.printError(error)}';
|
||||
}
|
||||
|
||||
private function write(text:String):Void {}
|
||||
|
||||
public function d(tag:String, message:String, ?error:Dynamic, ?p:haxe.PosInfos):Void {
|
||||
log(LogLevel.DEBUG, tag, message, error, p);
|
||||
}
|
||||
|
||||
public function i(tag:String, message:String, ?error:Dynamic, ?p:haxe.PosInfos):Void {
|
||||
log(LogLevel.INFO, tag, message, error, p);
|
||||
}
|
||||
|
||||
public function w(tag:String, message:String, ?error:Dynamic, ?p:haxe.PosInfos):Void {
|
||||
log(LogLevel.WARNING, tag, message, error, p);
|
||||
}
|
||||
|
||||
public function e(tag:String, message:String, ?error:Dynamic, ?p:haxe.PosInfos):Void {
|
||||
log(LogLevel.ERROR, tag, message, error, p);
|
||||
}
|
||||
}
|
||||
13
src/main/hw/log/ILogger.hx
Executable file
13
src/main/hw/log/ILogger.hx
Executable file
@@ -0,0 +1,13 @@
|
||||
package hw.log;
|
||||
|
||||
enum LogLevel {
|
||||
DEBUG; INFO; WARNING; ERROR;
|
||||
}
|
||||
|
||||
interface ILogger {
|
||||
public function log(level:LogLevel, tag:String, message:String, ?error:Dynamic, ?p:haxe.PosInfos):Void;
|
||||
public function d(tag:String, message:String, ?error:Dynamic, ?p:haxe.PosInfos):Void;
|
||||
public function i(tag:String, message:String, ?error:Dynamic, ?p:haxe.PosInfos):Void;
|
||||
public function w(tag:String, message:String, ?error:Dynamic, ?p:haxe.PosInfos):Void;
|
||||
public function e(tag:String, message:String, ?error:Dynamic, ?p:haxe.PosInfos):Void;
|
||||
}
|
||||
29
src/main/hw/log/JSLogger.hx
Executable file
29
src/main/hw/log/JSLogger.hx
Executable file
@@ -0,0 +1,29 @@
|
||||
package hw.log;
|
||||
|
||||
import hw.log.ILogger.LogLevel;
|
||||
import flash.external.ExternalInterface;
|
||||
import hw.log.BaseLogger;
|
||||
|
||||
|
||||
class JSLogger extends BaseLogger {
|
||||
|
||||
private var available:Bool;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
available = ExternalInterface.available;
|
||||
}
|
||||
|
||||
override public function e(tag:String, message:String, ?error:Dynamic, ?p:haxe.PosInfos):Void {
|
||||
if (available) {
|
||||
var text:String = buildString(LogLevel.ERROR, tag, message, error);
|
||||
try {ExternalInterface.call("console.error", text);} catch (error:Dynamic) {available = false;}
|
||||
}
|
||||
}
|
||||
|
||||
override private function write(text:String):Void {
|
||||
if (available) {
|
||||
try {ExternalInterface.call("console.log", text);} catch (error:Dynamic) {available = false;}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/main/hw/log/SocketLogger.hx
Executable file
61
src/main/hw/log/SocketLogger.hx
Executable file
@@ -0,0 +1,61 @@
|
||||
package hw.log;
|
||||
|
||||
import hw.log.BaseLogger;
|
||||
|
||||
#if js
|
||||
class SocketLogger extends BaseLogger {}
|
||||
#else
|
||||
|
||||
#if flash
|
||||
import flash.net.Socket;
|
||||
import flash.events.IOErrorEvent;
|
||||
import flash.events.SecurityErrorEvent;
|
||||
#else
|
||||
import sys.net.Host;
|
||||
import sys.net.Socket;
|
||||
#end
|
||||
|
||||
|
||||
class SocketLogger extends BaseLogger {
|
||||
|
||||
private var socket:Socket;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
socket = new Socket();
|
||||
#if flash
|
||||
socket.addEventListener(IOErrorEvent.IO_ERROR, function(error):Void {
|
||||
onError(error);
|
||||
});
|
||||
socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function(error):Void {
|
||||
onError(error);
|
||||
});
|
||||
socket.connect(CompilationOption.get("debug.address"), Std.parseInt(CompilationOption.get("debug.port")));
|
||||
#else
|
||||
try {
|
||||
socket.connect(new Host(CompilationOption.get("debug.address")), Std.parseInt(CompilationOption.get("debug.port")));
|
||||
} catch (error: Dynamic) {
|
||||
onError(error);
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
private function onError(error:Dynamic):Void {
|
||||
//trace("SocketLogger", "", error);
|
||||
}
|
||||
|
||||
override private function write(text:String):Void {
|
||||
try {
|
||||
#if flash
|
||||
socket.writeUTFBytes(text);
|
||||
socket.writeUTFBytes('\n');
|
||||
socket.flush();
|
||||
#else
|
||||
socket.write(text);
|
||||
socket.write('\n');
|
||||
#end
|
||||
} catch (error:Dynamic) {}
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
18
src/main/hw/log/TextFieldLogger.hx
Normal file
18
src/main/hw/log/TextFieldLogger.hx
Normal file
@@ -0,0 +1,18 @@
|
||||
package hw.log;
|
||||
|
||||
import flash.text.TextField;
|
||||
|
||||
|
||||
class TextFieldLogger extends BaseLogger {
|
||||
|
||||
private var textField:TextField;
|
||||
|
||||
public function new(textField:TextField) {
|
||||
super();
|
||||
this.textField = textField;
|
||||
}
|
||||
|
||||
override private function write(text:String):Void {
|
||||
this.textField.text += ("\n" + text);
|
||||
}
|
||||
}
|
||||
59
src/main/hw/log/TraceLogger.hx
Executable file
59
src/main/hw/log/TraceLogger.hx
Executable file
@@ -0,0 +1,59 @@
|
||||
package hw.log;
|
||||
|
||||
import haxe.PosInfos;
|
||||
#if cpp
|
||||
import cpp.Stdio;
|
||||
import cpp.ConstCharStar;
|
||||
#end
|
||||
|
||||
|
||||
class TraceLogger extends BaseLogger {
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static function print(v: Dynamic, ?infos : PosInfos) : Void {
|
||||
#if flash
|
||||
#if (fdb || native_trace)
|
||||
var str = flash.Boot.__string_rec(v, "");
|
||||
untyped __global__["trace"](str);
|
||||
#else
|
||||
untyped flash.Boot.__trace(v, infos);
|
||||
#end
|
||||
#elseif neko
|
||||
untyped {
|
||||
$print(v);
|
||||
$print("\n");
|
||||
}
|
||||
#elseif js
|
||||
if (js.Syntax.typeof(untyped console) != "undefined" && (untyped console).log != null)
|
||||
(untyped console).log(v);
|
||||
//untyped js.Boot.__trace(v, infos);
|
||||
#elseif android
|
||||
hw.log.AndroidLog.write(3, "", ConstCharStar.fromString(Std.string(v)));
|
||||
#elseif (php && php7)
|
||||
php.Boot.trace(v);
|
||||
#elseif php
|
||||
untyped __call__('_hx_trace', v);
|
||||
#elseif cpp
|
||||
Stdio.printf(ConstCharStar.fromString(Std.string(v)));
|
||||
//untyped __trace(v, null);
|
||||
#elseif cs
|
||||
cs.system.Console.WriteLine(v);
|
||||
#elseif java
|
||||
untyped __java__("java.lang.System.out.println(v)");
|
||||
#elseif lua
|
||||
untyped __define_feature__("use._hx_print",_hx_print(v));
|
||||
#elseif (python)
|
||||
python.Lib.println(v);
|
||||
#elseif hl
|
||||
var str = Std.string(v);
|
||||
Sys.println(str);
|
||||
#end
|
||||
}
|
||||
|
||||
override private function write(text:String):Void {
|
||||
print(text);
|
||||
}
|
||||
}
|
||||
20
src/main/hw/macro/ClassProvideMacro.hx
Normal file
20
src/main/hw/macro/ClassProvideMacro.hx
Normal file
@@ -0,0 +1,20 @@
|
||||
package hw.macro;
|
||||
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
|
||||
class ClassProvideMacro extends ClassTypeMacro {
|
||||
|
||||
public static var bundle(default, null):Array<{key: ClassType, value: ClassType}> = [];
|
||||
|
||||
public function new() {
|
||||
super(":provide");
|
||||
}
|
||||
|
||||
override public function apply(classType:ClassType, fields:Array<Field>):Array<Field> {
|
||||
var meta = MacroUtil.getClassMeta(classType, metaName);
|
||||
var valueType = meta.params.length == 0 ? classType : MacroUtil.getExprClassType(meta.params[0]);
|
||||
bundle.push({key: classType, value: valueType});
|
||||
return super.apply(classType, fields);
|
||||
}
|
||||
}
|
||||
21
src/main/hw/macro/ClassTypeMacro.hx
Normal file
21
src/main/hw/macro/ClassTypeMacro.hx
Normal file
@@ -0,0 +1,21 @@
|
||||
package hw.macro;
|
||||
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
|
||||
class ClassTypeMacro {
|
||||
|
||||
public var metaName(default, null):String;
|
||||
|
||||
public function new(metaName:String) {
|
||||
this.metaName = metaName;
|
||||
}
|
||||
|
||||
public function has(classType:ClassType):Bool {
|
||||
return classType.meta.has(metaName);
|
||||
}
|
||||
|
||||
public function apply(classType:ClassType, fields:Array<Field>):Array<Field> {
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
85
src/main/hw/macro/DispatcherMacro.hx
Normal file
85
src/main/hw/macro/DispatcherMacro.hx
Normal file
@@ -0,0 +1,85 @@
|
||||
package hw.macro;
|
||||
|
||||
import haxe.macro.TypeTools;
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.ExprTools;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
|
||||
class DispatcherMacro extends ClassTypeMacro {
|
||||
|
||||
public function new() {
|
||||
super(":dispatcher");
|
||||
}
|
||||
|
||||
private static inline function signalName(fieldName:String):String {
|
||||
if (fieldName.substr(0, 2) == "on") {
|
||||
return fieldName.substr(2, 1).toLowerCase() + fieldName.substr(3) + "Signal";
|
||||
} else {
|
||||
return fieldName + "Signal";
|
||||
}
|
||||
}
|
||||
|
||||
override public function apply(classType:ClassType, fields:Array<Field>):Array<Field> {
|
||||
var result:Array<Field> = fields.slice(0);
|
||||
var typeName = ExprTools.toString(classType.meta.extract(metaName)[0].params[0]);
|
||||
var type:ClassType = MacroUtil.getExprClassType(classType.meta.extract(metaName)[0].params[0]);
|
||||
var fields = type.fields.get();
|
||||
for (field in fields) {
|
||||
var argsTypes:Array<Type> = switch field.type {
|
||||
case TFun(args, _): args.map(function(arg) return arg.t);
|
||||
case _ : [];
|
||||
}
|
||||
var signal = 'Signal${argsTypes.length}';
|
||||
var type = TPath({
|
||||
pack: ['hw', 'signal'],
|
||||
name: 'Signal',
|
||||
sub: signal,
|
||||
params: argsTypes.map(function(t) return TPType(TypeTools.toComplexType(t))),
|
||||
});
|
||||
result.push({
|
||||
name: signalName(field.name),
|
||||
access: [APublic],
|
||||
pos: Context.currentPos(),
|
||||
kind: FProp('default', 'null', type, Context.parse('new hw.signal.Signal.${signal}()', Context.currentPos())),
|
||||
});
|
||||
}
|
||||
result.push({
|
||||
name: 'connect',
|
||||
access: [APublic],
|
||||
pos: Context.currentPos(),
|
||||
kind: FFun({
|
||||
args: [{
|
||||
name: 'listener',
|
||||
type: TPath({
|
||||
pack: type.pack,
|
||||
name: type.name,
|
||||
}),
|
||||
}],
|
||||
ret: null,
|
||||
expr: macro $b{fields.map(function(field) {
|
||||
return Context.parse('${signalName(field.name)}.connect(listener.${field.name})', Context.currentPos());
|
||||
})},
|
||||
}),
|
||||
});
|
||||
result.push({
|
||||
name: 'disconnect',
|
||||
access: [APublic],
|
||||
pos: Context.currentPos(),
|
||||
kind: FFun({
|
||||
args: [{
|
||||
name: 'listener',
|
||||
type: TPath({
|
||||
pack: type.pack,
|
||||
name: type.name,
|
||||
}),
|
||||
}],
|
||||
ret: null,
|
||||
expr: macro $b{fields.map(function(field) {
|
||||
return Context.parse('${signalName(field.name)}.disconnect(listener.${field.name})', Context.currentPos());
|
||||
})},
|
||||
}),
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
21
src/main/hw/macro/FieldMacro.hx
Normal file
21
src/main/hw/macro/FieldMacro.hx
Normal file
@@ -0,0 +1,21 @@
|
||||
package hw.macro;
|
||||
|
||||
import haxe.macro.Expr;
|
||||
|
||||
using hw.macro.MacroUtil;
|
||||
|
||||
class FieldMacro {
|
||||
public var metaName(default, null):String;
|
||||
|
||||
public function new(metaName:String) {
|
||||
this.metaName = metaName;
|
||||
}
|
||||
|
||||
public function has(field:Field):Bool {
|
||||
return field.getFieldMeta(metaName) != null;
|
||||
}
|
||||
|
||||
public function apply(field:Field):Array<Field> {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
46
src/main/hw/macro/FileUtil.hx
Normal file
46
src/main/hw/macro/FileUtil.hx
Normal file
@@ -0,0 +1,46 @@
|
||||
package hw.macro;
|
||||
|
||||
import yaml.YamlException;
|
||||
import yaml.Parser;
|
||||
import yaml.Yaml;
|
||||
import haxe.macro.Context;
|
||||
|
||||
class FileUtil {
|
||||
|
||||
public static function loadJsonFile(path:String):Dynamic {
|
||||
Context.registerModuleDependency(Context.getLocalModule(), path);
|
||||
var content = sys.io.File.getContent(path);
|
||||
var json = null;
|
||||
try {
|
||||
json = PositionJsonParser.parse(content, path);
|
||||
} catch (error:Dynamic) {
|
||||
Context.error(error, Context.makePosition({min:0, max:0, file:path}));
|
||||
}
|
||||
Context.parse(content, Context.makePosition({min:0, max:0, file:path}));
|
||||
return json;
|
||||
}
|
||||
|
||||
public static function loadYamlFile(path:String):Dynamic {
|
||||
path = sys.FileSystem.absolutePath(path);
|
||||
Context.registerModuleDependency(Context.getLocalModule(), path);
|
||||
var content = sys.io.File.getContent(path);
|
||||
var result = null;
|
||||
try {
|
||||
result = Yaml.parse(content, Parser.options().useObjects());
|
||||
PositionYamlParser.parse(path, content, result);
|
||||
} catch (error:YamlException) {
|
||||
Context.error(Std.string(error), Context.makePosition({min:0, max:0, file:path}));
|
||||
}
|
||||
Context.parse('/*$content*/""', Context.makePosition({min:0, max:0, file:path}));
|
||||
return result;
|
||||
}
|
||||
|
||||
public static function loadFile(path:String):Dynamic {
|
||||
var ext = path.split('.').pop();
|
||||
return switch(ext) {
|
||||
case 'json': loadJsonFile(path);
|
||||
case 'yml' | 'yaml': loadYamlFile(path);
|
||||
case x: throw 'Unsupported file format: "${x}"';
|
||||
}
|
||||
}
|
||||
}
|
||||
84
src/main/hw/macro/MacroUtil.hx
Normal file
84
src/main/hw/macro/MacroUtil.hx
Normal file
@@ -0,0 +1,84 @@
|
||||
package hw.macro;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.ExprTools;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
|
||||
class MacroUtil {
|
||||
|
||||
public static function getMetaParams(meta:MetadataEntry):Array<String> {
|
||||
return meta.params.map(function(param:Expr) return switch param.expr {
|
||||
case EConst(CString(value)): value;
|
||||
case _: null;
|
||||
});
|
||||
}
|
||||
|
||||
public inline static function DynamicType():ComplexType {
|
||||
return ComplexType.TPath({name:'Dynamic', pack:[], params:[]});
|
||||
}
|
||||
|
||||
public static function getClassMeta(classType:ClassType, metaName:String):Null<MetadataEntry> {
|
||||
for (meta in classType.meta.get()) {
|
||||
if (meta.name == metaName) {
|
||||
return meta;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getFieldMeta(field:Field, metaName:String):Null<MetadataEntry> {
|
||||
if (field.meta != null) {
|
||||
for (meta in field.meta) {
|
||||
if (meta.name == metaName) {
|
||||
return meta;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getExprString(expr:ExprDef) {
|
||||
return switch expr {
|
||||
case EConst(CString(value)): value;
|
||||
case _: null;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getComplexType(field:Field):Null<ComplexType> {
|
||||
return switch field.kind {
|
||||
case FProp(get, set, t, e): t;
|
||||
case FVar(t, e): t;
|
||||
case _: null;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getField(fields:Array<Field>, name:String):Null<Field> {
|
||||
return Lambda.find(fields, function(field:Field):Bool return field.name == name);
|
||||
}
|
||||
|
||||
public static function getExprClassType(expr:Expr):Null<ClassType> {
|
||||
var typeName = ExprTools.toString(expr);
|
||||
return switch Context.getType(typeName) {
|
||||
case TInst(t, _): t.get();
|
||||
case _: null;
|
||||
}
|
||||
}
|
||||
|
||||
public static function upgradeField(field:Field, expr:Expr, position:Int = 0):Field {
|
||||
switch field.kind {
|
||||
case FFun(f):
|
||||
var fieldExpr = f.expr;
|
||||
switch fieldExpr.expr {
|
||||
case EBlock(exprs):
|
||||
exprs.insert(position, expr);
|
||||
fieldExpr = macro $b{exprs};
|
||||
case _:
|
||||
fieldExpr = macro $b{[fieldExpr, expr]}
|
||||
}
|
||||
f.expr = fieldExpr;
|
||||
case _:
|
||||
}
|
||||
return field;
|
||||
}
|
||||
}
|
||||
237
src/main/hw/macro/PositionJsonParser.hx
Executable file
237
src/main/hw/macro/PositionJsonParser.hx
Executable file
@@ -0,0 +1,237 @@
|
||||
package hw.macro;
|
||||
|
||||
typedef JsonKeyPosition = {
|
||||
var min:Int;
|
||||
var max:Int;
|
||||
var file:String;
|
||||
}
|
||||
|
||||
class PositionJsonParser {
|
||||
|
||||
static public inline function parse(str : String, file : String) : Dynamic {
|
||||
return new PositionJsonParser(str, file).parseRec();
|
||||
}
|
||||
|
||||
var str : String;
|
||||
var pos : Int;
|
||||
var file : String;
|
||||
|
||||
function new( str : String, file:String ) {
|
||||
this.str = str;
|
||||
this.pos = 0;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
function getKeyPosition():JsonKeyPosition {
|
||||
return {
|
||||
min: pos,
|
||||
max: pos,
|
||||
file: file
|
||||
}
|
||||
}
|
||||
|
||||
function parseRec() : Dynamic {
|
||||
while( true ) {
|
||||
var c = nextChar();
|
||||
switch( c ) {
|
||||
case ' '.code, '\r'.code, '\n'.code, '\t'.code:
|
||||
// loop
|
||||
case '{'.code:
|
||||
var obj = {}, field = null, comma : Null<Bool> = null;
|
||||
var position = null;
|
||||
while( true ) {
|
||||
var c = nextChar();
|
||||
switch( c ) {
|
||||
case ' '.code, '\r'.code, '\n'.code, '\t'.code:
|
||||
// loop
|
||||
case '}'.code:
|
||||
if( field != null || comma == false )
|
||||
invalidChar();
|
||||
return obj;
|
||||
case ':'.code:
|
||||
if( field == null )
|
||||
invalidChar();
|
||||
Reflect.setField(obj,field,parseRec());
|
||||
Reflect.setField(obj,"$" + field,position);
|
||||
field = null;
|
||||
comma = true;
|
||||
case ','.code:
|
||||
if( comma ) comma = false else invalidChar();
|
||||
case '"'.code:
|
||||
if( comma ) invalidChar();
|
||||
position = getKeyPosition();
|
||||
field = parseString();
|
||||
default:
|
||||
invalidChar();
|
||||
}
|
||||
}
|
||||
case '['.code:
|
||||
var arr = [], comma : Null<Bool> = null;
|
||||
while( true ) {
|
||||
var c = nextChar();
|
||||
switch( c ) {
|
||||
case ' '.code, '\r'.code, '\n'.code, '\t'.code:
|
||||
// loop
|
||||
case ']'.code:
|
||||
if( comma == false ) invalidChar();
|
||||
return arr;
|
||||
case ','.code:
|
||||
if( comma ) comma = false else invalidChar();
|
||||
default:
|
||||
if( comma ) invalidChar();
|
||||
pos--;
|
||||
arr.push(parseRec());
|
||||
comma = true;
|
||||
}
|
||||
}
|
||||
case 't'.code:
|
||||
var save = pos;
|
||||
if( nextChar() != 'r'.code || nextChar() != 'u'.code || nextChar() != 'e'.code ) {
|
||||
pos = save;
|
||||
invalidChar();
|
||||
}
|
||||
return true;
|
||||
case 'f'.code:
|
||||
var save = pos;
|
||||
if( nextChar() != 'a'.code || nextChar() != 'l'.code || nextChar() != 's'.code || nextChar() != 'e'.code ) {
|
||||
pos = save;
|
||||
invalidChar();
|
||||
}
|
||||
return false;
|
||||
case 'n'.code:
|
||||
var save = pos;
|
||||
if( nextChar() != 'u'.code || nextChar() != 'l'.code || nextChar() != 'l'.code ) {
|
||||
pos = save;
|
||||
invalidChar();
|
||||
}
|
||||
return null;
|
||||
case '"'.code:
|
||||
return parseString();
|
||||
case '0'.code, '1'.code,'2'.code,'3'.code,'4'.code,'5'.code,'6'.code,'7'.code,'8'.code,'9'.code,'-'.code:
|
||||
return parseNumber(c);
|
||||
default:
|
||||
invalidChar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseString() {
|
||||
var start = pos;
|
||||
var buf = null;
|
||||
while( true ) {
|
||||
var c = nextChar();
|
||||
if( c == '"'.code )
|
||||
break;
|
||||
if( c == '\\'.code ) {
|
||||
if (buf == null) {
|
||||
buf = new StringBuf();
|
||||
}
|
||||
buf.addSub(str,start, pos - start - 1);
|
||||
c = nextChar();
|
||||
switch( c ) {
|
||||
case "r".code: buf.addChar("\r".code);
|
||||
case "n".code: buf.addChar("\n".code);
|
||||
case "t".code: buf.addChar("\t".code);
|
||||
case "b".code: buf.addChar(8);
|
||||
case "f".code: buf.addChar(12);
|
||||
case "/".code, '\\'.code, '"'.code: buf.addChar(c);
|
||||
case 'u'.code:
|
||||
var uc = Std.parseInt("0x" + str.substr(pos, 4));
|
||||
pos += 4;
|
||||
#if (neko || php || cpp)
|
||||
if( uc <= 0x7F )
|
||||
buf.addChar(uc);
|
||||
else if( uc <= 0x7FF ) {
|
||||
buf.addChar(0xC0 | (uc >> 6));
|
||||
buf.addChar(0x80 | (uc & 63));
|
||||
} else if( uc <= 0xFFFF ) {
|
||||
buf.addChar(0xE0 | (uc >> 12));
|
||||
buf.addChar(0x80 | ((uc >> 6) & 63));
|
||||
buf.addChar(0x80 | (uc & 63));
|
||||
} else {
|
||||
buf.addChar(0xF0 | (uc >> 18));
|
||||
buf.addChar(0x80 | ((uc >> 12) & 63));
|
||||
buf.addChar(0x80 | ((uc >> 6) & 63));
|
||||
buf.addChar(0x80 | (uc & 63));
|
||||
}
|
||||
#else
|
||||
buf.addChar(uc);
|
||||
#end
|
||||
default:
|
||||
throw "Invalid escape sequence \\" + String.fromCharCode(c) + " at position " + (pos - 1);
|
||||
}
|
||||
start = pos;
|
||||
}
|
||||
#if (neko || php || cpp)
|
||||
// ensure utf8 chars are not cut
|
||||
else if( c >= 0x80 ) {
|
||||
pos++;
|
||||
if( c >= 0xFC ) pos += 4;
|
||||
else if( c >= 0xF8 ) pos += 3;
|
||||
else if( c >= 0xF0 ) pos += 2;
|
||||
else if( c >= 0xE0 ) pos++;
|
||||
}
|
||||
#end
|
||||
else if( StringTools.isEof(c) )
|
||||
throw "Unclosed string";
|
||||
}
|
||||
if (buf == null) {
|
||||
return str.substr(start, pos - start - 1);
|
||||
}
|
||||
else {
|
||||
buf.addSub(str,start, pos - start - 1);
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
inline function parseNumber( c : Int ) : Dynamic {
|
||||
var start = pos - 1;
|
||||
var minus = c == '-'.code, digit = !minus, zero = c == '0'.code;
|
||||
var point = false, e = false, pm = false, end = false;
|
||||
while( true ) {
|
||||
c = nextChar();
|
||||
switch( c ) {
|
||||
case '0'.code :
|
||||
if (zero && !point) invalidNumber(start);
|
||||
if (minus) {
|
||||
minus = false; zero = true;
|
||||
}
|
||||
digit = true;
|
||||
case '1'.code,'2'.code,'3'.code,'4'.code,'5'.code,'6'.code,'7'.code,'8'.code,'9'.code :
|
||||
if (zero && !point) invalidNumber(start);
|
||||
if (minus) minus = false;
|
||||
digit = true; zero = false;
|
||||
case '.'.code :
|
||||
if (minus || point) invalidNumber(start);
|
||||
digit = false; point = true;
|
||||
case 'e'.code, 'E'.code :
|
||||
if (minus || zero || e) invalidNumber(start);
|
||||
digit = false; e = true;
|
||||
case '+'.code, '-'.code :
|
||||
if (!e || pm) invalidNumber(start);
|
||||
digit = false; pm = true;
|
||||
default :
|
||||
if (!digit) invalidNumber(start);
|
||||
pos--;
|
||||
end = true;
|
||||
}
|
||||
if (end) break;
|
||||
}
|
||||
var f = Std.parseFloat(str.substr(start, pos - start));
|
||||
var i = Std.int(f);
|
||||
return if( i == f ) i else f;
|
||||
}
|
||||
|
||||
inline function nextChar() {
|
||||
return StringTools.fastCodeAt(str,pos++);
|
||||
}
|
||||
|
||||
function invalidChar() {
|
||||
pos--; // rewind
|
||||
throw "Invalid char "+StringTools.fastCodeAt(str,pos)+" at position "+pos;
|
||||
}
|
||||
|
||||
function invalidNumber( start : Int ) {
|
||||
throw "Invalid number at position "+start+": " + str.substr(start, pos - start);
|
||||
}
|
||||
}
|
||||
46
src/main/hw/macro/PositionYamlParser.hx
Normal file
46
src/main/hw/macro/PositionYamlParser.hx
Normal file
@@ -0,0 +1,46 @@
|
||||
package hw.macro;
|
||||
|
||||
import haxe.DynamicAccess;
|
||||
|
||||
class PositionYamlParser {
|
||||
|
||||
private var filename:String;
|
||||
private var content:String;
|
||||
private var cache:Map<String, Int>;
|
||||
private var result:Dynamic;
|
||||
|
||||
private function new(filename:String, content:String, result:Dynamic) {
|
||||
this.filename = filename;
|
||||
this.content = content;
|
||||
this.result = result;
|
||||
this.cache = new Map();
|
||||
}
|
||||
|
||||
private function parseAll():Void {
|
||||
//parseBlock(result);
|
||||
}
|
||||
|
||||
private function parseBlock(result:DynamicAccess<Dynamic>):Void {
|
||||
for (field in result.keys()) {
|
||||
var offset = content.indexOf(field, cache.get(field));
|
||||
cache.set(field, offset);
|
||||
result.set("$" + field, {
|
||||
min:offset,
|
||||
max:offset + field.length,
|
||||
file:filename,
|
||||
});
|
||||
var value = result.get(field);
|
||||
if (Std.is(value, Array)) {
|
||||
for (item in cast(value, Array<Dynamic>)) {
|
||||
parseBlock(item);
|
||||
}
|
||||
} else {
|
||||
parseBlock(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static inline function parse(filename:String, content:String, result:Dynamic):Void {
|
||||
new PositionYamlParser(filename, content, result).parseAll();
|
||||
}
|
||||
}
|
||||
57
src/main/hw/macro/ProvideMacro.hx
Normal file
57
src/main/hw/macro/ProvideMacro.hx
Normal file
@@ -0,0 +1,57 @@
|
||||
package hw.macro;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
|
||||
using hw.macro.MacroUtil;
|
||||
|
||||
class ProvideMacro extends FieldMacro {
|
||||
|
||||
public function new() {
|
||||
super(":provide");
|
||||
}
|
||||
|
||||
override public function apply(field:Field):Array<Field> {
|
||||
var meta:MetadataEntry = field.getFieldMeta(metaName);
|
||||
var result:Array<Field> = [];
|
||||
var type:ComplexType = switch field.kind {
|
||||
case FVar(t): t;
|
||||
default: null;
|
||||
}
|
||||
var name:String = switch type {
|
||||
case TPath(p): p.name;
|
||||
default: null;
|
||||
}
|
||||
var provideType:String = meta.params.length == 0 ? null : MacroUtil.getExprString(meta.params[0].expr);
|
||||
var isStatic = Lambda.exists(field.access, function(a: Access) return a == AStatic);
|
||||
field.kind = FProp('get', 'set', type);
|
||||
var access = [APrivate, AInline];
|
||||
if (isStatic) access.push(AStatic);
|
||||
var args = [name];
|
||||
if (provideType != null) args.push('"${provideType}"');
|
||||
result.push({
|
||||
name: 'get_${field.name}',
|
||||
access: access,
|
||||
pos: field.pos,
|
||||
kind: FFun({
|
||||
args: [],
|
||||
expr: Context.parse('return hw.provider.Provider.instance.get(${args.join(',')})', field.pos),
|
||||
params: [],
|
||||
ret: type,
|
||||
})
|
||||
});
|
||||
args.insert(1, "value");
|
||||
result.push({
|
||||
name: 'set_${field.name}',
|
||||
access: access,
|
||||
pos: field.pos,
|
||||
kind: FFun({
|
||||
args: [{name: 'value', type: type}],
|
||||
expr: Context.parse('{hw.provider.Provider.instance.set(${args.join(',')}); return value;}', field.pos),
|
||||
params: [],
|
||||
ret: type,
|
||||
})
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
10
src/main/hw/macro/ResourceMacro.hx
Normal file
10
src/main/hw/macro/ResourceMacro.hx
Normal file
@@ -0,0 +1,10 @@
|
||||
package hw.macro;
|
||||
|
||||
using hw.macro.MacroUtil;
|
||||
|
||||
class ResourceMacro extends FieldMacro {
|
||||
|
||||
public function new() {
|
||||
super(":resource");
|
||||
}
|
||||
}
|
||||
43
src/main/hw/macro/SingletonMacro.hx
Normal file
43
src/main/hw/macro/SingletonMacro.hx
Normal file
@@ -0,0 +1,43 @@
|
||||
package hw.macro;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
|
||||
class SingletonMacro extends ClassTypeMacro {
|
||||
|
||||
public function new() {
|
||||
super(":singleton");
|
||||
}
|
||||
|
||||
|
||||
override public function apply(classType:ClassType, fields:Array<Field>):Array<Field> {
|
||||
var result:Array<Field> = fields.slice(0);
|
||||
var classTypePath:TypePath = {
|
||||
pack: classType.pack,
|
||||
name: classType.name,
|
||||
};
|
||||
var type:ComplexType = TPath(classTypePath);
|
||||
result.push({
|
||||
name: 'instance',
|
||||
access: [APublic, AStatic],
|
||||
pos: Context.currentPos(),
|
||||
kind: FProp('get', 'null', type),
|
||||
});
|
||||
var typeStr = classType.name;
|
||||
result.push({
|
||||
name: 'get_instance',
|
||||
access: [APrivate, AStatic],
|
||||
pos: Context.currentPos(),
|
||||
kind: FFun({
|
||||
args: [],
|
||||
ret: type,
|
||||
expr: macro $b{[
|
||||
macro if (instance == null) instance = new $classTypePath(),
|
||||
macro return instance
|
||||
]},
|
||||
}),
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
218
src/main/hw/macro/StyleMacro.hx
Normal file
218
src/main/hw/macro/StyleMacro.hx
Normal file
@@ -0,0 +1,218 @@
|
||||
package hw.macro;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
|
||||
using haxe.macro.ComplexTypeTools;
|
||||
using hw.macro.MacroUtil;
|
||||
using haxe.macro.MacroStringTools;
|
||||
|
||||
typedef TProperty<T> = {
|
||||
var field: Field;
|
||||
var defaultValue: T;
|
||||
}
|
||||
|
||||
class StyleMacro extends ClassTypeMacro {
|
||||
|
||||
public function new() {
|
||||
super(":style");
|
||||
}
|
||||
|
||||
private static function processPropertyField(field:Field):Array<Field> {
|
||||
var result:Array<Field> = [];
|
||||
var type:ComplexType = field.getComplexType();
|
||||
var meta = field.getFieldMeta(":style");
|
||||
var defaultValue:Expr = meta.params.length > 0 ? meta.params[0] : null;
|
||||
field.kind = FProp("get", "set", type);
|
||||
var defaultName = 'default_${field.name}';
|
||||
result.push({
|
||||
name: defaultName,
|
||||
access: [APrivate],
|
||||
pos: field.pos,
|
||||
kind: FVar(type, defaultValue),
|
||||
});
|
||||
var currentName = 'current_${field.name}';
|
||||
result.push({
|
||||
name: currentName,
|
||||
access: [APrivate],
|
||||
pos: field.pos,
|
||||
kind: FVar(type),
|
||||
});
|
||||
var themeName = 'theme_${field.name}';
|
||||
result.push({
|
||||
name: themeName,
|
||||
access: [APrivate],
|
||||
pos: field.pos,
|
||||
kind: FVar(type),
|
||||
});
|
||||
var getter = [];
|
||||
getter.push(macro var result = $i{currentName});
|
||||
getter.push(macro if (result == null) result = $i{themeName});
|
||||
getter.push(macro if (result == null) result = $i{defaultName});
|
||||
getter.push(macro return result);
|
||||
result.push({
|
||||
name: 'get_${field.name}',
|
||||
access: [APrivate],
|
||||
pos: field.pos,
|
||||
kind: FFun({
|
||||
args: [],
|
||||
expr: macro $b{getter},
|
||||
params: [],
|
||||
ret: type,
|
||||
})
|
||||
});
|
||||
result.push({
|
||||
name: 'set_${field.name}',
|
||||
access: [APrivate],
|
||||
pos: field.pos,
|
||||
kind: FFun({
|
||||
args: [{name: "value", type: type}],
|
||||
expr: macro $b{[
|
||||
macro $i{currentName} = value,
|
||||
macro return $i{field.name}
|
||||
]},
|
||||
params: [],
|
||||
ret: type,
|
||||
})
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private static function processStyledField(field:Field):Array<Field> {
|
||||
var type:ComplexType = field.getComplexType();
|
||||
var result:Array<Field> = [];
|
||||
field.kind = FProp("default", "set", type);
|
||||
var expr:Array<Expr> = [];
|
||||
expr.push(macro if (value == null) return null);
|
||||
expr.push(macro value.styleKey = (styleKey!=null?(styleKey+"."):"")+$v{field.name});
|
||||
expr.push(macro value.style = style);
|
||||
expr.push(macro $i{field.name} = value);
|
||||
expr.push(macro return $i{field.name});
|
||||
result.push({
|
||||
name: 'set_${field.name}',
|
||||
access: [APrivate],
|
||||
pos: field.pos,
|
||||
kind: FFun({
|
||||
args: [{name: "value", type: type}],
|
||||
expr: macro $b{expr},
|
||||
params: [],
|
||||
ret: type,
|
||||
})
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private static function buildStyleKeyField(styleds:Array<Field>, overrideField:Bool):Array<Field> {
|
||||
var result:Array<Field> = [];
|
||||
var type:ComplexType = "String".toComplex();
|
||||
if (!overrideField) {
|
||||
result.push({
|
||||
name: "styleKey",
|
||||
access: [APublic],
|
||||
pos: Context.currentPos(),
|
||||
kind: FProp("default", "set", type),
|
||||
});
|
||||
}
|
||||
var expr:Array<Expr> = [];
|
||||
if (overrideField) {
|
||||
expr.push(macro super.styleKey = value);
|
||||
}
|
||||
expr.push(macro styleKey = value);
|
||||
for (field in styleds) {
|
||||
expr.push(macro $i{field.name}.styleKey = styleKey+"."+$v{field.name});
|
||||
}
|
||||
expr.push(macro return styleKey);
|
||||
var access:Array<Access> = [APrivate];
|
||||
if (overrideField) {
|
||||
access.push(AOverride);
|
||||
}
|
||||
result.push({
|
||||
name: 'set_styleKey',
|
||||
access: access,
|
||||
pos: Context.currentPos(),
|
||||
kind: FFun({
|
||||
args: [{name: "value", type: type}],
|
||||
expr: macro $b{expr},
|
||||
params: [],
|
||||
ret: type,
|
||||
})
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private static function buildStyleField(properties:Array<Field>, styleds:Array<Field>, hasOnStyle:Bool, overrideField:Bool):Array<Field> {
|
||||
var result:Array<Field> = [];
|
||||
var type:ComplexType = "hw.view.theme.StyleId".toComplex();
|
||||
if (!overrideField) {
|
||||
result.push({
|
||||
name: "style",
|
||||
access: [APublic],
|
||||
pos: Context.currentPos(),
|
||||
kind: FProp("default", "set", type),
|
||||
});
|
||||
}
|
||||
var expr:Array<Expr> = [];
|
||||
if (overrideField) {
|
||||
expr.push(macro super.style = value);
|
||||
}
|
||||
expr.push(macro style = value);
|
||||
for (field in styleds) {
|
||||
expr.push(macro $i{field.name}.style = style);
|
||||
}
|
||||
for (field in properties) {
|
||||
var propertyName = 'theme_${field.name}';
|
||||
expr.push(macro $i{propertyName} = hw.provider.Provider.instance.get(hw.view.theme.ITheme).resolve((styleKey!=null?(styleKey+"."):"")+$v{field.name}, style));
|
||||
}
|
||||
for (field in styleds) {
|
||||
var propertyName = '${field.name}';
|
||||
expr.push(macro $i{propertyName} = hw.provider.Provider.instance.get(hw.view.theme.ITheme).resolve((styleKey!=null?(styleKey+"."):"")+$v{field.name}, style));
|
||||
}
|
||||
if (hasOnStyle) {
|
||||
expr.push(macro onStyle());
|
||||
}
|
||||
expr.push(macro return style);
|
||||
var access:Array<Access> = [APrivate];
|
||||
if (overrideField) {
|
||||
access.push(AOverride);
|
||||
}
|
||||
result.push({
|
||||
name: 'set_style',
|
||||
access: access,
|
||||
pos: Context.currentPos(),
|
||||
kind: FFun({
|
||||
args: [{name: "value", type: type}],
|
||||
expr: macro $b{expr},
|
||||
params: [],
|
||||
ret: type,
|
||||
})
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
override public function apply(classType:ClassType, fields:Array<Field>):Array<Field> {
|
||||
var overrideStyle = classType.getClassMeta(metaName).params.length > 0;
|
||||
var result:Array<Field> = fields.slice(0);
|
||||
var styleds:Array<Field> = [];
|
||||
var properties:Array<Field> = [];
|
||||
var newFields:Array<Field> = [];
|
||||
var hasOnStyle:Bool = fields.getField("onStyle") != null;
|
||||
for (field in fields) if (field.meta != null) {
|
||||
var meta = field.getFieldMeta(metaName);
|
||||
if (meta != null) {
|
||||
if (meta.params.length > 0) {
|
||||
newFields = newFields.concat(processPropertyField(field));
|
||||
properties.push(field);
|
||||
} else {
|
||||
newFields = newFields.concat(processStyledField(field));
|
||||
styleds.push(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
result = result
|
||||
.concat(newFields)
|
||||
.concat(buildStyleKeyField(styleds, overrideStyle))
|
||||
.concat(buildStyleField(properties, styleds, hasOnStyle, overrideStyle));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
214
src/main/hw/macro/TemplateMacro.hx
Normal file
214
src/main/hw/macro/TemplateMacro.hx
Normal file
@@ -0,0 +1,214 @@
|
||||
package hw.macro;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
import hw.macro.PositionJsonParser;
|
||||
import Lambda;
|
||||
|
||||
using hw.macro.MacroUtil;
|
||||
|
||||
class TemplateMacro extends ClassTypeMacro {
|
||||
|
||||
private var bindings:Map<String, String>;
|
||||
private var templateFile:String;
|
||||
private var template:Dynamic;
|
||||
private var i:Int;
|
||||
|
||||
public function new() {
|
||||
super(":template");
|
||||
}
|
||||
|
||||
private static function getSpecField(object:Dynamic, field:String):Dynamic {
|
||||
if (Reflect.hasField(object, "@" + field)) {
|
||||
return Reflect.field(object, "@" + field);
|
||||
} else if (Reflect.hasField(object, "$" + field)) {
|
||||
return Reflect.field(object, "$" + field);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function getPosition(?position:JsonKeyPosition):Position {
|
||||
var min = position == null ? 1 : position.min; // :-(
|
||||
var max = position == null ? 1 : position.max;
|
||||
var file = position == null || position.file == null ? templateFile : position.file;
|
||||
return Context.makePosition({min:min, max:max, file:file});
|
||||
}
|
||||
|
||||
private function specialValue(name:String, key:String, a:Array<String>, position:JsonKeyPosition, exprs:Array<Expr>):Dynamic {
|
||||
return switch (a[0]) {
|
||||
case "asset" | "a":
|
||||
switch a[1] {
|
||||
case "image":
|
||||
'openfl.Assets.getBitmapData("${a[2]}")';
|
||||
case _:
|
||||
a[2];
|
||||
}
|
||||
case "resource" | "r":
|
||||
var bindExpr = 'hw.provider.Provider.instance.get(hw.resources.IResources).${a[1]}.bind("${a[2]}", ${name}, "${key}")';
|
||||
exprs.push(Context.parse(bindExpr, getPosition(position)));
|
||||
null;
|
||||
case "translate" | "t":
|
||||
'new hw.translate.TranslateString("${a[1]}")';
|
||||
case "template":
|
||||
var template = FileUtil.loadFile(a[1]);
|
||||
return createValue(name, key, template, position, exprs);
|
||||
case _:
|
||||
Context.error('Unsupported prefix "${a[0]}"', getPosition(position));
|
||||
}
|
||||
}
|
||||
|
||||
private function createValue(name:String, key:String, value:Dynamic, position:JsonKeyPosition, exprs:Array<Expr>):Dynamic {
|
||||
return if (Std.is(value, Array)) {
|
||||
value.map(function(v) {
|
||||
return createValue(null, null, v, position, exprs);
|
||||
});
|
||||
} else if (Std.is(value, String)) {
|
||||
if (value.charAt(0) == "~" ) {
|
||||
value.substring(1, value.length);
|
||||
} else if (value.charAt(0) == "@" || value.charAt(0) == "$") {
|
||||
specialValue(name, key, value.substring(1, value.length).split(":"), position, exprs);
|
||||
} else if (~/(0x|#)[A-Fa-f\d]{6}/.match(value)) {
|
||||
Std.parseInt(StringTools.replace(Std.string(value), "#", "0x"));
|
||||
} else {
|
||||
"\"" + value + "\"";
|
||||
}
|
||||
} else if (Std.is(value, Float) || (Std.is(value, Bool))) {
|
||||
value;
|
||||
} else if (value != null) {
|
||||
var className:String = getSpecField(value, "class");
|
||||
if (className != null) {
|
||||
className;
|
||||
} else {
|
||||
var type:Dynamic = getSpecField(value, "type");
|
||||
if (type != null) {
|
||||
var varName = 'a${i++}';
|
||||
if (Std.is(type, Array)) {
|
||||
var args = cast(type,Array<Dynamic>).slice(1).join(",");
|
||||
exprs.push(Context.parse('var ${varName} = ${type[0]}(${args})', getPosition(position)));
|
||||
} else if (type == "Dynamic") {
|
||||
exprs.push(Context.parse('var ${varName} = cast {}', getPosition(position)));
|
||||
} else {
|
||||
exprs.push(Context.parse('var ${varName} = new ${type}()', getPosition(position)));
|
||||
}
|
||||
createElement(varName, value, exprs);
|
||||
varName;
|
||||
} else {
|
||||
createElement('${name}.${key}', value, exprs);
|
||||
'${name}.${key}';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
value;
|
||||
}
|
||||
}
|
||||
|
||||
private function createElement(name:String, data:Dynamic, exprs:Array<Expr>):String {
|
||||
if (Reflect.hasField(data, "id")) {
|
||||
var id = Reflect.field(data, "id");
|
||||
// ToDo: only for view?
|
||||
if (Std.is(id, String) && bindings.exists(id)) {
|
||||
var bind = bindings.get(id);
|
||||
exprs.push(Context.parse('this.${bind} = ${name}', getPosition()));
|
||||
bindings.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
for (key in Reflect.fields(data)) {
|
||||
if (key.charAt(0) == "$" || key.charAt(0) == "@") continue;
|
||||
var position = Reflect.field(data, "$" + key);
|
||||
var value = createValue(name, key, Reflect.field(data, key), position, exprs);
|
||||
if (value != null) {
|
||||
switch [key.charAt(0), key.charAt(key.length - 1)] {
|
||||
case ["+", _]:
|
||||
var e:Expr = Context.parse(value, getPosition(position));
|
||||
e = switch e.expr {
|
||||
case ECall(_, _): macro function(_) ${e};
|
||||
case _: e;
|
||||
}
|
||||
exprs.push(macro $p{[name, key.substr(1)]}.connect(${e}));
|
||||
case ["_", "_"]:
|
||||
//exprs.push(Context.parse('${name}["${key.substr(1, key.length - 2)}"] = ${value}', getPosition(position)));
|
||||
exprs.push(Context.parse('${name}.set("${key.substr(1, key.length - 2)}", ${value})', getPosition(position)));
|
||||
case _:
|
||||
exprs.push(Context.parse('${name}.${key} = ${value}', getPosition(position)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private function buildBuild(exprs:Array<Expr>):Field {
|
||||
return {
|
||||
name: "build",
|
||||
access: [Access.APrivate],
|
||||
pos: getPosition(),
|
||||
kind: FieldType.FFun({
|
||||
args: [],
|
||||
expr: macro $b{exprs},
|
||||
params: [],
|
||||
ret: null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private function upgradeConstructor(constructor:Field = null):Field {
|
||||
if (constructor == null) {
|
||||
constructor = {
|
||||
name: "new",
|
||||
access: [Access.APublic],
|
||||
pos: getPosition(),
|
||||
kind: FieldType.FFun({
|
||||
args: [],
|
||||
expr: macro super(),
|
||||
params: [],
|
||||
ret: null
|
||||
})
|
||||
}
|
||||
}
|
||||
MacroUtil.upgradeField(constructor, macro build(), 1);
|
||||
return constructor;
|
||||
}
|
||||
|
||||
private static function findViewsBindings(fields:Array<Field>):Map<String, String> {
|
||||
var result:Map<String, String> = new Map();
|
||||
for (field in fields) {
|
||||
var viewMeta = field.getFieldMeta(":view");
|
||||
if (viewMeta != null) {
|
||||
var viewId:String = viewMeta.params.length == 0 ? field.name : MacroUtil.getExprString(viewMeta.params[0].expr);
|
||||
result.set(viewId, field.name);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
override public function apply(classType:ClassType, fields:Array<Field>):Array<Field> {
|
||||
i = 0;
|
||||
var meta = classType.getClassMeta(metaName);
|
||||
var params = MacroUtil.getMetaParams(meta);
|
||||
var filePath = params[0];
|
||||
if (filePath == null) {
|
||||
filePath = classType.pack.join("/") + "/" + classType.name + ".yaml";
|
||||
}
|
||||
// ToDo: template builder
|
||||
templateFile = Context.resolvePath(filePath);
|
||||
template = FileUtil.loadFile(templateFile);
|
||||
bindings = findViewsBindings(fields);
|
||||
var result:Array<Field> = fields.slice(0);
|
||||
var exprs:Array<Expr> = [];
|
||||
createElement("this", template, exprs);
|
||||
result.push(buildBuild(exprs));
|
||||
var constructor = Lambda.find(result, function(f) return f.name == "new");
|
||||
if (constructor != null) {
|
||||
result.remove(constructor);
|
||||
}
|
||||
result.push(upgradeConstructor(constructor));
|
||||
if (Lambda.count(bindings) > 0) {
|
||||
var keys = Lambda.map({iterator: bindings.keys}, function(k) return '"${k}"').join(",");
|
||||
Context.error('Invalid @:view bindings: $keys', getPosition());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
172
src/main/hw/net/BaseLoader.hx
Executable file
172
src/main/hw/net/BaseLoader.hx
Executable file
@@ -0,0 +1,172 @@
|
||||
package hw.net;
|
||||
|
||||
import promhx.Deferred;
|
||||
import promhx.Promise;
|
||||
import haxe.Timer;
|
||||
import flash.net.URLRequestMethod;
|
||||
import flash.events.ProgressEvent;
|
||||
import hw.net.manage.ILoaderManager;
|
||||
import flash.utils.ByteArray;
|
||||
import flash.events.Event;
|
||||
|
||||
class BaseLoader<T> extends Deferred<T> implements ILoader<T> {
|
||||
|
||||
private static inline var TAG:String = "Loader";
|
||||
|
||||
//ToDo: move to LoaderManager
|
||||
public static var urlProcessors(default, null):Array<String->String> = new Array<String->String>();
|
||||
public static function prepareUrl(url:String):String { for (p in urlProcessors) url = p(url); return url; }
|
||||
|
||||
public static var proxy(default, default):String->String;
|
||||
|
||||
|
||||
public var timeout(default, default):Int;
|
||||
public var busy(default, null):Bool;
|
||||
public var completed(default, null):Float;
|
||||
|
||||
private var url:String;
|
||||
private var method:String;
|
||||
private var data:Null<Dynamic>;
|
||||
private var timer:Timer;
|
||||
|
||||
@:provide private var manager:ILoaderManager;
|
||||
|
||||
public function new(timeout = 0) {
|
||||
super();
|
||||
this.timeout = timeout;
|
||||
busy = false;
|
||||
completed = Math.NaN;
|
||||
}
|
||||
|
||||
public function request(url:String, method:String, data:Dynamic = null):Promise<T> {
|
||||
if (busy) {
|
||||
throwError("Busy");
|
||||
} else {
|
||||
busy = true;
|
||||
this.url = url;
|
||||
this.method = method;
|
||||
this.data = data;
|
||||
var url:String = this.url;
|
||||
//L.d(TAG, "Request: " + prepareUrl(url));
|
||||
//internalRequest(prepareUrl(url));
|
||||
manager.add(this);
|
||||
}
|
||||
return this.promise();
|
||||
}
|
||||
|
||||
private function cockTimeout():Void {
|
||||
if (timeout > 0) {
|
||||
timer = new Timer(timeout);
|
||||
timer.run = callTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
private function cancelTimeout():Void {
|
||||
if (timer != null) {
|
||||
timer.stop();
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function fromBytes(data:ByteArray):Promise<T> {
|
||||
if (busy) {
|
||||
throwError("Busy");
|
||||
} else {
|
||||
busy = true;
|
||||
internalFromBytes(data);
|
||||
}
|
||||
return this.promise();
|
||||
}
|
||||
|
||||
public function GET(url:String, data:Dynamic = null):Promise<T> {
|
||||
return request(url, URLRequestMethod.GET, data);
|
||||
}
|
||||
|
||||
public function POST(url:String, data:Dynamic = null):Promise<T> {
|
||||
return request(url, URLRequestMethod.POST, data);
|
||||
}
|
||||
|
||||
public function DELETE(url:String, data:Dynamic = null):Promise<T> {
|
||||
return request(url, URLRequestMethod.DELETE, data);
|
||||
}
|
||||
|
||||
private function internalRequest(url:String):Void {
|
||||
throw "Abstract";
|
||||
}
|
||||
|
||||
private function internalFromBytes(data:ByteArray):Void {
|
||||
throw "Abstract";
|
||||
}
|
||||
|
||||
private function onInit(e:Event):Void {}
|
||||
|
||||
private function onProgress(e:ProgressEvent):Void {
|
||||
completed = e.bytesLoaded / e.bytesTotal;
|
||||
}
|
||||
|
||||
private function onComplete(e:Event):Void {
|
||||
var data:T = extrudeResult(e);
|
||||
if (data != null) {
|
||||
resolve(data);
|
||||
} else {
|
||||
throwError("Data is null");
|
||||
}
|
||||
dispose();
|
||||
}
|
||||
|
||||
private function onSecurityError(e:Event):Void {
|
||||
if (proxy == null) {
|
||||
onError(e);
|
||||
} else {
|
||||
cancelTimeout();
|
||||
internalRequest(proxy(buildUrl()));
|
||||
}
|
||||
}
|
||||
|
||||
private function onError(e:Event):Void {
|
||||
throwError(e);
|
||||
dispose();
|
||||
}
|
||||
|
||||
private function callTimeout():Void {
|
||||
var error:String = "Timeout for: " + url;
|
||||
throwError(error);
|
||||
dispose();
|
||||
}
|
||||
|
||||
private function extrudeResult(e:Event):T {
|
||||
throw "Abstract";
|
||||
return null;
|
||||
}
|
||||
|
||||
private function dispose():Void {
|
||||
cancelTimeout();
|
||||
url = null;
|
||||
data = null;
|
||||
busy = false;
|
||||
completed = Math.NaN;
|
||||
manager.release(this);
|
||||
}
|
||||
|
||||
public function cancel():Void {
|
||||
throwError("Cancelled");
|
||||
dispose();
|
||||
}
|
||||
|
||||
private function buildUrl():String {
|
||||
var u:String = url;
|
||||
if (data != null && method == URLRequestMethod.GET) {
|
||||
var a:Array<String> = [];
|
||||
for (key in Reflect.fields(data)) {
|
||||
a.push(key + "=" + Reflect.field(data, key));
|
||||
}
|
||||
u += "?" + a.join("&");
|
||||
}
|
||||
return prepareUrl(u);
|
||||
}
|
||||
|
||||
public function run():Void {
|
||||
internalRequest(buildUrl());
|
||||
}
|
||||
}
|
||||
|
||||
77
src/main/hw/net/BaseMediaLoader.hx
Executable file
77
src/main/hw/net/BaseMediaLoader.hx
Executable file
@@ -0,0 +1,77 @@
|
||||
package hw.net;
|
||||
|
||||
import flash.events.ProgressEvent;
|
||||
import flash.system.Security;
|
||||
import flash.system.SecurityDomain;
|
||||
import flash.system.ApplicationDomain;
|
||||
import flash.system.LoaderContext;
|
||||
import flash.utils.ByteArray;
|
||||
import hw.net.BaseLoader;
|
||||
import flash.events.SecurityErrorEvent;
|
||||
import flash.events.IOErrorEvent;
|
||||
import flash.events.Event;
|
||||
import flash.net.URLRequest;
|
||||
import flash.display.Loader;
|
||||
|
||||
class BaseMediaLoader<T> extends BaseLoader<T> {
|
||||
|
||||
private var loader:Loader;
|
||||
|
||||
override private function internalRequest(url:String):Void {
|
||||
//L.d("BaseMediaLoader", "request: " + url);
|
||||
cockTimeout();
|
||||
loader = buildLoader();
|
||||
loader.load(new URLRequest(url), buildLoaderContext(false));
|
||||
}
|
||||
|
||||
override private function internalFromBytes(data:ByteArray):Void {
|
||||
loader = buildLoader();
|
||||
#if flash
|
||||
loader.loadBytes(data, buildLoaderContext(true));
|
||||
#else
|
||||
loader.loadBytes(data);
|
||||
#end
|
||||
}
|
||||
|
||||
private function buildLoaderContext(bytes:Bool):LoaderContext {
|
||||
#if flash
|
||||
return switch (Security.sandboxType) {
|
||||
case Security.REMOTE:
|
||||
//null;
|
||||
bytes ? null : new LoaderContext(true, ApplicationDomain.currentDomain, SecurityDomain.currentDomain);
|
||||
case Security.APPLICATION:
|
||||
var loaderContext:LoaderContext = new LoaderContext();
|
||||
loaderContext.allowLoadBytesCodeExecution = true;
|
||||
loaderContext;
|
||||
default:
|
||||
null;
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#end
|
||||
}
|
||||
|
||||
private function buildLoader():Loader {
|
||||
var loader:Loader = new Loader();
|
||||
loader.contentLoaderInfo.addEventListener(Event.INIT, onInit);
|
||||
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
|
||||
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onError);
|
||||
loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
|
||||
loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onProgress);
|
||||
return loader;
|
||||
}
|
||||
|
||||
override private function dispose():Void {
|
||||
super.dispose();
|
||||
if (loader != null) {
|
||||
loader.contentLoaderInfo.removeEventListener(Event.INIT, onInit);
|
||||
loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onComplete);
|
||||
loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, onError);
|
||||
loader.contentLoaderInfo.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
|
||||
loader.contentLoaderInfo.removeEventListener(ProgressEvent.PROGRESS, onProgress);
|
||||
#if flash try { loader.close(); } catch (error:Dynamic) {} #end
|
||||
loader = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
117
src/main/hw/net/BaseURLLoader.hx
Executable file
117
src/main/hw/net/BaseURLLoader.hx
Executable file
@@ -0,0 +1,117 @@
|
||||
package hw.net;
|
||||
|
||||
import flash.events.ErrorEvent;
|
||||
import haxe.Timer;
|
||||
import flash.utils.ByteArray;
|
||||
import flash.net.URLRequestHeader;
|
||||
import flash.net.URLRequestMethod;
|
||||
import flash.net.URLVariables;
|
||||
import flash.events.ProgressEvent;
|
||||
import flash.net.URLLoaderDataFormat;
|
||||
import flash.events.SecurityErrorEvent;
|
||||
import flash.events.IOErrorEvent;
|
||||
import flash.net.URLRequest;
|
||||
import flash.events.Event;
|
||||
import flash.net.URLLoader;
|
||||
import promhx.Thenable;
|
||||
|
||||
class BaseURLLoader<T> extends BaseLoader<T> {
|
||||
|
||||
private var dataFormat:URLLoaderDataFormat;
|
||||
private var loader:URLLoader;
|
||||
|
||||
public function new(dateFormat:URLLoaderDataFormat = null) {
|
||||
super();
|
||||
this.dataFormat = (dateFormat == null ? URLLoaderDataFormat.TEXT : dateFormat);
|
||||
}
|
||||
|
||||
override private function internalRequest(url:String):Void {
|
||||
cockTimeout();
|
||||
loader = buildLoader();
|
||||
var request:URLRequest = new URLRequest(url);
|
||||
if (method != URLRequestMethod.POST && method != URLRequestMethod.GET) {
|
||||
request.method = URLRequestMethod.POST;
|
||||
request.requestHeaders.push(new URLRequestHeader("X-HTTP-Method-Override", method));
|
||||
} else {
|
||||
request.method = method;
|
||||
}
|
||||
if (data != null && method == URLRequestMethod.POST) {
|
||||
var variables:URLVariables = new URLVariables();
|
||||
for (key in Reflect.fields(data)) {
|
||||
Reflect.setField(variables, key, Reflect.field(data, key));
|
||||
}
|
||||
request.data = variables;
|
||||
}
|
||||
loader.load(request);
|
||||
}
|
||||
|
||||
override private function internalFromBytes(data:ByteArray):Void {
|
||||
if (data == null) {
|
||||
throwError("Content not found");
|
||||
} else {
|
||||
var data:T = extrudeResultFromBytes(data);
|
||||
resolve(data);
|
||||
}
|
||||
dispose();
|
||||
}
|
||||
|
||||
private function extrudeResultFromBytes(bytes:ByteArray):T {
|
||||
throw "Abstract";
|
||||
return null;
|
||||
}
|
||||
|
||||
private function buildLoader():URLLoader {
|
||||
var loader:URLLoader = new URLLoader();
|
||||
loader.dataFormat = dataFormat;
|
||||
loader.addEventListener(Event.COMPLETE, onComplete);
|
||||
loader.addEventListener(IOErrorEvent.IO_ERROR, onError);
|
||||
loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
|
||||
loader.addEventListener(ProgressEvent.PROGRESS, onProgress);
|
||||
return loader;
|
||||
}
|
||||
|
||||
override private function dispose():Void {
|
||||
super.dispose();
|
||||
if (loader != null) {
|
||||
loader.removeEventListener(Event.COMPLETE, onComplete);
|
||||
loader.removeEventListener(IOErrorEvent.IO_ERROR, onError);
|
||||
loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
|
||||
loader.removeEventListener(ProgressEvent.PROGRESS, onProgress);
|
||||
try { loader.close(); } catch (error:Dynamic) {}
|
||||
loader = null;
|
||||
}
|
||||
}
|
||||
|
||||
override public function cancel():Void {
|
||||
if (loader != null) {
|
||||
try {loader.close();} catch (error:Dynamic) {}
|
||||
}
|
||||
super.cancel();
|
||||
}
|
||||
|
||||
override private function onError(e:Event):Void {
|
||||
var error:String = extrudeError(loader.data);
|
||||
if (error != null && error != "") {
|
||||
error = error + ". URL: " + url;
|
||||
} else if (Std.is(e, ErrorEvent)) {
|
||||
error = cast(e, ErrorEvent).text;
|
||||
} else {
|
||||
error = "Unknown Error. URL: " + url;
|
||||
}
|
||||
throwError(error);
|
||||
dispose();
|
||||
}
|
||||
|
||||
private function extrudeError(data:Dynamic):String {
|
||||
return if (data == null) null else {
|
||||
var s:String = Std.string(data);
|
||||
var r:EReg = ~/<h1>(.*?)<\/h1>/;
|
||||
if (r.match(s)) {
|
||||
r.matched(1);
|
||||
} else {
|
||||
s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
src/main/hw/net/BatchLoader.hx
Executable file
20
src/main/hw/net/BatchLoader.hx
Executable file
@@ -0,0 +1,20 @@
|
||||
package hw.net;
|
||||
|
||||
import promhx.Promise;
|
||||
import promhx.Deferred;
|
||||
|
||||
class BatchLoader<T> {
|
||||
|
||||
public var factory:Class<ILoader<T>>;
|
||||
|
||||
public function new(factory:Class<ILoader<T>>) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
public function GET(urls:Array<String>):Promise<Array<T>> {
|
||||
return Promise.whenAll(urls.map(function(url:String):Promise<T> {
|
||||
var loader:ILoader<T> = Type.createInstance(factory, []);
|
||||
return loader.GET(url);
|
||||
}));
|
||||
}
|
||||
}
|
||||
21
src/main/hw/net/BytesLoader.hx
Executable file
21
src/main/hw/net/BytesLoader.hx
Executable file
@@ -0,0 +1,21 @@
|
||||
package hw.net;
|
||||
|
||||
import flash.net.URLLoaderDataFormat;
|
||||
import flash.utils.ByteArray;
|
||||
import flash.net.URLLoader;
|
||||
import flash.events.Event;
|
||||
|
||||
class BytesLoader extends BaseURLLoader<ByteArray> {
|
||||
|
||||
public function new() {
|
||||
super(URLLoaderDataFormat.BINARY);
|
||||
}
|
||||
|
||||
override private function extrudeResult(e:Event):ByteArray {
|
||||
return cast(cast(e.currentTarget, URLLoader).data, ByteArray);
|
||||
}
|
||||
|
||||
override private function extrudeResultFromBytes(bytes:ByteArray):ByteArray {
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
54
src/main/hw/net/ExternalImageLoader.hx
Executable file
54
src/main/hw/net/ExternalImageLoader.hx
Executable file
@@ -0,0 +1,54 @@
|
||||
package hw.net;
|
||||
|
||||
import flash.system.Security;
|
||||
import flash.utils.ByteArray;
|
||||
import flash.display.LoaderInfo;
|
||||
import flash.display.Loader;
|
||||
import flash.display.Bitmap;
|
||||
import flash.events.Event;
|
||||
import flash.display.BitmapData;
|
||||
|
||||
class ExternalImageLoader extends BaseMediaLoader<BitmapData> {
|
||||
|
||||
private var internalLoader:InternalLoader;
|
||||
|
||||
override private function extrudeResult(e:Event):BitmapData {
|
||||
var loader:Loader = cast(e.currentTarget, LoaderInfo).loader;
|
||||
#if flash
|
||||
if (Security.sandboxType == Security.APPLICATION) {
|
||||
var content:Bitmap = cast(loader.content,Bitmap);
|
||||
return content.bitmapData;
|
||||
} else {
|
||||
#end
|
||||
internalLoader = new InternalLoader();
|
||||
internalLoader.fromBytes(loader.contentLoaderInfo.bytes)
|
||||
.then(function(data:BitmapData):Void {
|
||||
resolve(data);
|
||||
})
|
||||
.catchError(function(error:Dynamic):Void {
|
||||
throwError(e);
|
||||
});
|
||||
return null;
|
||||
#if flash} #end
|
||||
}
|
||||
|
||||
override public function dispose():Void {
|
||||
super.dispose();
|
||||
if (internalLoader != null) {
|
||||
internalLoader.dispose();
|
||||
internalLoader = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InternalLoader extends BaseMediaLoader<BitmapData> {
|
||||
|
||||
override private function extrudeResult(e:Event):BitmapData {
|
||||
var loader:Loader = cast(e.currentTarget, LoaderInfo).loader;
|
||||
var bitmapData:BitmapData = new BitmapData(Math.round(loader.width), Math.round(loader.height), true, 0x00000000);
|
||||
bitmapData.draw(loader);
|
||||
loader.unloadAndStop();
|
||||
return bitmapData;
|
||||
}
|
||||
}
|
||||
|
||||
19
src/main/hw/net/ILoader.hx
Executable file
19
src/main/hw/net/ILoader.hx
Executable file
@@ -0,0 +1,19 @@
|
||||
package hw.net;
|
||||
|
||||
import promhx.Promise;
|
||||
import flash.utils.ByteArray;
|
||||
|
||||
interface ILoader<T> {
|
||||
public var timeout(default, default):Int;
|
||||
public var busy(default, null):Bool;
|
||||
public var completed(default, null):Float;
|
||||
|
||||
public function request(url:String, method:String, data:Dynamic = null):Promise<T>;
|
||||
public function fromBytes(data:ByteArray):Promise<T>;
|
||||
public function GET(url:String, data:Dynamic = null):Promise<T>;
|
||||
public function POST(url:String, data:Dynamic = null):Promise<T>;
|
||||
public function DELETE(url:String, data:Dynamic = null):Promise<T>;
|
||||
public function cancel():Void;
|
||||
|
||||
public function run():Void;
|
||||
}
|
||||
16
src/main/hw/net/ImageLoader.hx
Executable file
16
src/main/hw/net/ImageLoader.hx
Executable file
@@ -0,0 +1,16 @@
|
||||
package hw.net;
|
||||
|
||||
import flash.display.LoaderInfo;
|
||||
import flash.display.Loader;
|
||||
import flash.display.Bitmap;
|
||||
import flash.events.Event;
|
||||
import flash.display.BitmapData;
|
||||
|
||||
class ImageLoader extends BaseMediaLoader<BitmapData> {
|
||||
|
||||
override private function extrudeResult(e:Event):BitmapData {
|
||||
var content:Bitmap = cast(cast(e.currentTarget, LoaderInfo).loader.content,Bitmap);
|
||||
return content.bitmapData;
|
||||
}
|
||||
}
|
||||
|
||||
33
src/main/hw/net/JsonLoader.hx
Executable file
33
src/main/hw/net/JsonLoader.hx
Executable file
@@ -0,0 +1,33 @@
|
||||
package hw.net;
|
||||
|
||||
import flash.utils.ByteArray;
|
||||
import flash.events.Event;
|
||||
import flash.net.URLLoader;
|
||||
import haxe.Json;
|
||||
|
||||
class JsonLoader<T> extends BaseURLLoader<T> {
|
||||
|
||||
override private function extrudeResult(e:Event):T {
|
||||
var str:String = null;
|
||||
var data:T = null;
|
||||
try {
|
||||
str = Std.string(cast(e.currentTarget, URLLoader).data);
|
||||
data = Json.parse(str);
|
||||
} catch (error:Dynamic) {
|
||||
throwError(error);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
override private function extrudeResultFromBytes(bytes:ByteArray):T {
|
||||
var str:String = null;
|
||||
var data:T = null;
|
||||
try {
|
||||
str = bytes.readUTFBytes(bytes.length);
|
||||
data = Json.parse(str);
|
||||
} catch (error:Dynamic) {
|
||||
throwError(error);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
16
src/main/hw/net/SwfLoader.hx
Executable file
16
src/main/hw/net/SwfLoader.hx
Executable file
@@ -0,0 +1,16 @@
|
||||
package hw.net;
|
||||
|
||||
import flash.display.MovieClip;
|
||||
import flash.display.LoaderInfo;
|
||||
import flash.display.Loader;
|
||||
import flash.events.Event;
|
||||
|
||||
class SwfLoader extends BaseMediaLoader<MovieClip> {
|
||||
|
||||
override private function extrudeResult(e:Event):MovieClip {
|
||||
var content:MovieClip = cast(cast(e.currentTarget, LoaderInfo).loader.content, MovieClip);
|
||||
content.gotoAndStop(0);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
11
src/main/hw/net/TextLoader.hx
Executable file
11
src/main/hw/net/TextLoader.hx
Executable file
@@ -0,0 +1,11 @@
|
||||
package hw.net;
|
||||
|
||||
import flash.net.URLLoader;
|
||||
import flash.events.Event;
|
||||
|
||||
class TextLoader extends BaseURLLoader<String> {
|
||||
|
||||
override private function extrudeResult(e:Event):String {
|
||||
return Std.string(cast(e.currentTarget, URLLoader).data);
|
||||
}
|
||||
}
|
||||
11
src/main/hw/net/XmlLoader.hx
Executable file
11
src/main/hw/net/XmlLoader.hx
Executable file
@@ -0,0 +1,11 @@
|
||||
package hw.net;
|
||||
|
||||
import flash.events.Event;
|
||||
import flash.net.URLLoader;
|
||||
|
||||
class XmlLoader extends BaseURLLoader<Xml> {
|
||||
|
||||
override private function extrudeResult(e:Event):Xml {
|
||||
return Xml.parse(Std.string(cast(e.currentTarget, URLLoader).data));
|
||||
}
|
||||
}
|
||||
10
src/main/hw/net/manage/ILoaderManager.hx
Executable file
10
src/main/hw/net/manage/ILoaderManager.hx
Executable file
@@ -0,0 +1,10 @@
|
||||
package hw.net.manage;
|
||||
|
||||
@:provide(LoaderManager) interface ILoaderManager {
|
||||
public var actives(default, null):Array<ILoader<Dynamic>>;
|
||||
public var queue(default, null):Array<ILoader<Dynamic>>;
|
||||
public var limit(default, default):Int;
|
||||
|
||||
public function add(loader:ILoader<Dynamic>):Void;
|
||||
public function release(loader:ILoader<Dynamic>):Void;
|
||||
}
|
||||
36
src/main/hw/net/manage/LoaderManager.hx
Executable file
36
src/main/hw/net/manage/LoaderManager.hx
Executable file
@@ -0,0 +1,36 @@
|
||||
package hw.net.manage;
|
||||
|
||||
class LoaderManager implements ILoaderManager {
|
||||
|
||||
private static inline var TAG:String = "LoaderManager";
|
||||
|
||||
public var actives(default, null):Array<ILoader<Dynamic>>;
|
||||
public var queue(default, null):Array<ILoader<Dynamic>>;
|
||||
public var limit(default, default):Int;
|
||||
|
||||
public function new(limit:Int = 10) {
|
||||
queue = new Array<ILoader<Dynamic>>();
|
||||
actives = new Array<ILoader<Dynamic>>();
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
public function add(loader:ILoader<Dynamic>):Void {
|
||||
if (actives.length >= limit) {
|
||||
queue.push(loader);
|
||||
} else {
|
||||
run(loader);
|
||||
}
|
||||
}
|
||||
|
||||
private function run(loader:ILoader<Dynamic>):Void {
|
||||
actives.push(loader);
|
||||
loader.run();
|
||||
}
|
||||
|
||||
public function release(loader:ILoader<Dynamic>):Void {
|
||||
actives.remove(loader);
|
||||
if (queue.length > 0 && actives.length < limit) {
|
||||
run(queue.shift());
|
||||
}
|
||||
}
|
||||
}
|
||||
74
src/main/hw/parser/Parser.hx
Normal file
74
src/main/hw/parser/Parser.hx
Normal file
@@ -0,0 +1,74 @@
|
||||
package hw.parser;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
import haxe.macro.TypeTools;
|
||||
import hw.macro.ClassProvideMacro;
|
||||
import hw.macro.ClassTypeMacro;
|
||||
import hw.macro.DispatcherMacro;
|
||||
import hw.macro.FieldMacro;
|
||||
import hw.macro.ProvideMacro;
|
||||
import hw.macro.ResourceMacro;
|
||||
import hw.macro.SingletonMacro;
|
||||
import hw.macro.StyleMacro;
|
||||
import hw.macro.TemplateMacro;
|
||||
|
||||
class Parser {
|
||||
|
||||
private static var classTypeMacros:Array<ClassTypeMacro> = [
|
||||
new ClassProvideMacro(),
|
||||
new TemplateMacro(),
|
||||
new DispatcherMacro(),
|
||||
new StyleMacro(),
|
||||
new SingletonMacro(),
|
||||
];
|
||||
|
||||
private static var fieldMacros:Array<FieldMacro> = [
|
||||
new ProvideMacro(),
|
||||
new ResourceMacro(),
|
||||
];
|
||||
|
||||
private static var processed:Map<String, Bool> = new Map();
|
||||
|
||||
private static function auto():Void {
|
||||
haxe.macro.Compiler.addGlobalMetadata("", "@:build(hw.parser.Parser.autoRun())", true, true, false);
|
||||
}
|
||||
|
||||
private static macro function autoRun():Array<Field> {
|
||||
var localType = Context.getLocalType();
|
||||
return switch localType {
|
||||
case Type.TInst(_.get() => ct, _):
|
||||
var localName = TypeTools.toString(localType);
|
||||
if (processed.exists(localName)) {
|
||||
return null;
|
||||
}
|
||||
var modify:Bool = false;
|
||||
var fields:Array<Field> = Context.getBuildFields();
|
||||
var result:Array<Field> = [];
|
||||
// process fields meta
|
||||
for (field in fields) {
|
||||
result.push(field);
|
||||
for (item in fieldMacros) {
|
||||
if (item.has(field)) {
|
||||
modify = true;
|
||||
result = result.concat(item.apply(field));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (modify) {
|
||||
fields = result;
|
||||
}
|
||||
// process class meta
|
||||
for (item in classTypeMacros) {
|
||||
if (item.has(ct)) {
|
||||
modify = true;
|
||||
fields = item.apply(ct, fields);
|
||||
}
|
||||
}
|
||||
processed.set(localName, true);
|
||||
modify ? fields : null;
|
||||
default: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
88
src/main/hw/provider/Provider.hx
Executable file
88
src/main/hw/provider/Provider.hx
Executable file
@@ -0,0 +1,88 @@
|
||||
package hw.provider;
|
||||
|
||||
abstract KeyType<T>(String) {
|
||||
|
||||
public function new(value:String) {
|
||||
this = value;
|
||||
}
|
||||
|
||||
@:from public static inline function fromClass<T>(value:Class<T>):KeyType<T> {
|
||||
return new KeyType<T>(Type.getClassName(value));
|
||||
}
|
||||
|
||||
@:from public static inline function fromEnum<T>(value:Enum<T>):KeyType<T> {
|
||||
return new KeyType<T>(Type.getEnumName(value));
|
||||
}
|
||||
}
|
||||
|
||||
@:singleton class Provider {
|
||||
|
||||
private static function key<T>(i:KeyType<T>, ?type:Dynamic):String {
|
||||
var result:String = Std.string(i);
|
||||
if (type != null) result += ':${Std.string(type)}';
|
||||
return result;
|
||||
}
|
||||
|
||||
public var factories:Map<String, Class<Dynamic>>;
|
||||
private var args:Map<String, Array<Dynamic>>;
|
||||
private var instances:Map<String, Dynamic>;
|
||||
|
||||
public function new() {
|
||||
factories = new Map();
|
||||
args = new Map();
|
||||
instances = new Map();
|
||||
resolveDefaultFactories();
|
||||
}
|
||||
|
||||
macro static function resolveDefaultFactories() {
|
||||
var result = [];
|
||||
for (item in hw.macro.ClassProvideMacro.bundle) {
|
||||
// ToDo: ClassType to Expr?
|
||||
var k = haxe.macro.Context.parse(haxe.macro.MacroStringTools.toDotPath(item.key.pack, item.key.name), haxe.macro.Context.currentPos());
|
||||
var v = haxe.macro.Context.parse(haxe.macro.MacroStringTools.toDotPath(item.value.pack, item.value.name), haxe.macro.Context.currentPos());
|
||||
result.push(macro setFactory(${k}, ${v}));
|
||||
}
|
||||
return macro $b{result};
|
||||
}
|
||||
|
||||
public function setFactory<T>(i:KeyType<T>, clazz:Class<T>, ?type:Dynamic, ?args:Array<Dynamic>):Void {
|
||||
var key = key(i, type);
|
||||
factories.set(key, clazz);
|
||||
if (args != null) this.args.set(key, args);
|
||||
}
|
||||
|
||||
public function set<T>(i:KeyType<T>, instance:T, ?type:Dynamic):Void {
|
||||
var key = key(i, type);
|
||||
instances.set(key, instance);
|
||||
}
|
||||
|
||||
public function get<T>(i:KeyType<T>, ?type:Dynamic):T {
|
||||
var key = key(i, type);
|
||||
if (instances.exists(key)) {
|
||||
return instances.get(key);
|
||||
} else if (factories.exists(key)) {
|
||||
var instance:T = Type.createInstance(factories.get(key), args.exists(key) ? args.get(key) : []);
|
||||
instances.set(key, instance);
|
||||
return instance;
|
||||
} else {
|
||||
throw 'Factory for "${key}" not found';
|
||||
}
|
||||
}
|
||||
|
||||
public function build<T>(i:Class<T>, ?type:Dynamic):T {
|
||||
var key = key(i, type);
|
||||
if (factories.exists(key)) {
|
||||
var instance:T = Type.createInstance(factories.get(key), args.exists(key) ? args.get(key) : []);
|
||||
return instance;
|
||||
} else {
|
||||
throw 'Factory for "${key}" not found';
|
||||
}
|
||||
}
|
||||
|
||||
public function setProperty<T>(i:Class<T>, field:String, value:Dynamic, ?type:Dynamic):Void {
|
||||
var o:Dynamic = get(i, type);
|
||||
if (o != null && Reflect.hasField(o, field)) {
|
||||
Reflect.setProperty(o, field, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/main/hw/resources/IResources.hx
Executable file
15
src/main/hw/resources/IResources.hx
Executable file
@@ -0,0 +1,15 @@
|
||||
package hw.resources;
|
||||
|
||||
import flash.display.BitmapData;
|
||||
import flash.display.MovieClip;
|
||||
import hw.resources.Resources;
|
||||
|
||||
@:provide(Resources) interface IResources {
|
||||
public var image(default, null):ResMap<BitmapData>;
|
||||
public var color(default, null):ResMap<Int>;
|
||||
public var movie(default, null):ResMap<MovieClip>;
|
||||
public var text(default, null):ResMap<String>;
|
||||
public var float(default, null):ResMap<Float>;
|
||||
public var int(default, null):ResMap<Int>;
|
||||
public var any(default, null):ResMap<Dynamic>;
|
||||
}
|
||||
66
src/main/hw/resources/Resources.hx
Executable file
66
src/main/hw/resources/Resources.hx
Executable file
@@ -0,0 +1,66 @@
|
||||
package hw.resources;
|
||||
|
||||
import flash.display.BitmapData;
|
||||
import flash.display.MovieClip;
|
||||
import haxe.ds.StringMap;
|
||||
|
||||
private typedef Listener = {object:Dynamic, field:String};
|
||||
|
||||
class ResMap<T> extends StringMap<T> {
|
||||
|
||||
private var listeners:StringMap<Array<Listener>>;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
listeners = new StringMap();
|
||||
}
|
||||
|
||||
public function put(key:String, value:T):Void {
|
||||
set(key, value);
|
||||
if (listeners.exists(key)) {
|
||||
for (f in listeners.get(key)) call(f, value);
|
||||
}
|
||||
}
|
||||
|
||||
public function bind(key:String, object:Dynamic, field:String):Void {
|
||||
var listener:Listener = {object:object, field:field};
|
||||
if (listeners.exists(key)) {
|
||||
listeners.set(key, listeners.get(key).filter(function(l) return l.object != object || l.field != field));
|
||||
listeners.get(key).push(listener);
|
||||
} else {
|
||||
listeners.set(key, [listener]);
|
||||
}
|
||||
if (exists(key)) call(listener, get(key));
|
||||
}
|
||||
|
||||
private function call(listener:Listener, value:T):Void {
|
||||
Reflect.setProperty(listener.object, listener.field, value);
|
||||
}
|
||||
|
||||
public function merge(value:Dynamic<T>):Void {
|
||||
for (field in Reflect.fields(value)) {
|
||||
put(field, Reflect.field(value, field));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Resources implements IResources {
|
||||
|
||||
public var image(default, null):ResMap<BitmapData>;
|
||||
public var color(default, null):ResMap<Int>;
|
||||
public var movie(default, null):ResMap<MovieClip>;
|
||||
public var text(default, null):ResMap<String>;
|
||||
public var float(default, null):ResMap<Float>;
|
||||
public var int(default, null):ResMap<Int>;
|
||||
public var any(default, null):ResMap<Dynamic>;
|
||||
|
||||
public function new() {
|
||||
image = new ResMap<BitmapData>();
|
||||
color = new ResMap<Int>();
|
||||
movie = new ResMap<MovieClip>();
|
||||
text = new ResMap<String>();
|
||||
float = new ResMap<Float>();
|
||||
int = new ResMap<Int>();
|
||||
any = new ResMap<Dynamic>();
|
||||
}
|
||||
}
|
||||
72
src/main/hw/signal/Signal.hx
Normal file
72
src/main/hw/signal/Signal.hx
Normal file
@@ -0,0 +1,72 @@
|
||||
package hw.signal;
|
||||
|
||||
typedef Signal<A> = Signal1<A>;
|
||||
|
||||
class BaseSignal<R> {
|
||||
|
||||
private var receivers:Array<R>;
|
||||
|
||||
public function new() {
|
||||
receivers = [];
|
||||
}
|
||||
|
||||
public function connect(receiver:R):Void {
|
||||
receivers.push(receiver);
|
||||
}
|
||||
|
||||
public function disconnect(receiver:R):Void {
|
||||
#if neko
|
||||
receivers = receivers.filter(function(r) return !Reflect.compareMethods(r, receiver));
|
||||
#else
|
||||
receivers.remove(receiver);
|
||||
#end
|
||||
}
|
||||
|
||||
public function dispose():Void {
|
||||
receivers = [];
|
||||
}
|
||||
}
|
||||
|
||||
typedef Receiver0 = Void -> Void;
|
||||
|
||||
class Signal0 extends BaseSignal<Receiver0> {
|
||||
|
||||
public function emit():Void {
|
||||
for (receiver in receivers) {
|
||||
receiver();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef Receiver1<A> = A -> Void;
|
||||
|
||||
class Signal1<A> extends BaseSignal<Receiver1<A>> {
|
||||
|
||||
public function emit(a:A):Void {
|
||||
for (receiver in receivers) {
|
||||
receiver(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef Receiver2<A, B> = A -> B -> Void;
|
||||
|
||||
class Signal2<A, B> extends BaseSignal<Receiver2<A, B>> {
|
||||
|
||||
public function emit(a:A, b:B):Void {
|
||||
for (receiver in receivers) {
|
||||
receiver(a, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef Receiver3<A, B, C> = A -> B -> C -> Void;
|
||||
|
||||
class Signal3<A, B, C> extends BaseSignal<Receiver3<A, B, C>> {
|
||||
|
||||
public function emit(a:A, b:B, c:C):Void {
|
||||
for (receiver in receivers) {
|
||||
receiver(a, b, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/main/hw/storage/IStorage.hx
Executable file
13
src/main/hw/storage/IStorage.hx
Executable file
@@ -0,0 +1,13 @@
|
||||
package hw.storage;
|
||||
|
||||
interface IStorage {
|
||||
public function exists(key:String):Bool;
|
||||
|
||||
public function write<T>(key:String, value:T):Void;
|
||||
|
||||
public function read<T>(key:String):Null<T>;
|
||||
|
||||
public function delete(key:String):Void;
|
||||
|
||||
public function clear():Void;
|
||||
}
|
||||
18
src/main/hw/storage/NoStorage.hx
Executable file
18
src/main/hw/storage/NoStorage.hx
Executable file
@@ -0,0 +1,18 @@
|
||||
package hw.storage;
|
||||
|
||||
class NoStorage implements IStorage {
|
||||
|
||||
public function exists(key:String):Bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function write<T>(key:String, value:T):Void {}
|
||||
|
||||
public function read<T>(key:String):Null<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function clear():Void {}
|
||||
|
||||
public function delete(key:String):Void {}
|
||||
}
|
||||
43
src/main/hw/storage/SharedObjectStorage.hx
Executable file
43
src/main/hw/storage/SharedObjectStorage.hx
Executable file
@@ -0,0 +1,43 @@
|
||||
package hw.storage;
|
||||
|
||||
import flash.net.SharedObject;
|
||||
import haxe.Serializer;
|
||||
import haxe.Unserializer;
|
||||
|
||||
class SharedObjectStorage implements IStorage {
|
||||
|
||||
private var so:SharedObject;
|
||||
|
||||
public function new(name:String = "storage") {
|
||||
so = SharedObject.getLocal(name);
|
||||
}
|
||||
|
||||
public function exists(key:String):Bool {
|
||||
return Reflect.hasField(so.data, key);
|
||||
}
|
||||
|
||||
public function write<T>(key:String, value:T):Void {
|
||||
so.setProperty(key, Serializer.run(value));
|
||||
so.flush();
|
||||
}
|
||||
|
||||
public function read<T>(key:String):Null<T> {
|
||||
if (exists(key)) {
|
||||
var data = Reflect.field(so.data, key);
|
||||
return new Unserializer(data).unserialize();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(key:String):Void {
|
||||
if (exists(key)) {
|
||||
Reflect.deleteField(so.data, key);
|
||||
so.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function clear():Void {
|
||||
so.clear();
|
||||
}
|
||||
}
|
||||
148
src/main/hw/text/BitmapTextField.hx
Executable file
148
src/main/hw/text/BitmapTextField.hx
Executable file
@@ -0,0 +1,148 @@
|
||||
package hw.text;
|
||||
|
||||
import haxe.Timer;
|
||||
import flash.text.TextFormat;
|
||||
import flash.events.Event;
|
||||
import flash.display.Bitmap;
|
||||
import flash.geom.Matrix;
|
||||
import flash.display.BitmapData;
|
||||
import flash.text.TextField;
|
||||
|
||||
class BitmapTextField extends TextField {
|
||||
|
||||
public var shadow(default, set):Bool = false;
|
||||
public var stroke(default, set):Bool = false;
|
||||
public var shadowColor(default, set):Int = 0x000000;
|
||||
public var shadowAlpha(default, set):Float = 0.6;
|
||||
|
||||
private static var K_HEIGHT = 1.185;
|
||||
|
||||
private var bitmap:Bitmap;
|
||||
|
||||
private var _text:String;
|
||||
private var _invalidated:Bool;
|
||||
private var _invalidatedNext:Bool;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
bitmap = new Bitmap();
|
||||
bitmap.smoothing = true;
|
||||
visible = false;
|
||||
addEventListener(Event.ADDED, onAdded);
|
||||
addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
|
||||
addEventListener(Event.REMOVED, onRemoved);
|
||||
addEventListener(Event.ENTER_FRAME, onEnterFrame);
|
||||
}
|
||||
|
||||
private function set_shadow(value) {
|
||||
if (shadow != value) {
|
||||
shadow = value;
|
||||
_invalidated = true;
|
||||
}
|
||||
return shadow;
|
||||
}
|
||||
|
||||
private function set_stroke(value) {
|
||||
if (stroke != value) {
|
||||
stroke = value;
|
||||
_invalidated = true;
|
||||
}
|
||||
return stroke;
|
||||
}
|
||||
|
||||
private function set_shadowColor(value) {
|
||||
if (shadowColor != value) {
|
||||
shadowColor = value;
|
||||
_invalidated = true;
|
||||
}
|
||||
return shadowColor;
|
||||
}
|
||||
|
||||
|
||||
private function set_shadowAlpha(value) {
|
||||
if (shadowAlpha != value) {
|
||||
shadowAlpha = value;
|
||||
_invalidated = true;
|
||||
}
|
||||
return shadowAlpha;
|
||||
}
|
||||
|
||||
private function onAdded(_) {
|
||||
parent.addChild(bitmap);
|
||||
_invalidated = true;
|
||||
}
|
||||
|
||||
private function onAddedToStage(_) {
|
||||
_invalidatedNext = true;
|
||||
}
|
||||
|
||||
private function onRemoved(_) {
|
||||
if (bitmap.parent != null) {
|
||||
bitmap.parent.removeChild(bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
private function onEnterFrame(_) {
|
||||
if (_text != text) {
|
||||
_text = text;
|
||||
_invalidated = true;
|
||||
}
|
||||
if (_invalidated) {
|
||||
redraw();
|
||||
}
|
||||
if (_invalidatedNext) {
|
||||
_invalidated = true;
|
||||
_invalidatedNext = false;
|
||||
}
|
||||
}
|
||||
|
||||
private function redraw() {
|
||||
var size = TextUtil.getSize(this);
|
||||
//ToDo: openfl 3.0.6 -> 3.1.0
|
||||
Timer.delay(function() {
|
||||
bitmap.bitmapData = draw(this, size.x, size.y, shadowColor, shadowAlpha, shadow, stroke);
|
||||
}, 1);
|
||||
bitmap.x = x;
|
||||
bitmap.y = y;
|
||||
_invalidated = false;
|
||||
}
|
||||
|
||||
public static function draw(textField:TextField, textWidth:Float, textHeight:Float, color:Int, alpha:Float, shadow:Bool, stroke:Bool):BitmapData {
|
||||
textField.visible = true;
|
||||
var bd = new BitmapData(Std.int(textWidth) + 6, Std.int(textHeight) + 6, true, 0x00000000);
|
||||
|
||||
if (shadow || stroke) {
|
||||
var baseTf = textField.getTextFormat();
|
||||
var tf = textField.getTextFormat();
|
||||
tf.color = color;
|
||||
textField.setTextFormat(tf);
|
||||
textField.alpha = alpha;
|
||||
|
||||
if (stroke) {
|
||||
for (p in [[1, 2], [2, 1], [1, 0], [0, 1]]) {
|
||||
var m = new Matrix();
|
||||
m.translate(p[0], p[1]);
|
||||
bd.draw(textField, m, null, null, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (shadow) {
|
||||
for (p in [[2, 3], [3, 2], [1, 0], [0, 1]]) {
|
||||
var m = new Matrix();
|
||||
m.translate(p[0], p[1]);
|
||||
bd.draw(textField, m, null, null, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
textField.alpha = 1.0;
|
||||
textField.setTextFormat(baseTf);
|
||||
}
|
||||
|
||||
var m = new Matrix();
|
||||
m.translate(1, 1);
|
||||
bd.draw(textField, m, null, null, null, false);
|
||||
|
||||
textField.visible = false;
|
||||
return bd;
|
||||
}
|
||||
}
|
||||
26
src/main/hw/text/TextUtil.hx
Normal file
26
src/main/hw/text/TextUtil.hx
Normal file
@@ -0,0 +1,26 @@
|
||||
package hw.text;
|
||||
|
||||
import flash.geom.Point;
|
||||
import flash.text.TextField;
|
||||
|
||||
class TextUtil {
|
||||
|
||||
private static var K_HEIGHT = 1.185;
|
||||
|
||||
public static function getSize(textField:TextField):Point {
|
||||
//ToDo: canvas.getContext("2d").measureText very slow
|
||||
#if js
|
||||
var s = 1;
|
||||
var t = textField.text;
|
||||
if (t != null) for (i in 0...t.length) {
|
||||
if (t.charCodeAt(i) == 10) s++;
|
||||
}
|
||||
var _textHeight = (textField.getTextFormat().size + 2) * s * K_HEIGHT;
|
||||
var _textWidth = textField.width;
|
||||
#else
|
||||
var _textWidth = textField.textWidth;
|
||||
var _textHeight = textField.textHeight * K_HEIGHT;
|
||||
#end
|
||||
return new Point(_textWidth, _textHeight);
|
||||
}
|
||||
}
|
||||
9
src/main/hw/translate/ITranslate.hx
Normal file
9
src/main/hw/translate/ITranslate.hx
Normal file
@@ -0,0 +1,9 @@
|
||||
package hw.translate;
|
||||
|
||||
interface ITranslate {
|
||||
public function get(key:String):String;
|
||||
public function format(key:String, args:Array<Dynamic>):String;
|
||||
public function formatPlurar(key:String, num:Int, args:Array<Dynamic>):String;
|
||||
public function getArray(key:String):Array<String>;
|
||||
public function getPlural(key:String, num:Int):String;
|
||||
}
|
||||
81
src/main/hw/translate/Translate.hx
Normal file
81
src/main/hw/translate/Translate.hx
Normal file
@@ -0,0 +1,81 @@
|
||||
package hw.translate;
|
||||
|
||||
class Translate implements ITranslate {
|
||||
|
||||
public var lang(default, null):String;
|
||||
private var data:Dynamic;
|
||||
|
||||
public function new(lang:String, data:Dynamic) {
|
||||
this.lang = lang;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public function format(key:String, args:Array<Dynamic>):String {
|
||||
var string:String = get(key);
|
||||
for (i in 0...args.length) {
|
||||
var arg:Dynamic = args[i];
|
||||
string = StringTools.replace(string, '{$i}', arg);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
public function formatPlurar(key:String, num:Int, args:Array<Dynamic>):String {
|
||||
var string:String = getPlural(key, num);
|
||||
for (i in 0...args.length) {
|
||||
var arg:Dynamic = args[i];
|
||||
string = StringTools.replace(string, '{$i}', arg);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
public function get(key:String):String {
|
||||
var result = getObject(key);
|
||||
return result != null ? result : key;
|
||||
}
|
||||
|
||||
public function getArray(key:String):Array<String> {
|
||||
var result = getObject(key);
|
||||
return result != null ? result : [];
|
||||
}
|
||||
|
||||
public function getPlural(key:String, num:Int):String {
|
||||
var array:Array<String> = getArray(key);
|
||||
switch (lang) {
|
||||
case "ru": return plurarRU(array, num);
|
||||
default: return plurarDefault(array, num);
|
||||
}
|
||||
}
|
||||
|
||||
private function getObject(key:String):Dynamic {
|
||||
var path:Array<String> = key.split(".");
|
||||
var tmp:Dynamic = data;
|
||||
for (i in 0...path.length) {
|
||||
if (Reflect.hasField(tmp, path[i])) {
|
||||
tmp = Reflect.field(tmp, path[i]);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
private function plurarRU(strings:Array<String>, num:Int):String {
|
||||
if (num > 10 && num < 20) return strings[2];
|
||||
var n:Int = num % 10;
|
||||
switch (n) {
|
||||
case 1: return strings[0];
|
||||
case 2: return strings[1];
|
||||
case 3: return strings[1];
|
||||
case 4: return strings[1];
|
||||
default: return strings[2];
|
||||
}
|
||||
}
|
||||
|
||||
private function plurarDefault(strings:Array<String>, num:Int):String {
|
||||
var n:Int = num % 10;
|
||||
switch (n) {
|
||||
case 1: return strings[0];
|
||||
default: return strings[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/main/hw/translate/TranslateString.hx
Normal file
32
src/main/hw/translate/TranslateString.hx
Normal file
@@ -0,0 +1,32 @@
|
||||
package hw.translate;
|
||||
|
||||
import hw.provider.Provider;
|
||||
|
||||
abstract TranslateString(String) from String to String {
|
||||
|
||||
inline public function new(value:String, args:Array<Dynamic> = null) {
|
||||
if (args != null) {
|
||||
this = Provider.get(ITranslate).format(value, args);
|
||||
} else {
|
||||
this = Provider.get(ITranslate).get(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract TranslateArrayString(String) from String to String {
|
||||
|
||||
inline public function new(value:String, index:Int) {
|
||||
this = Provider.get(ITranslate).getArray(value)[index];
|
||||
}
|
||||
}
|
||||
|
||||
abstract TranslatePluralString(String) from String to String {
|
||||
|
||||
inline public function new(value:String, num:Int, args:Array<Dynamic> = null) {
|
||||
if (args != null) {
|
||||
this = Provider.get(ITranslate).formatPlurar(value, num, args);
|
||||
} else {
|
||||
this = Provider.get(ITranslate).getPlural(value, num);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/main/hw/utils/NumberUtil.hx
Executable file
12
src/main/hw/utils/NumberUtil.hx
Executable file
@@ -0,0 +1,12 @@
|
||||
package hw.utils;
|
||||
|
||||
class NumberUtil {
|
||||
|
||||
public static inline function limitInt(value:Int, min:Int, max:Int):Int {
|
||||
return Math.round(Math.max(Math.min(max, value), min));
|
||||
}
|
||||
|
||||
public static inline function limitFloat(value:Float, min:Float, max:Float):Float {
|
||||
return Math.max(Math.min(max, value), min);
|
||||
}
|
||||
}
|
||||
11
src/main/hw/utils/ObjectUtil.hx
Normal file
11
src/main/hw/utils/ObjectUtil.hx
Normal file
@@ -0,0 +1,11 @@
|
||||
package hw.utils;
|
||||
|
||||
import haxe.Unserializer;
|
||||
import haxe.Serializer;
|
||||
|
||||
class ObjectUtil {
|
||||
|
||||
public static function clone<T>(object:T):T {
|
||||
return new Unserializer(Serializer.run(object)).unserialize();
|
||||
}
|
||||
}
|
||||
10
src/main/hw/utils/StringUtil.hx
Normal file
10
src/main/hw/utils/StringUtil.hx
Normal file
@@ -0,0 +1,10 @@
|
||||
package hw.utils;
|
||||
|
||||
using StringTools;
|
||||
|
||||
class StringUtil {
|
||||
|
||||
public static function title(value:String):String {
|
||||
return (value.substr(0, 1).toUpperCase() + value.substr(1)).replace("_", " ");
|
||||
}
|
||||
}
|
||||
50
src/main/hw/view/IView.hx
Executable file
50
src/main/hw/view/IView.hx
Executable file
@@ -0,0 +1,50 @@
|
||||
package hw.view;
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.geom.Rectangle;
|
||||
import hw.view.geometry.Geometry;
|
||||
import hw.view.geometry.SizeSet;
|
||||
import hw.view.group.IGroupView;
|
||||
import hw.view.skin.ISkin;
|
||||
import hw.view.theme.StyleId;
|
||||
|
||||
interface IView<C:DisplayObject> {
|
||||
public var geometry(default, set):Geometry;
|
||||
public var skin(default, set):ISkin<Dynamic>;
|
||||
|
||||
public var id(default, default):String;
|
||||
|
||||
public var x(default, set):Float;
|
||||
public var y(default, set):Float;
|
||||
|
||||
public var width(default, null):Float;
|
||||
public var height(default, null):Float;
|
||||
|
||||
public var size(default, null):SizeSet;
|
||||
|
||||
public var style(default, set):StyleId;
|
||||
|
||||
public var content(default, null):C;
|
||||
|
||||
public var parent(default, null):Null<IGroupView>;
|
||||
|
||||
public var visible(default, set):Bool;
|
||||
public var index(default, set):Int;
|
||||
public var mouseEnabled(default, set):Bool;
|
||||
|
||||
public var rect(get, null):Rectangle;
|
||||
|
||||
public function update():Void;
|
||||
|
||||
public function redraw():Void;
|
||||
|
||||
public function toUpdate():Void;
|
||||
|
||||
public function toUpdateParent():Void;
|
||||
|
||||
public function toRedraw():Void;
|
||||
|
||||
public function remove():Void;
|
||||
|
||||
public function setSize(width:Float, height:Float, type:String = "default"):Void;
|
||||
}
|
||||
73
src/main/hw/view/ImageView.hx
Executable file
73
src/main/hw/view/ImageView.hx
Executable file
@@ -0,0 +1,73 @@
|
||||
package hw.view;
|
||||
|
||||
import flash.display.BitmapData;
|
||||
import hw.net.ImageLoader;
|
||||
import hw.view.skin.BitmapSkin;
|
||||
import hw.view.utils.BitmapUtil;
|
||||
import hw.view.utils.DrawUtil.FillType;
|
||||
|
||||
class ImageView extends SpriteView {
|
||||
|
||||
public var image(default, set):BitmapData;
|
||||
public var imageUrl(default, set):String;
|
||||
public var color(default, set):Int = -1;
|
||||
public var fillType(default, set):FillType;
|
||||
public var stretch:Bool = true;
|
||||
|
||||
private var bitmapSkin:BitmapSkin = new BitmapSkin();
|
||||
private var coloredImage:BitmapData;
|
||||
|
||||
public function new(image:BitmapData = null) {
|
||||
super();
|
||||
fillType = FillType.DEFAULT;
|
||||
skin = bitmapSkin;
|
||||
if (image != null) {
|
||||
this.image = image;
|
||||
}
|
||||
}
|
||||
|
||||
private function set_image(value:BitmapData):BitmapData {
|
||||
if (image != value) {
|
||||
if (stretch) {
|
||||
setSize(value.width, value.height, "image");
|
||||
}
|
||||
image = value;
|
||||
if (color > -1) {
|
||||
coloredImage = BitmapUtil.colorize(image, color);
|
||||
}
|
||||
bitmapSkin.image = coloredImage == null ? image : coloredImage;
|
||||
toRedraw();
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
private function set_imageUrl(value:String):String {
|
||||
if (imageUrl != value) {
|
||||
imageUrl = value;
|
||||
new ImageLoader().GET(imageUrl).then(function(data) {
|
||||
image = data;
|
||||
}).catchError(function(e) L.w("ImageView", "load", e));
|
||||
}
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
private function set_fillType(value:FillType):FillType {
|
||||
if (fillType != value) {
|
||||
bitmapSkin.fillType = fillType = value;
|
||||
toRedraw();
|
||||
}
|
||||
return fillType;
|
||||
}
|
||||
|
||||
private function set_color(value:Int):Int {
|
||||
if (color != value) {
|
||||
color = value;
|
||||
if (image != null) {
|
||||
coloredImage = BitmapUtil.colorize(image, color);
|
||||
bitmapSkin.image = coloredImage;
|
||||
}
|
||||
toRedraw();
|
||||
}
|
||||
return color;
|
||||
}
|
||||
}
|
||||
57
src/main/hw/view/MovieView.hx
Executable file
57
src/main/hw/view/MovieView.hx
Executable file
@@ -0,0 +1,57 @@
|
||||
package hw.view;
|
||||
|
||||
import hw.net.SwfLoader;
|
||||
import flash.display.MovieClip;
|
||||
|
||||
//ToDo: sprite wrapper?
|
||||
class MovieView extends View<MovieClip> {
|
||||
|
||||
public var movie(get, set):MovieClip;
|
||||
public var movieUrl(default, set):String;
|
||||
|
||||
public function new(?movie:MovieClip) {
|
||||
super(movie);
|
||||
}
|
||||
|
||||
private function get_movie():MovieClip {
|
||||
return cast content;
|
||||
}
|
||||
|
||||
private function set_movie(value:MovieClip):MovieClip {
|
||||
var index:Int = 0;
|
||||
if (parent != null && content != null) {
|
||||
index = parent.container.getChildIndex(content);
|
||||
parent.container.removeChild(content);
|
||||
}
|
||||
content = value;
|
||||
content.visible = visible;
|
||||
if (parent != null) {
|
||||
parent.container.addChildAt(content, index);
|
||||
}
|
||||
invalidate();
|
||||
return cast content;
|
||||
}
|
||||
|
||||
private function set_movieUrl(value:String):String {
|
||||
movieUrl = value;
|
||||
new SwfLoader().GET(movieUrl)
|
||||
.then(function(data:MovieClip):Void {
|
||||
movie = data;
|
||||
});
|
||||
return movieUrl;
|
||||
}
|
||||
|
||||
override public function update():Void {
|
||||
if (contentSize && content != null) {
|
||||
width = content.loaderInfo.width;
|
||||
height = content.loaderInfo.height;
|
||||
}
|
||||
super.update();
|
||||
if (!contentSize && content != null) {
|
||||
var s:Float = Math.min(width / content.loaderInfo.width, height / content.loaderInfo.height);
|
||||
content.scaleX = content.scaleY = s;
|
||||
content.x = (width - content.loaderInfo.width * s) / 2;
|
||||
content.y = (height - content.loaderInfo.height * s) / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
91
src/main/hw/view/Root.hx
Executable file
91
src/main/hw/view/Root.hx
Executable file
@@ -0,0 +1,91 @@
|
||||
package hw.view;
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.display.StageAlign;
|
||||
import flash.display.StageScaleMode;
|
||||
import flash.errors.Error;
|
||||
import flash.events.Event;
|
||||
import flash.geom.Rectangle;
|
||||
import flash.Lib;
|
||||
import hw.provider.Provider;
|
||||
import hw.signal.Signal;
|
||||
import hw.view.group.IGroupView;
|
||||
|
||||
class Root {
|
||||
|
||||
public static function bind(view:IView<Dynamic>, autoSize:Bool = true) {
|
||||
new Root(view, autoSize);
|
||||
}
|
||||
|
||||
public static var instance(default, null):Root;
|
||||
|
||||
public var view(default, null):IView<Dynamic>;
|
||||
public var autoSize(default, default):Bool;
|
||||
public var onResize(default, null):Signal<Rectangle> = new Signal();
|
||||
|
||||
public function new(view:IView<Dynamic>, autoSize:Bool = true) {
|
||||
if (instance != null) throw new Error("Only one instance");
|
||||
instance = this;
|
||||
this.view = view;
|
||||
this.autoSize = autoSize;
|
||||
Lib.current.addChild(view.content);
|
||||
if (Std.is(view, IGroupView)) {
|
||||
Provider.instance.set(IGroupView, cast view);
|
||||
}
|
||||
var content:DisplayObject = view.content;
|
||||
if (content.stage == null) {
|
||||
content.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
|
||||
} else {
|
||||
onAddedToStage();
|
||||
}
|
||||
View.updater.update();
|
||||
}
|
||||
|
||||
private function onAddedToStage(?_):Void {
|
||||
var content:DisplayObject = view.content;
|
||||
content.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
|
||||
content.stage.scaleMode = StageScaleMode.NO_SCALE;
|
||||
content.stage.align = StageAlign.TOP_LEFT;
|
||||
View.updater.stage = content.stage;
|
||||
|
||||
content.stage.addEventListener(Event.RESIZE, onResizeEvent);
|
||||
content.stage.stageFocusRect = false;
|
||||
onResizeEvent();
|
||||
}
|
||||
|
||||
private function onResizeEvent(?_):Void {
|
||||
var content:DisplayObject = view.content;
|
||||
if (autoSize) {
|
||||
view.setSize(content.stage.stageWidth, content.stage.stageHeight, "stage");
|
||||
view.toUpdate();
|
||||
} else {
|
||||
view.x = (content.stage.stageWidth - view.width) / 2;
|
||||
view.y = (content.stage.stageHeight - view.height) / 2;
|
||||
}
|
||||
onResize.emit(new Rectangle(0, 0, content.stage.stageWidth, content.stage.stageHeight));
|
||||
}
|
||||
|
||||
public function dispose():Void {
|
||||
if (view != null) {
|
||||
view.content.stage.removeEventListener(Event.RESIZE, onResizeEvent);
|
||||
view = null;
|
||||
}
|
||||
onResize.dispose();
|
||||
}
|
||||
|
||||
private static function getViews(view:IView<Dynamic>):Array<IView<Dynamic>> {
|
||||
var result = [];
|
||||
result.push(view);
|
||||
if (Std.is(view, IGroupView)) {
|
||||
for (v in cast(view, IGroupView).views) {
|
||||
result = result.concat(getViews(v));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public function views():Array<IView<Dynamic>> {
|
||||
return getViews(view);
|
||||
}
|
||||
|
||||
}
|
||||
23
src/main/hw/view/SpriteView.hx
Executable file
23
src/main/hw/view/SpriteView.hx
Executable file
@@ -0,0 +1,23 @@
|
||||
package hw.view;
|
||||
|
||||
import flash.display.Sprite;
|
||||
import hw.view.skin.SpriteSkin;
|
||||
|
||||
class SpriteView extends View<Sprite> {
|
||||
|
||||
public function new() {
|
||||
super(new Sprite());
|
||||
skin = new SpriteSkin();
|
||||
}
|
||||
|
||||
override public function redraw():Void {
|
||||
this.content.graphics.clear(); // ToDo: in skin
|
||||
super.redraw();
|
||||
#if dev_layout
|
||||
var graphics = content.graphics;
|
||||
graphics.lineStyle(1, 0x00ff00);
|
||||
graphics.drawRect(0, 0, width, height);
|
||||
graphics.lineStyle();
|
||||
#end
|
||||
}
|
||||
}
|
||||
160
src/main/hw/view/View.hx
Executable file
160
src/main/hw/view/View.hx
Executable file
@@ -0,0 +1,160 @@
|
||||
package hw.view;
|
||||
|
||||
import flash.Lib;
|
||||
import flash.display.Stage;
|
||||
import flash.display.DisplayObject;
|
||||
import flash.display.InteractiveObject;
|
||||
import flash.geom.Rectangle;
|
||||
import hw.view.geometry.Geometry;
|
||||
import hw.view.geometry.SizeSet;
|
||||
import hw.view.group.IGroupView;
|
||||
import hw.view.skin.ISkin;
|
||||
import hw.view.theme.ITheme;
|
||||
|
||||
@:style class View<C:DisplayObject> implements IView<C> {
|
||||
|
||||
private static var counter:Int = 0;
|
||||
public static var updater(default, null):ViewUpdater = new ViewUpdater();
|
||||
|
||||
@:provide var theme:ITheme;
|
||||
|
||||
@:style public var geometry(default, default):Geometry;
|
||||
@:style public var skin(default, default):ISkin<Dynamic>;
|
||||
|
||||
public var id(default, default):String;
|
||||
|
||||
public var x(default, set):Float;
|
||||
public var y(default, set):Float;
|
||||
|
||||
public var width(default, null):Float;
|
||||
public var height(default, null):Float;
|
||||
|
||||
public var size(default, null):SizeSet;
|
||||
|
||||
public var content(default, null):C;
|
||||
|
||||
public var parent(default, null):Null<IGroupView>;
|
||||
|
||||
public var visible(default, set):Bool;
|
||||
public var index(default, set):Int;
|
||||
public var mouseEnabled(default, set):Bool = true;
|
||||
|
||||
public var rect(get, null):Rectangle;
|
||||
|
||||
private var stage(get, null):Stage;
|
||||
|
||||
private function get_stage():Stage {
|
||||
return Lib.current.stage;
|
||||
}
|
||||
|
||||
public function new(content:C) {
|
||||
id = Type.getClassName(Type.getClass(this)) + counter++;
|
||||
size = new SizeSet();
|
||||
this.content = content;
|
||||
x = 0;
|
||||
y = 0;
|
||||
width = 1;
|
||||
height = 1;
|
||||
geometry = new Geometry();
|
||||
visible = true;
|
||||
index = -1;
|
||||
}
|
||||
|
||||
private function onStyle():Void {
|
||||
toUpdate();
|
||||
toRedraw();
|
||||
}
|
||||
|
||||
public function toRedraw():Void {
|
||||
updater.toRedraw(this);
|
||||
}
|
||||
|
||||
public function toUpdate():Void {
|
||||
updater.toUpdate(this);
|
||||
}
|
||||
|
||||
public function toUpdateParent():Void {
|
||||
if (parent != null) {
|
||||
updater.toUpdate(parent);
|
||||
}
|
||||
}
|
||||
|
||||
public function update():Void {
|
||||
setSize(
|
||||
geometry.width.fixed - geometry.padding.horizontal,
|
||||
geometry.height.fixed - geometry.padding.vertical,
|
||||
"geometry"
|
||||
);
|
||||
}
|
||||
|
||||
public function redraw():Void {
|
||||
if (skin != null) {
|
||||
skin.draw(this);
|
||||
}
|
||||
}
|
||||
|
||||
public function setSize(width:Float, height:Float, type:String = "default"):Void {
|
||||
if (size.update([width, height], type)) {
|
||||
var s = size.toSize();
|
||||
this.width = s.width + geometry.padding.horizontal;
|
||||
this.height = s.height + geometry.padding.vertical;
|
||||
toUpdateParent();
|
||||
toRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
public function remove():Void {
|
||||
if (parent != null) parent.removeView(this);
|
||||
}
|
||||
|
||||
private function set_x(value:Float):Float {
|
||||
if (x != value) {
|
||||
x = content.x = value;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
private function set_y(value:Float):Float {
|
||||
if (y != value) {
|
||||
y = content.y = value;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
private function set_visible(value:Bool):Bool {
|
||||
if (visible != value) {
|
||||
visible = value;
|
||||
if (content != null) content.visible = visible;
|
||||
}
|
||||
return visible;
|
||||
}
|
||||
|
||||
private function set_index(value:Int):Int {
|
||||
if (index != value) {
|
||||
index = value;
|
||||
toUpdateParent();
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
private function set_mouseEnabled(value:Bool):Bool {
|
||||
if (mouseEnabled != value) {
|
||||
mouseEnabled = value;
|
||||
if (content != null && Std.is(content, InteractiveObject)) {
|
||||
cast(content, InteractiveObject).mouseEnabled = mouseEnabled;
|
||||
}
|
||||
}
|
||||
return mouseEnabled;
|
||||
}
|
||||
|
||||
private function get_rect():Rectangle {
|
||||
var x = this.x;
|
||||
var y = this.y;
|
||||
if (parent != null) {
|
||||
var rect = parent.rect;
|
||||
x += rect.x;
|
||||
y += rect.y;
|
||||
}
|
||||
return new Rectangle(x, y, width, height);
|
||||
}
|
||||
}
|
||||
55
src/main/hw/view/ViewUpdater.hx
Normal file
55
src/main/hw/view/ViewUpdater.hx
Normal file
@@ -0,0 +1,55 @@
|
||||
package hw.view;
|
||||
|
||||
import flash.events.Event;
|
||||
import flash.display.Stage;
|
||||
|
||||
class ViewUpdater {
|
||||
public var stage(null, set):Stage;
|
||||
private var updateViews:Array<IView<Dynamic>>;
|
||||
private var redrawViews:Array<IView<Dynamic>>;
|
||||
|
||||
public function new() {
|
||||
updateViews = [];
|
||||
redrawViews = [];
|
||||
}
|
||||
|
||||
private function set_stage(value:Stage):Stage {
|
||||
value.addEventListener(Event.ENTER_FRAME, onEnterFrame);
|
||||
return value;
|
||||
}
|
||||
|
||||
public function toUpdate(view:IView<Dynamic>):Void {
|
||||
if (updateViews.indexOf(view) == -1) updateViews.push(view);
|
||||
}
|
||||
|
||||
public function toRedraw(view:IView<Dynamic>):Void {
|
||||
if (redrawViews.indexOf(view) == -1) redrawViews.push(view);
|
||||
}
|
||||
|
||||
private function onEnterFrame(_):Void {
|
||||
update();
|
||||
redraw();
|
||||
if (updateViews.length > 0) {
|
||||
update();
|
||||
redraw();
|
||||
}
|
||||
}
|
||||
|
||||
public function isUpdate(view:IView<Dynamic>):Bool {
|
||||
return updateViews.indexOf(view) > -1;
|
||||
}
|
||||
|
||||
public function update():Void {
|
||||
while (updateViews.length > 0) {
|
||||
var v = updateViews.shift();
|
||||
v.update();
|
||||
}
|
||||
}
|
||||
|
||||
public function redraw():Void {
|
||||
while (redrawViews.length > 0) {
|
||||
var v = redrawViews.shift();
|
||||
v.redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
76
src/main/hw/view/data/ButtonGroup.hx
Normal file
76
src/main/hw/view/data/ButtonGroup.hx
Normal file
@@ -0,0 +1,76 @@
|
||||
package hw.view.data;
|
||||
|
||||
import hw.view.theme.StyleId;
|
||||
import hw.view.form.ToggleButtonView;
|
||||
import hw.view.data.DataView.Factory;
|
||||
import haxe.EnumTools;
|
||||
import hw.view.layout.HorizontalLayout;
|
||||
import hw.view.layout.ILayout;
|
||||
|
||||
using hw.utils.StringUtil;
|
||||
|
||||
class ButtonGroup<D> extends DataView<D, ToggleButtonView> {
|
||||
|
||||
public var selected(default, set):D;
|
||||
public var buttonStyle(default, set):StyleId;
|
||||
|
||||
public function new(?layout:ILayout) {
|
||||
super(layout != null ? layout : new HorizontalLayout());
|
||||
factory = buildFactory();
|
||||
onDataSelect.connect(function(value:D) selected = value);
|
||||
}
|
||||
|
||||
// ToDo: enum equals
|
||||
private function dataIndex(value:D):Int {
|
||||
for (i in 0...data.length) {
|
||||
if (Reflect.isEnumValue(value)) {
|
||||
if (EnumValueTools.equals(cast value, cast data[i])) {
|
||||
return i;
|
||||
}
|
||||
} else {
|
||||
if (value == data[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private function set_selected(value:D):D {
|
||||
selected = value;
|
||||
var index = dataIndex(value);
|
||||
for (i in 0...dataViews.length) {
|
||||
var view = dataViews[i];
|
||||
view.on = i == index;
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
private function set_buttonStyle(value:StyleId):StyleId {
|
||||
if (buttonStyle != value) {
|
||||
buttonStyle = value;
|
||||
for (view in dataViews) {
|
||||
view.style = buttonStyle;
|
||||
}
|
||||
}
|
||||
return buttonStyle;
|
||||
}
|
||||
|
||||
override private function rebuild():Void {
|
||||
super.rebuild();
|
||||
for (view in dataViews) {
|
||||
view.style = buttonStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public static function buildFactory<D>(?label:D -> String):Factory<D, ToggleButtonView> {
|
||||
if (label == null) {
|
||||
label = function(value:D) return Std.string(value).title();
|
||||
}
|
||||
return function(index:Int, value:D):ToggleButtonView {
|
||||
var result = new ToggleButtonView();
|
||||
result.text = label(value);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/main/hw/view/data/DataView.hx
Normal file
61
src/main/hw/view/data/DataView.hx
Normal file
@@ -0,0 +1,61 @@
|
||||
package hw.view.data;
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.events.MouseEvent;
|
||||
import hw.signal.Signal;
|
||||
import hw.view.group.GroupView;
|
||||
import hw.view.layout.ILayout;
|
||||
import hw.view.layout.VerticalLayout;
|
||||
|
||||
typedef Factory<D, V:IView<Dynamic>> = Int -> D -> V
|
||||
|
||||
class ActionDataView<D, V:IView<Dynamic>, A> extends DataView<D, V> {
|
||||
public var onDataAction(default, null):Signal2<D, A> = new Signal2();
|
||||
}
|
||||
|
||||
class DataView<D, V:IView<Dynamic>> extends GroupView {
|
||||
|
||||
public var data(default, set):Array<D>;
|
||||
public var factory(default, set):Factory<D, V>;
|
||||
public var onItemSelect(default, null):Signal3<Int, D, V> = new Signal3();
|
||||
public var onDataSelect(default, null):Signal<D> = new Signal();
|
||||
public var dataViews(default, null):Array<V>;
|
||||
|
||||
private var objectIndexes:Map<DisplayObject, Int> = new Map();
|
||||
|
||||
public function new(?layout:ILayout) {
|
||||
super(layout != null ? layout : new VerticalLayout());
|
||||
dataViews = [];
|
||||
}
|
||||
|
||||
private function set_data(value:Array<D>):Array<D> {
|
||||
data = value;
|
||||
if (factory != null) rebuild();
|
||||
return data;
|
||||
}
|
||||
|
||||
private function set_factory(value:Factory<D, V>):Factory<D, V> {
|
||||
factory = value;
|
||||
if (data != null) rebuild();
|
||||
return factory;
|
||||
}
|
||||
|
||||
private function rebuild():Void {
|
||||
for (view in dataViews) {
|
||||
view.content.removeEventListener(MouseEvent.CLICK, onItemClick);
|
||||
}
|
||||
objectIndexes = new Map();
|
||||
dataViews = Lambda.array(Lambda.mapi(data, factory));
|
||||
views = cast dataViews;
|
||||
for (i in 0...dataViews.length) {
|
||||
objectIndexes[dataViews[i].content] = i;
|
||||
dataViews[i].content.addEventListener(MouseEvent.CLICK, onItemClick);
|
||||
}
|
||||
}
|
||||
|
||||
private function onItemClick(event:MouseEvent):Void {
|
||||
var index = objectIndexes[event.currentTarget];
|
||||
onDataSelect.emit(data[index]);
|
||||
onItemSelect.emit(index, data[index], dataViews[index]);
|
||||
}
|
||||
}
|
||||
37
src/main/hw/view/form/ButtonImageView.hx
Normal file
37
src/main/hw/view/form/ButtonImageView.hx
Normal file
@@ -0,0 +1,37 @@
|
||||
package hw.view.form;
|
||||
|
||||
import flash.display.BitmapData;
|
||||
import hw.view.skin.ButtonBitmapSkin;
|
||||
import hw.view.skin.ISkin;
|
||||
import hw.view.utils.BitmapUtil;
|
||||
import hw.view.utils.DrawUtil.FillType;
|
||||
import hw.net.ImageLoader;
|
||||
|
||||
class ButtonImageView extends ButtonView {
|
||||
|
||||
public var image(default, set):BitmapData;
|
||||
private var bitmapSkin:ButtonBitmapSkin = new ButtonBitmapSkin();
|
||||
|
||||
public function new(image:BitmapData = null) {
|
||||
super();
|
||||
if (image != null) {
|
||||
this.image = image;
|
||||
}
|
||||
}
|
||||
|
||||
override private function set_skin(value:SkinSet):SkinSet {
|
||||
value = value.slice(0);
|
||||
value.unshift(bitmapSkin);
|
||||
return super.set_skin(value);
|
||||
}
|
||||
|
||||
private function set_image(value:BitmapData):BitmapData {
|
||||
if (image != value) {
|
||||
setContentSize(value.width, value.height, "image");
|
||||
image = value;
|
||||
bitmapSkin.image = image;
|
||||
toRedraw();
|
||||
}
|
||||
return image;
|
||||
}
|
||||
}
|
||||
110
src/main/hw/view/form/ButtonView.hx
Executable file
110
src/main/hw/view/form/ButtonView.hx
Executable file
@@ -0,0 +1,110 @@
|
||||
package hw.view.form;
|
||||
|
||||
import hw.view.skin.ButtonColorSkin;
|
||||
import hw.signal.Signal;
|
||||
import flash.events.MouseEvent;
|
||||
|
||||
enum ButtonState {
|
||||
UP;
|
||||
OVER;
|
||||
DOWN;
|
||||
DISABLED;
|
||||
}
|
||||
|
||||
class ButtonView extends LabelView {
|
||||
|
||||
public static var TYPE = "button";
|
||||
|
||||
public var disabled(default, set):Bool;
|
||||
public var state(get, null):ButtonState;
|
||||
public var onPress(default, null):Signal<ButtonView>;
|
||||
public var propagation(default, default):Bool;
|
||||
|
||||
private var overed:Bool;
|
||||
private var downed:Bool;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
skin = new ButtonColorSkin();
|
||||
style = "button";
|
||||
propagation = true;
|
||||
overed = false;
|
||||
downed = false;
|
||||
state = ButtonState.UP;
|
||||
onPress = new Signal();
|
||||
content.buttonMode = true;
|
||||
content.mouseChildren = false;
|
||||
#if js
|
||||
content.addEventListener(MouseEvent.MOUSE_UP, onMouseClick);
|
||||
#else
|
||||
content.addEventListener(MouseEvent.CLICK, onMouseClick);
|
||||
#end
|
||||
#if !mobile
|
||||
content.addEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
|
||||
content.addEventListener(MouseEvent.MOUSE_OUT, onMouseOut);
|
||||
#end
|
||||
content.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
|
||||
content.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
|
||||
}
|
||||
|
||||
private function onMouseClick(event:MouseEvent):Void {
|
||||
#if js if (downed) { #end
|
||||
if (!propagation) event.stopImmediatePropagation();
|
||||
if (!disabled) onPress.emit(this);
|
||||
#if js } #end
|
||||
}
|
||||
|
||||
private function onMouseOver(event:MouseEvent):Void {
|
||||
overed = true;
|
||||
toRedraw();
|
||||
}
|
||||
|
||||
private function onMouseOut(event:MouseEvent):Void {
|
||||
overed = false;
|
||||
toRedraw();
|
||||
}
|
||||
|
||||
private function onMouseDown(event:MouseEvent):Void {
|
||||
downed = true;
|
||||
if (content.stage != null) {
|
||||
content.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
|
||||
toRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
private function onMouseUp(event:MouseEvent):Void {
|
||||
downed = false;
|
||||
if (content.stage != null) {
|
||||
content.stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
|
||||
toRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
private function set_disabled(value:Bool):Bool {
|
||||
if (disabled != value) {
|
||||
disabled = value;
|
||||
content.buttonMode = !disabled;
|
||||
toRedraw();
|
||||
}
|
||||
return disabled;
|
||||
}
|
||||
|
||||
private function get_state():ButtonState {
|
||||
if (disabled) return DISABLED;
|
||||
#if mobile
|
||||
return downed ? DOWN : UP;
|
||||
#else
|
||||
return (downed && overed) ? DOWN : overed ? OVER : UP;
|
||||
#end
|
||||
}
|
||||
|
||||
public function dispose():Void {
|
||||
onPress.dispose();
|
||||
content.removeEventListener(MouseEvent.CLICK, onMouseClick);
|
||||
content.removeEventListener(MouseEvent.MOUSE_UP, onMouseClick);
|
||||
content.removeEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
|
||||
content.removeEventListener(MouseEvent.MOUSE_OUT, onMouseOut);
|
||||
content.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
|
||||
content.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
|
||||
}
|
||||
}
|
||||
80
src/main/hw/view/form/InputView.hx
Executable file
80
src/main/hw/view/form/InputView.hx
Executable file
@@ -0,0 +1,80 @@
|
||||
package hw.view.form;
|
||||
|
||||
import hw.view.text.TextView;
|
||||
import flash.events.Event;
|
||||
import flash.events.KeyboardEvent;
|
||||
import flash.text.TextField;
|
||||
import flash.text.TextFieldAutoSize;
|
||||
import flash.text.TextFieldType;
|
||||
import flash.text.TextFormat;
|
||||
import flash.text.TextFormatAlign;
|
||||
import hw.signal.Signal;
|
||||
|
||||
class InputView extends TextView {
|
||||
|
||||
public var hint(default, set):String;
|
||||
public var onChange(default, null):Signal<String> = new Signal();
|
||||
|
||||
private var hintTextField:TextField;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
style = "input";
|
||||
textField.type = TextFieldType.INPUT;
|
||||
textField.addEventListener(Event.CHANGE, onTextChange);
|
||||
textField.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
|
||||
textField.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
|
||||
|
||||
hintTextField = buildHintTextField();
|
||||
content.addChild(hintTextField);
|
||||
font.align = TextFormatAlign.LEFT;
|
||||
}
|
||||
|
||||
private function set_hint(value:String):String {
|
||||
if (hint != value) {
|
||||
hint = value;
|
||||
toUpdate();
|
||||
}
|
||||
return hint;
|
||||
}
|
||||
|
||||
private function buildHintTextField():TextField {
|
||||
var textField:TextField = new TextField();
|
||||
textField.autoSize = TextFieldAutoSize.NONE;
|
||||
textField.type = TextFieldType.DYNAMIC;
|
||||
textField.multiline = false;
|
||||
textField.defaultTextFormat = new TextFormat("Arial", 16, 0xa0a0a0);
|
||||
textField.mouseEnabled = false;
|
||||
return textField;
|
||||
}
|
||||
|
||||
private function onTextChange(event:Event):Void {
|
||||
hintTextField.visible = (textField.text == "");
|
||||
}
|
||||
|
||||
private function onKeyUp(event:KeyboardEvent):Void {
|
||||
event.stopImmediatePropagation();
|
||||
text = textField.text;
|
||||
onChange.emit(_text);
|
||||
}
|
||||
|
||||
private function onKeyDown(event:KeyboardEvent):Void {
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
override public function update():Void {
|
||||
super.update();
|
||||
var htf:TextFormat = textField.defaultTextFormat;
|
||||
htf.color = 0xa0a0a0;
|
||||
htf.size -= 2;
|
||||
hintTextField.defaultTextFormat = htf;
|
||||
hintTextField.text = hint == null ? "" : hint;
|
||||
placeTextField(hintTextField);
|
||||
}
|
||||
|
||||
public function dispose():Void {
|
||||
textField.removeEventListener(Event.CHANGE, onTextChange);
|
||||
textField.removeEventListener(KeyboardEvent.KEY_UP, onKeyUp);
|
||||
textField.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
|
||||
}
|
||||
}
|
||||
19
src/main/hw/view/form/LabelView.hx
Executable file
19
src/main/hw/view/form/LabelView.hx
Executable file
@@ -0,0 +1,19 @@
|
||||
package hw.view.form;
|
||||
|
||||
import hw.view.geometry.HAlign;
|
||||
import hw.view.geometry.VAlign;
|
||||
import hw.view.text.TextView;
|
||||
|
||||
class LabelView extends TextView {
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
style = "label";
|
||||
fill = false;
|
||||
textField.selectable = false;
|
||||
textField.wordWrap = false;
|
||||
textField.multiline = true;
|
||||
layout.hAlign = HAlign.CENTER;
|
||||
layout.vAlign = VAlign.MIDDLE;
|
||||
}
|
||||
}
|
||||
157
src/main/hw/view/form/SelectView.hx
Normal file
157
src/main/hw/view/form/SelectView.hx
Normal file
@@ -0,0 +1,157 @@
|
||||
package hw.view.form;
|
||||
|
||||
import flash.events.MouseEvent;
|
||||
import flash.geom.Point;
|
||||
import hw.signal.Signal;
|
||||
import hw.view.data.DataView;
|
||||
import hw.view.geometry.HAlign;
|
||||
import hw.view.geometry.Position;
|
||||
import hw.view.group.GroupView;
|
||||
import hw.view.group.IGroupView;
|
||||
import hw.view.layout.VerticalLayout;
|
||||
import hw.view.skin.Skin;
|
||||
import hw.view.theme.StyleId;
|
||||
using hw.utils.StringUtil;
|
||||
|
||||
class SelectIdView<D, K> extends SelectView<D> {
|
||||
public var selectedId(get, set):K;
|
||||
public var idResolver(default, default):D -> K;
|
||||
|
||||
private var _selectedId:K;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
idResolver = function(item:D):K return Reflect.field(item, "id");
|
||||
}
|
||||
|
||||
override private function set_selected(value:D):D {
|
||||
var result = super.set_selected(value);
|
||||
_selectedId = idResolver(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private inline function get_selectedId():K {
|
||||
return _selectedId;
|
||||
}
|
||||
|
||||
private function set_selectedId(value:K):K {
|
||||
if (_selectedId != value) {
|
||||
_selectedId = value;
|
||||
if (data != null) {
|
||||
for (item in data) {
|
||||
if (idResolver(item) == _selectedId) {
|
||||
selected = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return _selectedId;
|
||||
}
|
||||
}
|
||||
|
||||
class SelectView<D> extends GroupView {
|
||||
|
||||
public var currentView(default, null):ButtonView;
|
||||
public var dataView(default, null):DataView<D, LabelView>;
|
||||
|
||||
public var labelStyle(default, set):StyleId;
|
||||
public var labelBuilder(default, set):D -> String;
|
||||
public var data(default, set):Array<D>;
|
||||
public var selected(default, set):D;
|
||||
public var onSelect(default, null):Signal<D> = new Signal();
|
||||
|
||||
@:provide private static var root:IGroupView;
|
||||
|
||||
public function new() {
|
||||
super(new VerticalLayout());
|
||||
skin = Skin.transparent();
|
||||
currentView = new ButtonView();
|
||||
currentView.onPress.connect(function(_) toggle());
|
||||
dataView = new DataView();
|
||||
dataView.style = "dropdown";
|
||||
dataView.geometry.position = ABSOLUTE;
|
||||
dataView.factory = factory;
|
||||
dataView.onDataSelect.connect(function(value:D):Void {
|
||||
selected = value;
|
||||
close();
|
||||
});
|
||||
views = [currentView];
|
||||
labelBuilder = function(value:D):String return Std.string(value).title();
|
||||
}
|
||||
|
||||
private function factory(index:Int, value:D):LabelView {
|
||||
var result = new LabelView();
|
||||
result.layout.hAlign = LEFT;
|
||||
result.geometry.width.percent = 100;
|
||||
if (labelStyle != null) {
|
||||
result.style = labelStyle;
|
||||
}
|
||||
result.text = labelBuilder(value);
|
||||
return result;
|
||||
}
|
||||
|
||||
private function set_labelStyle(value:StyleId):StyleId {
|
||||
if (labelStyle != value) {
|
||||
labelStyle = value;
|
||||
currentView.style = labelStyle;
|
||||
for (view in dataView.dataViews) {
|
||||
view.style = labelStyle;
|
||||
}
|
||||
}
|
||||
return labelStyle;
|
||||
}
|
||||
|
||||
private function set_labelBuilder(value:D -> String):D -> String {
|
||||
labelBuilder = value;
|
||||
for (i in 0...dataView.dataViews.length) {
|
||||
dataView.dataViews[i].text = labelBuilder(data[i]);
|
||||
}
|
||||
return labelBuilder;
|
||||
}
|
||||
|
||||
private function set_data(value:Array<D>):Array<D> {
|
||||
data = value;
|
||||
dataView.data = value;
|
||||
return data;
|
||||
}
|
||||
|
||||
private function set_selected(value:D):D {
|
||||
selected = value;
|
||||
currentView.text = labelBuilder(selected);
|
||||
onSelect.emit(selected);
|
||||
return selected;
|
||||
}
|
||||
|
||||
public function toggle():Void {
|
||||
if (root.containsView(dataView)) {
|
||||
close();
|
||||
} else {
|
||||
open();
|
||||
}
|
||||
}
|
||||
|
||||
public function open():Void {
|
||||
root.addView(dataView);
|
||||
content.stage.addEventListener(MouseEvent.MOUSE_DOWN, onStageMouseClick);
|
||||
}
|
||||
|
||||
public function close():Void {
|
||||
content.stage.removeEventListener(MouseEvent.MOUSE_DOWN, onStageMouseClick);
|
||||
root.removeView(dataView);
|
||||
}
|
||||
|
||||
override public function update():Void {
|
||||
super.update();
|
||||
var rect = currentView.rect;
|
||||
dataView.geometry.margin.left = rect.left;
|
||||
dataView.geometry.margin.top = rect.top + rect.height + layout.margin;
|
||||
}
|
||||
|
||||
private function onStageMouseClick(event:MouseEvent):Void {
|
||||
var objects = content.stage.getObjectsUnderPoint(new Point(event.stageX, event.stageY));
|
||||
if (objects.indexOf(content) == -1 && objects.indexOf(dataView.content) == -1) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/main/hw/view/form/ToggleButtonView.hx
Executable file
31
src/main/hw/view/form/ToggleButtonView.hx
Executable file
@@ -0,0 +1,31 @@
|
||||
package hw.view.form;
|
||||
|
||||
class ToggleButtonView extends ButtonView {
|
||||
|
||||
public var on(default, set):Bool;
|
||||
|
||||
public var onText(default, set):String;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
}
|
||||
|
||||
private function set_on(value:Bool):Bool {
|
||||
on = value;
|
||||
toUpdate();
|
||||
toRedraw();
|
||||
return on;
|
||||
}
|
||||
|
||||
private function set_onText(value:String):String {
|
||||
if (onText != value) {
|
||||
onText = value;
|
||||
toUpdate();
|
||||
}
|
||||
return onText;
|
||||
}
|
||||
|
||||
override private function currentText():String {
|
||||
return on && onText != null ? onText : super.currentText();
|
||||
}
|
||||
}
|
||||
93
src/main/hw/view/frame/FrameSwitcher.hx
Executable file
93
src/main/hw/view/frame/FrameSwitcher.hx
Executable file
@@ -0,0 +1,93 @@
|
||||
package hw.view.frame;
|
||||
|
||||
import hw.animate.IAnimate;
|
||||
import hw.signal.Signal;
|
||||
import hw.view.group.GroupView;
|
||||
import hw.view.IView;
|
||||
|
||||
class FrameSwitcher extends GroupView {
|
||||
|
||||
public var current(default, null):Null<FrameView<Dynamic>>;
|
||||
public var onSwitch:Signal<FrameView<Dynamic>> = new Signal();
|
||||
|
||||
public var factory(default, default):Map<String, Class<FrameView<Dynamic>>>;
|
||||
|
||||
public var frames(default, default):Map<String, FrameView<Dynamic>>;
|
||||
|
||||
public var animateFactory(default, default):Class<IAnimate>;
|
||||
|
||||
private var animate:IAnimate;
|
||||
private var history:Array<{id:String, data:Dynamic}>;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
factory = new Map();
|
||||
frames = new Map();
|
||||
history = [];
|
||||
current = null;
|
||||
}
|
||||
|
||||
private function buildAnimate(view:IView<Dynamic>):Null<IAnimate> {
|
||||
if (animateFactory != null) {
|
||||
return Type.createInstance(animateFactory, [view]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function resolveFrame<T>(id:String):FrameView<T> {
|
||||
if (!frames.exists(id)) {
|
||||
if (!factory.exists(id)) {
|
||||
throw 'frame "$id" not found';
|
||||
}
|
||||
frames.set(id, Type.createInstance(factory.get(id), []));
|
||||
}
|
||||
return cast frames.get(id);
|
||||
}
|
||||
|
||||
public function change<T>(id:String, data:T = null):FrameView<T> {
|
||||
var prev = null;
|
||||
if (current != null) {
|
||||
if (current.frameId == id) {
|
||||
current.onShow(data);
|
||||
return cast current;
|
||||
}
|
||||
prev = current;
|
||||
}
|
||||
current = resolveFrame(id);
|
||||
if (current == null) {
|
||||
throw 'frame "$id" not found';
|
||||
}
|
||||
if (animate != null) {
|
||||
animate.cancel();
|
||||
}
|
||||
animate = buildAnimate(current);
|
||||
if (animate != null && prev != null) {
|
||||
animate.start(function(_) removePrev(prev));
|
||||
} else {
|
||||
removePrev(prev);
|
||||
}
|
||||
addView(current);
|
||||
toUpdate();
|
||||
update();
|
||||
focus(current);
|
||||
current.onShow(data);
|
||||
history.push({id:current.frameId, data:data});
|
||||
onSwitch.emit(current);
|
||||
return cast current;
|
||||
}
|
||||
|
||||
public function back():Void {
|
||||
if (history.length > 1) {
|
||||
history.pop();
|
||||
var item = history.pop();
|
||||
change(item.id, item.data);
|
||||
}
|
||||
}
|
||||
|
||||
private function removePrev(prev:Null<FrameView<Dynamic>>):Void {
|
||||
if (prev != null) {
|
||||
prev.onHide();
|
||||
removeView(prev);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/main/hw/view/frame/FrameView.hx
Normal file
20
src/main/hw/view/frame/FrameView.hx
Normal file
@@ -0,0 +1,20 @@
|
||||
package hw.view.frame;
|
||||
|
||||
import hw.view.group.GroupView;
|
||||
import hw.view.layout.ILayout;
|
||||
import hw.view.layout.VerticalLayout;
|
||||
|
||||
class FrameView<D> extends GroupView {
|
||||
public var frameId(default, null):String;
|
||||
|
||||
public function new(frameId:String, ?layout:ILayout) {
|
||||
super(layout != null ? layout : new VerticalLayout());
|
||||
style = "frame";
|
||||
this.frameId = frameId;
|
||||
this.geometry.stretch = true;
|
||||
}
|
||||
|
||||
public function onShow(data:D):Void {}
|
||||
|
||||
public function onHide():Void {}
|
||||
}
|
||||
79
src/main/hw/view/geometry/Box.hx
Normal file
79
src/main/hw/view/geometry/Box.hx
Normal file
@@ -0,0 +1,79 @@
|
||||
package hw.view.geometry;
|
||||
|
||||
abstract Box(Array<Float>) {
|
||||
public var left(get, set):Float;
|
||||
public var right(get, set):Float;
|
||||
public var top(get, set):Float;
|
||||
public var bottom(get, set):Float;
|
||||
public var vertical(get, never):Float;
|
||||
public var horizontal(get, never):Float;
|
||||
public var empty(get, never):Bool;
|
||||
|
||||
inline public function new(value:Array<Float> = null) {
|
||||
this = switch value {
|
||||
case [a]: [a, a, a, a];
|
||||
case [a, b]: [a, a, b, b];
|
||||
case [a, b, c, d]: [a, b, c, d];
|
||||
case _: [0, 0, 0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
inline private function get_left():Float {
|
||||
return this[0];
|
||||
}
|
||||
|
||||
inline private function set_left(value:Float):Float {
|
||||
return this[0] = value;
|
||||
}
|
||||
|
||||
inline private function get_right():Float {
|
||||
return this[1];
|
||||
}
|
||||
|
||||
inline private function set_right(value:Float):Float {
|
||||
return this[1] = value;
|
||||
}
|
||||
|
||||
inline private function get_top():Float {
|
||||
return this[2];
|
||||
}
|
||||
|
||||
inline private function set_top(value:Float):Float {
|
||||
return this[2] = value;
|
||||
}
|
||||
|
||||
inline private function get_bottom():Float {
|
||||
return this[3];
|
||||
}
|
||||
|
||||
inline private function set_bottom(value:Float):Float {
|
||||
return this[3] = value;
|
||||
}
|
||||
|
||||
inline private function get_vertical():Float {
|
||||
return top + bottom;
|
||||
}
|
||||
|
||||
inline private function get_horizontal():Float {
|
||||
return left + right;
|
||||
}
|
||||
|
||||
inline private function get_empty():Bool {
|
||||
return switch this {
|
||||
case [0, 0, 0, 0]: true;
|
||||
case _: false;
|
||||
}
|
||||
}
|
||||
|
||||
public function clone():Box {
|
||||
return new Box(this);
|
||||
}
|
||||
|
||||
@:from static public function fromArray(value:Array<Float>):Box {
|
||||
return new Box(value);
|
||||
}
|
||||
|
||||
@:from static public function fromFloat(value:Float):Box {
|
||||
return new Box([value]);
|
||||
}
|
||||
}
|
||||
56
src/main/hw/view/geometry/Geometry.hx
Normal file
56
src/main/hw/view/geometry/Geometry.hx
Normal file
@@ -0,0 +1,56 @@
|
||||
package hw.view.geometry;
|
||||
|
||||
@:style class Geometry {
|
||||
@:style(0) public var width(default, default):SizeValue;
|
||||
@:style(0) public var height(default, default):SizeValue;
|
||||
@:style(0) public var padding(default, default):Box;
|
||||
@:style(0) public var margin(default, default):Box;
|
||||
@:style(HAlign.NONE) public var hAlign(default, default):HAlign;
|
||||
@:style(VAlign.NONE) public var vAlign(default, default):VAlign;
|
||||
@:style(Position.LAYOUT) public var position(default, default):Position;
|
||||
@:style(-1) public var ratio(default, default):Null<Float>;
|
||||
public var stretch(null, set):Bool;
|
||||
|
||||
public function new() {
|
||||
}
|
||||
|
||||
private function set_stretch(value:Bool):Bool {
|
||||
if (value) {
|
||||
width.percent = 100;
|
||||
height.percent = 100;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public function setSize(width:SizeValue, height:SizeValue):Geometry {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
return this;
|
||||
}
|
||||
|
||||
public function setMargin(margin:Box):Geometry {
|
||||
this.margin = margin;
|
||||
return this;
|
||||
}
|
||||
|
||||
public function setPadding(padding:Box):Geometry {
|
||||
this.padding = padding;
|
||||
return this;
|
||||
}
|
||||
|
||||
public function setAlign(hAlign:HAlign, vAlign:VAlign):Geometry {
|
||||
this.hAlign = hAlign;
|
||||
this.vAlign = vAlign;
|
||||
return this;
|
||||
}
|
||||
|
||||
public function setPosition(position:Position):Geometry {
|
||||
this.position = position;
|
||||
return this;
|
||||
}
|
||||
|
||||
public function setRatio(ratio:Float):Geometry {
|
||||
this.ratio = ratio;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
8
src/main/hw/view/geometry/HAlign.hx
Executable file
8
src/main/hw/view/geometry/HAlign.hx
Executable file
@@ -0,0 +1,8 @@
|
||||
package hw.view.geometry;
|
||||
|
||||
@:enum abstract HAlign(String) from String to String {
|
||||
var NONE = "none";
|
||||
var LEFT = "left";
|
||||
var CENTER = "center";
|
||||
var RIGHT = "right";
|
||||
}
|
||||
6
src/main/hw/view/geometry/Position.hx
Normal file
6
src/main/hw/view/geometry/Position.hx
Normal file
@@ -0,0 +1,6 @@
|
||||
package hw.view.geometry;
|
||||
|
||||
@:enum abstract Position(String) from String to String {
|
||||
var LAYOUT = "layout";
|
||||
var ABSOLUTE = "absolute";
|
||||
}
|
||||
51
src/main/hw/view/geometry/Size.hx
Normal file
51
src/main/hw/view/geometry/Size.hx
Normal file
@@ -0,0 +1,51 @@
|
||||
package hw.view.geometry;
|
||||
|
||||
abstract Size(Array<Float>) {
|
||||
public var width(get, set):Float;
|
||||
public var height(get, set):Float;
|
||||
public var empty(get, never):Bool;
|
||||
|
||||
inline public function new(value:Array<Float>) {
|
||||
this = switch(value) {
|
||||
case []: [-1, -1];
|
||||
case [a]: [a, a];
|
||||
case [a, b]: [a, b];
|
||||
case x: x;
|
||||
}
|
||||
}
|
||||
|
||||
inline private function get_width():Float {
|
||||
return this[0];
|
||||
}
|
||||
|
||||
inline private function set_width(value:Float):Float {
|
||||
return this[0] = value;
|
||||
}
|
||||
|
||||
inline private function get_height():Float {
|
||||
return this[1];
|
||||
}
|
||||
|
||||
inline private function set_height(value:Float):Float {
|
||||
return this[1] = value;
|
||||
}
|
||||
|
||||
inline private function get_empty():Bool {
|
||||
return switch this {
|
||||
case [-1, -1]: true;
|
||||
case _: false;
|
||||
}
|
||||
}
|
||||
|
||||
@:to public inline function toArray():Array<Float> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@:from static public inline function fromArray(value:Array<Float>):Size {
|
||||
return new Size(value);
|
||||
}
|
||||
|
||||
@:from static public inline function fromFloat(value:Float):Size {
|
||||
return new Size([value]);
|
||||
}
|
||||
}
|
||||
31
src/main/hw/view/geometry/SizeSet.hx
Normal file
31
src/main/hw/view/geometry/SizeSet.hx
Normal file
@@ -0,0 +1,31 @@
|
||||
package hw.view.geometry;
|
||||
|
||||
import haxe.ds.StringMap;
|
||||
|
||||
class SizeSet extends StringMap<Size> {
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
}
|
||||
|
||||
public function update(value:Size, type:String = "default"):Bool {
|
||||
var existValue:Size = get(type);
|
||||
if (existValue == null || value.width != existValue.width || value.height != existValue.height) {
|
||||
set(type, value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function toSize(?exclude:String):Size {
|
||||
var result:Size = 0;
|
||||
for (type in keys()) {
|
||||
if (exclude == null || type.indexOf(exclude) == -1) {
|
||||
var value = get(type);
|
||||
result.width = Math.max(result.width, value.width);
|
||||
result.height = Math.max(result.height, value.height);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
74
src/main/hw/view/geometry/SizeValue.hx
Normal file
74
src/main/hw/view/geometry/SizeValue.hx
Normal file
@@ -0,0 +1,74 @@
|
||||
package hw.view.geometry;
|
||||
|
||||
enum SizeType {
|
||||
FIXED;
|
||||
PERCENT;
|
||||
}
|
||||
|
||||
abstract SizeValue({type:SizeType, value:Float}) {
|
||||
public var type(get, set):SizeType;
|
||||
public var value(get, set):Float;
|
||||
|
||||
public var fixed(get, set):Float;
|
||||
public var percent(get, set):Float;
|
||||
|
||||
public function new(type:SizeType, value:Float) {
|
||||
this = {type: type, value: value};
|
||||
}
|
||||
|
||||
private inline function get_type():SizeType {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
private inline function set_type(value:SizeType):SizeType {
|
||||
return this.type = value;
|
||||
}
|
||||
|
||||
private inline function get_value():Float {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
private inline function set_value(value:Float):Float {
|
||||
return this.value = value;
|
||||
}
|
||||
|
||||
private inline function get_fixed():Float {
|
||||
return switch type {
|
||||
case FIXED: value;
|
||||
case _: 0;
|
||||
}
|
||||
}
|
||||
|
||||
private inline function set_fixed(value:Float):Float {
|
||||
this.type = FIXED;
|
||||
return this.value = value;
|
||||
}
|
||||
|
||||
private inline function get_percent():Float {
|
||||
return switch type {
|
||||
case PERCENT: value;
|
||||
case _: 0;
|
||||
}
|
||||
}
|
||||
|
||||
private inline function set_percent(value:Float):Float {
|
||||
this.type = PERCENT;
|
||||
return this.value = value;
|
||||
}
|
||||
|
||||
@:from static public inline function fromInt(value:Int):SizeValue {
|
||||
return new SizeValue(FIXED, value);
|
||||
}
|
||||
|
||||
@:from static public inline function fromFloat(value:Float):SizeValue {
|
||||
return new SizeValue(FIXED, value);
|
||||
}
|
||||
|
||||
@:from static public inline function fromString(value:String):SizeValue {
|
||||
if (value.substr(value.length - 1) == "%") {
|
||||
return new SizeValue(PERCENT, Std.parseFloat(value));
|
||||
} else {
|
||||
return new SizeValue(FIXED, Std.parseFloat(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/main/hw/view/geometry/VAlign.hx
Executable file
8
src/main/hw/view/geometry/VAlign.hx
Executable file
@@ -0,0 +1,8 @@
|
||||
package hw.view.geometry;
|
||||
|
||||
@:enum abstract VAlign(String) from String to String {
|
||||
var NONE = "none";
|
||||
var TOP = "top";
|
||||
var MIDDLE = "middle";
|
||||
var BOTTOM = "bottom";
|
||||
}
|
||||
268
src/main/hw/view/group/GroupView.hx
Executable file
268
src/main/hw/view/group/GroupView.hx
Executable file
@@ -0,0 +1,268 @@
|
||||
package hw.view.group;
|
||||
|
||||
import flash.geom.Point;
|
||||
import flash.events.TouchEvent;
|
||||
import flash.geom.Rectangle;
|
||||
import flash.display.DisplayObjectContainer;
|
||||
import flash.display.Sprite;
|
||||
import flash.events.MouseEvent;
|
||||
import hw.signal.Signal;
|
||||
import hw.view.geometry.Position;
|
||||
import hw.view.layout.DefaultLayout;
|
||||
import hw.view.layout.ILayout;
|
||||
import hw.view.list.HScrollBarView;
|
||||
import hw.view.list.ScrollBarView;
|
||||
import hw.view.list.VScrollBarView;
|
||||
|
||||
class OverflowControl {
|
||||
public var overflow(default, set):Float = 0;
|
||||
public var offset(default, set):Float = 0;
|
||||
public var size(default, default):Float = 0;
|
||||
public var scroll(null, null):ScrollBarView;
|
||||
public var offsetSignal(default, null):Signal<Float>;
|
||||
|
||||
private var scrollFactory:Void -> ScrollBarView;
|
||||
|
||||
public function new(scrollFactory:Void -> ScrollBarView) {
|
||||
this.scrollFactory = scrollFactory;
|
||||
offsetSignal = new Signal();
|
||||
}
|
||||
|
||||
private function set_offset(value:Float):Float {
|
||||
if (offset != value) {
|
||||
offset = checkOffset(value);
|
||||
if (scroll != null) {
|
||||
scroll.position = -offset / size / overflow;
|
||||
}
|
||||
offsetSignal.emit(offset);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
private function checkOffset(value:Float):Float {
|
||||
if (value > 0) {
|
||||
return 0;
|
||||
} else if (value < -size * (overflow - 1)) {
|
||||
return -size * (overflow - 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public function set_overflow(value:Float):Float {
|
||||
if (overflow != value) {
|
||||
overflow = value;
|
||||
if (overflow > 1 && scroll == null) {
|
||||
scroll = scrollFactory();
|
||||
scroll.onScroll.connect(function(value) {
|
||||
offset = value > 0 ? -value * overflow * size : 0;
|
||||
});
|
||||
}
|
||||
if (scroll != null) {
|
||||
scroll.ratio = 1 / overflow;
|
||||
}
|
||||
offset = checkOffset(offset);
|
||||
}
|
||||
return overflow;
|
||||
}
|
||||
}
|
||||
|
||||
@:style(true) class GroupView extends SpriteView implements IGroupView {
|
||||
public var container(default, null):DisplayObjectContainer;
|
||||
|
||||
public var views(default, set):Array<IView<Dynamic>>;
|
||||
@:style public var layout(default, default):ILayout;
|
||||
@:style public var overflow(default, default):Overflow;
|
||||
|
||||
public var overflowX(default, set):Float;
|
||||
public var overflowY(default, set):Float;
|
||||
|
||||
private var _mask:Sprite;
|
||||
private var scrollX(get, null):HScrollBarView;
|
||||
private var scrollY(get, null):VScrollBarView;
|
||||
private var maskEnable(default, set):Bool;
|
||||
|
||||
public var overflowControlX(default, null):OverflowControl;
|
||||
public var overflowControlY(default, null):OverflowControl;
|
||||
|
||||
public function new(?layout:ILayout, ?overflow:Overflow) {
|
||||
super();
|
||||
container = new Sprite();
|
||||
content.addChild(container);
|
||||
_mask = new Sprite();
|
||||
this.layout = layout != null ? layout : new DefaultLayout();
|
||||
this.overflow = overflow != null ? overflow : new Overflow();
|
||||
views = [];
|
||||
overflowControlX = new OverflowControl(function() return scrollX);
|
||||
overflowControlX.offsetSignal.connect(function(value) container.x = value);
|
||||
overflowControlY = new OverflowControl(function() return scrollY);
|
||||
overflowControlY.offsetSignal.connect(function(value) container.y = value);
|
||||
content.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheelEvent);
|
||||
content.addEventListener(TouchEvent.TOUCH_BEGIN, onTouchBegin);
|
||||
}
|
||||
|
||||
private function set_maskEnable(value:Bool):Bool {
|
||||
if (maskEnable != value) {
|
||||
maskEnable = value;
|
||||
if (maskEnable) {
|
||||
content.addChild(_mask);
|
||||
container.mask = _mask;
|
||||
} else {
|
||||
content.removeChild(_mask);
|
||||
container.mask = null;
|
||||
}
|
||||
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private function onMouseWheelEvent(event:MouseEvent):Void {
|
||||
if (overflowY > 1) {
|
||||
#if flash event.preventDefault(); #end
|
||||
overflowControlY.offset += event.delta * 15;
|
||||
}
|
||||
}
|
||||
|
||||
private var touchPoint:Point;
|
||||
|
||||
private function onTouchBegin(event:TouchEvent):Void {
|
||||
if (overflowY > 1) {
|
||||
touchPoint = new Point(event.stageX, event.stageY);
|
||||
content.stage.addEventListener(TouchEvent.TOUCH_MOVE, onTouchMove);
|
||||
content.stage.addEventListener(TouchEvent.TOUCH_END, onTouchEnd);
|
||||
}
|
||||
}
|
||||
|
||||
private function onTouchMove(event:TouchEvent):Void {
|
||||
if (overflowY > 1) {
|
||||
overflowControlY.offset -= touchPoint.y - event.stageY;
|
||||
}
|
||||
if (overflowX > 1) {
|
||||
overflowControlX.offset -= touchPoint.x - event.stageX;
|
||||
}
|
||||
touchPoint = new Point(event.stageX, event.stageY);
|
||||
}
|
||||
|
||||
private function onTouchEnd(event:TouchEvent):Void {
|
||||
event.preventDefault();
|
||||
stage.removeEventListener(TouchEvent.TOUCH_MOVE, onTouchMove);
|
||||
stage.removeEventListener(TouchEvent.TOUCH_END, onTouchEnd);
|
||||
}
|
||||
|
||||
private function get_scrollX():HScrollBarView {
|
||||
if (scrollX == null) {
|
||||
scrollX = new HScrollBarView();
|
||||
scrollX.geometry.position = Position.ABSOLUTE;
|
||||
addView(scrollX);
|
||||
container.removeChild(scrollX.content);
|
||||
content.addChild(scrollX.content);
|
||||
}
|
||||
return scrollX;
|
||||
}
|
||||
|
||||
private function get_scrollY():VScrollBarView {
|
||||
if (scrollY == null) {
|
||||
scrollY = new VScrollBarView();
|
||||
scrollY.geometry.position = Position.ABSOLUTE;
|
||||
addView(scrollY);
|
||||
container.removeChild(scrollY.content);
|
||||
content.addChild(scrollY.content);
|
||||
}
|
||||
return scrollY;
|
||||
}
|
||||
|
||||
public function set_overflowX(value:Float):Float {
|
||||
maskEnable = value > 1;
|
||||
return overflowX = overflowControlX.overflow = value;
|
||||
}
|
||||
|
||||
public function set_overflowY(value:Float):Float {
|
||||
maskEnable = value > 1;
|
||||
return overflowY = overflowControlY.overflow = value;
|
||||
}
|
||||
|
||||
override public function update():Void {
|
||||
super.update();
|
||||
layout.place(this, views);
|
||||
for (view in views) {
|
||||
view.update();
|
||||
if (view.index > -1) {
|
||||
container.setChildIndex(view.content, Std.int(Math.min(views.length - 1, view.index)));
|
||||
}
|
||||
}
|
||||
overflowControlX.size = width;
|
||||
overflowControlY.size = height;
|
||||
_mask.graphics.clear();
|
||||
_mask.graphics.beginFill(0, 1);
|
||||
if (!Math.isNaN(width) && !Math.isNaN(height) && width > 0 && height > 0) {
|
||||
_mask.graphics.drawRect(0, 0, width, height);
|
||||
}
|
||||
_mask.graphics.endFill();
|
||||
}
|
||||
|
||||
public function set_views(value:Array<IView<Dynamic>>):Array<IView<Dynamic>> {
|
||||
removeAllViews();
|
||||
if (views == null) views = [];
|
||||
for (view in value) addView(view);
|
||||
return views;
|
||||
}
|
||||
|
||||
public function containsView(view:IView<Dynamic>):Bool {
|
||||
return views.indexOf(view) > -1;
|
||||
}
|
||||
|
||||
public function addView(view:IView<Dynamic>):IView<Dynamic> {
|
||||
views.push(view);
|
||||
if (view.content != null) container.addChild(view.content);
|
||||
view.parent = this;
|
||||
toUpdate();
|
||||
return view;
|
||||
}
|
||||
|
||||
public function insertView(view:IView<Dynamic>, index:Int):IView<Dynamic> {
|
||||
if (index < 0) index = views.length + index;
|
||||
views.insert(index, view);
|
||||
if (view.content != null) container.addChild(view.content);
|
||||
view.parent = this;
|
||||
toUpdate();
|
||||
return view;
|
||||
}
|
||||
|
||||
public function addViewFirst(view:IView<Dynamic>):IView<Dynamic> {
|
||||
views.unshift(view);
|
||||
container.addChild(view.content);
|
||||
view.parent = this;
|
||||
toUpdate();
|
||||
return view;
|
||||
}
|
||||
|
||||
public function removeView(view:IView<Dynamic>):IView<Dynamic> {
|
||||
view.parent = null;
|
||||
views.remove(view);
|
||||
if (view.content != null) container.removeChild(view.content);
|
||||
toUpdate();
|
||||
return view;
|
||||
}
|
||||
|
||||
public function removeAllViews():Void {
|
||||
if (views != null) {
|
||||
for (view in views.slice(0)) {
|
||||
if (view != scrollX && view != scrollY) {
|
||||
removeView(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function focus(?view:IView<Dynamic>):Void {
|
||||
if (container.stage != null) {
|
||||
container.stage.focus = view != null ? view.content : content;
|
||||
}
|
||||
}
|
||||
|
||||
override private function get_rect():Rectangle {
|
||||
var result = super.get_rect();
|
||||
result.x += overflowControlX.offset;
|
||||
result.y += overflowControlY.offset;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
10
src/main/hw/view/group/HGroupView.hx
Executable file
10
src/main/hw/view/group/HGroupView.hx
Executable file
@@ -0,0 +1,10 @@
|
||||
package hw.view.group;
|
||||
|
||||
import hw.view.layout.HorizontalLayout;
|
||||
|
||||
class HGroupView extends GroupView {
|
||||
|
||||
public function new() {
|
||||
super(new HorizontalLayout());
|
||||
}
|
||||
}
|
||||
30
src/main/hw/view/group/IGroupView.hx
Executable file
30
src/main/hw/view/group/IGroupView.hx
Executable file
@@ -0,0 +1,30 @@
|
||||
package hw.view.group;
|
||||
|
||||
import flash.display.DisplayObjectContainer;
|
||||
import hw.view.group.Overflow.Overflow;
|
||||
import hw.view.layout.ILayout;
|
||||
|
||||
interface IGroupView extends IView<Dynamic> {
|
||||
public var layout(default, set):ILayout;
|
||||
public var overflow(default, set):Overflow;
|
||||
|
||||
public var overflowX(default, set):Float;
|
||||
public var overflowY(default, set):Float;
|
||||
|
||||
public var container(default, null):DisplayObjectContainer;
|
||||
public var views(default, set):Array<IView<Dynamic>>;
|
||||
|
||||
public function containsView(view:IView<Dynamic>):Bool;
|
||||
|
||||
public function addView(view:IView<Dynamic>):IView<Dynamic>;
|
||||
|
||||
public function addViewFirst(view:IView<Dynamic>):IView<Dynamic>;
|
||||
|
||||
public function insertView(view:IView<Dynamic>, index:Int):IView<Dynamic>;
|
||||
|
||||
public function removeView(view:IView<Dynamic>):IView<Dynamic>;
|
||||
|
||||
public function removeAllViews():Void;
|
||||
|
||||
public function focus(?view:IView<Dynamic>):Void;
|
||||
}
|
||||
17
src/main/hw/view/group/Overflow.hx
Normal file
17
src/main/hw/view/group/Overflow.hx
Normal file
@@ -0,0 +1,17 @@
|
||||
package hw.view.group;
|
||||
|
||||
@:enum abstract OverflowType(String) from String to String {
|
||||
var STRETCH = "stretch";
|
||||
var SCROLL = "scroll";
|
||||
var CROP = "crop";
|
||||
}
|
||||
|
||||
@:style class Overflow {
|
||||
@:style(STRETCH) public var x(default, default):OverflowType;
|
||||
@:style(STRETCH) public var y(default, default):OverflowType;
|
||||
|
||||
public function new(?overflowX:OverflowType, ?overflowY:OverflowType) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
10
src/main/hw/view/group/VGroupView.hx
Executable file
10
src/main/hw/view/group/VGroupView.hx
Executable file
@@ -0,0 +1,10 @@
|
||||
package hw.view.group;
|
||||
|
||||
import hw.view.layout.VerticalLayout;
|
||||
|
||||
class VGroupView extends GroupView {
|
||||
|
||||
public function new() {
|
||||
super(new VerticalLayout());
|
||||
}
|
||||
}
|
||||
105
src/main/hw/view/layout/DefaultLayout.hx
Executable file
105
src/main/hw/view/layout/DefaultLayout.hx
Executable file
@@ -0,0 +1,105 @@
|
||||
package hw.view.layout;
|
||||
|
||||
import hw.view.geometry.HAlign;
|
||||
import hw.view.geometry.Position;
|
||||
import hw.view.geometry.VAlign;
|
||||
import hw.view.group.IGroupView;
|
||||
import hw.view.group.Overflow;
|
||||
|
||||
class DefaultLayout extends Layout {
|
||||
|
||||
override public function place(group:IGroupView, views:Array<IView<Dynamic>>):Void {
|
||||
var width:Float = 0;
|
||||
var height:Float = 0;
|
||||
for (view in views) {
|
||||
setViewWidth(group, view);
|
||||
setViewHeight(group, view);
|
||||
placeViewHorizontal(group, view);
|
||||
placeViewVertical(group, view);
|
||||
var size = view.size.toSize("percent");
|
||||
width = Math.max(width, size.width + view.geometry.margin.horizontal);
|
||||
height = Math.max(height, size.height + view.geometry.margin.horizontal);
|
||||
}
|
||||
setGroupSize(group, width, height);
|
||||
}
|
||||
|
||||
private function setGroupSize(group:IGroupView, width:Float, height:Float):Void {
|
||||
group.setSize(
|
||||
switch group.overflow.x {
|
||||
case STRETCH: width;
|
||||
case _: 0;
|
||||
},
|
||||
switch group.overflow.y {
|
||||
case STRETCH: height;
|
||||
case _: 0;
|
||||
},
|
||||
"group"
|
||||
);
|
||||
var ox = switch group.overflow.x {
|
||||
case SCROLL:
|
||||
width / (group.width - group.geometry.padding.horizontal);
|
||||
case _: 1;
|
||||
}
|
||||
group.overflowX = ox;
|
||||
var oy = switch group.overflow.y {
|
||||
case SCROLL:
|
||||
height / (group.height - group.geometry.padding.vertical);
|
||||
case _: 1;
|
||||
}
|
||||
group.overflowY = oy;
|
||||
}
|
||||
|
||||
private function filterViews(group:IGroupView, views:Array<IView<Dynamic>>):Array<IView<Dynamic>> {
|
||||
return Lambda.array(Lambda.filter(views, function(view:IView<Dynamic>):Bool {
|
||||
return switch (view.geometry.position) {
|
||||
case ABSOLUTE:
|
||||
setViewWidth(group, view);
|
||||
setViewHeight(group, view);
|
||||
placeViewHorizontal(group, view);
|
||||
placeViewVertical(group, view);
|
||||
false;
|
||||
case LAYOUT:
|
||||
var result:Bool = view.visible;
|
||||
result;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private function setViewWidth(group:IGroupView, view:IView<Dynamic>):Void {
|
||||
view.setSize(view.geometry.width.percent / 100 * (group.width - view.geometry.margin.horizontal - group.geometry.padding.horizontal) - view.geometry.padding.horizontal, 0, "percent.width");
|
||||
}
|
||||
|
||||
private function setViewHeight(group:IGroupView, view:IView<Dynamic>):Void {
|
||||
view.setSize(0, view.geometry.height.percent / 100 * (group.height - view.geometry.margin.vertical - group.geometry.padding.vertical) - view.geometry.padding.vertical, "percent.height");
|
||||
}
|
||||
|
||||
private function placeViewHorizontal(group:IGroupView, view:IView<Dynamic>):Void {
|
||||
var align:HAlign = view.geometry.hAlign;
|
||||
if (align == NONE) align = hAlign;
|
||||
switch (align) {
|
||||
case LEFT | NONE:
|
||||
view.x = group.geometry.padding.left + view.geometry.margin.left;
|
||||
case CENTER:
|
||||
view.x = (group.width - view.width) / 2 +
|
||||
(group.geometry.padding.left - group.geometry.padding.right) +
|
||||
(view.geometry.margin.left - view.geometry.margin.right);
|
||||
case RIGHT:
|
||||
view.x = group.width - view.width - group.geometry.padding.right - view.geometry.margin.right;
|
||||
}
|
||||
}
|
||||
|
||||
private function placeViewVertical(group:IGroupView, view:IView<Dynamic>):Void {
|
||||
var align:VAlign = view.geometry.vAlign;
|
||||
if (align == NONE) align = vAlign;
|
||||
switch (align) {
|
||||
case TOP | NONE:
|
||||
view.y = group.geometry.padding.top + view.geometry.margin.top;
|
||||
case MIDDLE:
|
||||
view.y = (group.height - view.height) / 2 +
|
||||
(group.geometry.padding.top - group.geometry.padding.bottom) +
|
||||
(view.geometry.margin.top - view.geometry.margin.bottom);
|
||||
case BOTTOM:
|
||||
view.y = group.height - view.height - group.geometry.padding.bottom - view.geometry.margin.bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/main/hw/view/layout/HorizontalLayout.hx
Executable file
55
src/main/hw/view/layout/HorizontalLayout.hx
Executable file
@@ -0,0 +1,55 @@
|
||||
package hw.view.layout;
|
||||
|
||||
import hw.view.geometry.HAlign;
|
||||
import hw.view.geometry.SizeValue;
|
||||
import hw.view.geometry.VAlign;
|
||||
import hw.view.group.IGroupView;
|
||||
|
||||
class HorizontalLayout extends DefaultLayout {
|
||||
|
||||
override public function place(group:IGroupView, views:Array<IView<Dynamic>>):Void {
|
||||
views = filterViews(group, views);
|
||||
|
||||
var fixedSize:Float = margin * (views.length - 1);
|
||||
var leftSize:Float = group.width - group.geometry.padding.horizontal;
|
||||
|
||||
var maxSize:Float = 0;
|
||||
|
||||
for (view in views) {
|
||||
switch view.geometry.width.type {
|
||||
case PERCENT:
|
||||
leftSize -= (view.geometry.margin.horizontal);
|
||||
case FIXED:
|
||||
fixedSize += (view.width + view.geometry.margin.horizontal);
|
||||
}
|
||||
maxSize = Math.max(maxSize, view.size.toSize("percent").height + view.geometry.padding.vertical + view.geometry.margin.vertical);
|
||||
}
|
||||
|
||||
setGroupSize(group, fixedSize, maxSize);
|
||||
|
||||
leftSize -= fixedSize;
|
||||
for (view in views) {
|
||||
switch view.geometry.width.type {
|
||||
case PERCENT:
|
||||
var result = view.geometry.width.value / 100 * leftSize;
|
||||
fixedSize += result + view.geometry.margin.horizontal;
|
||||
view.setSize(result - view.geometry.padding.horizontal, 0, "percent.width");
|
||||
case _:
|
||||
};
|
||||
}
|
||||
|
||||
var x:Float = 0;
|
||||
switch hAlign {
|
||||
case LEFT | NONE: x = group.geometry.padding.left;
|
||||
case CENTER: x = Math.max((group.width - fixedSize) / 2, 0) + group.geometry.padding.left - group.geometry.padding.right;
|
||||
case RIGHT: x = group.width - fixedSize - group.geometry.padding.right;
|
||||
}
|
||||
|
||||
for (view in views) {
|
||||
view.x = x + view.geometry.margin.left;
|
||||
x += (view.width + view.geometry.margin.horizontal + margin);
|
||||
setViewHeight(group, view);
|
||||
placeViewVertical(group, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/main/hw/view/layout/ILayout.hx
Executable file
18
src/main/hw/view/layout/ILayout.hx
Executable file
@@ -0,0 +1,18 @@
|
||||
package hw.view.layout;
|
||||
|
||||
import hw.view.geometry.HAlign;
|
||||
import hw.view.geometry.VAlign;
|
||||
import hw.view.group.IGroupView;
|
||||
import hw.view.theme.IStylable;
|
||||
|
||||
interface ILayout extends IStylable {
|
||||
public var hAlign(get, set):HAlign;
|
||||
public var vAlign(get, set):VAlign;
|
||||
public var margin(get, set):Null<Float>;
|
||||
|
||||
public function place(group:IGroupView, views:Array<IView<Dynamic>>):Void;
|
||||
|
||||
public function setAlign(hAlign:HAlign, vAlign:VAlign):ILayout;
|
||||
|
||||
public function setMargin(margin:Float):ILayout;
|
||||
}
|
||||
27
src/main/hw/view/layout/Layout.hx
Normal file
27
src/main/hw/view/layout/Layout.hx
Normal file
@@ -0,0 +1,27 @@
|
||||
package hw.view.layout;
|
||||
|
||||
import hw.view.geometry.HAlign;
|
||||
import hw.view.geometry.VAlign;
|
||||
import hw.view.group.IGroupView;
|
||||
|
||||
@:style class Layout implements ILayout {
|
||||
|
||||
@:style(HAlign.NONE) public var hAlign(default, default):HAlign;
|
||||
@:style(VAlign.NONE) public var vAlign(default, default):VAlign;
|
||||
@:style(0) public var margin(default, default):Null<Float>;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function place(group:IGroupView, views:Array<IView<Dynamic>>):Void {}
|
||||
|
||||
public function setAlign(hAlign:HAlign, vAlign:VAlign):ILayout {
|
||||
this.hAlign = hAlign;
|
||||
this.vAlign = vAlign;
|
||||
return this;
|
||||
}
|
||||
|
||||
public function setMargin(margin:Float):ILayout {
|
||||
this.margin = margin;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
81
src/main/hw/view/layout/TailLayout.hx
Normal file
81
src/main/hw/view/layout/TailLayout.hx
Normal file
@@ -0,0 +1,81 @@
|
||||
package hw.view.layout;
|
||||
|
||||
import hw.view.geometry.VAlign;
|
||||
import hw.view.group.IGroupView;
|
||||
|
||||
typedef Row = {
|
||||
var width:Float;
|
||||
var height:Float;
|
||||
var views:Array<IView<Dynamic>>;
|
||||
}
|
||||
|
||||
class TailLayout extends DefaultLayout {
|
||||
|
||||
@:style(0) public var rowSize:Null<Int>;
|
||||
@:style(false) public var stretch:Null<Bool>;
|
||||
|
||||
private function placeRow(group:IGroupView, y:Float, row:Row):Void {
|
||||
var x:Float = (group.width - row.width) / 2;
|
||||
for (v in row.views) {
|
||||
v.x = x;
|
||||
var vy = switch group.layout.vAlign {
|
||||
case TOP | NONE: y;
|
||||
case MIDDLE: y + (row.height - v.height) / 2;
|
||||
case BOTTOM: y + (row.height - v.height);
|
||||
}
|
||||
v.y = vy;
|
||||
x += v.width + margin;
|
||||
}
|
||||
}
|
||||
|
||||
override public function place(group:IGroupView, views:Array<IView<Dynamic>>):Void {
|
||||
var rows:Array<Row> = [];
|
||||
var row:Row = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
views: [],
|
||||
}
|
||||
var w:Float = 0;
|
||||
var size = group.size.toSize("group");
|
||||
for (view in filterViews(group, views)) {
|
||||
setViewWidth(group, view);
|
||||
setViewHeight(group, view);
|
||||
if (
|
||||
(rowSize > 0 && row.views.length >= rowSize) ||
|
||||
(/*rowSize == 0 && */row.width + view.width + margin + group.geometry.margin.horizontal > size.width && !stretch)
|
||||
) {
|
||||
row.width -= margin;
|
||||
w = Math.max(w, row.width);
|
||||
rows.push(row);
|
||||
row = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
views: [],
|
||||
};
|
||||
}
|
||||
row.views.push(view);
|
||||
row.width += view.width + margin;
|
||||
row.height = Math.max(row.height, view.height);
|
||||
}
|
||||
row.width -= margin;
|
||||
w = Math.max(w, row.width);
|
||||
rows.push(row);
|
||||
var h:Float = Lambda.fold(rows, function(row, h) return row.height + h, 0) + margin * (rows.length - 1);
|
||||
var y:Float = Math.max(group.geometry.padding.top, (group.height - h) / 2);
|
||||
|
||||
y = group.geometry.padding.top;
|
||||
if (h < group.height) {
|
||||
y = switch vAlign {
|
||||
case TOP | NONE: group.geometry.padding.top;
|
||||
case MIDDLE: (group.height - h) / 2;
|
||||
case BOTTOM: group.height - h - group.geometry.padding.bottom;
|
||||
}
|
||||
}
|
||||
for (row in rows) {
|
||||
placeRow(group, y, row);
|
||||
y += row.height + margin;
|
||||
}
|
||||
|
||||
setGroupSize(group, w, h);
|
||||
}
|
||||
}
|
||||
55
src/main/hw/view/layout/VerticalLayout.hx
Executable file
55
src/main/hw/view/layout/VerticalLayout.hx
Executable file
@@ -0,0 +1,55 @@
|
||||
package hw.view.layout;
|
||||
|
||||
import hw.view.geometry.HAlign;
|
||||
import hw.view.geometry.SizeValue;
|
||||
import hw.view.geometry.VAlign;
|
||||
import hw.view.group.IGroupView;
|
||||
|
||||
class VerticalLayout extends DefaultLayout {
|
||||
|
||||
override public function place(group:IGroupView, views:Array<IView<Dynamic>>):Void {
|
||||
views = filterViews(group, views);
|
||||
|
||||
var fixedSize:Float = margin * (views.length - 1);
|
||||
var leftSize:Float = group.height - group.geometry.padding.vertical;
|
||||
|
||||
var maxSize:Float = 0;
|
||||
|
||||
for (view in views) {
|
||||
switch view.geometry.height.type {
|
||||
case PERCENT:
|
||||
leftSize -= (view.geometry.margin.vertical);
|
||||
case FIXED:
|
||||
fixedSize += (Math.max(view.geometry.height.value, view.height) + view.geometry.margin.vertical);
|
||||
}
|
||||
maxSize = Math.max(maxSize, view.size.toSize("percent").width + view.geometry.padding.horizontal + view.geometry.margin.horizontal);
|
||||
}
|
||||
|
||||
setGroupSize(group, maxSize, fixedSize);
|
||||
|
||||
leftSize -= fixedSize;
|
||||
for (view in views) {
|
||||
switch view.geometry.height.type {
|
||||
case PERCENT:
|
||||
var result = view.geometry.height.value / 100 * leftSize;
|
||||
fixedSize += result + view.geometry.margin.vertical;
|
||||
view.setSize(0, result - view.geometry.padding.vertical, "percent.height");
|
||||
case _:
|
||||
};
|
||||
}
|
||||
|
||||
var y:Float = 0;
|
||||
switch vAlign {
|
||||
case TOP | NONE: y = group.geometry.padding.top;
|
||||
case MIDDLE: y = Math.max((group.height - fixedSize) / 2, 0) + group.geometry.padding.top - group.geometry.padding.bottom;
|
||||
case BOTTOM: y = group.height - fixedSize - group.geometry.padding.bottom;
|
||||
}
|
||||
|
||||
for (view in views) {
|
||||
view.y = y + view.geometry.margin.top;
|
||||
y += (view.height + view.geometry.margin.vertical + margin);
|
||||
setViewWidth(group, view);
|
||||
placeViewHorizontal(group, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user