Created
May 13, 2014 19:29
-
-
Save cwkx/ed4996e9a37c1abb9e14 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package game; | |
import flixel.FlxG; | |
import flixel.FlxSprite; | |
import flixel.group.FlxSpriteGroup; | |
import flixel.util.FlxMath; | |
import flixel.util.FlxPoint; | |
import flixel.util.FlxSpriteUtil; | |
import flixel.input.touch.FlxTouch; | |
import flash.events.MouseEvent; | |
// Todo for a complete system: | |
// 1. Support existing FlxButtons and organize with event functions. | |
// Scroll class in Y-axis with steps and error regions | |
class ScrollNotch extends FlxSprite | |
{ | |
public var changed:Bool = true; | |
public var dragging:Bool = false; | |
public var useable:Bool = true; | |
public var errorRegion:Float = 5; | |
public var enableDragging:Bool = false; | |
public var value(default, null):Int; | |
// Constraints | |
private var _steps:Int = -1; | |
private var _min:Float = 0; | |
private var _max:Float = 0; | |
private var _vertical:Bool = false; | |
// Optional | |
private var _lastFraction:Float = -1; | |
private var _spriteGroup:ScrollSpriteGroup = null; | |
/** | |
* Creates a ScrollNotch at a specified position with a specified one-frame graphic. | |
* Specify the number of steps either to "infinite" continuous <=0, or to discrete steps (1 or more). | |
* | |
* @param X The initial X position of the ScrollNotch. | |
* @param Y The initial Y position of the ScrollNotch. | |
* @param Min The minimum XY constraint of the ScrollNotch. | |
* @param Max The maximum XY constraint of the ScrollNotch. | |
* @param Steps The number of draggable steps in the ScrollNotch (e.g. set to 3 for steps at the [top, middle, bottom]). | |
* @param SpriteGroup The associated spritegroup to call its changed() function. | |
*/ | |
public function new(X:Float = 0, Y:Float = 0, ?Graphic:Dynamic, Vertical:Bool, Min:Float, Max:Float, Steps:Int=0, ?SpriteGroup:ScrollSpriteGroup, ?EnableDragging:Bool):Void | |
{ | |
super(X, Y, Graphic); | |
reconfigure(X, Y, Vertical, Min, Max, Steps); | |
if (SpriteGroup != null) | |
_spriteGroup = SpriteGroup; | |
if (EnableDragging != null) | |
enableDragging = EnableDragging; | |
else | |
enableDragging = true; | |
} | |
// Can also be used to set direct values by calling it twice: setValueDelta(-LARGE); setValueDelta(X); | |
public function setValueDelta(delta:Int):Void | |
{ | |
// Determine the slider position addition "t" | |
var t = 1 / _steps; | |
t *= (_max - _min); | |
if (_steps < 0) t = 1; // Handle 0 or negative step case correctly, whereby we want to move smoothly (1 pixel at a time) | |
if (_steps == 0) t = 0; // Handle 1 step case correctly, whereby we dont want to scroll as we want the notch always at the top | |
if (_vertical) | |
{ | |
y += delta*t; | |
y = Math.min(Math.max(y, _min), _max); | |
} | |
else | |
{ | |
x += delta*t; | |
x = Math.min(Math.max(x, _min), _max); | |
} | |
var fraction:Float = getFraction(); | |
if (fraction != _lastFraction) | |
{ | |
_lastFraction = fraction; | |
if (_spriteGroup != null) | |
_spriteGroup.changed(fraction, _vertical); | |
value = Math.round(fraction * _steps); | |
} | |
} | |
public function setFraction(fraction:Float) | |
{ | |
if (fraction != _lastFraction) | |
{ | |
if (_vertical) | |
y = FlxMath.lerp(_min, _max, fraction); | |
else | |
x = FlxMath.lerp(_min, _max, fraction); | |
_lastFraction = fraction; | |
if (_spriteGroup != null) | |
_spriteGroup.changed(fraction, _vertical); | |
value = Math.round(fraction * _steps); | |
} | |
} | |
// Returns 0..1 value of slider | |
public function getFraction():Float | |
{ | |
if (_vertical) | |
return (y - _min) / (_max - _min); | |
else | |
return (x - _min) / (_max - _min); | |
} | |
// Returns current associated steps | |
public function getStep() | |
{ | |
return Math.round(getFraction() * _steps); | |
} | |
public function getSize() | |
{ | |
return _max - _min; | |
} | |
// Call this to reconfigure the scroll data and constraints, e.g. to reasign it to another scroll area | |
public function reconfigure(X:Float, Y:Float, Vertical:Bool, Min:Float, Max:Float, Steps:Int = 0):Void | |
{ | |
x = X; | |
y = Y; | |
// Constraints | |
_vertical = Vertical; | |
_min = Min; | |
_max = Max; | |
_steps = Steps - 1; // we use 0 internally as a valid step | |
} | |
// Check if the mouse is over the scroll area, with the specified error region boundary (useful for touch devices) | |
private function mouseOver():Bool | |
{ | |
if (_vertical) | |
return FlxMath.pointInCoordinates(FlxG.mouse.screenX, FlxG.mouse.screenY, | |
x - errorRegion, _min - errorRegion, | |
width + 2 * errorRegion, _max - _min + 2 * errorRegion); | |
else | |
return FlxMath.pointInCoordinates(FlxG.mouse.screenX, FlxG.mouse.screenY, | |
_min - errorRegion, y - errorRegion, | |
_max - _min + 2 * errorRegion, height + 2 * errorRegion); | |
} | |
override public function update():Void | |
{ | |
if (!visible || !useable) | |
return; | |
if (!enableDragging) | |
dragging = false; | |
// Update the target value whenever the slider is being used | |
if (FlxG.mouse.pressed) | |
{ | |
if (mouseOver()) | |
dragging = true; | |
} | |
else | |
{ | |
dragging = false; | |
// Handle scrolling | |
if (FlxG.mouse.wheel != 0 && mouseOver()) | |
{ | |
setValueDelta(FlxG.mouse.wheel > 0 ? -1 : 1); | |
changed = true; | |
} | |
} | |
// Constrained dragging | |
if (dragging) | |
{ | |
if (_vertical) | |
{ | |
// Constrain min/max | |
y = Math.min(Math.max(FlxG.mouse.screenY, _min), _max); | |
// Constrain step (linear interpolation between slider extremes where t = getFraction()) | |
y = _steps < 0 ? y : _steps == 0 ? _min : _min + (_max - _min) * (getStep() / _steps); | |
} | |
else | |
{ | |
x = Math.min(Math.max(FlxG.mouse.screenX, _min), _max); | |
x = _steps < 0 ? x : _steps == 0 ? _min : _min + (_max - _min) * (getStep() / _steps); | |
} | |
var fraction:Float = getFraction(); | |
if (fraction != _lastFraction) | |
{ | |
_lastFraction = fraction; | |
if (_spriteGroup != null) | |
_spriteGroup.changed(fraction, _vertical); | |
value = Math.round(fraction * _steps); | |
changed = true; | |
} | |
} | |
} | |
} | |
// Contains FlxSpriteGroup area as well, and lets you map one scroll to multiple areas. | |
class ScrollSpriteGroup extends FlxSpriteGroup | |
{ | |
public var positions:Array<FlxPoint>; | |
private var _stampRegion:FlxSprite; // Transparent sprite that we stamp data to | |
private var _lastX:Float = -1.0; | |
private var _lastY:Float = -1.0; | |
/** | |
* Creates a FlxSpriteGroup with a "stamp region", e.g. a transparent FlxSprite of which the child data is stamped onto during a scroll change event. | |
* | |
* @param X The initial X position of the ScrollSpriteGroup. | |
* @param Y The initial Y position of the ScrollSpriteGroup. | |
* @param Width The Width of the stamp region. | |
* @param Height The Height of the stamp region. | |
*/ | |
public function new(X:Float = 0, Y:Float = 0, Width:Int = 0, Height:Int = 0) | |
{ | |
super(X, Y); | |
_stampRegion = new FlxSprite(0, 0); | |
_stampRegion.makeGraphic(Width, Height, 0); | |
positions = new Array<FlxPoint>(); | |
// Stamp region is always the first member - which is important as we skip it during the stamping process | |
add(_stampRegion).visible = true; | |
} | |
// Todo: add axis param here | |
// Must be called manually after adding members! | |
public function changed(?fraction:Float, ?vertical:Bool) | |
{ | |
if (fraction != null) | |
{ | |
if (vertical == null) | |
{ | |
_lastX = _lastY = 0; | |
} | |
else | |
{ | |
if (vertical == true) | |
_lastY = fraction; | |
else | |
_lastX = fraction; | |
} | |
} | |
FlxSpriteUtil.fill(_stampRegion, 0x00ffffff); | |
var maxX:Float = Math.NEGATIVE_INFINITY; | |
var maxY:Float = Math.NEGATIVE_INFINITY; | |
// Recalculate content bounds incase any members have changed | |
for (i in 1 ... members.length) | |
{ | |
maxX = Math.max(_stampRegion.width, Math.max(maxX, (positions[i].x - x) + members[i].width)); | |
maxY = Math.max(_stampRegion.height, Math.max(maxY, (positions[i].y - y) + members[i].height)); | |
} | |
for (i in 1 ... members.length) | |
{ | |
// Get local x and y variables as we stamp them in a local region | |
var lx = positions[i].x - x; | |
var ly = positions[i].y - y; | |
members[i].x = Std.int((1 - _lastX) * (maxX - _stampRegion.width) - (maxX - _stampRegion.width) + lx) + x; | |
members[i].y = Std.int((1 - _lastY) * (maxY - _stampRegion.height) - (maxY - _stampRegion.height) + ly) + y; | |
_stampRegion.stamp(members[i], Std.int((1 - _lastX) * (maxX - _stampRegion.width ) - (maxX - _stampRegion.width ) + lx), | |
Std.int((1 - _lastY) * (maxY - _stampRegion.height) - (maxY - _stampRegion.height) + ly)); | |
} | |
} | |
// Make any sprites that we add to the group invisible by default | |
override public function add(Sprite:FlxSprite):FlxSprite | |
{ | |
Sprite.visible = false; | |
positions.push(FlxPoint.get(Sprite.x + x, Sprite.y + y)); | |
return super.add(Sprite); | |
} | |
override private function set_visible(Value:Bool):Bool | |
{ | |
return visible = Value; | |
} | |
override public function destroy() | |
{ | |
super.destroy(); | |
_stampRegion = null; | |
} | |
} | |
// This is a little ToggleButton that can work inside a ScrollSpriteGroup. | |
// Input graphic = 4 frames [Normal, Highlighted, Normal-Pressed, Highlighted-Pressed] | |
class ScrollToggleButton extends FlxSprite | |
{ | |
public var allowSwiping:Bool = true; | |
// States | |
private var _overlapFound = false; | |
private var _pressedTouch:FlxTouch; | |
private var _pressedMouse:Bool = false; | |
private var _status:Int = 0; | |
private var _parent:ScrollSpriteGroup; | |
private var _onPressed:Void->Void; | |
/** | |
* Creates a FlxSpriteGroup with a "stamp region", e.g. a transparent FlxSprite of which the child data is stamped onto during a scroll change event. | |
* | |
* @param X The initial X position of the ScrollSpriteGroup. | |
* @param Y The initial Y position of the ScrollSpriteGroup. | |
* @param Graphic 4 frames [Normal, Highlighted, Normal-Pressed, Highlighted-Pressed]. | |
* @param Parent The parent ScrollSpriteGroup so we can check its visibility and call its changed() event when the buttons highlighted to redraw it. | |
*/ | |
public function new(X:Float = 0, Y:Float = 0, ?Graphic:Dynamic, Parent:ScrollSpriteGroup, ?OnPressed:Void->Void) | |
{ | |
super(X, Y); | |
loadGraphic(Graphic, true, 7, 6); | |
scrollFactor.set(); | |
_parent = Parent; | |
_onPressed = OnPressed; | |
} | |
public override function update():Void | |
{ | |
super.update(); | |
// Return if outside the clip bounds | |
if (!_parent.visible || (y + height < _parent.y) || (y > _parent.y + _parent.height)) | |
return; | |
if (cameras == null) | |
cameras = FlxG.cameras.list; | |
// We're looking for any touch / mouse overlaps with this button | |
_overlapFound = false; | |
// Have a look at all cameras | |
for (camera in cameras) | |
{ | |
#if !FLX_NO_MOUSE | |
FlxG.mouse.getWorldPosition(camera, _point); | |
if (overlapsPoint(_point, true, camera)) | |
{ | |
_overlapFound = true; | |
updateStatus(true, FlxG.mouse.justPressed, FlxG.mouse.pressed); | |
break; | |
} | |
#end | |
#if !FLX_NO_TOUCH | |
for (touch in FlxG.touches.list) | |
{ | |
touch.getWorldPosition(camera, _point); | |
if (overlapsPoint(_point, true, camera)) | |
{ | |
_overlapFound = true; | |
updateStatus(true, touch.justPressed, touch.pressed, touch); | |
break; | |
} | |
} | |
#end | |
} | |
if (!_overlapFound) | |
updateStatus(false, false, false); | |
} | |
public function getState():Bool | |
{ | |
return !(_status <= 1); | |
} | |
public function setState(state:Bool) | |
{ | |
_status = state ? 2 : 0; | |
stateChanged(); | |
} | |
private function stateChanged():Void | |
{ | |
frame = framesData.frames[_status]; | |
_parent.changed(); | |
} | |
private function up():Void | |
{ | |
_status = _status <= 1 ? 2 : 0; | |
stateChanged(); | |
_onPressed(); | |
} | |
private function down():Void | |
{ | |
_status = _status <= 1 ? 0 : 2; | |
stateChanged(); | |
} | |
private function over():Void | |
{ | |
_status = _status <= 1 ? 1 : 3; | |
stateChanged(); | |
} | |
private function out():Void | |
{ | |
_status = _status <= 1 ? 0 : 2; | |
stateChanged(); | |
} | |
private function updateStatus(Overlap:Bool, JustPressed:Bool, Pressed:Bool, ?Touch:FlxTouch):Void | |
{ | |
if (Overlap) | |
{ | |
if (JustPressed) | |
{ | |
_pressedTouch = Touch; | |
if (Touch == null) | |
_pressedMouse = true; | |
down(); | |
} | |
else if (_status == 0 || _status == 2) | |
{ | |
// Allow "swiping" to press a button (dragging it over the button while pressed) | |
if (allowSwiping && Pressed) | |
down(); | |
else | |
over(); | |
} | |
} | |
else if (_status != 0 && _status != 2) | |
out(); | |
#if !FLX_NO_MOUSE | |
if (Overlap && FlxG.mouse.justReleased) | |
up(); | |
#end | |
// onUp | |
#if !FLX_NO_TOUCH | |
if ((_pressedTouch != null) && _pressedTouch.justReleased) | |
up(); | |
#end | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment