[add] FileSource

This commit is contained in:
2020-01-21 17:42:27 +03:00
parent c0b13202f3
commit e744b9e8aa
26 changed files with 480 additions and 158 deletions

View File

@@ -0,0 +1,73 @@
package ru.m.puzzlez;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.net.FileReference;
import haxe.io.Bytes;
import haxe.io.BytesData;
import promhx.Deferred;
import promhx.Promise;
typedef FileContent = {
var name:String;
var content:Bytes;
}
class Callback<T> {
private var callback:T -> Void;
public function new(callback: T -> Void) {
this.callback = callback;
}
public function execute(result:T):Void {
this.callback(result);
}
}
class FileUtil {
#if android
private static var fileUtilBrowse = lime.system.JNI.createStaticMethod(
"ru.m.android.FileUtil",
"browse",
"(Lorg/haxe/lime/HaxeObject;)V"
);
#end
public static function browse():Promise<FileContent> {
var d = new Deferred<FileContent>();
var file = new FileReference();
file.addEventListener(Event.SELECT, function(event:Event) {
cast(event.target, FileReference).load();
});
file.addEventListener(IOErrorEvent.IO_ERROR, function(event:IOErrorEvent) {
d.throwError(event);
});
file.addEventListener(ProgressEvent.PROGRESS, function(event:ProgressEvent) {
//trace('progress', '${event}');
});
file.addEventListener(Event.COMPLETE, function(event:Event) {
var f:FileReference = cast event.target;
d.resolve({
name: f.name,
content: Bytes.ofData(f.data),
});
});
file.browse();
#if android
fileUtilBrowse(new Callback<BytesData>(function(result:BytesData):Void {
d.resolve({
name: "",
content: Bytes.ofData(result),
});
}));
#end
return d.promise();
}
public static function save(content:FileContent):Void {
var file = new FileReference();
file.save(content.content.getData(), content.name);
}
}

View File

@@ -0,0 +1,23 @@
package ru.m.puzzlez;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.events.Event;
import haxe.io.Bytes;
import promhx.Deferred;
import promhx.Promise;
class ImageUtil {
public static function bytesToImage(bytes:Bytes):Promise<BitmapData> {
var def = new Deferred();
var loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(event:Event) {
def.resolve(cast(cast(event.target, LoaderInfo).content, Bitmap).bitmapData);
});
loader.loadBytes(bytes);
return def.promise();
}
}

View File

@@ -1,9 +1,10 @@
package ru.m.puzzlez.core;
import flash.geom.Rectangle;
import ru.m.puzzlez.core.Id;
typedef GamePreset = {
var image:ImageSource;
var image:ImageId;
var grid:Grid;
var tableRect:Rectangle;
var imageRect:Rectangle;

View File

@@ -1,5 +1,6 @@
package ru.m.puzzlez.core;
import ru.m.puzzlez.core.Id.ImageId;
import flash.geom.Point;
import flash.geom.Rectangle;
import ru.m.puzzlez.core.Part;
@@ -62,7 +63,7 @@ class BoundsMap {
class GameUtil {
public static function buildPreset(image:ImageSource):GamePreset {
public static function buildPreset(image:ImageId):GamePreset {
var size = 8;
var offsetX = 500;
var offsetY = 200;

View File

@@ -0,0 +1,24 @@
package ru.m.puzzlez.core;
typedef SourceId = String;
abstract ImageId({source:SourceId, id:String}) {
public var source(get, never):SourceId;
private inline function get_source():SourceId return this.source;
public var id(get, never):String;
private inline function get_id():String return this.id;
public function new(source:SourceId, id:String) {
this = {source: source, id: id};
}
@:from public static function fromString(value:String):ImageId {
var args = value.split("_");
return new ImageId(args[0], args[1]);
}
@:to public function toString():String {
return '${this.source}_${this.id}';
}
}

View File

@@ -1,6 +0,0 @@
package ru.m.puzzlez.core;
enum ImageSource {
ASSET(name:String);
URL(url:String, ?preview:String);
}

View File

@@ -0,0 +1,33 @@
package ru.m.puzzlez.storage;
import flash.display.BitmapData;
import openfl.Assets;
import openfl.utils.AssetType;
import promhx.Promise;
import ru.m.puzzlez.core.Id;
class AssetSource implements IImageSource<Dynamic> {
public static var ID:SourceId = "asset";
public var id(default, never):SourceId = ID;
private var data:Promise<Array<ImageId>>;
public function new() {
}
private function resolveData():Array<ImageId> {
return [for (name in Assets.list(AssetType.IMAGE).filter(function(name:String) return name.substr(0, 15) == "resources/image")) new ImageId(id, name)];
}
public function getList(?type:Dynamic):Promise<Array<ImageId>> {
if (data == null) {
data = Promise.promise(resolveData());
}
return data;
}
public function loadImage(id:ImageId, preview:Bool = false):Promise<BitmapData> {
return Promise.promise(Assets.getBitmapData(id.id));
}
}

View File

@@ -1,21 +0,0 @@
package ru.m.puzzlez.storage;
import openfl.Assets;
import openfl.utils.AssetType;
import promhx.Promise;
import ru.m.puzzlez.core.ImageSource;
@:provide class AssetStorage implements ISourceStorage<Dynamic> {
var data:Promise<Array<ImageSource>>;
public function new() {
}
public function resolve(?type:Dynamic):Promise<Array<ImageSource>> {
if (data == null) {
data = Promise.promise([for (name in Assets.list(AssetType.IMAGE).filter(function(name:String) return name.substr(0, 15) == "resources/image")) ASSET(name)]);
}
return data;
}
}

View File

@@ -0,0 +1,48 @@
package ru.m.puzzlez.storage;
import flash.display.BitmapData;
import flash.net.SharedObject;
import haxe.crypto.Md5;
import haxe.io.Bytes;
import promhx.Promise;
import ru.m.puzzlez.core.Id;
class FileSource implements IImageSource<Dynamic> {
public static var ID:SourceId = "file";
public var id(default, never):SourceId = ID;
private var listData:SharedObject;
public function new() {
listData = SharedObject.getLocal('${id}_list');
}
public function getList(?type:Dynamic):Promise<Array<ImageId>> {
return Promise.promise([for (name in Reflect.fields(listData.data)) new ImageId(id, name)]);
}
public function loadImage(id:ImageId, preview:Bool = false):Promise<BitmapData> {
var fileData = SharedObject.getLocal(id);
return ImageUtil.bytesToImage(Bytes.ofHex(Reflect.field(fileData.data, "image")));
}
public function append(data:Bytes):ImageId {
var name = Md5.make(data).toHex();
var imageId = new ImageId(id, name);
var fileData = SharedObject.getLocal(imageId);
fileData.setProperty("image", data.toHex());
fileData.flush();
listData.setProperty(name, true);
listData.flush();
return imageId;
}
public function remove(id:ImageId):Void {
var fileData = SharedObject.getLocal(id);
fileData.clear();
fileData.flush();
Reflect.deleteField(listData.data, id);
listData.flush();
}
}

View File

@@ -0,0 +1,11 @@
package ru.m.puzzlez.storage;
import flash.display.BitmapData;
import promhx.Promise;
import ru.m.puzzlez.core.Id;
interface IImageSource<T> {
public var id(default, never):SourceId;
public function getList(?type:T):Promise<Array<ImageId>>;
public function loadImage(id:ImageId, preview:Bool = false):Promise<BitmapData>;
}

View File

@@ -1,8 +0,0 @@
package ru.m.puzzlez.storage;
import promhx.Promise;
import ru.m.puzzlez.core.ImageSource;
interface ISourceStorage<T> {
public function resolve(?type:T):Promise<Array<ImageSource>>;
}

View File

@@ -7,23 +7,40 @@ import flash.display.LoaderInfo;
import flash.display.PNGEncoderOptions;
import flash.events.Event;
import flash.geom.Rectangle;
import flash.net.SharedObject;
import flash.utils.ByteArray;
import haxework.net.ImageLoader;
import haxework.storage.SharedObjectStorage;
import openfl.utils.Assets;
import promhx.Deferred;
import promhx.Promise;
import ru.m.puzzlez.core.ImageSource;
import Reflect;
import ru.m.puzzlez.core.Id;
@:provide class ImageStorage extends SharedObjectStorage {
@:provide class ImageStorage {
public var sources(default, null):Map<SourceId, IImageSource<Dynamic>>;
private var cache:Map<String, Promise<BitmapData>>;
private var enabled:Bool;
public function new() {
super("image_1");
sources = new Map();
cache = new Map();
enabled = false;
register(new AssetSource());
register(new FileSource());
register(new PixabaySource());
}
public function register(source:IImageSource<Dynamic>):Void {
sources.set(source.id, source);
}
public function read(id:ImageId):BitmapData {
var so = SharedObject.getLocal(id);
return Reflect.field(so.data, "");
}
public function write(id:ImageId, data:BitmapData):Void {
var so = SharedObject.getLocal(id);
so.setProperty("", data);
so.flush();
}
public static function serialize(image:BitmapData):ByteArray {
@@ -42,24 +59,12 @@ import ru.m.puzzlez.core.ImageSource;
return def.promise();
}
public function resolve(source:ImageSource, preview:Bool = false):Promise<BitmapData> {
var key = '${source}_${preview}';
public function resolve(id:ImageId, preview:Bool = false):Promise<BitmapData> {
var key = '${id}_${preview}';
if (cache.exists(key)) {
return cache.get(key);
}
var result:Promise<BitmapData> = enabled && exists(key) ?
unserialize(Reflect.field(so.data, key)) :
(switch source {
case ASSET(name):
Promise.promise(Assets.getBitmapData(name));
case URL(url, previewUrl):
new ImageLoader().GET(preview && previewUrl != null ? previewUrl : url);
}).then(function(image) {
if (enabled) {
so.setProperty(key, serialize(image));
}
return image;
});
var result = sources.get(id.source).loadImage(id, preview);
cache.set(key, result);
return result;
}

View File

@@ -0,0 +1,94 @@
package ru.m.puzzlez.storage;
import flash.display.BitmapData;
import haxework.net.ImageLoader;
import haxework.net.JsonLoader;
import promhx.Promise;
import ru.m.puzzlez.core.Id;
typedef PixabayImage = {
var id:Int;
var largeImageURL:String;
var webformatURL:String;
var previewURL:String;
}
typedef PixabayResponse = {
var hits:Array<PixabayImage>;
}
@:enum abstract PixabayCategory(String) from String to String {
var FASHION = "fashion";
var NATURE = "nature";
var BACKGROUNDS = "backgrounds";
var SCIENCE = "science";
var EDUCATION = "education";
var PEOPLE = "people";
var FEELINGS = "feelings";
var RELIGION = "religion";
var HEALTH = "health";
var PLACES = "places";
var ANIMALS = "animals";
var INDUSTRY = "industry";
var FOOD = "food";
var COMPUTER = "computer";
var SPORTS = "sports";
var TRANSPORTATION = "transportation";
var TRAVEL = "travel";
var BUILDINGS = "buildings";
var BUSINESS = "business";
var MUSIC = "music";
}
@:enum abstract PixabayImageType(String) from String to String {
var ALL = "all";
var PHOTO = "photo";
var ILLUSTRATION = "illustration";
var VECTOR = "vector";
}
class PixabaySource implements IImageSource<PixabayCategory> {
public static var ID:SourceId = "pixabay";
public var id(default, never):SourceId = ID;
private var baseUrl:String = "https://pixabay.com/api/";
private var key:String = "14915210-5eae157281211e0ad28bc8def";
private var cache:Map<String, Promise<PixabayImage>>;
public function new() {
cache = new Map();
}
public function getList(?type:PixabayCategory):Promise<Array<ImageId>> {
return new JsonLoader<PixabayResponse>()
.GET('${baseUrl}?key=${key}&category=${type}&image_type=${PixabayImageType.PHOTO}')
.then(function(response:PixabayResponse) {
var result = [];
for (item in response.hits) {
var imageId = new ImageId(id, Std.string(item.id));
cache.set(imageId, Promise.promise(item));
result.push(imageId);
}
return [for (item in response.hits) new ImageId(id, Std.string(item.id))];
});
}
private function getImage(id:ImageId):Promise<PixabayImage> {
if (!cache.exists(id)) {
cache.set(id, new JsonLoader<PixabayResponse>()
.GET('${baseUrl}?key=${key}&id=${id.id}')
.then(function(response:PixabayResponse) {
return response.hits[0];
}));
}
return cache.get(id);
}
public function loadImage(id:ImageId, preview:Bool = false):Promise<BitmapData> {
return getImage(id).pipe(function(data:PixabayImage) {
return new ImageLoader().GET(preview ? data.previewURL : data.largeImageURL);
});
}
}

View File

@@ -1,66 +0,0 @@
package ru.m.puzzlez.storage;
import haxework.net.JsonLoader;
import haxework.storage.SharedObjectStorage;
import promhx.Promise;
import ru.m.puzzlez.core.ImageSource;
typedef PixabayImage = {
var id:Int;
var largeImageURL:String;
var webformatURL:String;
var previewURL:String;
}
typedef PixabayResponse = {
var hits:Array<PixabayImage>;
}
@:enum abstract PixabayCategory(String) from String to String {
var FASHION = "fashion";
var NATURE = "nature";
var BACKGROUNDS = "backgrounds";
var SCIENCE = "science";
var EDUCATION = "education";
var PEOPLE = "people";
var FEELINGS = "feelings";
var RELIGION = "religion";
var HEALTH = "health";
var PLACES = "places";
var ANIMALS = "animals";
var INDUSTRY = "industry";
var FOOD = "food";
var COMPUTER = "computer";
var SPORTS = "sports";
var TRANSPORTATION = "transportation";
var TRAVEL = "travel";
var BUILDINGS = "buildings";
var BUSINESS = "business";
var MUSIC = "music";
}
@:provide class PixabayStorage extends SharedObjectStorage implements ISourceStorage<PixabayCategory> {
private var key:String;
private var enabled:Bool;
public function new() {
super("pixabay_1");
key = "14915210-5eae157281211e0ad28bc8def";
enabled = false;
}
public function resolve(?type:PixabayCategory):Promise<Array<ImageSource>> {
return enabled && exists(type) ?
Promise.promise(read(type)) :
new JsonLoader<PixabayResponse>()
.GET('https://pixabay.com/api/?key=${key}&category=${type}')
.then(function(result:PixabayResponse) {
var result = [for (item in result.hits) URL(item.largeImageURL, item.previewURL)];
if (enabled) {
write(type, result);
}
return result;
});
}
}

View File

@@ -4,11 +4,11 @@ import haxework.view.frame.FrameSwitcher;
import haxework.view.frame.FrameView;
import ru.m.puzzlez.core.Game;
import ru.m.puzzlez.core.GameUtil;
import ru.m.puzzlez.core.Id;
import ru.m.puzzlez.core.IGame;
import ru.m.puzzlez.core.ImageSource;
import ru.m.puzzlez.render.IRender;
@:template class GameFrame extends FrameView<ImageSource> {
@:template class GameFrame extends FrameView<ImageId> {
public static var ID = "game";
@:view private var render:IRender;
@@ -19,7 +19,7 @@ import ru.m.puzzlez.render.IRender;
super(ID);
}
override public function onShow(image:ImageSource):Void {
override public function onShow(image:ImageId):Void {
onHide();
game = new Game(GameUtil.buildPreset(image));
game.signal.connect(render.onGameEvent);

View File

@@ -0,0 +1,8 @@
package ru.m.puzzlez.view;
import ru.m.puzzlez.storage.IImageSource;
typedef ImageListSource<T> = {
var source:IImageSource<T>;
@:optional var type:T;
}

View File

@@ -1,28 +1,37 @@
package ru.m.puzzlez.view;
import haxework.view.data.DataView;
import haxework.view.form.ButtonView;
import haxework.view.frame.FrameSwitcher;
import haxework.view.frame.FrameView;
import haxework.view.ImageView;
import haxework.view.utils.DrawUtil;
import openfl.Assets;
import ru.m.puzzlez.core.ImageSource;
import ru.m.puzzlez.core.Id;
import ru.m.puzzlez.FileUtil;
import ru.m.puzzlez.storage.FileSource;
import ru.m.puzzlez.storage.ImageStorage;
@:template class ImagesFrame extends FrameView<Array<ImageSource>> {
@:template class ImagesFrame extends FrameView<ImageListSource<Dynamic>> {
public static var ID = "images";
@:view var images:DataView<ImageSource, ImageView>;
@:view var images:DataView<ImageId, ImageView>;
@:view var select:ButtonView;
@:provide var imageStorage:ImageStorage;
@:provide var switcher:FrameSwitcher;
private var source:ImageListSource<Dynamic>;
public function new() {
super(ID);
}
override public function onShow(data:Array<ImageSource>):Void {
override public function onShow(data:ImageListSource<Dynamic>):Void {
if (data != null) {
images.data = data;
source = data;
select.visible = source.source.id == FileSource.ID;
images.data = [];
data.source.getList(data.type).then(function(result) images.data = result);
}
}
@@ -30,7 +39,7 @@ import ru.m.puzzlez.storage.ImageStorage;
//images.data = [];
}
private function imageViewFactory(index:Int, image:ImageSource):ImageView {
private function imageViewFactory(index:Int, image:ImageId):ImageView {
var result = new ImageView();
result.style = "view";
result.stretch = false;
@@ -41,7 +50,16 @@ import ru.m.puzzlez.storage.ImageStorage;
return result;
}
private function start(image:ImageSource):Void {
private function selectFile():Void {
FileUtil.browse().then(function(data:FileContent) {
var fileSource:FileSource = cast source.source;
var imageId = fileSource.append(data.content);
images.data.push(imageId);
images.data = images.data;
});
}
private function start(image:ImageId):Void {
switcher.change(GameFrame.ID, image);
}

View File

@@ -12,6 +12,11 @@ views:
+onDataSelect: ~start
geometry.margin: 5
overflow.y: scroll
- id: select
$type: haxework.view.form.ButtonView
text: Select...
+onPress: ~selectFile()
visible: false
- $type: haxework.view.form.ButtonView
text: Back
geometry.position: absolute

View File

@@ -4,45 +4,36 @@ import haxework.view.data.DataView;
import haxework.view.form.ButtonView;
import haxework.view.frame.FrameSwitcher;
import haxework.view.frame.FrameView;
import ru.m.puzzlez.core.ImageSource;
import ru.m.puzzlez.storage.AssetStorage;
import ru.m.puzzlez.storage.ISourceStorage;
import ru.m.puzzlez.storage.PixabayStorage;
typedef Source<T> = {
var storage:ISourceStorage<T>;
@:optional var type:T;
}
import ru.m.puzzlez.storage.AssetSource;
import ru.m.puzzlez.storage.FileSource;
import ru.m.puzzlez.storage.ImageStorage;
import ru.m.puzzlez.storage.PixabaySource;
@:template class StartFrame extends FrameView<Dynamic> {
public static var ID = "start";
@:view var sources:DataView<Source<Dynamic>, ButtonView>;
@:provide var assetStorage:AssetStorage;
@:provide var pixabayStorage:PixabayStorage;
@:view var sources:DataView<ImageListSource<Dynamic>, ButtonView>;
@:provide var storage:ImageStorage;
@:provide var switcher:FrameSwitcher;
public function new() {
super(ID);
var data:Array<Source<Dynamic>> = [];
data.push({storage: assetStorage});
var data:Array<ImageListSource<Dynamic>> = [];
data.push({source: storage.sources.get(AssetSource.ID), type: "asset"});
data.push({source: storage.sources.get(FileSource.ID), type: "file"});
for (type in AbstractEnumTools.getValues(PixabayCategory)) {
data.push({storage: pixabayStorage, type: type});
data.push({source: storage.sources.get(PixabaySource.ID), type: type});
}
sources.data = data;
}
private function sourceViewFactory(index:Int, source:Source<Dynamic>):ButtonView {
private function sourceViewFactory(index:Int, source:ImageListSource<Dynamic>):ButtonView {
var result = new ButtonView();
result.text = Std.string(source.type != null ? source.type : "custom");
return result;
}
private function load(source:Source<Dynamic>):Void {
source.storage.resolve(source.type).then(onLoaded);
}
private function onLoaded(result:Array<ImageSource>):Void {
switcher.change(ImagesFrame.ID, result);
private function load(source:ImageListSource<Dynamic>):Void {
switcher.change(ImagesFrame.ID, source);
}
}