Friday, June 3, 2016

Moving to HaxeFlixel forums

I realized that my random blog may not be the best place to find readers interested in HaxeFlixel development, when there is now a DevLog section at the HaxeFlixel forums.  So further posts will be found at my new development log there unless they are unrelated to HaxeFlixel development.  There's one there already about a FlxScrollableArea / FlxSpriteGroup / offscreen content gotcha that might save you some time.  Enjoy!

Monday, April 18, 2016

Mash Robot testing with HaxeFlixel!

I know I had said to stay tuned for certain coverage adventures.  I had an unexpected idea that took me on a wild tangent, on which I still am.  But the tangent involves HaxeFlixel and testing and scrollbars and all sorts of things.

So today's post is about creating a button-mashing robot to use for automated random stress testing, inspired by a post I saw on StackExchange.

I thought it would be fairly simple to do this in HaxeFlixel, since I had already delved into the VCR code to fix an issue with mouse event playback (the pull request hasn't been merged just yet, but I haven't had cause to change it yet, whenever I've used the updated version.)  It was.  Almost.  Well, it still was in the end, but took some more delving and debugging until I realized what was happening.

My first approach was to use the same approach I had taken with the VCR, and just go down a level into OpenFL and send input events there.  For various reasons, this didn't have the effect I had intended, but I won't go into them here.

So my next course of action was to leverage the VCR playback code itself (beyond just my sometimes-OpenFL-calling workaround), to keep things more integrated with HaxeFlixel's update flow.  This seemed to work on the keyboard end of things, but not with the mouse.

Where I ran into trouble is that I wanted my masher robot to work a bit differently than the VCR setup in flixel, but I hadn't acknowledged this difference enough when analyzing why it wasn't working.  The difference was that with the normal VCR, either you're recording, or you're playing back, never both.  My masher robot, OTOH, I wanted to pretend to be playing back, while still recording so that if the robot managed to crash my game, I had a way of reproducing it later.

Once I had this figured out, I saw that thankfully, HaxeFlixel makes it easy to override the spot where it would normally (when recording) update the mouse position using data from OpenFL (from the DisplayObject class, in the form of the instance.)  All I had to do was make a simple new class derived from FlxMouse, override its update() with everything except the top portion that calls setGlobalScreenPositionUnsafe(), and then replace it in my Main.hx like so:

FlxG.inputs.replace(FlxG.mouse, new MashMouse(FlxG.mouse.cursorContainer));

With that, everything worked great.  I now have a robot that sometimes clicks entirely randomly, usually clicks a random FlxObject, and presses random buttons, all the while recording everything it does.

If there's interest, I could package and release this later, as I think it can be a useful testing tool.

Monday, February 29, 2016

Note for Perl programmers using Haxe on compiler error "Float has no field MyVariableName"

You're missing a dot...instead of "for (i in 0..blah)" you want "for (i in 0...blah)" (three dots).  Also note that the latter number is excluded from the loop, unlike in Perl.

Tuesday, February 16, 2016

Code coverage coming along for FlxScrollableArea...which has a new release!

In fact, technically if we're just talking about FlxScrollableArea.hx, it's complete.  Thus, scrollable-area 0.0.2-alpha is out!  Just in time for the release of HaxeFlixel 4.0.0, which it's already compatible with.  (In fact, it wasn't compatible with any previous version, because I needed the dev branch for certain fixes.)

Unit testing paid off already, because I discovered two bugs while writing the tests.

What's incomplete is coverage and further testing of the scrollbars themselves, in particular a simulation of dragability.

Code Coverage Result: 81.29%

Missing Code Coverage: gimmicky.FlxScrollbar [61.45%]
Because FlxScrollbar has to update FlxScrollableArea as it goes, I've left it loosely coupled, but this means that to test the former to full coverage, I will need to mock and stub the latter.  Stay tuned for adventures with Mockatoo!  (Yeah, because blogs are tuned into.  Don't forget to jump up and down and hold your arms at funny angles to improve your reception.)

Buddy likes magic too! BDD FTW!

I was able to get tests going with a similar test.hxml for Buddy:

-main TestMain
-cp tests
-cp source
-cp C:/HaxeToolkit/haxe/lib/flixel/git
-D flixel=3.3.12
-cp C:/HaxeToolkit/haxe/lib/openfl/3,6,0
-D openfl=3.6.0
-cp C:/HaxeToolkit/haxe/lib/lime/2,9,0
-D lime=2.9.0
-cp C:/HaxeToolkit/haxe/lib/buddy/0,18,1
-D buddy=0.18.1
-cp C:/HaxeToolkit/haxe/lib/promhx/1,0,21/src/main
-D promhx=1.0.21
-cp C:\HaxeToolkit\haxe\lib\openfl/3,6,0/extern
-D reporter=buddy.reporting.TraceReporter
-D native-trace
-D openfl-next
-D tools=2.9.0
-D flash-use-stage
-D no-compilation
-D openfl-flash
-D fdb-ci
-D web
-swf-lib export/flash/obj/assets.swf
-swf-version 11.8
-swf export/test/flash/bin/buddyhftest.swf
-swf-header 640:480:60:000000
-cp export/flash/haxe

All that was then needed was the same magic line, just in the new() of the Tests class.  And also a bit of trace redirection.  As ciscoheat says, enjoy your new Buddy!

Working coverage and unit testing, even autocompletion!

The key to getting munit and mcover working was in the test.hxml file.  This worked for me in the end:

## All targets

-main TestMain
-lib munit
-lib hamcrest
-cp C:/HaxeToolkit/haxe/lib/flixel/git
-D flixel=3.3.12
-cp C:/HaxeToolkit/haxe/lib/openfl/3,6,0
-D openfl=3.6.0
-cp C:/HaxeToolkit/haxe/lib/lime/2,9,0
-D lime=2.9.0
-cp C:/HaxeToolkit/haxe/lib/hscript/2,0,5
-D hscript=2.0.5
-cp C:\HaxeToolkit\haxe\lib\openfl/3,6,0/extern
-cp source
-cp test
-cp export/flash/haxe
--macro mcover.MCover.coverage([''],['source'])


## Flash 9+

-swf-version 11.8
-swf export/test/flash/coveragetest.swf


## CPP

#-cpp export/test/cpp

A few notes:
  1. "--each" is great.  DRY: it applies to all subsequent targets.
  2. Despite "--each" being great, we're not really deriving full benefit from it at the moment, because CPP is commented out.  That's because I don't seem to be able to test on a CPP target.
  3. The magic line (see previous post) creating a FlxGame object in TestMain.hx is still required if you want to test anything that refers to FlxG, otherwise it won't exist. 
  4. The "-cp" lines I took from building a project that uses my library, then looking at the debug.hxml file generated in projectroot/export/flash/haxe.  Thanks to ciscoheat for inspiration in that regard.
(You may notice that ciscoheat is the developer behind Buddy, rather than munit.  I was also trying to set up Buddy to do BDD rather than TDD here.  Since I seem to have them both working now, I've decided that munit/mcover is a better fit for my library testing, while Buddy is a better fit for testing my actual game.  Although, code coverage would be nice there too, so maybe I'll use both, or something.)

That was great, but then I found out my earlier example of making a unit test:

haxelib run munit ct characterAIAimsAndFires -for Character

...was broken in a couple ways:

  1. Tests need to be named ending in "Test" (well, "Test.hx") otherwise they aren't picked up by "munit ct".  This isn't done automatically if you supply the wrong name on the command line like I did.
  2. They also need to start with an uppercase letter ("munit ct" also doesn't do this automatically.)
So, we instead just need this:

haxelib run munit ct CharacterAIAimsAndFiresTest -for Character

...and then everything's happy.  Almost.  The compiler is still barfing in the output pane whenever I type.  Turns out that this is just due to how autocomplete works, at least within FlashDevelop, because it looks for a Project.xml file to figure out which libraries and other source files to autocomplete from.  All I needed to do was create a (much simpler than normal) Project.xml in my project root:

<?xml version="1.0" encoding="utf-8"?>
 <!-- This file exists solely so that autocompletion works on FlashDevelop.  It's not intended to actually build the project. -->
 <!--Minimum without FLX_NO_GAMEPAD: 11.8, without FLX_NO_NATIVE_CURSOR: 11.2 -->
 <set name="SWF_VERSION" value="11.8" />
 <classpath name="source" />
 <haxelib name="flixel"/>
 <haxelib name="mcover"/>

...and then autocomplete worked like a champ for "Assert." and anything HaxeFlixel-related.

Now for the icing on the cake, I went to Project->Properties->Build and changed the pre-build command line to:

"$(CompilerPath)/haxelib" run munit t -coverage

And now I can run the test suite and get code coverage output simply by pressing F5.

So, the next challenges are:
  • cpp testing
  • android testing
  • does the magic FlxGame line work with Buddy too?
  • being able to check individual pixels in a cross-platform way would be a very solid way of testing scrollbars, IMHO
I hope this was helpful to somebody.  :)

Monday, February 15, 2016

A bit of progress

That issue with IBitmapDrawable...well, I didn't narrow down the exact line that was causing it, but an updated test.hxml file definitely helps (ignoring the cpp target problem, which this doesn't address, hence that target is commented out for now):

## All targets

-main TestMain
-lib munit
-lib hamcrest
-cp C:/HaxeToolkit/haxe/lib/flixel/git
-D flixel=3.3.12
-cp C:/HaxeToolkit/haxe/lib/openfl/3,6,0
-D openfl=3.6.0
-cp C:/HaxeToolkit/haxe/lib/lime/2,9,0
-D lime=2.9.0
-cp C:/HaxeToolkit/haxe/lib/hscript/2,0,5
-D hscript=2.0.5
-cp C:\HaxeToolkit\haxe\lib\openfl/3,6,0/extern
-cp source
-D native-trace
-cp test
-D openfl-next
-D tools=2.9.0
-D flash-use-stage
-D no-compilation
-D openfl-flash
-D fdb
-D web
-D noflxg
-cp export/flash/haxe
--macro mcover.MCover.coverage([''],['source'])


## Flash 9+

-swf-version 11.8
-swf export/test/coveragetest.swf


## CPP

#-cpp export/cpp_test

With that, it actually compiles and runs, and the example test passes, with "haxelib run munit t."  Great!  Now, how about an actual test?  To be continued...

Unit testing scrollable-area

Seemed like a good idea to start with a library rather than my whole project.

So I made a new git branch locally, then ran "munit ct" as in an earlier post.  All good.

I'd like the library to work with html5, but it will probably not test properly in a JavaScript-only vacuum, so I think I will steal some code from openfl to try to get html5 testing to work.

Now I'm facing new trouble: even the as3 step in test.hxml (the first platform...) is not working with the default example:

...scrollable-area>haxelib run munit t
Massive Unit - Copyright 2016 Massive Interactive. Version 2.1.2
   haxe -main TestMain -lib munit -lib hamcrest -cp src -cp test -swf-version 11
 -cmd lime\ build\ html5 -swf build/as3_test.swf
HaxeWrapper.hx:73: 'lime\' is not recognized as an internal or external command,

HaxeWrapper.hx:73: operable program or batch file.
HaxeWrapper.hx:73: Error: Command failed with error 1
Error: Error compiling hxml for as3
Target as3 ...scrollable-area\build\as3_test.s

So, where is it getting this "lime\" from?  It looks like, because test.hxml includes this line with spaces in it, that somewhere along the line the spaces get escaped, and then another place along the line, a command interpreter, the escaping doesn't seem to count and the system chokes on the backslash.

Hmm.  Well, it could just be because it's old code, which I hadn't at first noticed (openfl-validation is deprecated.)  The more recent version of openfl's test.hxml looks a bit different.  That's because they're making use of their own "haxelib run openfl" script.  Where does that come from?  Haxelib's docs say to look for a file named "run.n".  It's in openfl's repo root, but it's precompiled.  Looking at its changelog, it seems RunScript.hx was modified at the same time, so probably run.n comes from there.  But the source doesn't even mention a "build" how can test.hxml be calling "haxelib run openfl build html5" or "haxelib run openfl build flash" successfully?

I guess the final line passes on the parameters to haxelib.  But haxelib doesn't have a "build" parameter either.  So I'm still not quite sure how this functions, exactly.

Making scrollable areas swipe-able in HaxeFlixel

So, this weekend I went to add some basic code to scrollable-area to make it more touch-friendly, because using 20px-thick scrollbars on an actual phone is a PITA. At least on what I endearingly term "my low-budget crap phone."

Anyway, I ran into a problem: FlxG.swipes reports *any* touch event as a swipe. This isn't a problem in and of itself, as it's useful for custom code. The problem is that, even a distance=0 swipe, which is clearly a tap by anyone's algorithm, shows up in FlxG.swipes. This means we must implement some custom code to distinguish between swipes and taps. This may not seem terribly complicated at first, but...well, for example, read this wonderful resource to get an idea of the situation.

The next part of the problem, which is a little bit more of a problem, is that basing an algorithm on pixels, rather than a real-world unit of measurement like centimetres, means that the algorithm will work vastly differently on a "retina" device versus an older-generation, lower-density budget device like mine.  If I calibrate to retina devices, what would be a 0.75 cm "tap" there could be a 2.25 cm "swipe" on my crap phone...and I wouldn't want a nearly inch-long gesture to necessarily be interpreted as a tap.

So we should know the DPI.  Is this possible in HaxeFlixel?  Sure, if you want to include custom code for each platform like this library does.  A possible workaround, we really just need to start overriding Android Java code, and more or less just exposing the results of this one call, I think.

And if we want something cleaner?  Thankfully, there's something in the pipe.  Because my game is not far enough along just yet that I'm testing a lot on phones, I will opt to wait.  Also because the Java override technique would mean scrollable-area would go from being a fairly straightforward include (even though you have to set up your content offscreen for it, at the moment) to something much more integrated.  So, hopefully by the time I'm ready for swipeyness, Lime will be too.  :)

Thursday, February 11, 2016

Writing an actual test...not yet.

First, I had to copy this very important line to TestMain.hx, and its requisite import lines.

Now, I'm curious if MUnit was tested with FlashDevelop, because I'm getting something strange regarding code completion.  If I type "Assert." (even in the example file, whose test passes; right below a line also beginning with "Assert.") and wait to see which methods I can use, I get the following in the Output pane:

Error: Could not process argument #--macro
Class name must start with uppercase character

It does not pop up a list of methods. I get the same error if I hit F4 over the token Assert, which is coloured blue to show it's correctly imported., that's problem 1.  Update: I found this handy info from here:

There’s just one last thing required to properly get up and running writing tests: adding the munit library to the project so that you can get code-completion on munit’s API. Open application.xml and add the line <haxelib name="munit" /> to the classpath, haxe libs section so it looks like:

<!-- classpath, haxe libs -->
<source path="src" />
<haxelib name="openfl" />
<haxelib name="actuate" />
<haxelib name="munit" />

VoilĂ —for realsies!
Except that adding the "munit" line as above--to a default project.xml I had to create (with current templates I have yet to come across application.xml so I believe that's outdated and/or openfl-specific rather than haxeflixel-specific)--only changed the error whenever I try autocompletion to:

Invalid commandline class : test.ExampleTest should be ExampleTest
./test/ExampleTest.hx:3: characters 7-31 : Type not found : massive.munit.util.Timer

That's on the import line.  If I remove that, then it can't find the Timer class referenced in the example.  Back to the drawing board...well, hopefully not that far back.

Meanwhile, if I try to write a test anyway, my characterAIAimsAndFires class was apparently mal-named, despite following the naming convention in the help, because the compiler complains (via macros, while I type code for the test) that the file name ought not to start with a lowercase letter.  So now it's CharacterAIAimsAndFires.  In its setup(), I call FlxG.switchState() to get my main world set up--just for now, until I properly decouple some of my code, I suppose.

Just adding the above and trying to compile, I get an error about TestMain, that it can't find FlxG.  That's because test.hxml also needs some lines like these:

-lib flixel
-lib flixel-addons
-lib scrollable-area
-lib nape
-lib advanced-layout

...or at least the first one, in this case. But I thought since I'll be testing pretty broadly, I should include whatever I have included in my Project.xml via haxelib directives.

 Turns out that's not enough:

HaxeWrapper.hx:73: C:\HaxeToolkit\haxe\lib\openfl/3,6,0/openfl/display/IBitmapDrawable.hx:17: characters 8-66 : Type not found : RenderSession
Error: Error compiling hxml for as3

Now, even if I add "-lib openfl" and "-lib lime", I still get this.  So that's problem 2, which is significantly bigger than problem 1...

I see that IBitmapDrawable refers to RenderSession via the package openfl._internal.renderer, but its entire contents are conditionally compiled "#if (!display && !flash)".  I guess either display or flash is defined true when compiling with munit, but not normally.

Likewise if I target cpp instead of flash (which my game compiles to without trouble) in the test.hxml file, I get this:

HaxeWrapper.hx:73: C:\HaxeToolkit\haxe\lib\flixel/git/flixel/FlxGame.hx:3: characters 7-27 : You cannot access the flash package while targeting cpp (for flash.display.Bitmap)
HaxeWrapper.hx:73: test/unit/TestMain.hx:1: characters 7-21 :     referenced here
Error: Error compiling hxml for cpp

Welcome to the new blog

I decided to put development posts in their own blog.  Here.

Unit testing my HaxeFlixel-based code

Since I'm still in the prototyping stage, I won't say that I should have added unit testing to my project earlier, necessarily.  TDDers may argue otherwise.  So be it.

Anyhoo, it's time to see how well MUnit and a HaxeFlixel project will play together.  Pretty well, I'm assuming, since HF itself uses MUnit for its unit tests.

So how do we get set up?  As the docs say, we install it and run it with haxelib.

The help displayed when running it gave some additional hints:

Massive Unit - Copyright 2016 Massive Interactive. Version 2.1.2
Usage: munit [subcommand] [options]
Type 'help ' for help on a specific subcommand.

Available commands:
   gen (g) : Generate a test runner based on classes in a test src directory
   run (r) : Runs a single unit test target and generates results
   test (t) : Updates, compiles and runs all targets from an hxml file
   create (ct) : Create test class
   config (c) : Modify default project specific settings for munit
   report (re) : Generate reports for CI environments and 3rd party tools

So, what I needed to do next was CD to my project's root, then run it again with the config (c) option.  It asked me for a bunch of paths, which were different from the standard HF demo template I had started with.  The last three, templates and coverage related, I left as null.

After that, it was time to create a new test, using the ct option.  I have a Character class already made, so I ran this:

haxelib run munit ct characterAIAimsAndFires -for Character

It seems I didn't need to run the gen (g) option at this point, because it had already made the required files.  Time to run the tests, then, with the test (t) option.

Oops, it complains, "Error: Library hamcrest is not installed."  I did what it said and installed hamcrest using haxelib.  Not sure why that wasn't an auto-installed dependency.  Maybe it only applies to certain platforms.

Trying again, I got a new error: "Error: Error compiling hxml for as2."  To get past this, I had to customize the auto-generated test.hxml by chopping out the as2, neko, and other sections I don't need.  Then, success!  I got a result on the command line saying the whole as3 platform passed ("PLATFORMS TESTED: 1, PASSED: 1, FAILED: 0, ERRORS: 0, TIME: 1.839") and also a browser window saying the 2 individual tests within it passed ("PASSED  Tests: 2  Passed: 2  Failed: 0 Errors: 0 Ignored: 0 Time: 0.255.")

Now to write an actual test...

Saturday, January 30, 2016

Scrollbars usable!

They're not complete yet, but they're quite usable for my use case now, as of this commit.  The other features will come later, unless of course you'd like to...

Fork me on GitHub
You're welcome, Reddit person. If that's not what you were looking for, I hope you find it somewhere (or make it) and share it :)

Huzzah! Scrollbars, drawn.

It's a bit unfortunate, but I had to scrap the idea of using FlxCamera.zoom because the calculations got too messy for me.  So instead I'm back to the original, which is that you lay out everything manually yourself.

What did I do about the merry-go-round?  Your layout code works with the scroll area code.  You tell it whether you intend to FIT_WIDTH or FIT_HEIGHT, and then it tells you whether you can expect a scrollbar, or, in the weird glitchy zone of the merry-go-round, whether to do the opposite of what you normally would, so that it's a bit less glitchy.

There are some off-by-one conversion areas which make the glitchy zone still a tiny bit glitchy, but overall I'm fairly happy with the result.  This is a spoiler (if not much of one) from a game whose name is still under wraps, but you'll hear about more soon.  These show FIT_HEIGHT, although probably you would typically want FIT_WIDTH, which is more like a web page with a vertical scroll bar.

Normal window

Getting too short (you can see the scrollbar almost running out; the "CC" trophy is the last one)

 Too short for a scrollbar, but too tall to max the height of everything (this would be where the merry-go-round would normally happen)
Short and wide enough that we can max the height of everything without a scrollbar
The code is not yet complete (only the drawing is done, not the input response) but it's available here:

Input response will be next.  For now, you can plunk those .hx files in your own project's source/flixel/addons subdirectory and create a FlxScrollableArea in your state's create() function.  This was made and tested with a non-standard scale mode in mind, where you want full control over resizing your content, so the next step is to override onResize() in your state, and there set the .viewPort property of your FlxScrollableArea, re-layout your content based on its .bestMode/horizontalScrollbarHeight/verticalScrollbarWidth properties, then finally set the .content property to match your newly-laid-out content.

Scrollbar Merry-Go-Round

I didn't have too much time yesterday to work on this, but what I did manage to get done was the drawing code, so I now have a class that draws its own scrollbars correctly.

Meanwhile, I noticed that my state class was having to guess whether to leave space for scrollbars or not.  I opened up the scrollbar class to make this a gettable property, which helped, but there was a still a snag...

The Scrollbar Merry-Go-Round...of doom

I guess I'm not surprised to see this in my own basic code, because I've seen this for years, and still do, in various (otherwise professional) web pages.  Not often (ever?) in a scrolling context, but in a CSS :hover selector.  Once triggered, the merry-go-round functions automatically without moving the mouse pointer.  What happens is this:

  1. You mouse over an element, near its edge.
  2. The :hover effect causes a change in proportions in the element (e.g., the text goes bold, so the element has to get wider, or rewraps and gets taller, etc.).
  3. Because the element's edges move, it is now is no longer under the mouse pointer.
  4. The :hover selector no longer applies.
  5. The element returns to its original dimensions.
  6. The mouse pointer is suddenly hovering over it again, like it was in the first step, because we haven't moved it.
  7. The browser processes the above steps repeatedly in the most jittery, jarring rapid succession.
It's perhaps not clear how to solve such a problem at the CSS level, other than to make your selector style not change the size of the element in any way.  The only other option would be a bunch of JavaScript or browser hacking so that, until the mouse pointer actually moves, you cannot return to a non-hovering state.  (I'm sure that fixing this at the browser level would break lots of pages, e.g. games that rely on the hover effect even when the mouse cursor is not moving, because elements might be moving on their own.)

Unfortunately, in my case, neither of these is the answer that I'm looking for.

In my case, I have some content that I would like to take up the full height of the window, unless the content does not fit the width, in which case I would like the content to take up the full height minus the scrollbar thickness.  Sounds simple enough.  However, in practice, there is a set of window sizes that lead to the Scrollbar Merry-Go-Round.  It's not quite as automatic as the CSS case above, but it's visually at least as bad, because of how big scrollbars normally are.

If you're simplistic enough in the logic in the state class, and just have something like this in onResize:

content.setGraphicSize( 0, Lib.current.stage.stageHeight - (_scrollable.horizontalScrollbarIsVisible?_scrollbarThickness:0) );

...then you will find that, depending on whether you are resizing up or down, and/or the results of the last resize, your visual result will alternate between having scrollbars and not having them, and in both cases not look very good.

This is because we're basing our new content size on whether scrollbars are needed for the current content size.

Even if you try to make it slightly smarter (if that much more inefficient) and call onResize again if and only if the need for scrollbars changed after you resized the content, it's at least as visually quirky.

So what can be done?  On a small display, the first solution for the CSS problem (don't change the size) means that I will have a scrollbar's thickness taking up a chunk of the window whether I need it to or not (and this gets worse when the scrollable area is not the whole window.)  The second solution doesn't apply because unlike the CSS merry-go-round, my scrollbar problem requires mouse movement to show its full glitchy glory.

I had written this comment detailing my thoughts, which are hopefully possible to implement:

The parent state shouldn't have to worry about whether or not a scrollbar is showing, or even about resizing content.  After all, we have cameras and they can zoom.

We have to calculate the content's ratio and find the point at which it will fit with unneeded scrollbars (or each one independently?  even more complex...).  We also need the point at which it will fit without scrollbars.  In between these two points is the merry-go-round, and instead of this, we should just take away the scrollbars (or a given scrollbar?) and use up part of its space, but not enough to require scrollbars again.

Time to figure out some calculations...

Although, this solution seems to assume my particular context, that I want the content to take up as much vertical space as possible, and no vertical scrollbar should ever show.  Probably a more normal context is the other way around.  And then there's a third context, where the content would not change, and you might have any combination of scrollbars but no merry-go-round.  Perhaps the camera subclass can have these three as options.

Thursday, January 28, 2016

On the Road to HaxeFlixel Scrollbars

I found out that scrolling how I would like it is fairly feasible using a FlxCamera derivative.  If I just update .scroll (rather than calling .setPosition() as I had initially thought) I can control the camera how I envisioned in this class. It so far doesn't do much beyond a basic FlxCamera, but will hopefully soon have scrollbar-drawing and mouse-handling code.
package flixel.ui;

import flixel.FlxCamera;
import flixel.math.FlxRect;

* An area of the screen that has automatic scrollbars, if needed.
* @author Gimmicky Apps
class FlxScrollableArea extends FlxCamera
* Creates a specialized FlxCamera that can be added to FlxG.cameras.
* @param ViewPort the area on the screen, in absolute pixels, that will show content.
* @param Content the area (probably off-screen) to be viewed in ViewPort
public function new(ViewPort:FlxRect, Content:FlxRect)
super( ViewPort.x ), ViewPort.y ), ViewPort.width ), ViewPort.height ),
1 );
_bounds = Content;
scroll.x = Content.x;
scroll.y = Content.y;
I was thinking of trying FlxSliders for the scrollbars, but visually I don't think they fit well. That's OK, a basic scrollbar will not be hard to draw. :)

The Quest for a HaxeFlixel Scrollable Area

Something tells me this is a wheel I probably shouldn't be re-inventing.  However, I don't see many alternatives.

The wheel to which I refer?  A scrollbar.  (Or swipe-ability in the case of touch.)  In HaxeFlixel.  Google it in the past year.  See?  It's pretty much just this one person lately asking for it on Reddit.

But they're not the only one over the years, just the most recent.

You might find this thread and Joshua Granick's post which refer to his Scrollbar.hx and seem promising, but it was made with NME/Flash in mind.  It's not clear whether this would work well with HaxeFlixel, targeting other platforms like Android.  It's certainly not made with the same model in mind, using timers in place of an overridden update() function.  If it would work, it would need forking and one could probably replace Utils.constrain with FlxMath.bound, and use HF's own tweening library.

You might find this flixel-ui issue showing more interest, but also a dead-end in terms of finding something ready-made, even if you're wanting something within the flixel-ui world instead of vanilla HaxeFlixel.  Later this question was posted, the end result being a suggestion of a code bounty to move things along.  (Although, Gama11 also linked to this code, which had been viewed 193 times as of this writing.  Of the things I found this seems the furthest along, but it's now outdated, and at that point it was also incomplete.)

I'm wondering if it's time to simply extend FlxCamera with a new class, FlxUserScrolledCamera, barring a name having more finesse coming to mind.  The idea would be, place all your content off-screen, wherever is most convenient.  Then, create a new FlxUserScrolledCamera, whose bounds are around your off-screen content, and voila--either the user can swipe at it to scroll around, or use the scroll bars (which auto-hide), depending on the platform.  Optionally they could zoom with pinch gestures or a mousewheel.  The scroll bars themselves, if any, would be drawn somewhere on-screen, to be viewed not by the camera itself but by the main camera.

To me this represents the simplest solution, but maybe it's too good to be true.  If not, I might as well just do it and share the results!

Beware the Invalid BitmapData

Just a note to fellow HaxeFlixel developers, in case it saves you some time.

If you extend a FlxTypedGroup<FlxSprite>, don't instantiate a variable of its kind in a class definition, outside a function.  I.e. don't do this:

class XYZ {
public var myFtgInstance = new extendedFtg();

Instead, move the myFtgInstance = new extendedFtg(); into your new() or create() or whatever function is applicable.

The context for my story is that I did not at first realize there were FlxSubStates, so I rolled my own Popup class (extending FlxTypedGroup<FlxSprite>).  In the end, I sort of liked how mine was organized better, anyway.  I extended Popup into a few different specialized Popups, and they worked fine.

A few weeks later I went to make a new specialized Popup for a different state, and got a very hard-to-track-down runtime error, Invalid BitmapData, whose stack trace did not even originate in my project's code.

In the end, it was only because my state class code foolishly tried to instantiate the Popup derivative outside any function.  Unfortunately, figuring this out was not straightforward at all, in the end being fixed only thanks to an inspired guess.