├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── Project.xml
├── README.md
├── aseprite
├── banner.aseprite
├── banner.png
├── button.aseprite
├── chair.aseprite
├── cover.aseprite
├── cover.png
└── icon.aseprite
├── assets
├── icon.svg
└── images
│ ├── button.png
│ └── chair.png
└── source
├── AssetPaths.hx
├── Main.hx
└── PlayState.hx
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | export/
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "openfl.lime-vscode-extension"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Build + Debug",
6 | "type": "lime",
7 | "request": "launch"
8 | },
9 | {
10 | "name": "Debug",
11 | "type": "lime",
12 | "request": "launch",
13 | "preLaunchTask": null
14 | },
15 | {
16 | "name": "Macro",
17 | "type": "haxe-eval",
18 | "request": "launch"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "search.exclude": {
3 | "export/**/*.hx": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "lime",
6 | "command": "test",
7 | "group": {
8 | "kind": "build",
9 | "isDefault": true
10 | }
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Stackr
2 |
3 | Download at https://austineast.itch.io/stackr!
4 |
--------------------------------------------------------------------------------
/aseprite/banner.aseprite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AustinEast/stackr/2e14d4a7ce01fe947cf3ab448cbc6d5ce63b7399/aseprite/banner.aseprite
--------------------------------------------------------------------------------
/aseprite/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AustinEast/stackr/2e14d4a7ce01fe947cf3ab448cbc6d5ce63b7399/aseprite/banner.png
--------------------------------------------------------------------------------
/aseprite/button.aseprite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AustinEast/stackr/2e14d4a7ce01fe947cf3ab448cbc6d5ce63b7399/aseprite/button.aseprite
--------------------------------------------------------------------------------
/aseprite/chair.aseprite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AustinEast/stackr/2e14d4a7ce01fe947cf3ab448cbc6d5ce63b7399/aseprite/chair.aseprite
--------------------------------------------------------------------------------
/aseprite/cover.aseprite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AustinEast/stackr/2e14d4a7ce01fe947cf3ab448cbc6d5ce63b7399/aseprite/cover.aseprite
--------------------------------------------------------------------------------
/aseprite/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AustinEast/stackr/2e14d4a7ce01fe947cf3ab448cbc6d5ce63b7399/aseprite/cover.png
--------------------------------------------------------------------------------
/aseprite/icon.aseprite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AustinEast/stackr/2e14d4a7ce01fe947cf3ab448cbc6d5ce63b7399/aseprite/icon.aseprite
--------------------------------------------------------------------------------
/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/images/button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AustinEast/stackr/2e14d4a7ce01fe947cf3ab448cbc6d5ce63b7399/assets/images/button.png
--------------------------------------------------------------------------------
/assets/images/chair.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AustinEast/stackr/2e14d4a7ce01fe947cf3ab448cbc6d5ce63b7399/assets/images/chair.png
--------------------------------------------------------------------------------
/source/AssetPaths.hx:
--------------------------------------------------------------------------------
1 | package;
2 |
3 | @:build(flixel.system.FlxAssets.buildFileReferences("assets", true))
4 | class AssetPaths {}
--------------------------------------------------------------------------------
/source/Main.hx:
--------------------------------------------------------------------------------
1 | package;
2 |
3 | import flixel.FlxGame;
4 | import openfl.display.Sprite;
5 |
6 | class Main extends Sprite
7 | {
8 | public function new()
9 | {
10 | super();
11 | addChild(new FlxGame(120, 120, PlayState, 1, 60, 60, true));
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/source/PlayState.hx:
--------------------------------------------------------------------------------
1 | package;
2 |
3 | import haxe.io.Path;
4 | import lime.system.FileWatcher;
5 | import flixel.util.FlxSave;
6 | import openfl.display.BitmapData;
7 | import openfl.filters.ShaderFilter;
8 | import openfl.display.StageQuality;
9 | import flixel.addons.ui.FlxInputText;
10 | import flixel.system.FlxAssets;
11 | import flixel.ui.FlxButton;
12 | import flixel.text.FlxText;
13 | import flixel.util.FlxColor;
14 | import flixel.tweens.FlxEase;
15 | import flixel.tweens.FlxTween;
16 | import flixel.FlxCamera;
17 | import flixel.math.FlxMath;
18 | import flixel.FlxG;
19 | import flixel.FlxSprite;
20 | import flixel.group.FlxGroup.FlxTypedGroup;
21 | import flixel.FlxState;
22 |
23 | #if sys
24 | import systools.Dialogs;
25 | #end
26 |
27 | class Colors
28 | {
29 | public static var DARKGREY = 0xff212121;
30 | public static var MEDGREY = 0xff303030;
31 | public static var GREY = 0xff424242;
32 | }
33 |
34 | class PlayState extends FlxState
35 | {
36 | var save_file:FlxSave;
37 |
38 | var save_slot:String = 'app';
39 |
40 | var file_path(default, set):String;
41 |
42 | var sprite_width(default, set):Int;
43 |
44 | var sprite_height(default, set):Int;
45 |
46 | var sprite_offset(default, set):Float = 1;
47 |
48 | var sprite_rotation(default, set):Float;
49 |
50 | var rotation_speed:Float = 5;
51 |
52 | var sprites:FlxTypedGroup;
53 |
54 | var default_sprites:FlxTypedGroup;
55 |
56 | var sprites_camera:FlxCamera;
57 |
58 | var ui_height = 40;
59 |
60 | var ui_padding = 4;
61 |
62 | var zoom:Float;
63 |
64 | var last_mouse_x:Float;
65 |
66 | var scroll_speed:Float = 20;
67 |
68 | var lerp:Float = 0.15;
69 |
70 | var file_watcher:FileWatcher;
71 |
72 | override public function create():Void
73 | {
74 | super.create();
75 |
76 | FlxG.mouse.useSystemCursor = true;
77 | FlxG.autoPause = false;
78 |
79 | sprites_camera = new FlxCamera(0, 0, FlxG.width, FlxG.height - ui_height);
80 | sprites_camera.bgColor = Colors.DARKGREY;
81 | sprites_camera.setFilters([new ShaderFilter(new FlxShader())]);
82 | sprites_camera.scroll.y -= 4;
83 | FlxG.cameras.add(sprites_camera);
84 | FlxCamera.defaultCameras = [sprites_camera];
85 | FlxG.game.stage.quality = StageQuality.LOW;
86 | FlxG.resizeWindow(480, 480);
87 |
88 | zoom = 2;
89 |
90 | sprites = new FlxTypedGroup();
91 | default_sprites = new FlxTypedGroup();
92 |
93 | for (i in 0...16)
94 | {
95 | var sprite = default_sprites.recycle(FlxSprite);
96 | sprite.loadGraphic(AssetPaths.chair__png, true, 10, 10);
97 | sprite.animation.frameIndex = i;
98 | sprite.x = FlxG.width * 0.5 - sprite.width * 0.5;
99 | sprite.y = sprites_camera.height * 0.5 - sprite.height * 0.5;
100 | sprite.y -= i * sprite_offset;
101 | }
102 |
103 | add(sprites);
104 | add(default_sprites);
105 |
106 | save_file = new FlxSave();
107 | save_file.bind(save_slot);
108 |
109 | sprite_width = save_file.data.sprite_width == null ? 8 : save_file.data.sprite_width;
110 | sprite_height = save_file.data.sprite_height == null ? 8 : save_file.data.sprite_height;
111 | file_path = save_file.data.file_path;
112 |
113 | init_ui();
114 |
115 | refresh_sprite();
116 | }
117 |
118 | override public function update(elapsed:Float):Void
119 | {
120 | super.update(elapsed);
121 |
122 | // Camera controls
123 |
124 | // Drag your mouse to Rotate the Sprites' angle
125 | sprite_rotation -= FlxG.mouse.pressed ? FlxG.mouse.x - last_mouse_x : rotation_speed * elapsed;
126 | last_mouse_x = FlxG.mouse.x;
127 |
128 | // Arrow keys to scroll the cam
129 | var up:Bool = false;
130 | var down:Bool = false;
131 | var left:Bool = false;
132 | var right:Bool = false;
133 |
134 | if (FlxG.keys.pressed.UP) up = true;
135 | if (FlxG.keys.pressed.DOWN) down = true;
136 | if (FlxG.keys.pressed.LEFT) left = true;
137 | if (FlxG.keys.pressed.RIGHT) right = true;
138 |
139 | if (up && down) up = down = false;
140 | if (left && right) left = right = false;
141 |
142 | if (up) sprites_camera.scroll.y -= scroll_speed * elapsed;
143 | if (down) sprites_camera.scroll.y += scroll_speed * elapsed;
144 | if (left) sprites_camera.scroll.x -= scroll_speed * elapsed;
145 | if (right) sprites_camera.scroll.x += scroll_speed * elapsed;
146 |
147 | // Scroll the mouse to zoom in-or-out
148 | zoom += FlxG.mouse.wheel * 0.2;
149 | zoom = FlxMath.bound(zoom, 0.5, 8);
150 | sprites_camera.zoom += (zoom - sprites_camera.zoom) * lerp;
151 | }
152 |
153 | function refresh_sprite()
154 | {
155 | sprites.forEach(sprite -> sprite.kill());
156 |
157 | var title = 'Stackr - ';
158 |
159 | if (file_path != null)
160 | {
161 | title += new Path(file_path).file;
162 | var img = BitmapData.fromFile(file_path);
163 | if (img != null)
164 | {
165 | if (sprite_width > 0 && sprite_height > 0 && img.width % sprite_width == 0 && img.height % sprite_height == 0)
166 | {
167 | var frames = Std.int((img.width / sprite_width) * (img.height / sprite_height));
168 | for (i in 0...frames)
169 | {
170 | var sprite = sprites.recycle(FlxSprite);
171 | sprite.loadGraphic(img, true, sprite_width, sprite_height);
172 | sprite.animation.frameIndex = i;
173 | sprite.x = FlxG.width * 0.5 - sprite.width * 0.5;
174 | sprite.y = sprites_camera.height * 0.5 - sprite.height * 0.5;
175 | sprite.y -= i * sprite_offset;
176 |
177 | save_file.data.file_path = file_path;
178 | save_file.data.sprite_width = sprite_width;
179 | save_file.data.sprite_height = sprite_height;
180 | save_file.flush();
181 | }
182 |
183 | FlxG.stage.application.window.title = title;
184 |
185 | default_sprites.exists = false;
186 | if (!sprites.exists) {
187 | sprites.exists = true;
188 | }
189 |
190 | scale_up(sprites);
191 |
192 | return;
193 | }
194 | else title += ' (Invalid Width/Height)';
195 | }
196 | else title += ' (Invalid File Loaded)';
197 | }
198 | else title += 'No File Loaded';
199 |
200 | FlxG.stage.application.window.title = title;
201 |
202 | if (!default_sprites.exists) {
203 | scale_up(default_sprites);
204 | default_sprites.exists = true;
205 | }
206 | sprites.exists = false;
207 | }
208 |
209 | function open_file()
210 | {
211 | #if sys
212 | var filters = {
213 | count: 1,
214 | descriptions: ["PNG files", "JPEG files"],
215 | extensions: ["*.png","*.jpg;*.jpeg"]
216 | };
217 |
218 | var result = Dialogs.openFile('Open spritesheet', 'Open the spritesheet to preview', filters);
219 |
220 | if (result != null && result.length > 0)
221 | {
222 | file_path = result[0];
223 | refresh_sprite();
224 | }
225 | #end
226 | }
227 |
228 | function toggle_filter()
229 | {
230 | sprites_camera.filtersEnabled = !sprites_camera.filtersEnabled;
231 | }
232 |
233 | function init_ui()
234 | {
235 | var ui_top = FlxG.height - ui_height;
236 |
237 | var font_size = 8;
238 |
239 | var ui_camera = FlxG.camera;
240 | ui_camera.setSize(FlxG.width, ui_height);
241 | ui_camera.y = ui_top;
242 | ui_camera.bgColor = Colors.MEDGREY;
243 |
244 | var ui_load_btn = new FlxButton(ui_padding, ui_padding, 'Load', open_file);
245 | ui_load_btn.label.setFormat(FlxAssets.FONT_DEFAULT, font_size);
246 | ui_load_btn.loadGraphic(AssetPaths.button__png, true, 36, 14);
247 | for (point in ui_load_btn.labelOffsets) { point.y = -1; }
248 | ui_load_btn.camera = ui_camera;
249 |
250 | var ui_filter_btn = new FlxButton(ui_padding, ui_padding + ui_load_btn.height + ui_padding, 'Filter', toggle_filter);
251 | ui_filter_btn.label.setFormat(FlxAssets.FONT_DEFAULT, font_size);
252 | ui_filter_btn.loadGraphic(AssetPaths.button__png, true, 36, 14);
253 | for (point in ui_filter_btn.labelOffsets) { point.y = -1; }
254 | ui_filter_btn.camera = ui_camera;
255 |
256 | var ui_width_text = new FlxText(ui_load_btn.x + ui_load_btn.width + ui_padding + 12, ui_padding, 38, 'width:', font_size);
257 | ui_width_text.alignment = FlxTextAlign.RIGHT;
258 | ui_width_text.camera = ui_camera;
259 |
260 | var ui_height_text = new FlxText(ui_width_text.x, ui_width_text.y + ui_width_text.height + ui_padding, 38, 'height:', font_size);
261 | ui_height_text.alignment = FlxTextAlign.RIGHT;
262 | ui_height_text.camera = ui_camera;
263 |
264 | var ui_width_text_number = new FlxInputText(ui_height_text.x + ui_height_text.width + ui_padding, ui_width_text.y, 18, '$sprite_width', font_size, FlxColor.WHITE, Colors.GREY);
265 | ui_width_text_number.alignment = FlxTextAlign.RIGHT;
266 | ui_width_text_number.filterMode = FlxInputText.ONLY_NUMERIC;
267 | ui_width_text_number.fieldBorderThickness = 0;
268 | ui_width_text_number.callback = (text, action) -> {
269 | var w = Std.parseInt(text);
270 | sprite_width = w == null ? 0 : w;
271 | }
272 | ui_width_text_number.camera = ui_camera;
273 |
274 | var ui_height_text_number = new FlxInputText(ui_width_text_number.x, ui_height_text.y, 18, '$sprite_height', font_size, FlxColor.WHITE, Colors.GREY);
275 | ui_height_text_number.alignment = FlxTextAlign.RIGHT;
276 | ui_height_text_number.filterMode = FlxInputText.ONLY_NUMERIC;
277 | ui_height_text_number.fieldBorderThickness = 0;
278 | ui_height_text_number.callback = (text, action) -> {
279 | var h = Std.parseInt(text);
280 | sprite_height = h == null ? 0 : h;
281 | }
282 | ui_height_text_number.camera = ui_camera;
283 |
284 | add(ui_load_btn);
285 | add(ui_filter_btn);
286 | add(ui_width_text);
287 | add(ui_width_text_number);
288 | add(ui_height_text_number);
289 | add(ui_height_text);
290 | }
291 |
292 | function scale_up(sprites:FlxTypedGroup)
293 | {
294 | for (i in 0...sprites.members.length)
295 | {
296 | sprites.members[i].scale.set(0, 0);
297 | FlxTween.tween(sprites.members[i].scale, { x: 1, y: 1}, Math.max(1.5 - i * 0.1, 0.5), { ease: FlxEase.elasticOut, startDelay: i * 0.013});
298 | }
299 | }
300 |
301 | function set_file_path(v:String)
302 | {
303 | if (file_watcher == null)
304 | {
305 | file_watcher = new FileWatcher();
306 | file_watcher.onModify.add(path -> if (path == file_path) refresh_sprite());
307 | }
308 | file_watcher.removeDirectory(new Path(file_path).dir);
309 | file_watcher.addDirectory(new Path(v).dir);
310 |
311 | return file_path = v;
312 | }
313 |
314 | function set_sprite_width(v:Int)
315 | {
316 | sprite_width = v;
317 | refresh_sprite();
318 | return sprite_width;
319 | }
320 |
321 | function set_sprite_height(v:Int)
322 | {
323 | sprite_height = v;
324 | refresh_sprite();
325 | return sprite_height;
326 | }
327 |
328 | function set_sprite_offset(v:Float)
329 | {
330 | sprite_offset = v;
331 | if (sprites != null) for (i in 0...sprites.members.length) sprites.members[i].y = FlxG.camera.height * 0.5 - sprites.members[i].height * 0.5 - i * v;
332 | return sprite_offset;
333 | }
334 |
335 | function set_sprite_rotation(v:Float)
336 | {
337 | if (sprites != null) sprites.forEach(s -> s.angle = v);
338 | if (default_sprites != null) default_sprites.forEach(s -> s.angle = v);
339 | return sprite_rotation = v;
340 | }
341 | }
342 |
--------------------------------------------------------------------------------