Skip to content

Instantly share code, notes, and snippets.

@cwkx
Created May 13, 2014 19:29
Show Gist options
  • Save cwkx/ed4996e9a37c1abb9e14 to your computer and use it in GitHub Desktop.
Save cwkx/ed4996e9a37c1abb9e14 to your computer and use it in GitHub Desktop.
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