[server] add module
This commit is contained in:
99
src/app/haxe/ru/m/puzzlez/view/GameFrame.hx
Normal file
99
src/app/haxe/ru/m/puzzlez/view/GameFrame.hx
Normal file
@@ -0,0 +1,99 @@
|
||||
package ru.m.puzzlez.view;
|
||||
|
||||
import haxe.Timer;
|
||||
import hw.view.frame.FrameSwitcher;
|
||||
import hw.view.frame.FrameView;
|
||||
import hw.view.popup.ConfirmView;
|
||||
import promhx.Promise;
|
||||
import ru.m.puzzlez.core.Game;
|
||||
import ru.m.puzzlez.core.GameEvent;
|
||||
import ru.m.puzzlez.core.GameState;
|
||||
import ru.m.puzzlez.core.IGame;
|
||||
import ru.m.puzzlez.render.IRender;
|
||||
import ru.m.puzzlez.storage.GameStorage;
|
||||
import ru.m.puzzlez.storage.SettingsStorage;
|
||||
import ru.m.puzzlez.view.popup.BackgroundPopup;
|
||||
import ru.m.puzzlez.view.popup.PreviewPopup;
|
||||
|
||||
@:template class GameFrame extends FrameView<GameState> {
|
||||
public static var ID = "game";
|
||||
|
||||
@:view private var render:IRender;
|
||||
private var game:IGame;
|
||||
@:provide var switcher:FrameSwitcher;
|
||||
@:provide var storage:GameStorage;
|
||||
@:provide var settings:SettingsStorage;
|
||||
|
||||
private var saveTimer:Timer;
|
||||
|
||||
public function new() {
|
||||
super(ID);
|
||||
}
|
||||
|
||||
override public function onShow(state:GameState):Void {
|
||||
onHide();
|
||||
game = new Game(state);
|
||||
game.signal.connect(render.onGameEvent);
|
||||
game.signal.connect(onGameEvent);
|
||||
render.signal.connect(game.signal.emit);
|
||||
game.start();
|
||||
}
|
||||
|
||||
override public function onHide():Void {
|
||||
if (saveTimer != null) {
|
||||
save();
|
||||
}
|
||||
if (game != null) {
|
||||
render.signal.disconnect(game.signal.emit);
|
||||
game.stop();
|
||||
game.dispose();
|
||||
game = null;
|
||||
}
|
||||
}
|
||||
|
||||
private function toSave():Void {
|
||||
if (saveTimer == null) {
|
||||
saveTimer = Timer.delay(save, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
private function save():Void {
|
||||
if (saveTimer != null) {
|
||||
saveTimer.stop();
|
||||
saveTimer = null;
|
||||
}
|
||||
if (game != null) {
|
||||
storage.save(game.state);
|
||||
}
|
||||
}
|
||||
|
||||
private function onGameEvent(event:GameEvent):Void {
|
||||
switch event {
|
||||
case START(_) | ACTION(PART_PUT(_, _)):
|
||||
toSave();
|
||||
case _:
|
||||
}
|
||||
}
|
||||
|
||||
private function showPreview():Void {
|
||||
PreviewPopup.instance.showPreview(game.state);
|
||||
}
|
||||
|
||||
private function choiseBackground():Void {
|
||||
BackgroundPopup.instance.choise(settings.background).then(background -> {
|
||||
if (background != null) {
|
||||
settings.background = background;
|
||||
render.toRedraw();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function back():Void {
|
||||
(game.state.status == COMPLETE ? Promise.promise(true) : ConfirmView.confirm("Exit?"))
|
||||
.then(result -> {
|
||||
if (result) {
|
||||
switcher.change(ImageListFrame.ID, storage.statusSource(game.state.status));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
41
src/app/haxe/ru/m/puzzlez/view/GameFrame.yaml
Normal file
41
src/app/haxe/ru/m/puzzlez/view/GameFrame.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
style: frame
|
||||
layout:
|
||||
$type: hw.view.layout.HorizontalLayout
|
||||
views:
|
||||
- $type: hw.view.group.VGroupView
|
||||
geometry.height: 100%
|
||||
geometry.padding: 5
|
||||
layout.margin: 10
|
||||
views:
|
||||
- $type: hw.view.form.ButtonView
|
||||
style: icon.image
|
||||
+onPress: ~showPreview()
|
||||
- $type: hw.view.SpriteView
|
||||
geometry.stretch: true
|
||||
- $type: hw.view.form.ToggleButtonView
|
||||
style: icon.lock
|
||||
on: true
|
||||
+onPress: |
|
||||
~button -> {
|
||||
render.manager.locked = !render.manager.locked;
|
||||
cast(button, hw.view.form.ToggleButtonView).on = !render.manager.locked;
|
||||
}
|
||||
- $type: hw.view.form.ButtonView
|
||||
style: icon.restore
|
||||
+onPress: ~render.manager.reset()
|
||||
- $type: hw.view.form.ButtonView
|
||||
style: icon.setting
|
||||
geometry.margin.top: 30
|
||||
+onPress: ~choiseBackground()
|
||||
- id: render
|
||||
$type: ru.m.puzzlez.render.Render
|
||||
geometry.width: 100%
|
||||
geometry.height: 100%
|
||||
- $type: hw.view.form.ButtonView
|
||||
style: icon.close
|
||||
geometry.position: absolute
|
||||
geometry.hAlign: right
|
||||
geometry.vAlign: top
|
||||
geometry.margin: 5
|
||||
+onPress: ~back()
|
||||
51
src/app/haxe/ru/m/puzzlez/view/ImageListFrame.hx
Normal file
51
src/app/haxe/ru/m/puzzlez/view/ImageListFrame.hx
Normal file
@@ -0,0 +1,51 @@
|
||||
package ru.m.puzzlez.view;
|
||||
|
||||
import hw.view.form.ButtonView;
|
||||
import hw.view.form.LabelView;
|
||||
import hw.view.frame.FrameSwitcher;
|
||||
import hw.view.frame.FrameView;
|
||||
import ru.m.puzzlez.core.ImageListSource;
|
||||
import ru.m.puzzlez.FileUtil;
|
||||
import ru.m.puzzlez.source.FileSource;
|
||||
import ru.m.puzzlez.storage.GameStorage;
|
||||
import ru.m.puzzlez.storage.ImageStorage;
|
||||
import ru.m.puzzlez.view.common.ImageDataList;
|
||||
|
||||
@:template class ImageListFrame extends FrameView<ImageListSource> {
|
||||
public static var ID = "image_list";
|
||||
|
||||
@:view("header") var headerView:LabelView;
|
||||
@:view("images") var imagesView:ImageDataList;
|
||||
@:view var select:ButtonView;
|
||||
|
||||
@:provide var gameStorage:GameStorage;
|
||||
@:provide var imageStorage:ImageStorage;
|
||||
@:provide var switcher:FrameSwitcher;
|
||||
|
||||
public function new() {
|
||||
super(ID);
|
||||
}
|
||||
|
||||
override public function onShow(data:ImageListSource):Void {
|
||||
imagesView.reset();
|
||||
if (data != null) {
|
||||
headerView.text = data.title;
|
||||
// ToDo:
|
||||
select.visible = Std.is(data.source, FileSource);
|
||||
imagesView.page.filter = data.filter;
|
||||
imagesView.source = data.source;
|
||||
imagesView.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private function selectFile():Void {
|
||||
FileUtil.browse().then((data:FileContent) -> {
|
||||
var fileSource:FileSource = cast imageStorage.sources.get(FileSource.ID);
|
||||
fileSource.append(data.content).then(_ -> imagesView.refresh());
|
||||
});
|
||||
}
|
||||
|
||||
private function back():Void {
|
||||
switcher.change(StartFrame.ID);
|
||||
}
|
||||
}
|
||||
20
src/app/haxe/ru/m/puzzlez/view/ImageListFrame.yaml
Normal file
20
src/app/haxe/ru/m/puzzlez/view/ImageListFrame.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
style: frame
|
||||
views:
|
||||
- id: header
|
||||
$type: hw.view.form.LabelView
|
||||
style: label.header
|
||||
- id: images
|
||||
$type: ru.m.puzzlez.view.common.ImageDataList
|
||||
geometry.stretch: true
|
||||
- id: select
|
||||
$type: hw.view.form.ButtonView
|
||||
text: Select...
|
||||
+onPress: ~selectFile()
|
||||
visible: false
|
||||
- $type: hw.view.form.ButtonView
|
||||
text: Back
|
||||
geometry.position: absolute
|
||||
geometry.hAlign: right
|
||||
geometry.vAlign: top
|
||||
+onPress: ~back()
|
||||
108
src/app/haxe/ru/m/puzzlez/view/LoadingWrapper.hx
Normal file
108
src/app/haxe/ru/m/puzzlez/view/LoadingWrapper.hx
Normal file
@@ -0,0 +1,108 @@
|
||||
package ru.m.puzzlez.view;
|
||||
|
||||
import hw.view.form.LabelView;
|
||||
import flash.text.TextFormatAlign;
|
||||
import hw.view.geometry.HAlign;
|
||||
import hw.view.geometry.Position;
|
||||
import hw.view.geometry.VAlign;
|
||||
import hw.view.group.IGroupView;
|
||||
import hw.view.IView;
|
||||
import hw.view.text.TextView;
|
||||
import promhx.Promise;
|
||||
|
||||
enum State {
|
||||
NONE;
|
||||
LOADING;
|
||||
ERROR(error:Dynamic);
|
||||
}
|
||||
|
||||
class LoadingWrapper {
|
||||
public var promise(null, set):Promise<Dynamic>;
|
||||
|
||||
private function set_promise(value:Promise<Dynamic>):Promise<Dynamic> {
|
||||
state = LOADING;
|
||||
value
|
||||
.then(_ -> state = NONE)
|
||||
.catchError(error -> state = ERROR(error));
|
||||
return value;
|
||||
}
|
||||
|
||||
public var state(default, set):State;
|
||||
|
||||
private function set_state(value:State):State {
|
||||
if (state != value) {
|
||||
state = value;
|
||||
switch state {
|
||||
case NONE:
|
||||
overlay = null;
|
||||
case LOADING:
|
||||
overlay = loadingView;
|
||||
case ERROR(error):
|
||||
L.e("wrapper", "", error);
|
||||
cast(errorView, TextView).text = Std.string(error);
|
||||
overlay = errorView;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
private var view:IGroupView;
|
||||
|
||||
private var overlay(default, set):IView<Dynamic>;
|
||||
|
||||
private function set_overlay(value:IView<Dynamic>):IView<Dynamic> {
|
||||
if (overlay != null && view.containsView(overlay)) {
|
||||
view.removeView(overlay);
|
||||
}
|
||||
overlay = value;
|
||||
if (overlay != null) {
|
||||
view.addView(overlay);
|
||||
}
|
||||
return overlay;
|
||||
}
|
||||
|
||||
private var loadingView(get, null):IView<Dynamic>;
|
||||
|
||||
private function get_loadingView():IView<Dynamic> {
|
||||
if (loadingView == null) {
|
||||
loadingView = buildLoadingView();
|
||||
}
|
||||
return loadingView;
|
||||
}
|
||||
|
||||
private var errorView(get, null):IView<Dynamic>;
|
||||
|
||||
private function get_errorView():IView<Dynamic> {
|
||||
if (errorView == null) {
|
||||
errorView = buildErrorView();
|
||||
}
|
||||
return errorView;
|
||||
}
|
||||
|
||||
public function new(view:IGroupView) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
private function buildLoadingView():IView<Dynamic> {
|
||||
var view = new LabelView();
|
||||
view.geometry.position = ABSOLUTE;
|
||||
view.geometry.hAlign = CENTER;
|
||||
view.geometry.vAlign = MIDDLE;
|
||||
view.text = "Loading...";
|
||||
return view;
|
||||
}
|
||||
|
||||
private function buildErrorView():IView<Dynamic> {
|
||||
var view = new TextView();
|
||||
view.geometry.position = ABSOLUTE;
|
||||
view.geometry.hAlign = CENTER;
|
||||
view.geometry.vAlign = MIDDLE;
|
||||
view.geometry.stretch = true;
|
||||
view.style = "text.error";
|
||||
return view;
|
||||
}
|
||||
|
||||
public function reset():Void {
|
||||
overlay = null;
|
||||
}
|
||||
}
|
||||
63
src/app/haxe/ru/m/puzzlez/view/PresetFrame.hx
Normal file
63
src/app/haxe/ru/m/puzzlez/view/PresetFrame.hx
Normal file
@@ -0,0 +1,63 @@
|
||||
package ru.m.puzzlez.view;
|
||||
|
||||
import hw.geom.IntPoint;
|
||||
import hw.view.data.DataView;
|
||||
import hw.view.form.ToggleButtonView;
|
||||
import hw.view.frame.FrameSwitcher;
|
||||
import hw.view.frame.FrameView;
|
||||
import ru.m.puzzlez.core.GameUtil;
|
||||
import ru.m.puzzlez.core.Id;
|
||||
import ru.m.puzzlez.storage.ImageStorage;
|
||||
import ru.m.puzzlez.view.common.PresetView;
|
||||
|
||||
@:template class PresetFrame extends FrameView<ImageId> {
|
||||
public static var ID = "preset";
|
||||
|
||||
@:view("image") var imageView:PresetView;
|
||||
@:view("sizes") var sizesView:DataView<IntPoint, ToggleButtonView>;
|
||||
|
||||
@:provide var imageStorage:ImageStorage;
|
||||
@:provide var switcher:FrameSwitcher;
|
||||
|
||||
private var imageId:ImageId;
|
||||
|
||||
public function new() {
|
||||
super(ID);
|
||||
sizesView.data = [
|
||||
new IntPoint(3, 2),
|
||||
new IntPoint(4, 3),
|
||||
new IntPoint(5, 4),
|
||||
new IntPoint(6, 5),
|
||||
new IntPoint(7, 6),
|
||||
new IntPoint(8, 7),
|
||||
new IntPoint(10, 8),
|
||||
];
|
||||
}
|
||||
|
||||
private function factory(index:Int, size:IntPoint):ToggleButtonView {
|
||||
var result = new ToggleButtonView();
|
||||
result.text = '${size.x}x${size.y}';
|
||||
return result;
|
||||
}
|
||||
|
||||
private function selectSize(size:IntPoint):Void {
|
||||
imageView.state = GameUtil.buildState(GameUtil.buildPreset(imageId, size.x, size.y));
|
||||
for (i in 0...sizesView.dataViews.length) {
|
||||
sizesView.dataViews[i].on = size == sizesView.data[i];
|
||||
}
|
||||
}
|
||||
|
||||
override public function onShow(data:ImageId):Void {
|
||||
super.onShow(data);
|
||||
imageId = data;
|
||||
selectSize(sizesView.data[sizesView.data.length - 1]);
|
||||
}
|
||||
|
||||
private function start():Void {
|
||||
switcher.change(GameFrame.ID, imageView.state);
|
||||
}
|
||||
|
||||
private function back():Void {
|
||||
switcher.change(ImageListFrame.ID);
|
||||
}
|
||||
}
|
||||
36
src/app/haxe/ru/m/puzzlez/view/PresetFrame.yaml
Normal file
36
src/app/haxe/ru/m/puzzlez/view/PresetFrame.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
style: frame
|
||||
layout.margin: 10
|
||||
views:
|
||||
- $type: hw.view.form.LabelView
|
||||
text: Puzzle configure
|
||||
style: label.header
|
||||
- $type: hw.view.group.HGroupView
|
||||
geometry.width: 100%
|
||||
layout.hAlign: center
|
||||
layout.vAlign: middle
|
||||
views:
|
||||
- id: sizes
|
||||
$type: hw.view.data.DataView
|
||||
layout:
|
||||
$type: hw.view.layout.TailLayout
|
||||
hAlign: center
|
||||
stretch: true
|
||||
margin: 5
|
||||
factory: ~factory
|
||||
+onDataSelect: ~selectSize
|
||||
- $type: hw.view.form.ButtonView
|
||||
style: button.active
|
||||
geometry.margin.left: 15
|
||||
text: Start
|
||||
+onPress: ~start()
|
||||
- id: image
|
||||
$type: ru.m.puzzlez.view.common.PresetView
|
||||
geometry.stretch: true
|
||||
geometry.margin: 15
|
||||
- $type: hw.view.form.ButtonView
|
||||
text: Back
|
||||
geometry.position: absolute
|
||||
geometry.hAlign: right
|
||||
geometry.vAlign: top
|
||||
+onPress: ~back()
|
||||
34
src/app/haxe/ru/m/puzzlez/view/PuzzlezAppView.hx
Normal file
34
src/app/haxe/ru/m/puzzlez/view/PuzzlezAppView.hx
Normal file
@@ -0,0 +1,34 @@
|
||||
package ru.m.puzzlez.view;
|
||||
|
||||
import flash.events.KeyboardEvent;
|
||||
import flash.ui.Keyboard;
|
||||
import hw.view.form.ButtonView;
|
||||
import hw.view.frame.FrameSwitcher;
|
||||
import hw.view.group.VGroupView;
|
||||
import hw.app.App;
|
||||
|
||||
@:template class PuzzlezAppView extends VGroupView {
|
||||
|
||||
@:view("switcher") var switcherView:FrameSwitcher;
|
||||
@:view("fullscreen") var fullscreenButton:ButtonView;
|
||||
|
||||
@:provide static var switcher:FrameSwitcher;
|
||||
@:provide static var app:App;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
fullscreenButton.visible = app.fullScreenSupport;
|
||||
app.fullScreenSignal.connect(fs -> fullscreenButton.style = 'icon.${fs ? "compress" : "expand"}');
|
||||
switcher = switcherView;
|
||||
switcher.change(StartFrame.ID);
|
||||
stage.addEventListener(KeyboardEvent.KEY_DOWN, (event:KeyboardEvent) -> {
|
||||
switch event.keyCode {
|
||||
case Keyboard.ESCAPE:
|
||||
switcher.change(StartFrame.ID);
|
||||
case Keyboard.F:
|
||||
app.fullScreen = !app.fullScreen;
|
||||
case _:
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
20
src/app/haxe/ru/m/puzzlez/view/PuzzlezAppView.yaml
Normal file
20
src/app/haxe/ru/m/puzzlez/view/PuzzlezAppView.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
views:
|
||||
- $type: hw.view.frame.FrameSwitcher
|
||||
id: switcher
|
||||
geometry.stretch: true
|
||||
style: dark
|
||||
factory:
|
||||
_start_: {$class: ru.m.puzzlez.view.StartFrame}
|
||||
_image_list_: {$class: ru.m.puzzlez.view.ImageListFrame}
|
||||
_preset_: {$class: ru.m.puzzlez.view.PresetFrame}
|
||||
_game_: {$class: ru.m.puzzlez.view.GameFrame}
|
||||
- id: fullscreen
|
||||
$type: hw.view.form.ButtonView
|
||||
geometry.position: absolute
|
||||
geometry.hAlign: right
|
||||
geometry.vAlign: bottom
|
||||
geometry.margin: 10
|
||||
style: icon.expand
|
||||
+onPress: ~_ -> app.fullScreen = !app.fullScreen
|
||||
visible: false
|
||||
81
src/app/haxe/ru/m/puzzlez/view/StartFrame.hx
Normal file
81
src/app/haxe/ru/m/puzzlez/view/StartFrame.hx
Normal file
@@ -0,0 +1,81 @@
|
||||
package ru.m.puzzlez.view;
|
||||
|
||||
import hw.view.data.DataView;
|
||||
import hw.view.form.ButtonView;
|
||||
import hw.view.frame.FrameSwitcher;
|
||||
import hw.view.frame.FrameView;
|
||||
import ru.m.data.IDataSource;
|
||||
import ru.m.pixabay.PixabayApi;
|
||||
import ru.m.puzzlez.core.GameState.GameStatus;
|
||||
import ru.m.puzzlez.core.ImageListSource;
|
||||
import ru.m.puzzlez.source.AssetSource;
|
||||
import ru.m.puzzlez.source.FileSource;
|
||||
import ru.m.puzzlez.source.PixabaySource;
|
||||
import ru.m.puzzlez.storage.GameStorage;
|
||||
import ru.m.puzzlez.storage.ImageStorage;
|
||||
import ru.m.update.Updater;
|
||||
|
||||
@:template class StartFrame extends FrameView<Dynamic> {
|
||||
public static var ID = "start";
|
||||
|
||||
@:view var sources:DataView<ImageListSource, ButtonView>;
|
||||
@:view("load") var loadButton:ButtonView;
|
||||
@:view("complete") var completeButton:ButtonView;
|
||||
@:view("update") var updateButton:ButtonView;
|
||||
|
||||
@:provide var storage:ImageStorage;
|
||||
@:provide var switcher:FrameSwitcher;
|
||||
@:provide var gameStorage:GameStorage;
|
||||
@:provide static var appUpdater:Updater;
|
||||
|
||||
public function new() {
|
||||
super(ID);
|
||||
var data:Array<ImageListSource> = [];
|
||||
data.push({title: "Assets", source: storage.sources.get(AssetSource.ID)});
|
||||
data.push({title: "Files", source: storage.sources.get(FileSource.ID)});
|
||||
var pixabay:PixabaySource = cast storage.sources.get(PixabaySource.ID);
|
||||
for (type in AbstractEnumTools.getValues(PixabayCategory)) {
|
||||
data.push(pixabay.categorySource(type));
|
||||
}
|
||||
sources.data = data;
|
||||
}
|
||||
|
||||
private function refresh():Void {
|
||||
var startedRequest:Page = {index: 0, count: 0, filter: ["status" => STARTED]};
|
||||
gameStorage.getIndexPage(startedRequest).then(page -> {
|
||||
var total = page.total;
|
||||
loadButton.text = 'Resume (${total})';
|
||||
loadButton.disabled = total == 0;
|
||||
});
|
||||
var completeRequest:Page = {index: 0, count: 0, filter: ["status" => COMPLETE]};
|
||||
gameStorage.getIndexPage(completeRequest).then(page -> {
|
||||
var total = page.total;
|
||||
completeButton.text = 'Complete (${total})';
|
||||
completeButton.disabled = total == 0;
|
||||
});
|
||||
}
|
||||
|
||||
override public function onShow(data:Dynamic):Void {
|
||||
refresh();
|
||||
appUpdater.check().then((info:Null<PackageInfo>) -> {
|
||||
if (info != null) {
|
||||
updateButton.visible = true;
|
||||
updateButton.text = 'Update ${info.version}';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function sourceViewFactory(index:Int, source:ImageListSource):ButtonView {
|
||||
var result = new ButtonView();
|
||||
result.text = source.title;
|
||||
return result;
|
||||
}
|
||||
|
||||
private function load(source:ImageListSource):Void {
|
||||
switcher.change(ImageListFrame.ID, source);
|
||||
}
|
||||
|
||||
private function games(status:GameStatus):Void {
|
||||
switcher.change(ImageListFrame.ID, gameStorage.statusSource(status));
|
||||
}
|
||||
}
|
||||
57
src/app/haxe/ru/m/puzzlez/view/StartFrame.yaml
Normal file
57
src/app/haxe/ru/m/puzzlez/view/StartFrame.yaml
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
style: frame
|
||||
views:
|
||||
- $type: hw.view.group.HGroupView
|
||||
geometry.hAlign: center
|
||||
geometry.margin.top: 15
|
||||
layout.vAlign: middle
|
||||
views:
|
||||
- $type: hw.view.ImageView
|
||||
image: $a:image:resources/icon.png
|
||||
stretch: false
|
||||
fillType: CONTAIN
|
||||
geometry.width: 96
|
||||
geometry.height: 96
|
||||
- $type: hw.view.form.LabelView
|
||||
text: $r:text:app.name
|
||||
font.size: 50
|
||||
- id: sources
|
||||
$type: hw.view.data.DataView
|
||||
layout:
|
||||
$type: hw.view.layout.TailLayout
|
||||
rowSize: 5
|
||||
margin: 10
|
||||
vAlign: middle
|
||||
geometry.stretch: true
|
||||
factory: ~sourceViewFactory
|
||||
+onDataSelect: ~load
|
||||
geometry.margin: 5
|
||||
overflow.y: scroll
|
||||
- $type: hw.view.group.HGroupView
|
||||
geometry.width: 100%
|
||||
layout.vAlign: middle
|
||||
layout.margin: 10
|
||||
views:
|
||||
- id: load
|
||||
$type: hw.view.form.ButtonView
|
||||
text: Load
|
||||
+onPress: ~games('started')
|
||||
- id: complete
|
||||
$type: hw.view.form.ButtonView
|
||||
text: Complete
|
||||
+onPress: ~games('complete')
|
||||
- $type: hw.view.SpriteView
|
||||
geometry.width: 100%
|
||||
- $type: hw.view.form.LabelView
|
||||
text: $r:text:app.version
|
||||
geometry.position: absolute
|
||||
geometry.hAlign: right
|
||||
geometry.vAlign: top
|
||||
- id: update
|
||||
$type: hw.view.form.ButtonView
|
||||
style: button.active
|
||||
geometry.position: absolute
|
||||
geometry.hAlign: left
|
||||
geometry.vAlign: top
|
||||
+onPress: ~appUpdater.download()
|
||||
visible: false
|
||||
92
src/app/haxe/ru/m/puzzlez/view/common/ImageDataList.hx
Normal file
92
src/app/haxe/ru/m/puzzlez/view/common/ImageDataList.hx
Normal file
@@ -0,0 +1,92 @@
|
||||
package ru.m.puzzlez.view.common;
|
||||
|
||||
import hw.view.data.DataView;
|
||||
import hw.view.form.ToggleButtonView;
|
||||
import hw.view.frame.FrameSwitcher;
|
||||
import hw.view.group.VGroupView;
|
||||
import hw.view.popup.ConfirmView;
|
||||
import ru.m.data.IDataSource;
|
||||
import ru.m.puzzlez.core.Id;
|
||||
import ru.m.puzzlez.source.FileSource;
|
||||
import ru.m.puzzlez.storage.GameStorage;
|
||||
import ru.m.puzzlez.storage.ImageStorage;
|
||||
import ru.m.puzzlez.view.common.PuzzleImageView;
|
||||
|
||||
@:template class ImageDataList extends VGroupView {
|
||||
|
||||
public var source:IDataIndex<ImageId>;
|
||||
public var page:Page;
|
||||
|
||||
@:view("images") var imagesView:ActionDataView<ImageId, PuzzleImageView, Action>;
|
||||
@:view("paginator") var paginatorView:PaginatorView;
|
||||
private var loading:LoadingWrapper;
|
||||
|
||||
@:provide var switcher:FrameSwitcher;
|
||||
@:provide var gameStorage:GameStorage;
|
||||
@:provide var imageStorage:ImageStorage;
|
||||
|
||||
public var data(default, set):DataPage<ImageId>;
|
||||
|
||||
private function set_data(value:DataPage<ImageId>):DataPage<ImageId> {
|
||||
data = value;
|
||||
imagesView.data = data.data;
|
||||
paginatorView.page = data;
|
||||
return data;
|
||||
}
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
loading = new LoadingWrapper(imagesView);
|
||||
page = {index: 0, count: 6, order: [{key: "date", reverse: true}]};
|
||||
}
|
||||
|
||||
private function pageFactory(index:Int, value:Int):ToggleButtonView {
|
||||
var result = new ToggleButtonView();
|
||||
result.text = '${value}';
|
||||
result.on = data.page.index == value;
|
||||
return result;
|
||||
}
|
||||
|
||||
private function start(imageId:ImageId):Void {
|
||||
gameStorage.get(imageId).then(state -> {
|
||||
if (state != null) {
|
||||
switcher.change(GameFrame.ID, state);
|
||||
} else {
|
||||
switcher.change(PresetFrame.ID, imageId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function onAction(imageId:ImageId, action:Action):Void {
|
||||
switch action {
|
||||
case REMOVE:
|
||||
var fileSource:FileSource = cast imageStorage.sources.get(FileSource.ID);
|
||||
if (fileSource != null) {
|
||||
ConfirmView.confirm("Delete image?").then(result -> {
|
||||
if (result) {
|
||||
fileSource.remove(imageId).then(_ -> refresh());
|
||||
}
|
||||
});
|
||||
}
|
||||
case CLEAN:
|
||||
ConfirmView.confirm("Delete state?").then(result -> {
|
||||
if (result) {
|
||||
gameStorage.delete(imageId).then(_ -> refresh());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function refresh():Void {
|
||||
loading.promise = source.getIndexPage(page).then(data -> this.data = data);
|
||||
}
|
||||
|
||||
public function reset():Void {
|
||||
page.index = 0;
|
||||
data = {
|
||||
page: page,
|
||||
total: 0,
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
23
src/app/haxe/ru/m/puzzlez/view/common/ImageDataList.yaml
Normal file
23
src/app/haxe/ru/m/puzzlez/view/common/ImageDataList.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
views:
|
||||
- id: images
|
||||
$type: hw.view.data.ActionDataView
|
||||
layout:
|
||||
$type: hw.view.layout.TailLayout
|
||||
rowSize: 3
|
||||
margin: 5
|
||||
vAlign: middle
|
||||
geometry.stretch: true
|
||||
factory: ~ru.m.puzzlez.view.common.PuzzleImageView.factory
|
||||
+onDataSelect: ~start
|
||||
+onDataAction: ~onAction
|
||||
geometry.margin: 5
|
||||
overflow.y: scroll
|
||||
- id: paginator
|
||||
$type: ru.m.puzzlez.view.common.PaginatorView
|
||||
geometry.width: 100%
|
||||
+onPageSelect: |
|
||||
~(index) -> {
|
||||
page.index = index;
|
||||
refresh();
|
||||
}
|
||||
82
src/app/haxe/ru/m/puzzlez/view/common/PaginatorView.hx
Normal file
82
src/app/haxe/ru/m/puzzlez/view/common/PaginatorView.hx
Normal file
@@ -0,0 +1,82 @@
|
||||
package ru.m.puzzlez.view.common;
|
||||
|
||||
import hw.signal.Signal;
|
||||
import hw.view.data.DataView;
|
||||
import hw.view.form.ToggleButtonView;
|
||||
import hw.view.layout.TailLayout;
|
||||
import ru.m.data.IDataSource.DataPage;
|
||||
|
||||
enum PaginatorAction {
|
||||
START;
|
||||
JUMP(distance:Int);
|
||||
INDEX(index:Int);
|
||||
END;
|
||||
}
|
||||
|
||||
class PaginatorButtonView extends ToggleButtonView {
|
||||
public var action(default, set):PaginatorAction;
|
||||
|
||||
private function set_action(value:PaginatorAction):PaginatorAction {
|
||||
action = value;
|
||||
text = switch action {
|
||||
case INDEX(index): Std.string(index);
|
||||
case x: Std.string(x);
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
public static function factory(index:Int, action:PaginatorAction):PaginatorButtonView {
|
||||
var result = new PaginatorButtonView();
|
||||
result.action = action;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class PaginatorView extends DataView<PaginatorAction, PaginatorButtonView> {
|
||||
|
||||
public var page(default, set):DataPage<Dynamic>;
|
||||
public var onPageSelect(default, null):Signal<Int>;
|
||||
|
||||
private function set_page(value:DataPage<Dynamic>):DataPage<Dynamic> {
|
||||
page = value;
|
||||
refresh();
|
||||
return page;
|
||||
}
|
||||
|
||||
public function new() {
|
||||
super(new TailLayout());
|
||||
layout.margin = 3;
|
||||
onPageSelect = new Signal();
|
||||
factory = PaginatorButtonView.factory;
|
||||
onDataSelect.connect(onAction);
|
||||
}
|
||||
|
||||
private function onAction(action:PaginatorAction):Void {
|
||||
onPageSelect.emit(switch action {
|
||||
case START: 0;
|
||||
case INDEX(index): index;
|
||||
case JUMP(distance): page.page.index + distance;
|
||||
case END: Std.int((page.total - 1) / page.page.count);
|
||||
});
|
||||
}
|
||||
|
||||
private function refresh():Void {
|
||||
var total:Int = Std.int((page.total - 1) / page.page.count);
|
||||
var start:Int = Std.int(Math.max(page.page.index - 2, 0));
|
||||
var end:Int = Std.int(Math.min(page.page.index + 2, total));
|
||||
var data = [for (index in start...end + 1) INDEX(index)];
|
||||
if (start > 0) {
|
||||
data.unshift(START);
|
||||
}
|
||||
if (end < total) {
|
||||
data.push(END);
|
||||
}
|
||||
this.data = data;
|
||||
for (button in dataViews) {
|
||||
button.on = switch button.action {
|
||||
case INDEX(index): index == page.page.index;
|
||||
case x: false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
src/app/haxe/ru/m/puzzlez/view/common/PresetView.hx
Normal file
85
src/app/haxe/ru/m/puzzlez/view/common/PresetView.hx
Normal file
@@ -0,0 +1,85 @@
|
||||
package ru.m.puzzlez.view.common;
|
||||
|
||||
import flash.display.BitmapData;
|
||||
import flash.display.Graphics;
|
||||
import flash.display.Shape;
|
||||
import hw.view.group.GroupView;
|
||||
import ru.m.puzzlez.core.GameState;
|
||||
import ru.m.puzzlez.render.RenderUtil;
|
||||
import ru.m.puzzlez.storage.ImageStorage;
|
||||
|
||||
class PresetView extends GroupView {
|
||||
@:provide var imageStorage:ImageStorage;
|
||||
|
||||
public var scale(get, set):Float;
|
||||
|
||||
private function get_scale():Float {
|
||||
return table.scaleX;
|
||||
}
|
||||
|
||||
private function set_scale(value:Float):Float {
|
||||
var result = table.scaleX = table.scaleY = value;
|
||||
table.x = (width - state.preset.imageRect.width * value) / 2;
|
||||
table.y = (height - state.preset.imageRect.height * value) / 2;
|
||||
return result;
|
||||
}
|
||||
|
||||
public var state(default, set):GameState;
|
||||
|
||||
private function set_state(value:GameState):GameState {
|
||||
state = value;
|
||||
this.image = null;
|
||||
table.graphics.clear();
|
||||
loading.promise = imageStorage.resolve(state.preset.imageId).then(image -> {
|
||||
this.image = RenderUtil.cropImage(image, state.preset.imageRect);
|
||||
toRedraw();
|
||||
toUpdate();
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
private var loading:LoadingWrapper;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
table = new Shape();
|
||||
content.addChild(table);
|
||||
loading = new LoadingWrapper(this);
|
||||
}
|
||||
|
||||
private var table:Shape;
|
||||
private var image:BitmapData;
|
||||
|
||||
override public function redraw():Void {
|
||||
if (state == null || image == null) {
|
||||
return;
|
||||
}
|
||||
var preset = state.preset;
|
||||
var partWidth = preset.imageRect.width / preset.grid.width;
|
||||
var partHeight = preset.imageRect.height / preset.grid.height;
|
||||
var graphics:Graphics = table.graphics;
|
||||
graphics.clear();
|
||||
graphics.beginBitmapFill(image, null, false, true);
|
||||
graphics.drawRect(0, 0, preset.imageRect.width, preset.imageRect.height);
|
||||
graphics.endFill();
|
||||
|
||||
for (part in state.parts) {
|
||||
var rect = part.rect.clone();
|
||||
rect.x = part.gridX * part.rect.width;
|
||||
rect.y = part.gridY * part.rect.height;
|
||||
var path = RenderUtil.builder.build(rect, part.bounds);
|
||||
for (value in RenderUtil.borderSettings) {
|
||||
graphics.lineStyle(1, value.color, value.opacity);
|
||||
path.move(value.offset.x, value.offset.y).draw(graphics);
|
||||
graphics.lineStyle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public function update():Void {
|
||||
super.update();
|
||||
if (state != null) {
|
||||
scale = Math.min(width / state.preset.imageRect.width, height / state.preset.imageRect.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
78
src/app/haxe/ru/m/puzzlez/view/common/PuzzleImageView.hx
Normal file
78
src/app/haxe/ru/m/puzzlez/view/common/PuzzleImageView.hx
Normal file
@@ -0,0 +1,78 @@
|
||||
package ru.m.puzzlez.view.common;
|
||||
|
||||
import hw.view.data.DataView;
|
||||
import hw.view.form.ButtonView;
|
||||
import hw.view.form.LabelView;
|
||||
import hw.view.group.GroupView;
|
||||
import hw.view.ImageView;
|
||||
import ru.m.puzzlez.core.GameUtil;
|
||||
import ru.m.puzzlez.core.Id;
|
||||
import ru.m.puzzlez.source.FileSource;
|
||||
import ru.m.puzzlez.storage.GameStorage;
|
||||
import ru.m.puzzlez.storage.ImageStorage;
|
||||
|
||||
enum Action {
|
||||
CLEAN;
|
||||
REMOVE;
|
||||
}
|
||||
|
||||
@:template class PuzzleImageView extends GroupView {
|
||||
|
||||
public var imageId(default, set):ImageId;
|
||||
|
||||
private function set_imageId(value:ImageId):ImageId {
|
||||
if (imageId != value) {
|
||||
imageId = value;
|
||||
loading.promise = imageStorage.resolve(imageId, true).then(data -> imageView.image = data);
|
||||
}
|
||||
return imageId;
|
||||
}
|
||||
|
||||
public var text(default, set):String;
|
||||
|
||||
private function set_text(value:String):String {
|
||||
if (text != value) {
|
||||
text = value;
|
||||
labelView.text = text;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@:view("image") var imageView:ImageView;
|
||||
@:view("label") var labelView:LabelView;
|
||||
@:view("clean") var cleanButton:ButtonView;
|
||||
@:view("remove") var removeButton:ButtonView;
|
||||
@:provide static var imageStorage:ImageStorage;
|
||||
@:provide static var gameStorage:GameStorage;
|
||||
private var loading:LoadingWrapper;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
cleanButton.visible = false;
|
||||
removeButton.visible = false;
|
||||
loading = new LoadingWrapper(this);
|
||||
}
|
||||
|
||||
private function emit(action:Action):Void {
|
||||
var dataView:ActionDataView<Dynamic, PuzzleImageView, Action> = Std.instance(parent, ActionDataView);
|
||||
if (dataView != null) {
|
||||
var index = dataView.dataViews.indexOf(this);
|
||||
dataView.onDataAction.emit(dataView.data[index], action);
|
||||
}
|
||||
}
|
||||
|
||||
public static function factory(index:Int, imageId:ImageId):PuzzleImageView {
|
||||
var result = new PuzzleImageView();
|
||||
result.imageId = imageId;
|
||||
gameStorage.get(imageId).then(state -> {
|
||||
if (state != null) {
|
||||
var progress = GameUtil.calcProgress(state);
|
||||
result.text = '${progress.complete}/${progress.total}';
|
||||
result.cleanButton.visible = true;
|
||||
} else if (imageId.source == FileSource.ID) {
|
||||
result.removeButton.visible = true;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
20
src/app/haxe/ru/m/puzzlez/view/common/PuzzleImageView.yaml
Normal file
20
src/app/haxe/ru/m/puzzlez/view/common/PuzzleImageView.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
style: view
|
||||
views:
|
||||
- id: image
|
||||
$type: hw.view.ImageView
|
||||
geometry.stretch: true
|
||||
stretch: false
|
||||
fillType: COVER
|
||||
- id: label
|
||||
$type: hw.view.form.LabelView
|
||||
- id: remove
|
||||
$type: hw.view.form.ButtonView
|
||||
propagation: false
|
||||
style: icon.control.small.close.red
|
||||
+onPress: ~emit(Action.REMOVE)
|
||||
- id: clean
|
||||
$type: hw.view.form.ButtonView
|
||||
propagation: false
|
||||
style: icon.control.small.close.orange
|
||||
+onPress: ~emit(Action.CLEAN)
|
||||
94
src/app/haxe/ru/m/puzzlez/view/popup/BackgroundPopup.hx
Normal file
94
src/app/haxe/ru/m/puzzlez/view/popup/BackgroundPopup.hx
Normal file
@@ -0,0 +1,94 @@
|
||||
package ru.m.puzzlez.view.popup;
|
||||
|
||||
import hw.color.Color;
|
||||
import hw.view.data.DataView;
|
||||
import hw.view.form.ButtonView;
|
||||
import hw.view.popup.PopupView;
|
||||
import hw.view.skin.Skin;
|
||||
import hw.view.SpriteView;
|
||||
import hw.view.utils.DrawUtil;
|
||||
import openfl.Assets;
|
||||
import openfl.utils.AssetType;
|
||||
import promhx.Promise;
|
||||
import ru.m.puzzlez.core.Id.ImageId;
|
||||
import ru.m.puzzlez.render.Background;
|
||||
import ru.m.puzzlez.storage.ImageStorage;
|
||||
|
||||
@:singleton @:template class BackgroundPopup extends PopupView<Background> {
|
||||
|
||||
private static var colorsList:Array<Color> = [
|
||||
'#FFFFFF',
|
||||
'#001f3f',
|
||||
'#0074D9',
|
||||
'#7FDBFF',
|
||||
'#39CCCC',
|
||||
'#3D9970',
|
||||
'#2ECC40',
|
||||
'#01FF70',
|
||||
'#FFDC00',
|
||||
'#FF851B',
|
||||
'#FF4136',
|
||||
'#85144b',
|
||||
'#F012BE',
|
||||
'#B10DC9',
|
||||
'#111111',
|
||||
'#AAAAAA',
|
||||
'#DDDDDD',
|
||||
];
|
||||
|
||||
@:view("selected") var selectedView:SpriteView;
|
||||
@:view("colors") var colorsView:DataView<Color, ButtonView>;
|
||||
@:view("textures") var texturesView:DataView<ImageId, ButtonView>;
|
||||
|
||||
public var selected(default, set):Background;
|
||||
|
||||
@:provide static var imageStorage:ImageStorage;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
colorsView.data = colorsList;
|
||||
var textures = [];
|
||||
for (name in Assets.list(AssetType.IMAGE)) {
|
||||
if (StringTools.startsWith(name, 'resources/texture')) {
|
||||
textures.push(new ImageId('asset', name));
|
||||
}
|
||||
}
|
||||
texturesView.data = textures;
|
||||
}
|
||||
|
||||
private function set_selected(value:Background):Background {
|
||||
selected = value;
|
||||
switch selected {
|
||||
case NONE:
|
||||
selectedView.skin = null;
|
||||
case COLOR(color):
|
||||
selectedView.skin = Skin.color(color);
|
||||
case IMAGE(id):
|
||||
imageStorage.resolve(id).then(result -> {
|
||||
selectedView.skin = Skin.bitmap(result, REPEAT);
|
||||
selectedView.toRedraw();
|
||||
});
|
||||
}
|
||||
selectedView.toRedraw();
|
||||
return selected;
|
||||
}
|
||||
|
||||
private function colorButtonFactory(index:Int, color:Color):ButtonView {
|
||||
var result = new ButtonView();
|
||||
result.text = " ";
|
||||
result.skin = Skin.buttonColor(color);
|
||||
return result;
|
||||
}
|
||||
|
||||
private function textureButtonFactory(index:Int, imageId:ImageId):ButtonView {
|
||||
var result = new ButtonView();
|
||||
result.text = " ";
|
||||
result.skin = Skin.buttonBitmap(Assets.getBitmapData(imageId.id), REPEAT);
|
||||
return result;
|
||||
}
|
||||
|
||||
public function choise(current:Background):Promise<Background> {
|
||||
selected = current;
|
||||
return show();
|
||||
}
|
||||
}
|
||||
49
src/app/haxe/ru/m/puzzlez/view/popup/BackgroundPopup.yaml
Normal file
49
src/app/haxe/ru/m/puzzlez/view/popup/BackgroundPopup.yaml
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
view:
|
||||
$type: hw.view.group.VGroupView
|
||||
geometry.width: 660
|
||||
geometry.height: 200
|
||||
geometry.padding: 10
|
||||
geometry.hAlign: center
|
||||
geometry.vAlign: middle
|
||||
layout.margin: 10
|
||||
style: frame
|
||||
views:
|
||||
- $type: hw.view.group.HGroupView
|
||||
geometry.width: 100%
|
||||
layout.margin: 10
|
||||
views:
|
||||
- id: header
|
||||
text: Choise background
|
||||
$type: hw.view.form.LabelView
|
||||
- id: selected
|
||||
$type: hw.view.SpriteView
|
||||
geometry.width: 100%
|
||||
geometry.height: 200
|
||||
- id: colors
|
||||
$type: hw.view.data.DataView
|
||||
factory: ~colorButtonFactory
|
||||
geometry.width: 100%
|
||||
layout:
|
||||
$type: hw.view.layout.TailLayout
|
||||
margin: 5
|
||||
+onDataSelect: ~(color) -> selected = COLOR(color)
|
||||
- id: textures
|
||||
$type: hw.view.data.DataView
|
||||
factory: ~textureButtonFactory
|
||||
geometry.width: 100%
|
||||
layout:
|
||||
$type: hw.view.layout.TailLayout
|
||||
margin: 5
|
||||
+onDataSelect: ~(imageId) -> selected = IMAGE(imageId)
|
||||
- $type: hw.view.group.HGroupView
|
||||
geometry.width: 100%
|
||||
layout.hAlign: center
|
||||
layout.margin: 10
|
||||
views:
|
||||
- $type: hw.view.form.ButtonView
|
||||
text: Default
|
||||
+onPress: ~close(NONE)
|
||||
- $type: hw.view.form.ButtonView
|
||||
text: OK
|
||||
+onPress: ~close(selected)
|
||||
22
src/app/haxe/ru/m/puzzlez/view/popup/PreviewPopup.hx
Normal file
22
src/app/haxe/ru/m/puzzlez/view/popup/PreviewPopup.hx
Normal file
@@ -0,0 +1,22 @@
|
||||
package ru.m.puzzlez.view.popup;
|
||||
|
||||
import flash.events.MouseEvent;
|
||||
import hw.view.popup.PopupView;
|
||||
import promhx.Promise;
|
||||
import ru.m.puzzlez.core.GameState;
|
||||
import ru.m.puzzlez.view.common.PresetView;
|
||||
|
||||
@:singleton @:template class PreviewPopup extends PopupView<Dynamic> {
|
||||
|
||||
@:view("preview") var previewView:PresetView;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
content.addEventListener(MouseEvent.CLICK, _ -> close(null));
|
||||
}
|
||||
|
||||
public function showPreview(state:GameState):Promise<Dynamic> {
|
||||
previewView.state = state;
|
||||
return show();
|
||||
}
|
||||
}
|
||||
29
src/app/haxe/ru/m/puzzlez/view/popup/PreviewPopup.yaml
Normal file
29
src/app/haxe/ru/m/puzzlez/view/popup/PreviewPopup.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
view:
|
||||
$type: hw.view.group.VGroupView
|
||||
geometry.width: 100%
|
||||
geometry.height: 100%
|
||||
geometry.padding: 10
|
||||
geometry.hAlign: center
|
||||
geometry.vAlign: middle
|
||||
layout.margin: 10
|
||||
style: frame
|
||||
views:
|
||||
- $type: hw.view.group.HGroupView
|
||||
geometry.width: 100%
|
||||
layout.margin: 10
|
||||
views:
|
||||
- id: header
|
||||
text: Image preview
|
||||
$type: hw.view.form.LabelView
|
||||
- id: preview
|
||||
$type: ru.m.puzzlez.view.common.PresetView
|
||||
geometry.stretch: true
|
||||
- $type: hw.view.group.HGroupView
|
||||
geometry.width: 100%
|
||||
layout.hAlign: center
|
||||
layout.margin: 10
|
||||
views:
|
||||
- $type: hw.view.form.ButtonView
|
||||
text: OK
|
||||
+onPress: ~close(null)
|
||||
Reference in New Issue
Block a user