[hw] rename haxework package to hw

This commit is contained in:
2020-03-24 20:58:54 +03:00
parent 7b7819fe6e
commit 279baa1113
162 changed files with 613 additions and 540 deletions

View 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);
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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;
}

View 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);
}
}

View 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
View 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
View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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),
];
}
}

View 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
View 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);
}
}

View 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;
}
}

View 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
View 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
View 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
View 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
View 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

View 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
View 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);
}
}

View 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);
}
}

View 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;
}
}

View 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;
}
}

View 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 [];
}
}

View 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}"';
}
}
}

View 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;
}
}

View 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);
}
}

View 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();
}
}

View 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;
}
}

View File

@@ -0,0 +1,10 @@
package hw.macro;
using hw.macro.MacroUtil;
class ResourceMacro extends FieldMacro {
public function new() {
super(":resource");
}
}

View 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;
}
}

View 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;
}
}

View 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
View 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());
}
}

View 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
View 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
View 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
View 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;
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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));
}
}

View 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;
}

View 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());
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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>;
}

View 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>();
}
}

View 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
View 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;
}

View 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 {}
}

View 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();
}
}

View 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;
}
}

View 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);
}
}

View 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;
}

View 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];
}
}
}

View 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
View 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);
}
}

View 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();
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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);
}
}

View 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();
}
}
}

View 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;
}
}
}

View 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]);
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

View 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();
}
}
}

View 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();
}
}

View 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);
}
}
}

View 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 {}
}

View 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]);
}
}

View 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;
}
}

View 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";
}

View File

@@ -0,0 +1,6 @@
package hw.view.geometry;
@:enum abstract Position(String) from String to String {
var LAYOUT = "layout";
var ABSOLUTE = "absolute";
}

View 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]);
}
}

View 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;
}
}

View 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));
}
}
}

View 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";
}

View 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;
}
}

View File

@@ -0,0 +1,10 @@
package hw.view.group;
import hw.view.layout.HorizontalLayout;
class HGroupView extends GroupView {
public function new() {
super(new HorizontalLayout());
}
}

View 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;
}

View 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;
}
}

View File

@@ -0,0 +1,10 @@
package hw.view.group;
import hw.view.layout.VerticalLayout;
class VGroupView extends GroupView {
public function new() {
super(new VerticalLayout());
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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;
}

View 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;
}
}

View 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);
}
}

View 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