Tone Designer
配色をする際、トーンを計画したあとに色相を調整すると楽に配色が決まります.
トーンベースで配色を考えることができるツールが欲しくて作ってみました.
(トーンとは明度と彩度のペアに名前を付けたものです.
”カラートーン” で検索すると参考になるページが色々ヒットすると思います.)
素敵な配色ができたらぜひforkしてね!
TonePresetを編集するとプリセットのコンボボックスの内容が変わります.
2010.5.22
save時の出力とファイル名を修正.
プリセット LOLLIPOP、HYSTEIC、VIDEO_GAME80Sを追加
@author imajuk
import com.bit101.components.RangeSlider;
/**
* Copyright imajuk ( http://wonderfl.net/user/imajuk )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/hamj
*/
package
{
/**
* 配色をする際、トーンを計画したあとに色相を調整すると楽に配色が決まります.
* トーンベースで配色を考えることができるツールが欲しくて作ってみました.
* (トーンとは明度と彩度のペアに名前を付けたものです.
* ”カラートーン” で検索すると参考になるページが色々ヒットすると思います.)
*
* 素敵な配色ができたらぜひforkしてね!
* TonePresetを編集するとプリセットのコンボボックスの内容が変わります.
*
* 2010.5.22
* save時の出力とファイル名を修正.
* プリセット LOLLIPOP、HYSTEIC、VIDEO_GAME80Sを追加
*
* @author imajuk
*/
import com.bit101.components.Label;
import com.bit101.components.Window;
// import com.bit101.components.RangeSlider;
import com.bit101.components.Knob;
import com.bit101.components.PushButton;
import com.bit101.components.ComboBox;
import com.bit101.components.HUISlider;
import com.bit101.components.Panel;
import flash.net.FileReference;
import flash.utils.ByteArray;
import flash.display.BitmapData;
import flash.utils.Dictionary;
import flash.events.Event;
import flash.display.DisplayObject;
import flash.display.StageScaleMode;
import flash.display.StageAlign;
import flash.display.Sprite;
import mx.graphics.codec.PNGEncoder;
public class ToneDesigner extends Sprite
{
//=================================
// model
//=================================
private var tones : ToneCollection;
//=================================
// view
//=================================
private var sample1 : Sample;
private var sample2 : Sample;
private var sContainer : Sprite;
private var output : Output;
//=================================
// UI
//=================================
private var panel : Panel;
private var comps : Array = [];
private var sliders : Array = [];
private var toneItems : Array;
private var suspendSliderUpdating : Boolean;
private var dic : Dictionary = new Dictionary(true);
public function ToneDesigner()
{
//=================================
// stage setting
//=================================
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
//=================================
// prepare view
//=================================
sContainer = addChild(new Sprite()) as Sprite;
sample1 = sContainer.addChild(new Sample(100, 400)) as Sample;
sample2 = sContainer.addChild(new Sample(100, 400)) as Sample;
panel = addChild(new Panel(this, 329, 5)) as Panel;
panel.setSize(120, 440);
panel.alpha = .8;
output = stage.addChild(new Output()) as Output;
output.x = 300;
output.y = 25;
output.visible = false;
//=================================
// prepare model
//=================================
tones = TonePreset.getPreset(TonePreset.COOL);
//for Tone selector
toneItems =
ColorTone.getAll().map(
function(toneKind : String, ...param):ColorTone
{
return new ColorTone(toneKind, 10);
}
);
update();
}
private function update() : void
{
buildUI();
draw();
layout();
}
private function buildUI() : void
{
//=================================
// reset components
//=================================
comps.forEach(
function(d : DisplayObject, ...param):void
{
DisplayObjectUtil.removeChild(d);
});
comps = [];
sliders = [];
var cx : int = 10;
var cy : int = 10;
//=================================
// preset selector
//=================================
var ps:ComboBox = new ComboBox(panel, cx, cy, "presets");
TonePreset.getAll().forEach(
function(tone:ToneCollection, ...param):void
{
ps.addItem(tone);
}
);
ps.listItemHeight = 15;
ps.setSize(100, 15);
ps.addEventListener(
Event.SELECT,
function():void
{
tones = ToneCollection(ps.selectedItem);
update();
}
);
cy += ps.height + 10;
comps.push(ps);
//=================================
// tone controlers
//=================================
tones.forEach(
function(tone : ColorTone, idx : int, ...param):void
{
//=================================
// remove tone button
//=================================
var minus : PushButton =
new PushButton(panel, cx, cy + 1, "-",
function():void
{
if (tones.length <= 1)
return;
//remove a tone
tones.remove(idx);
//re-calc data
tones.increase(getToneFromSliderIndex(idx).value / tones.length);
update();
});
minus.setSize(13, 13);
comps.push(minus);
//=================================
// add tone button
//=================================
var plus : PushButton =
new PushButton(panel, cx, cy + 15, "+",
function():void
{
//create new tone
var newTone : ColorTone = toneItems[0].clone();
//re-calc data
tones.decrease(newTone.value / sliders.length);
//insert created one
tones.add(idx + 1, newTone);
//suspend slider's event handlers
suspendSliderUpdating = true;
update();
//recover slider's event handlers
suspendSliderUpdating = false;
});
plus.setSize(13, 13);
comps.push(plus);
//=================================
// tone selector
//=================================
var combo : ComboBox = new ComboBox(panel, cx + 20, cy, tone.label);
//set combobox items
toneItems.forEach(
function(item:ColorTone, ...param):void
{
combo.addItem(item);
}
);
//set up selector
combo.addEventListener(
Event.SELECT,
function():void
{
var t : ColorTone = ColorTone(combo.selectedItem).clone();
t.value = tones.getToneAmount(idx);
dic[slider] = tones.replace(idx, t);
draw();
}
);
combo.setSize(80, 13);
comps.push(combo);
//=================================
// slider for tone amount
//=================================
var slider : HUISlider =
new HUISlider(
panel, cx + 11, cy + 12, "",
function(e : Event):void
{
if (sliders.length <= 1)
return;
if (suspendSliderUpdating)
return;
//re-calc data
calc(idx, HUISlider(e.target));
draw();
});
slider.setSliderParams(0, 100, tones.getToneAmount(idx));
slider.setSize(115, 15);
comps.push(slider);
sliders.push(slider);
//register th tone to dictionary in order to find out it from reference of slider
dic[slider] = tones.getTone(idx);
cy += combo.height + slider.height + 5;
});
//=================================
// hue adjustment controlers
//=================================
var me:Sprite = this;
var hue : PushButton =
new PushButton(
panel, cx, stage.stageHeight - 75, "hue adjustment",
function():void
{
var window:Window = new Window(me, 275, 290, "hue adjustment");
window.setSize(120, 155);
// window.hasCloseButton = true;
window.alpha = .8;
window.addEventListener(
Event.CLOSE,
function():void
{
window.removeEventListener(Event.CLOSE, arguments.callee);
me.removeChild(window);
}
);
var wx:int = 10;
var wy:int = 25;
//=================================
// label
//=================================
var label:Label = new Label(window, wx+25, wy, "hue range");
wy += 20;
//=================================
// hue range
//=================================
var range : RangeSlider = new RangeSlider(RangeSlider.HORIZONTAL, window, wx, wy);
range.labelMode = "always";
range.labelPosition = "bottom";
range.setSize(95, 10);
range.minimum = 0;
range.maximum = 360;
range.highValue = tones.hueMax;
range.lowValue = tones.hueMin;
range.addEventListener(
Event.CHANGE,
function():void
{
tones.hueMin = range.lowValue;
tones.hueMax = range.highValue;
draw();
}
);
wy += range.height + 15;
//=================================
// hue wheel
//=================================
var knob:Knob =
new Knob(
window, label.x, wy, "hue wheel",
function():void
{
tones.hueRotation = knob.value;
draw();
});
knob.maximum = 360;
knob.value = tones.hueRotation;
wy += knob.height;
//=================================
// close button
//=================================
var close : PushButton = new PushButton(window, 105, 5, "", function():void
{
window.dispatchEvent(new Event(Event.CLOSE));
});
close.setSize(10, 10);
});
comps.push(hue);
//=================================
// save button
//=================================
var save : PushButton =
new PushButton(
panel, cx, stage.stageHeight - 50, "save",
function():void
{
var b : BitmapData =
new BitmapData(stage.stageWidth, stage.stageHeight, false, 0xFFFFFF);
panel.visible = false;
output.visible = true;
b.draw(stage);
panel.visible = true;
output.visible = false;
var encorder : PNGEncoder = new PNGEncoder();
var bytes : ByteArray = encorder.encode(b);
var fr : FileReference = new FileReference();
fr.save(bytes, "tones" + int(Math.random() * 0xFFFFFF) + ".png");
});
save.setSize(45, 20);
comps.push(save);
//=================================
// redraw button
//=================================
var redraw : PushButton =
new PushButton(
panel, cx + save.width, stage.stageHeight - 50, "redraw",
function():void
{
draw();
}
);
redraw.setSize(45, 20);
comps.push(redraw);
}
private function draw() : void
{
sample1.draw(tones, 100);
sample2.draw(tones);
output.update(tones);
}
private function layout() : void
{
var margin:int = 4;
sample2.x = sample1.canvasWidth + margin;
sContainer.x = int((stage.stageWidth - panel.width - (sample1.canvasWidth * 2 + margin)) * .5);
sContainer.y = int((stage.stageHeight - sample1.canvasHeight) * .5);
}
private function getToneFromSliderIndex(idx : int) : ColorTone
{
return getToneFromSlider(sliders[idx]);
}
private function getToneFromSlider(slider : HUISlider) : ColorTone
{
return ColorTone(dic[slider]);
}
private function calc(idx : Number, changed : HUISlider) : void
{
//suspend slider's event handlers
suspendSliderUpdating = true;
var diff : Number = (tones.getToneAmount(idx) - changed.value);
tones.setToneAmount(idx, changed.value);
var a : Number = diff / (sliders.length - 1);
var others : Array =
sliders.filter(
function(s : HUISlider, ...param):Boolean
{
var v : Number = getToneFromSlider(s).value + a;
return (s != changed) && (v >= 0 && v <= 100);
}
);
others.forEach(
function(s : HUISlider, ...param):void
{
var tone:ColorTone = getToneFromSlider(s);
tone.value += diff / others.length;
s.value = tone.value;
}
);
//recover slider's event handlers
suspendSliderUpdating = false;
}
}
}
import com.bit101.components.Style;
import com.bit101.components.Label;
import com.bit101.components.Component;
import com.bit101.components.Text;
import flash.display.DisplayObjectContainer;
import flash.display.DisplayObject;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.geom.Rectangle;
import flash.display.Shape;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.Bitmap;
import fl.motion.easing.Exponential;
import frocessing.color.ColorHSV;
/**
* preset of ToneCollection
*/
class TonePreset
{
/**
* プリセットの名前です.
* 整数はidなので重複しないように注意!
*/
public static const MODERN : int = 0;
public static const COOL : int = 1;
public static const CLASSIC : int = 2;
public static const SUMMER : int = 3;
public static const JAPANESE : int = 4;
public static const LOLLIPOP : int = 5;
public static const HYSTEIC : int = 6;
public static const VIDEO_GAME80S : int = 7;
/**
* すべてのプリセットの配列を返します.
* プリセットを追加した場合はここも忘れずに編集してね!.
*/
public static function getAll() : Array
{
return [
getPreset(COOL),
getPreset(MODERN),
getPreset(LOLLIPOP),
getPreset(CLASSIC),
getPreset(SUMMER),
getPreset(HYSTEIC),
getPreset(JAPANESE),
getPreset(VIDEO_GAME80S)
];
}
/**
* プリセットのトーンを変えたい時はcase文を編集
*/
public static function getPreset(id : int) : ToneCollection
{
var preset:ToneCollection;
switch(id)
{
case MODERN:
preset =
new ToneCollection(
"MODERN",
[
new ColorTone(ColorTone.LIGHT_GRAYISH, 3),
new ColorTone(ColorTone.PALE, 12),
new ColorTone(ColorTone.LIGHT, 7),
new ColorTone(ColorTone.DARK, 21),
new ColorTone(ColorTone.VIVID, 1),
new ColorTone(ColorTone.DARK_GRAYISH, 9),
new ColorTone(ColorTone.PALE, 34),
new ColorTone(ColorTone.BRIGHT, 4),
new ColorTone(ColorTone.LIGHT_GRAYISH, 9)
],
0,360
);
break;
case COOL:
preset =
new ToneCollection(
"COOL",
[
new ColorTone(ColorTone.LIGHT_GRAYISH, 3),
new ColorTone(ColorTone.PALE, 12),
new ColorTone(ColorTone.LIGHT, 7),
new ColorTone(ColorTone.DARK, 21),
new ColorTone(ColorTone.VIVID, 1),
new ColorTone(ColorTone.DARK_GRAYISH, 9),
new ColorTone(ColorTone.DEEP, 34),
new ColorTone(ColorTone.DULL, 4),
new ColorTone(ColorTone.LIGHT_GRAYISH, 9)
],
240, 360
);
break;
case CLASSIC:
preset =
new ToneCollection(
"CLASSIC",
[
new ColorTone(ColorTone.LIGHT_GRAYISH, 2),
new ColorTone(ColorTone.DARK, 14),
new ColorTone(ColorTone.BRIGHT, 2),
new ColorTone(ColorTone.PALE, 19),
new ColorTone(ColorTone.DULL, 15),
new ColorTone(ColorTone.DARK_GRAYISH, 46),
new ColorTone(ColorTone.LIGHT_GRAYISH, 2)
],
36, 71
);
break;
case SUMMER:
preset =
new ToneCollection(
"SUMMER",
[
new ColorTone(ColorTone.PALE, 11),
new ColorTone(ColorTone.LIGHT, 8),
new ColorTone(ColorTone.VIVID, 3),
new ColorTone(ColorTone.STRONG, 12),
new ColorTone(ColorTone.PALE, 61),
new ColorTone(ColorTone.SOFT, 5)
],
125, 217
);
break;
case JAPANESE:
preset =
new ToneCollection(
"JAPANESE TRAD",
[
new ColorTone(ColorTone.LIGHT_GRAYISH, 8),
new ColorTone(ColorTone.GRAYISH, 2),
new ColorTone(ColorTone.PALE, 5),
new ColorTone(ColorTone.DARK, 33),
new ColorTone(ColorTone.DULL, 4),
new ColorTone(ColorTone.LIGHT_GRAYISH, 38),
new ColorTone(ColorTone.PALE, 10)
],
260, 360, 65
);
break;
case LOLLIPOP:
preset =
new ToneCollection(
"LOLLIPOP",
[
new ColorTone(ColorTone.VIVID, 2),
new ColorTone(ColorTone.PALE, 11),
new ColorTone(ColorTone.LIGHT, 22),
new ColorTone(ColorTone.PALE, 4),
new ColorTone(ColorTone.VIVID, 61)
],
297, 360, 11
);
break;
case HYSTEIC:
preset =
new ToneCollection(
"HYSTEIC",
[
new ColorTone(ColorTone.PALE, 7),
new ColorTone(ColorTone.STRONG, 3),
new ColorTone(ColorTone.VIVID, 20),
new ColorTone(ColorTone.STRONG, 3),
new ColorTone(ColorTone.BRIGHT, 51),
new ColorTone(ColorTone.PALE, 16)
],
178, 360, 72
);
break;
case VIDEO_GAME80S:
preset =
new ToneCollection(
"VIDEO_GAME80S",
[
new ColorTone(ColorTone.DARK_GRAYISH, 8),
new ColorTone(ColorTone.STRONG, 6),
new ColorTone(ColorTone.VIVID, 34),
new ColorTone(ColorTone.STRONG, 4),
new ColorTone(ColorTone.BRIGHT, 18),
new ColorTone(ColorTone.DEEP, 28)
],
231, 360, 25
);
break;
}
return preset;
}
}
/**
* collection of Tone
*/
class ToneCollection
{
public var label : String;
public var hueResolver : HueResolver;
private var data : Array;
public function ToneCollection(
label:String,
data : Array,
hueMin : Number,
hueMax : Number,
hueRotation : Number = 0
)
{
this.label = label;
this.data = data;
this.hueResolver = new HueResolver(this, hueMin, hueMax, hueRotation);
}
public function getToneAmount(idx : Number) : Number
{
return data[idx].value;
}
public function setToneAmount(idx : Number, value : Number) : void
{
data[idx].value = value;
}
public function forEach(closure : Function) : void
{
data.forEach(closure);
}
public function remove(idx : int) : void
{
data.splice(idx, 1);
}
public function add(idx : int, newTone : ColorTone) : void
{
data.splice(idx, 0, newTone);
}
public function replace(idx : int, newTone : ColorTone) : ColorTone
{
data[idx] = newTone;
return newTone;
}
public function get length() : int
{
return data.length;
return 0;
}
public function increase(value : Number) : void
{
forEach(function(tone : ColorTone, ...param):void
{
tone.value += value;
});
}
public function decrease(value : Number) : void
{
increase(-value);
}
public function getTone(idx : int) : ColorTone
{
return data[idx];
}
public function get hueMin() : Number
{
return hueResolver.hueMin;
}
public function set hueMin(value:Number) : void
{
hueResolver.hueMin = value;
hueResolver.reset();
}
public function get hueMax() : Number
{
return hueResolver.hueMax;
}
public function set hueMax(value:Number) : void
{
hueResolver.hueMax = value;
hueResolver.reset();
}
public function get hueRotation() : Number
{
return hueResolver.hueRotation;
}
public function set hueRotation(value:Number) : void
{
hueResolver.hueRotation = value;
}
}
class ColorTone
{
//=================================
// all kind of Color Tone
//=================================
public static const PALE : String = "PALE";
public static const LIGHT_GRAYISH : String = "LIGHT_GRAYISH";
public static const GRAYISH : String = "GRAYISH";
public static const DARK_GRAYISH : String = "DARK_GRAYISH";
public static const LIGHT : String = "LIGHT";
public static const SOFT : String = "SOFT";
public static const DULL : String = "DULL";
public static const DARK : String = "DARK";
public static const BRIGHT : String = "BRIGHT";
public static const STRONG : String = "STRONG";
public static const DEEP : String = "DEEP";
public static const VIVID : String = "VIVID";
//=================================
// defination of Color Tone
// [彩度min, 彩度max, 明度min, 明度max]
//=================================
internal static const DEF_PALE : Array = [7, 20, 100, 100];
internal static const DEF_LIGHT_GRAYISH : Array = [5, 20, 70, 90];
internal static const DEF_GRAYISH : Array = [5, 20, 40, 50];
internal static const DEF_DARK_GRAYISH : Array = [5, 10, 7, 32];
internal static const DEF_LIGHT : Array = [30, 68, 100, 100];
internal static const DEF_SOFT : Array = [30, 68, 80, 90];
internal static const DEF_DULL : Array = [30, 50, 40, 70];
internal static const DEF_DARK : Array = [50, 65, 10, 30];
internal static const DEF_BRIGHT : Array = [90, 100, 77, 90];
internal static const DEF_STRONG : Array = [100, 100, 47, 67];
internal static const DEF_DEEP : Array = [100, 100, 40, 45];
internal static const DEF_VIVID : Array = [100, 100, 100, 100];
public static function getToneAs(toneKind : String, hue : Number) : uint
{
var tone : Array = ColorTone["DEF_" + toneKind];
var satiation : Number = MathUtil.random(tone[0], tone[1]);
var value : Number = MathUtil.random(tone[2], tone[3]);
return new ColorHSV(hue, satiation * .01, value * .01).value;
}
public static function getAll() : Array
{
return [PALE, LIGHT_GRAYISH, GRAYISH, DARK_GRAYISH, LIGHT, SOFT, DULL, DARK, BRIGHT, STRONG, DEEP, VIVID];
}
public var label : String;
public var value : Number;
/**
* コンストラクタ
* @param kind トーンの種類. すべてのトーンの種類はColorToneに定義されています.
* @param value 全体に占めるこのトーンの割合
*/
public function ColorTone(kind : String, value : Number)
{
this.label = kind;
this.value = value;
}
public function toString() : String
{
return label + " : " + int(value) + "%";
}
public function clone() : ColorTone
{
return new ColorTone(label, value);
}
}
class HueResolver
{
public var hueMin : Number;
public var hueMax : Number;
private var tones : ToneCollection;
private var internalHue : Array;
private var hue : Array;
private var cursor : int = 0;
private var _hueRotation : Number;
public function set hueRotation(hueRotation : Number) : void
{
_hueRotation = hueRotation;
rotate();
}
public function get hueRotation() : Number
{
return _hueRotation;
}
public function HueResolver(
tones : ToneCollection,
hueMin : Number,
hueMax : Number,
hueRotation : Number = 0
)
{
this.tones = tones;
this.hueMin = hueMin;
this.hueMax = hueMax;
this._hueRotation = hueRotation;
reset();
}
public function next() : Number
{
cursor ++;
if (cursor >= 1000)
cursor = 0;
return hue[cursor];
}
public function reset() : void
{
internalHue = [];
for (var i : int = 0;i < 1000;i++)
{
internalHue.push(MathUtil.random(hueMin, hueMax));
}
rotate();
}
private function rotate() : void
{
hue =
internalHue.map(
function(h : int, ...param):Number
{
return h + _hueRotation;
}
);
}
}
class Sample extends Sprite
{
public var canvasWidth : int;
public var canvasHeight : int;
private var canvas : BitmapData;
private var box : Sprite;
private var bMask : Shape;
private var rect : Rectangle = new Rectangle();
public function Sample(canvasWidth:int, canvasHeight : int)
{
this.canvasWidth = canvasWidth;
this.canvasHeight = canvasHeight;
canvas = new BitmapData(450, 450, false, 0xFFFFFF);
box = addChild(new Sprite()) as Sprite;
box.addChild(new Bitmap(canvas));
bMask = box.addChild(new Shape()) as Shape;
box.mask = bMask;
}
public function draw(
tones:ToneCollection,
max:int = 1
) : void
{
var px : int = 0;
var py : int = 0;
//clear canvas
canvas.fillRect(canvas.rect, 0xFFFFFF);
//draw
tones.forEach(
function(tone : ColorTone, ...param):void
{
var internalY : int = 0;
var toneHeight : int = canvasHeight * tone.value * .01;
while(internalY < toneHeight)
{
var rHeight : int = Exponential.easeIn(Math.random(), 1, max - 1, 1);
if (rHeight == 0)
rHeight = 1;
rect.x = px;
rect.y = py + internalY;
rect.width = canvasWidth;
rect.height = rHeight;
internalY += rHeight;
if (internalY > toneHeight)
rect.height = rHeight - (internalY - toneHeight);
canvas.fillRect(
rect,
ColorTone.getToneAs(tone.label, tones.hueResolver.next())
);
}
py += toneHeight;
}
);
bMask.graphics.clear();
bMask.graphics.beginFill(0);
bMask.graphics.drawRoundRect(0, 0, rect.width, rect.y + rect.height, canvasHeight * .025);
}
}
class Output extends Sprite
{
private var ta : Text;
public function Output()
{
ta = addChild(new Text()) as Text;
ta.setSize(130, 250);
}
public function update(tones : ToneCollection) : void
{
var r:HueResolver = tones.hueResolver;
var h1:Number = int((r.hueMin + r.hueRotation) % 360);
var h2:Number = int((r.hueMax + r.hueRotation) % 360);
ta.text =
tones.label + " : \n\n" +
" hue(" + h1 + " ... " + h2 +")\n" +
" ---------------------------\n";
tones.forEach(
function(tone:ColorTone, ...param):void
{
ta.text += " " + tone +"\n";
}
);
}
}
class MathUtil
{
/**
* 任意の範囲からランダムな数値を返す
*/
public static function random(min:Number, max:Number):Number
{
return Math.random() * (max - min) + min;
}
}
class DisplayObjectUtil
{
/**
* DisplayObjectContainer.removeChild()は、
* 削除したいchildがコンテナに含まれているかどうか確認しません.
* もし、childがコンテナに含まれていない場合はランタイムエラーになります.
* このメソッドはchildを削除する前にそのchildがchildがコンテナに
* 含まれているかどうかをチェックします.
*
* 明示的にランタイムエラーをキャッチしたい場合は使用しないで下さい.
*/
public static function removeChild(child:DisplayObject):DisplayObject
{
if (!child)
return child;
var container:DisplayObjectContainer = child.parent;
if (!container)
return child;
if (container.contains(child))
container.removeChild(child);
return child;
}
}
class RangeSlider extends Component
{
protected var _back:Sprite;
protected var _highLabel:Label;
protected var _highValue:Number = 100;
protected var _labelMode:String = ALWAYS;
protected var _labelPosition:String;
protected var _labelPrecision:int = 0;
protected var _lowLabel:Label;
protected var _lowValue:Number = 0;
protected var _maximum:Number = 100;
protected var _maxHandle:Sprite;
protected var _minimum:Number = 0;
protected var _minHandle:Sprite;
protected var _orientation:String = VERTICAL;
protected var _tick:Number = 1;
public static const ALWAYS:String = "always";
public static const BOTTOM:String = "bottom";
public static const HORIZONTAL:String = "horizontal";
public static const LEFT:String = "left";
public static const MOVE:String = "move";
public static const NEVER:String = "never";
public static const RIGHT:String = "right";
public static const TOP:String = "top";
public static const VERTICAL:String = "vertical";
/**
* Constructor
* @param orientation Whether the slider will be horizontal or vertical.
* @param parent The parent DisplayObjectContainer on which to add this Slider.
* @param xpos The x position to place this component.
* @param ypos The y position to place this component.
* @param defaultHandler The event handling function to handle the default event for this component (change in this case).
*/
public function RangeSlider(orientation:String, parent:DisplayObjectContainer=null, xpos:Number=0, ypos:Number=0, defaultHandler:Function = null)
{
_orientation = orientation;
super(parent, xpos, ypos);
if(defaultHandler != null)
{
addEventListener(Event.CHANGE, defaultHandler);
}
}
/**
* Initializes the component.
*/
protected override function init():void
{
super.init();
if(_orientation == HORIZONTAL)
{
setSize(110, 10);
_labelPosition = TOP;
}
else
{
setSize(10, 110);
_labelPosition = RIGHT;
}
}
/**
* Creates and adds the child display objects of this component.
*/
protected override function addChildren():void
{
super.addChildren();
_back = new Sprite();
_back.filters = [getShadow(2, true)];
addChild(_back);
_minHandle = new Sprite();
_minHandle.filters = [getShadow(1)];
_minHandle.addEventListener(MouseEvent.MOUSE_DOWN, onDragMin);
_minHandle.buttonMode = true;
_minHandle.useHandCursor = true;
addChild(_minHandle);
_maxHandle = new Sprite();
_maxHandle.filters = [getShadow(1)];
_maxHandle.addEventListener(MouseEvent.MOUSE_DOWN, onDragMax);
_maxHandle.buttonMode = true;
_maxHandle.useHandCursor = true;
addChild(_maxHandle);
_lowLabel = new Label(this);
_highLabel = new Label(this);
_lowLabel.visible = (_labelMode == ALWAYS);
}
/**
* Draws the back of the slider.
*/
protected function drawBack():void
{
_back.graphics.clear();
_back.graphics.beginFill(Style.BACKGROUND);
_back.graphics.drawRect(0, 0, _width, _height);
_back.graphics.endFill();
}
/**
* Draws the handles of the slider.
*/
protected function drawHandles():void
{
_minHandle.graphics.clear();
_minHandle.graphics.beginFill(Style.BUTTON_FACE);
_maxHandle.graphics.clear();
_maxHandle.graphics.beginFill(Style.BUTTON_FACE);
if(_orientation == HORIZONTAL)
{
_minHandle.graphics.drawRect(1, 1, _height - 2, _height - 2);
_maxHandle.graphics.drawRect(1, 1, _height - 2, _height - 2);
}
else
{
_minHandle.graphics.drawRect(1, 1, _width - 2, _width - 2);
_maxHandle.graphics.drawRect(1, 1, _width - 2, _width - 2);
}
_minHandle.graphics.endFill();
positionHandles();
}
/**
* Adjusts positions of handles when value, maximum or minimum have changed.
* TODO: Should also be called when slider is resized.
*/
protected function positionHandles():void
{
var range:Number;
if(_orientation == HORIZONTAL)
{
range = _width - _height * 2;
_minHandle.x = (_lowValue - _minimum) / (_maximum - _minimum) * range;
_maxHandle.x = _height + (_highValue - _minimum) / (_maximum - _minimum) * range;
}
else
{
range = _height - _width * 2;
_minHandle.y = _height - _width - (_lowValue - _minimum) / (_maximum - _minimum) * range;
_maxHandle.y = _height - _width * 2 - (_highValue - _minimum) / (_maximum - _minimum) * range;
}
updateLabels();
}
/**
* Sets the text and positions the labels.
*/
protected function updateLabels():void
{
_lowLabel.text = getLabelForValue(lowValue);
_highLabel.text = getLabelForValue(highValue);
_lowLabel.draw();
_highLabel.draw();
if(_orientation == VERTICAL)
{
_lowLabel.y = _minHandle.y + (_width - _lowLabel.height) * 0.5;
_highLabel.y = _maxHandle.y + (_width - _highLabel.height) * 0.5;
if(_labelPosition == LEFT)
{
_lowLabel.x = -_lowLabel.width - 5;
_highLabel.x = -_highLabel.width - 5;
}
else
{
_lowLabel.x = _width + 5;
_highLabel.x = _width + 5;
}
}
else
{
_lowLabel.x = _minHandle.x - _lowLabel.width + _height;
_highLabel.x = _maxHandle.x;
if(_labelPosition == BOTTOM)
{
_lowLabel.y = _height + 2;
_highLabel.y = _height + 2;
}
else
{
_lowLabel.y = -_lowLabel.height;
_highLabel.y = -_highLabel.height;
}
}
}
/**
* Generates a label string for the given value.
* @param value The number to create a label for.
*/
protected function getLabelForValue(value:Number):String
{
var str:String = (Math.round(value * Math.pow(10, _labelPrecision)) / Math.pow(10, _labelPrecision)).toString();
if(_labelPrecision > 0)
{
var decimal:String = str.split(".")[1] || "";
if(decimal.length == 0) str += ".";
for(var i:int = decimal.length; i < _labelPrecision; i++)
{
str += "0";
}
}
return str;
}
///////////////////////////////////
// public methods
///////////////////////////////////
/**
* Draws the visual ui of the component.
*/
override public function draw():void
{
super.draw();
drawBack();
drawHandles();
}
///////////////////////////////////
// event handlers
///////////////////////////////////
/**
* Internal mouseDown handler for the low value handle. Starts dragging the handle.
* @param event The MouseEvent passed by the system.
*/
protected function onDragMin(event:MouseEvent):void
{
stage.addEventListener(MouseEvent.MOUSE_UP, onDrop);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMinSlide);
if(_orientation == HORIZONTAL)
{
_minHandle.startDrag(false, new Rectangle(0, 0, _maxHandle.x - _height, 0));
}
else
{
_minHandle.startDrag(false, new Rectangle(0, _maxHandle.y + _width, 0, _height - _maxHandle.y - _width * 2));
}
if(_labelMode == MOVE)
{
_lowLabel.visible = true;
_highLabel.visible = true;
}
}
/**
* Internal mouseDown handler for the high value handle. Starts dragging the handle.
* @param event The MouseEvent passed by the system.
*/
protected function onDragMax(event:MouseEvent):void
{
stage.addEventListener(MouseEvent.MOUSE_UP, onDrop);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMaxSlide);
if(_orientation == HORIZONTAL)
{
_maxHandle.startDrag(false, new Rectangle(_minHandle.x + _height, 0, _width - _height - _minHandle.x - _height, 0));
}
else
{
_maxHandle.startDrag(false, new Rectangle(0, 0, 0, _minHandle.y - _width));
}
if(_labelMode == MOVE)
{
_lowLabel.visible = true;
_highLabel.visible = true;
}
}
/**
* Internal mouseUp handler. Stops dragging the handle.
* @param event The MouseEvent passed by the system.
*/
protected function onDrop(event:MouseEvent):void
{
stage.removeEventListener(MouseEvent.MOUSE_UP, onDrop);
stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMinSlide);
stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMaxSlide);
stopDrag();
if(_labelMode == MOVE)
{
_lowLabel.visible = false;
_highLabel.visible = false;
}
}
/**
* Internal mouseMove handler for when the low value handle is being moved.
* @param event The MouseEvent passed by the system.
*/
protected function onMinSlide(event:MouseEvent):void
{
var oldValue:Number = _lowValue;
if(_orientation == HORIZONTAL)
{
_lowValue = _minHandle.x / (_width - _height * 2) * (_maximum - _minimum) + _minimum;
}
else
{
_lowValue = (_height - _width - _minHandle.y) / (height - _width * 2) * (_maximum - _minimum) + _minimum;
}
if(_lowValue != oldValue)
{
dispatchEvent(new Event(Event.CHANGE));
}
updateLabels();
}
/**
* Internal mouseMove handler for when the high value handle is being moved.
* @param event The MouseEvent passed by the system.
*/
protected function onMaxSlide(event:MouseEvent):void
{
var oldValue:Number = _highValue;
if(_orientation == HORIZONTAL)
{
_highValue = (_maxHandle.x - _height) / (_width - _height * 2) * (_maximum - _minimum) + _minimum;
}
else
{
_highValue = (_height - _width * 2 - _maxHandle.y) / (_height - _width * 2) * (_maximum - _minimum) + _minimum;
}
if(_highValue != oldValue)
{
dispatchEvent(new Event(Event.CHANGE));
}
updateLabels();
}
/**
* Gets / sets the minimum value of the slider.
*/
public function set minimum(value:Number):void
{
_minimum = value;
_maximum = Math.max(_maximum, _minimum);
_lowValue = Math.max(_lowValue, _minimum);
_highValue = Math.max(_highValue, _minimum);
positionHandles();
}
public function get minimum():Number
{
return _minimum;
}
/**
* Gets / sets the maximum value of the slider.
*/
public function set maximum(value:Number):void
{
_maximum = value;
_minimum = Math.min(_minimum, _maximum);
_lowValue = Math.min(_lowValue, _maximum);
_highValue = Math.min(_highValue, _maximum);
positionHandles();
}
public function get maximum():Number
{
return _maximum;
}
/**
* Gets / sets the low value of this slider.
*/
public function set lowValue(value:Number):void
{
_lowValue = value;
_lowValue = Math.min(_lowValue, _highValue);
_lowValue = Math.max(_lowValue, _minimum);
positionHandles();
dispatchEvent(new Event(Event.CHANGE));
}
public function get lowValue():Number
{
return Math.round(_lowValue / _tick) * _tick;
}
/**
* Gets / sets the high value for this slider.
*/
public function set highValue(value:Number):void
{
_highValue = value;
_highValue = Math.max(_highValue, _lowValue);
_highValue = Math.min(_highValue, _maximum);
positionHandles();
dispatchEvent(new Event(Event.CHANGE));
}
public function get highValue():Number
{
return Math.round(_highValue / _tick) * _tick;
}
/**
* Sets / gets when the labels will appear. Can be "never", "move", or "always"
*/
public function set labelMode(value:String):void
{
_labelMode = value;
_highLabel.visible = (_labelMode == ALWAYS);
_lowLabel.visible = (_labelMode == ALWAYS);
}
public function get labelMode():String
{
return _labelMode;
}
/**
* Sets / gets where the labels will appear. "left" or "right" for vertical sliders, "top" or "bottom" for horizontal.
*/
public function set labelPosition(value:String):void
{
_labelPosition = value;
updateLabels();
}
public function get labelPosition():String
{
return _labelPosition;
}
/**
* Sets / gets how many decimal points of precisions will be displayed on the labels.
*/
public function set labelPrecision(value:int):void
{
_labelPrecision = value;
updateLabels();
}
public function get labelPrecision():int
{
return _labelPrecision;
}
/**
* Gets / sets the tick value of this slider. This round the value to the nearest multiple of this number.
*/
public function set tick(value:Number):void
{
_tick = value;
updateLabels();
}
public function get tick():Number
{
return _tick;
}
}