[add] frames views

This commit is contained in:
2020-01-17 14:51:03 +03:00
parent a4a33708a1
commit 10a01a971c
19 changed files with 341 additions and 139 deletions

View File

@@ -0,0 +1,38 @@
package ru.m;
#if macro
import haxe.macro.Context;
import haxe.macro.Expr;
using haxe.macro.Tools;
#end
class AbstractEnumTools {
public static macro function getValues(typePath:Expr):Expr {
// Get the type from a given expression converted to string.
// This will work for identifiers and field access which is what we need,
// it will also consider local imports. If expression is not a valid type path or type is not found,
// compiler will give a error here.
var type = Context.getType(typePath.toString());
// Switch on the type and check if it's an abstract with @:enum metadata
switch (type.follow()) {
case TAbstract(_.get() => ab, _) if (ab.meta.has(":enum")):
// @:enum abstract values are actually static fields of the abstract implementation class,
// marked with @:enum and @:impl metadata. We generate an array of expressions that access those fields.
// Note that this is a bit of implementation detail, so it can change in future Haxe versions, but it's been
// stable so far.
var valueExprs = [];
for (field in ab.impl.get().statics.get()) {
if (field.meta.has(":enum") && field.meta.has(":impl")) {
var fieldName = field.name;
valueExprs.push(macro $typePath.$fieldName);
}
}
// Return collected expressions as an array declaration.
return macro $a{valueExprs};
default:
// The given type is not an abstract, or doesn't have @:enum metadata, show a nice error message.
throw new Error(type.toString() + " should be @:enum abstract", typePath.pos);
}
}
}

20
src/haxe/ru/m/puzzlez/Const.hx Executable file
View File

@@ -0,0 +1,20 @@
package ru.m.puzzlez;
import flash.Lib;
import flash.system.Capabilities;
class Const {
public static var FPS:Int;
public static var BUILD:String;
public static var VERSION:String;
public static var NAME:String;
public static var DEBUG:Bool;
public static function init():Void {
FPS = Std.parseInt(Lib.current.stage.application.meta.get("fps"));
BUILD = CompilationOption.get("build");
VERSION = Lib.current.stage.application.meta.get("version");
NAME = Lib.current.stage.application.meta.get("name");
DEBUG = Capabilities.isDebugger;
}
}

View File

@@ -12,7 +12,9 @@ class PuzzlezApp extends App {
LinuxIcon.apply();
#end
var app = new PuzzlezApp(new PuzzlezTheme());
app.start(new PuzzlezAppView());
var view = new PuzzlezAppView();
app.start(view);
view.launch();
L.d("Puzzlez", "started");
}
}

View File

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

View File

@@ -6,12 +6,16 @@ class ProgressView extends LabelView {
public function new() {
super();
text = 'Loading 0/0';
update();
text = 'Loading...';
}
public function setProgress(current:Int, total:Int):Void {
text = 'Loading ${current}/${total}';
}
override private function set_text(value:String):String {
var result = super.set_text(value);
update();
return result;
}
}

View File

@@ -84,7 +84,7 @@ class Render extends SpriteView implements IRender {
table.graphics.drawRect(state.preset.imageRect.x, state.preset.imageRect.y, state.preset.imageRect.width, state.preset.imageRect.height);
table.graphics.endFill();
table.graphics.lineStyle();
progress.setProgress(0, state.parts.length);
progress.text = "Loading image";
content.addChild(progress.content);
imageStorage.resolve(state.preset.image).then(onImageResolved);
toUpdate();

View File

@@ -5,14 +5,14 @@ import openfl.utils.AssetType;
import promhx.Promise;
import ru.m.puzzlez.core.ImageSource;
@:provide class AssetStorage implements ISourceStorage {
@:provide class AssetStorage implements ISourceStorage<Dynamic> {
var data:Promise<Array<ImageSource>>;
public function new() {
}
public function resolve(?type:String):Promise<Array<ImageSource>> {
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)]);
}

View File

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

View File

@@ -17,7 +17,7 @@ import ru.m.puzzlez.core.ImageSource;
@:provide class ImageStorage extends SharedObjectStorage {
private var cache:Map<ImageSource, Promise<BitmapData>>;
private var cache:Map<String, Promise<BitmapData>>;
private var enabled:Bool;
public function new() {
@@ -42,25 +42,25 @@ import ru.m.puzzlez.core.ImageSource;
return def.promise();
}
public function resolve(source:ImageSource):Promise<BitmapData> {
if (cache.exists(source)) {
return cache.get(source);
public function resolve(source:ImageSource, preview:Bool = false):Promise<BitmapData> {
var key = '${source}_${preview}';
if (cache.exists(key)) {
return cache.get(key);
}
var key = Std.string(source);
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):
new ImageLoader().GET(url);
case URL(url, previewUrl):
new ImageLoader().GET(preview && previewUrl != null ? previewUrl : url);
}).then(function(image) {
if (enabled) {
so.setProperty(key, serialize(image));
}
return image;
});
cache.set(source, result);
cache.set(key, result);
return result;
}
}

View File

@@ -5,35 +5,41 @@ 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 = {
hits:Array<{
largeImageURL:String,
}>
var hits:Array<PixabayImage>;
}
enum PixabayCategory {
FASHION;
NATURE;
BACKGROUNDS;
SCIENCE;
EDUCATION;
PEOPLE;
FEELINGS;
RELIGION;
HEALTH;
PLACES;
ANIMALS;
INDUSTRY;
FOOD;
COMPUTER;
SPORTS;
TRANSPORTATION;
TRAVEL; BUILDINGS;
BUSINESS;
MUSIC;
@: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 {
@:provide class PixabayStorage extends SharedObjectStorage implements ISourceStorage<PixabayCategory> {
private var key:String;
private var enabled:Bool;
@@ -44,13 +50,13 @@ enum PixabayCategory {
enabled = false;
}
public function resolve(?type:String):Promise<Array<ImageSource>> {
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}&q=${type}')
.GET('https://pixabay.com/api/?key=${key}&category=${type}')
.then(function(result:PixabayResponse) {
var result = [for (item in result.hits) URL(item.largeImageURL)];
var result = [for (item in result.hits) URL(item.largeImageURL, item.previewURL)];
if (enabled) {
write(type, result);
}

View File

@@ -0,0 +1,42 @@
package ru.m.puzzlez.view;
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.IGame;
import ru.m.puzzlez.core.ImageSource;
import ru.m.puzzlez.render.IRender;
@:template class GameFrame extends FrameView<ImageSource> {
public static var ID = "game";
@:view private var render:IRender;
private var game:IGame;
@:provide var switcher:FrameSwitcher;
public function new() {
super(ID);
}
override public function onShow(image:ImageSource):Void {
onHide();
game = new Game(GameUtil.buildPreset(image));
game.signal.connect(render.onGameEvent);
render.signal.connect(game.signal.emit);
game.start();
}
override public function onHide():Void {
if (game != null) {
render.signal.disconnect(game.signal.emit);
game.stop();
game.dispose();
game = null;
}
}
private function back():Void {
switcher.change(ImagesFrame.ID);
}
}

View File

@@ -0,0 +1,17 @@
---
style: frame
geometry.width: 100%
geometry.height: 100%
overflow.x: crop
overflow.y: crop
views:
- id: render
$type: ru.m.puzzlez.render.Render
geometry.width: 100%
geometry.height: 100%
- $type: haxework.view.form.ButtonView
text: Back
geometry.position: absolute
geometry.hAlign: right
geometry.vAlign: top
+onPress: ~back()

View File

@@ -0,0 +1,51 @@
package ru.m.puzzlez.view;
import haxework.view.data.DataView;
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.storage.ImageStorage;
@:template class ImagesFrame extends FrameView<Array<ImageSource>> {
public static var ID = "images";
@:view var images:DataView<ImageSource, ImageView>;
@:provide var imageStorage:ImageStorage;
@:provide var switcher:FrameSwitcher;
public function new() {
super(ID);
}
override public function onShow(data:Array<ImageSource>):Void {
if (data != null) {
images.data = data;
}
}
override public function onHide():Void {
//images.data = [];
}
private function imageViewFactory(index:Int, image:ImageSource):ImageView {
var result = new ImageView();
result.style = "view";
result.stretch = false;
result.fillType = FillType.COVER;
result.setSize(192, 128);
result.image = Assets.getBitmapData("resources/icon.png");
imageStorage.resolve(image, true).then(function(image) result.image = image);
return result;
}
private function start(image:ImageSource):Void {
switcher.change(GameFrame.ID, image);
}
private function back():Void {
switcher.change(StartFrame.ID);
}
}

View File

@@ -0,0 +1,20 @@
---
style: frame
views:
- id: images
$type: haxework.view.data.DataView
layout:
$type: haxework.view.layout.TailLayout
margin: 5
vAlign: middle
geometry.stretch: true
factory: ~imageViewFactory
+onDataSelect: ~start
geometry.margin: 5
overflow.y: scroll
- $type: haxework.view.form.ButtonView
text: Back
geometry.position: absolute
geometry.hAlign: right
geometry.vAlign: top
+onPress: ~back()

View File

@@ -1,66 +1,33 @@
package ru.m.puzzlez.view;
import haxework.view.data.DataView;
import haxework.view.form.ButtonView;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
import haxework.resources.IResources;
import haxework.view.frame.FrameSwitcher;
import haxework.view.group.VGroupView;
import haxework.view.ImageView;
import haxework.view.utils.DrawUtil;
import openfl.utils.Assets;
import ru.m.puzzlez.core.Game;
import ru.m.puzzlez.core.GameUtil;
import ru.m.puzzlez.core.IGame;
import ru.m.puzzlez.core.ImageSource;
import ru.m.puzzlez.render.IRender;
import ru.m.puzzlez.storage.AssetStorage;
import ru.m.puzzlez.storage.ImageStorage;
import ru.m.puzzlez.storage.PixabayStorage;
@:template class PuzzlezAppView extends VGroupView {
@:view private var images:DataView<ImageSource, ImageView>;
@:view private var render:IRender;
private var game:IGame;
@:provide var imageStorage:ImageStorage;
@:provide var assetStorage:AssetStorage;
@:provide var pixabayStorage:PixabayStorage;
@:view("switcher") var switcherView:FrameSwitcher;
@:provide var switcher:FrameSwitcher;
@:provide var resources:IResources;
public function new() {
super();
assetStorage.resolve().then(function(data) images.data = data);
Const.init();
resources.text.put("version", Const.VERSION);
resources.text.put("name", Const.NAME);
switcher = switcherView;
}
private function imageViewFactory(index:Int, image:ImageSource):ImageView {
var result = new ImageView();
result.style = "view";
result.stretch = false;
result.fillType = FillType.COVER;
result.setSize(192, 128);
result.image = Assets.getBitmapData("resources/icon.png");
imageStorage.resolve(image).then(function(image) result.image = image);
return result;
}
private function moreImages(view:ButtonView):Void {
view.visible = false;
view.toUpdateParent();
pixabayStorage.resolve("nature").then(function(data) images.data = images.data.concat(data));
}
public function start(image:ImageSource):Void {
stop();
game = new Game(GameUtil.buildPreset(image));
game.signal.connect(render.onGameEvent);
render.signal.connect(game.signal.emit);
game.start();
}
public function stop():Void {
if (game != null) {
render.signal.disconnect(game.signal.emit);
game.stop();
game.dispose();
game = null;
}
public function launch():Void {
content.stage.stageFocusRect = false;
switcher.change(StartFrame.ID);
stage.addEventListener(KeyboardEvent.KEY_DOWN, function(event:KeyboardEvent) {
switch event.keyCode {
case Keyboard.ESCAPE:
switcher.change(StartFrame.ID);
}
});
}
}

View File

@@ -1,42 +1,10 @@
style: background
layout.hAlign: center
layout.vAlign: middle
---
views:
- $type: haxework.view.group.HGroupView
- $type: haxework.view.frame.FrameSwitcher
id: switcher
geometry.stretch: true
geometry.margin: 5
views:
- $type: haxework.view.group.VGroupView
geometry.height: 100%
layout.hAlign: center
views:
- $type: haxework.view.form.LabelView
text: Puzzle'z
font.size: 42
- $type: haxework.view.form.ButtonView
text: Shuffle
+onPress: ~game.shuffle()
- id: images
$type: haxework.view.data.DataView
layout:
$type: haxework.view.layout.VerticalLayout
margin: 5
geometry.height: 100%
factory: ~imageViewFactory
+onDataSelect: ~start
geometry.margin: 5
overflow.y: scroll
- $type: haxework.view.form.ButtonView
text: More
+onPress: ~moreImages
- $type: haxework.view.group.GroupView
style: frame
geometry.width: 100%
geometry.height: 100%
overflow.x: crop
overflow.y: crop
views:
- id: render
$type: ru.m.puzzlez.render.Render
geometry.width: 100%
geometry.height: 100%
style: dark
factory:
_start_: {$class: ru.m.puzzlez.view.StartFrame}
_images_: {$class: ru.m.puzzlez.view.ImagesFrame}
_game_: {$class: ru.m.puzzlez.view.GameFrame}

View File

@@ -0,0 +1,48 @@
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 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;
}
@: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;
@:provide var switcher:FrameSwitcher;
public function new() {
super(ID);
var data:Array<Source<Dynamic>> = [];
data.push({storage: assetStorage});
for (type in AbstractEnumTools.getValues(PixabayCategory)) {
data.push({storage: pixabayStorage, type: type});
}
sources.data = data;
}
private function sourceViewFactory(index:Int, source:Source<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);
}
}

View File

@@ -0,0 +1,19 @@
---
style: frame
views:
- $type: haxework.view.form.LabelView
text: $r:text:name
geometry.margin.top: 15
geometry.hAlign: center
font.size: 40
- id: sources
$type: haxework.view.data.DataView
layout:
$type: haxework.view.layout.TailLayout
margin: 10
vAlign: middle
geometry.stretch: true
factory: ~sourceViewFactory
+onDataSelect: ~load
geometry.margin: 5
overflow.y: scroll