JRubyでFilthyRichClients その3

Filthy Rich Clientsの第3章。
paintComponentやpaintをオーバーライドするときや、画像のgetGraphicsメソッドなどで
取得できるGraphicsオブジェクトの話。
Graphicsオブジェクトの状態を変化させて、レンダリングを操作できる。
以下の属性が、Filthy Rich Clientsについて、最も役にたつと書いてある。
属性を変えるためのセッター、ゲッターなど関連するメソッドも一緒に書くと

  • Foreground Color
    • setColor
    • getColor
  • Background Color
    • setBackground
    • getBackground
  • Font
    • setFont
    • getFont
  • Stroke
    • setStroke
    • getStroke
  • RenderingHint
    • setRenderingHint
    • getRenderingHint
    • addRenderingHint
  • Clip
    • clipRect
    • clip
    • setClip
    • getClip
    • getClipBounds
  • Composite
    • setComposite
    • getComposite
  • Paint
    • setPaint
    • getPaint
  • Transform
    • rotate
    • scale
    • translate
    • transform
    • setTransform
    • getTransform

以下はTransformを色々と変更して、図形や文字を描画している。

include Java

include_class %w( JFrame JComponent SwingUtilities ).map{|s| 'javax.swing.' + s}
include_class %w( Color ).map{|s| 'java.awt.' + s}

class RotateComponent < JComponent
  def paintComponent(g)
    g2d = g.create()
    
    # 背景の描画
    g2d.setColor(Color::WHITE)
    g2d.fillRect(0, 0, getWidth, getHeight)
    
    # 四角形の描画
    g2d.setColor(Color::GRAY.brighter())
    g2d.fillRect(50, 50, 50, 50)
        
    g2d.rotate(degree_to_radian(45))
    g2d.setColor(Color::GRAY.darker())
    g2d.fillRect(50, 50, 50, 50)
        
    g2d = g.create()
    g2d.rotate(degree_to_radian(45), 75, 75)
    g2d.setColor(Color::BLACK)
    g2d.fillRect(50, 50, 50, 50)

    g2d.dispose()
    
    # 文字列の描画
    g2d = g.create()
    g2d.translate(150, 150)
    total_degree = 0
    4.times do
      g2d.drawString("rotate: " + total_degree.to_s + "-degree", 10, 0)
      g2d.rotate(degree_to_radian(90))
      total_degree += 90      
    end
    g2d.dispose()
  end
  
  private
  def degree_to_radian(deg)
    Math::PI / 180.0 * deg
  end
end

SwingUtilities.invokeLater do
  f = JFrame.new
  f.add(RotateComponent.new)
  f.setSize(300, 300)
  f.setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE)
  f.setLocationRelativeTo(nil)
  f.setVisible(true)  
end

実行結果


カスタムJComponentを作って、paintComponentでtransformを色々設定している。
Graphicsの属性を変えるときは、g2d = g.create() として、新しいGraphicsオブジェクトを作り、
新しいGraphicsオブジェクトに属性を設定するようにしている。

JRubyでFilthyRichClients その2

描画をカスタマイズするときは、普通はpaintComponentをオーバーライドするんだけど、
その部品の子供や境界線にも影響を与えたいときはpaintをオーバーライドしてもいい。
swing部品を半透明するなどのように、部品全てに影響を与えたいときはpaintのオーバーライドが使えるみたい。
下のコードでは、paintをオーバーライドしてJButtonを半透明にする。一度、BufferdImageに本来の絵を描いてから、
AlphaCompositeで半透明を設定して、BufferdImageの絵を描画している。

include Java

include_class %w( JFrame JPanel JButton SwingUtilities ).map{|s| 'javax.swing.' + s}
include_class %w( AlphaComposite Color ).map{|s| 'java.awt.' + s}

class TranslucentButton < JButton

  def initialize(text)
    super(text)
    setOpaque(false)
    @btn_image = nil
  end

  def paint(g)
    if(@btn_image == nil ||
       @btn_image.getWidth != getWidth ||
       @btn_image.getHeight != getHeight)
      @btn_image = getGraphicsConfiguration().createCompatibleImage(getWidth, getHeight)
    end

    # super呼び出しで、@btn_imageにボタンの絵を描画する。
    g_btn = @btn_image.getGraphics
    g_btn.setClip(g.getClip)
    super(g_btn)
    g_btn.dispose
    
    # AlphaCompositeを半透明(0.5)に設定して@btn_imageをグラフィクスに描画する。
    g.setComposite(AlphaComposite.getInstance(AlphaComposite::SRC_OVER, 0.5))
    g.drawImage(@btn_image, 0, 0, nil)
  end
end

class CheckBoard < JPanel
  CHECK_SIZE = 60.0

  def paintComponent(g)
    # 背景を白で塗っておく
    g.setColor(Color.white)
    g.fillRect(0, 0, getWidth, getHeight)
    g.setColor(Color.black)

    #左上と右下のブロックを黒で塗る
    (getWidth / CHECK_SIZE).ceil.times do |i|
      (getHeight / CHECK_SIZE).ceil.times do |j|
        g.fillRect(CHECK_SIZE*i               , CHECK_SIZE*j               , CHECK_SIZE/2, CHECK_SIZE/2)
        g.fillRect(CHECK_SIZE*i + CHECK_SIZE/2, CHECK_SIZE*j + CHECK_SIZE/2, CHECK_SIZE/2, CHECK_SIZE/2)
      end
    end
  end
end

SwingUtilities.invokeLater do
  panel = CheckBoard.new
  panel.add(TranslucentButton.new("TranslucentButton"))
  f = JFrame.new("TranslucentButton")
  f.add(panel)
  f.setSize(300, 300)
  f.setVisible(true)
  f.setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE)
end

実行結果


重い処理のあつかい

重い処理をEDT上で行うと、画面がフリーズしてしまうので、EDT上では重い処理はやらない。
以下は良くない書き方。

include Java

include_class %w( JFrame JButton SwingUtilities ).map{|s| 'javax.swing.' + s}

class FreezeEDT < JFrame
  def initialize(title)
    super(title)
    freezer = JButton.new("Freeze")
    freezer.addActionListener do
      puts "重い処理  start"
      sleep 3
      puts "重い処理 finish"
    end
    add(freezer)
    pack
  end
end

SwingUtilities.invokeLater do
  FreezeEDT.new("FreezeEDT").setVisible(true)
end

実行結果


ボタンをクリックすると、addActionListenerに渡しているブロックが実行され、画面がフリーズする。



なので、

  • 重い処理をするときは、EDT上でやらないで、スレッドを生成して処理させる。
  • スレッドの中にSwing部品を更新するような処理を入れるときは、その処理をSwingUtilities.invokeLaterに渡す。

以下はこれをふまえたやり方。

include Java

include_class %w( JFrame JLabel JButton SwingUtilities SwingConstants ).map{|s| 'javax.swing.' + s}
include_class 'java.awt.BorderLayout'

class SwingThreading < JFrame
  def initialize(title)
    super(title)
    count = 0
    label = JLabel.new(count.to_s, SwingConstants::CENTER)
    add(label, BorderLayout::CENTER)
    
    btn = JButton.new("Increment")
    btn.addActionListener do
      Thread.start do
        sleep 3 # 重い処理
        count += 1
        SwingUtilities.invokeLater{ label.setText(count.to_s) }
      end
    end
    add(btn, BorderLayout::SOUTH)
    
    pack
  end
end

SwingUtilities.invokeLater do
  SwingThreading.new("SwingThreading").setVisible(true)
end

実行結果


画面がフリーズすることは無くなった。ただ、ボタンを連続で押すと以前の処理が終了していなくても、
新たに処理が追加されてしまうので、それが嫌ならチェックするコードも必要になる。

JRubyでFilthyRichClients


半年ほど前にFilthyRichClientsを読んだけど、いろいろ忘れているので復習する。
せっかくなので、JRubyでサンプルコードを書きなおしながら勉強してみる。
まずは、chapter2の楕円とハイライト表示のコードを書いてみよう。

楕円を描く

require 'java'

include_class 'java.awt.Color'
include_class %w( JFrame JComponent SwingUtilities).map{|s| 'javax.swing.' + s}

class OvalComponent < JComponent
  def paintComponent(g)
    g.setColor(getBackground())
    g.fillRect(0, 0, getWidth(), getHeight())
    g.setColor(Color::RED)
    g.fillOval(0, 0, getWidth(), getHeight())
  end
end

SwingUtilities.invokeLater do
  f = JFrame.new
  f.setTitle("OvalComponent")
  f.add(OvalComponent.new)
  f.setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE);
  f.setSize(200, 200);
  f.setVisible(true);
end

実行結果

JComponentを継承したクラスをつくって、paintComponentをオーバーライドした。
setColorで赤色に設定して、fillOvalで円を描いている。赤い楕円が表示される。

ハイライト表示

JButtonのテキストにハイライトを付ける。

require 'java'

include_class %w( AlphaComposite Color FlowLayout RadialGradientPaint ).map{|s| 'java.awt.' + s}
include_class %w( JFrame JButton SwingUtilities ).map{|s| 'javax.swing.' + s}
include_class 'java.awt.image.BufferedImage'
include_class 'java.awt.geom.Point2D'

class HighlightButton < JButton
  HIGHLIGHT_SIZE = 18
  
  def initialize(text)
    super(text)
    @highlight_image = create_highlight_image()
  end
  
  def create_highlight_image
    img = BufferedImage.new(HIGHLIGHT_SIZE, HIGHLIGHT_SIZE, BufferedImage::TYPE_INT_ARGB)
    g = img.createGraphics()
    
    # 背景
    g.setComposite(AlphaComposite::Clear)
    g.fillRect(0, 0, HIGHLIGHT_SIZE, HIGHLIGHT_SIZE)
    
    # グラデーションを作成
    g.setComposite(AlphaComposite::SrcOver)
    center = Point2D::Float.new(HIGHLIGHT_SIZE/2, HIGHLIGHT_SIZE/2)
    radius = HIGHLIGHT_SIZE/2.0
    dist = [0.0, 0.85].to_java(:float)
    colors = [Color.white, Color.new(255, 255, 255, 0)].to_java('java.awt.Color'.to_sym)
    paint = RadialGradientPaint.new(center, radius, dist, colors)
    
    g.setPaint(paint)
    g.fillOval(0, 0, HIGHLIGHT_SIZE, HIGHLIGHT_SIZE);
    g.dispose()
    img
  end
  
  def paintComponent(g)
    super(g)
    g.drawImage(@highlight_image, getWidth()/4, getHeight()/4, nil)
  end
end

SwingUtilities.invokeLater do
  f = JFrame.new
  f.setTitle("OvalComponent")
  f.getContentPane().setLayout(FlowLayout.new);
  f.add(JButton.new("standard"))
  f.add(HighlightButton.new("highlight"))
  f.setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE);
  f.setSize(100, 100)
  f.setVisible(true);
end

実行結果

JButtonを継承するクラスでpaintComponentをオーバーライドした。
create_highlight_imageの中がごちゃごちゃしてるけど、BufferedImageをつくって、そこにグラデーションを書いています。
paintComponentの中では、まずデフォルトの描画を行い(superの呼び出し)、
そのあとcreate_highlight_imageで作ったImageを描画しています。下のJButtonのほうに、微妙にハイライトが付いている。

RadialGradientPaintをつくるのに、色々とto_javaするのが大変。。。もっと上手な方法あるのかな。

nanocのソースを読む

nanocのソースを読んで、使えるコマンドなどを調べてみよう。

まずは、nanocのコマンドであるnanoc3ファイルを見てみる。
以下のソースはほぼすべて抜粋です。

nanoc3

require 'nanoc3'
require 'nanoc3/cli'

# Run base
Nanoc3::CLI::Base.shared_base.run(ARGV)

requireして、引数をshared_base.runに渡している。

Nanoc3::CLI::Base.shared_base

def self.shared_base
  @shared_base ||= Nanoc3::CLI::Base.new
end
=を使って最初の一回だけnewするようにしている。だからshared_baseなのかな。。

最初のrunは、Nanoc3::CLI::Base#runのようだ。
次はrunを見てみる。

Nanoc3::CLI::Base#run

def run(args)
  super(args)
rescue Interrupt => e
  exit(1)
rescue StandardError, ScriptError => e
  print_error(e)
  exit(1)
end

super(args)となっているので、スーパークラスを見ればよいのか。

module Nanoc3::CLI

  class Base < Cri::Base

Cri::Base#runを見れば良いようだ。Criはnanoc3をインストールされたときに、
一緒にインストールされたgemのようです。Criがいつrequireされてたかというと、最初のrequire 'nanoc3/cli'を
したときにnanoc3/cliの中でrequireされていた。

Cri::Base#run

    def run(args)
      # Check arguments
      if args.length == 0
        @help_command.run([], [])
        exit 1
      end
      # もっと続く。。。

何も引数を与えずにnanoc3すると、@help_command.runが呼ばれてexitする。
@help_commandは何かというと

#Cri::Base
attr_accessor :help_command
#Nanoc3::CLI::Base
self.help_command = Nanoc3::CLI::Commands::Help.new

このHelpクラスのようにCommandっぽいクラスは\nanoc3\cli\commandsディレクトリの下に

autocompile.rb
compile.rb
create_item.rb
create_layout.rb
create_site.rb
debug.rb
help.rb
info.rb
update.rb
view.rb

とあって、Helpクラスもhelp.rbに書いてある。nanocのヘルプを呼ぶと、これらに対応するような
commandがある。

[saliy@localhost ~]$ nanoc3
nanoc, a static site compiler written in Ruby.

Available commands:

    autocompile          start the autocompiler
    compile              compile items of this site
    create_item          create a item
    create_layout        create a layout
    create_site          create a site
    debug                show debug information for this site
    help                 show help for a command
    info                 show info about available plugins
    update               update the data stored by the data source to a newer version
    view                 start the web server that serves static files

なので、"nanoc3 コマンド名"を実行すると、コマンドに対応するクラスのrunが呼ばれるのかな、と想像してみる。

Nanoc3::CLI::Commands::Help#run

    def run(options, arguments)
      # Check arguments
      if arguments.size > 1
        $stderr.puts "usage: #{usage}"
        exit 1
      end

      if arguments.length == 0
        # Build help text
        text = ''

        # Add title
        text << "nanoc, a static site compiler written in Ruby.\n"
      # もっと続く。。。
        puts text

いまの場合、arguments.lengthは0。
textにnanocのヘルプを詰めてputsする。そして、呼び出し元に戻るとexit 1 して終了。
nanoc3を何も引数を付けずに呼び出すと、Nanoc3::CLI::Commands::Help#runで、ヘルプが出力されることが分かった。
もうちょっと読んでみよう。

nanocをつかう

HTMLジェネレータnanocをつかってみようと思います。

インストール

[root@localhost ~]# gem-1.9.1 install nanoc3

インストールするとnanoc3コマンドがつかえるようになります。

サイト作成

create_siteでサイトを作成します。

[saliy@localhost ~]$ nanoc3 create_site nanoc_test
      create  config.yaml
      create  Rakefile
      create  Rules
      create  content/index.html
      create  content/stylesheet.css
      create  layouts/default.html
Created a blank nanoc site at 'nanoc_test'. Enjoy!

作成されたファイルが表示されます。

compile

先ほど作成されたファイルをもとにcompileでhtmlをつくります。

[saliy@localhost nanoc_test]$ nanoc3 compile
Loading site data...
Compiling site...
      create  [0.01s]  output/index.html
      create  [0.00s]  output/style.css

Site compiled in 0.07s.

outputディレクトリに作成されました。

確認

作成されたhtmlをブラウザで確認します。

[saliy@localhost nanoc_test]$ nanoc3  view
[2010-05-19 00:15:02] INFO  WEBrick 1.3.1
[2010-05-19 00:15:02] INFO  ruby 1.9.1 (2010-01-10) [i686-linux]
[2010-05-19 00:15:03] INFO  WEBrick::HTTPServer#start: pid=9750 port=3000

3000ポートでwebrickが動いているようなので、ブラウザで3000ポートを指定してアクセスします。

nanoc3 viewをするには、rack,adsfをインストールしている必要があるようなので、無ければインストールします。

[saliy@localhost nanoc_test]$ nanoc3  view
[2010-05-19 00:15:02] INFO  WEBrick 1.3.1
[2010-05-19 00:15:02] INFO  ruby 1.9.1 (2010-01-10) [i686-linux]
[2010-05-19 00:15:03] INFO  WEBrick::HTTPServer#start: pid=9750 port=3000

とりあえず、こんな感じで動かしてみた。もうちょっと詳しく調べてみよう。

チェックボックスをつくる

イベントの勉強にチェックボックスを自作してみた。

イベントクラス

package my.events 
{
	import flash.events.Event;
	 
	public class CheckBoxEvent extends Event {
		
		/**
		 * The <code>CheckBoxEvent.CHANGE</code> constant defines the values of the <code>type</code>
		 * property of the event object for a <code>checkBoxChange</code> event.
		 * 
		 * @eventType CheckBoxChange
		 */		
		public static const CHANGE:String = "checkBoxChange";
		
		private var _isOn:Boolean;
		
		/**
		 * Constructor.
		 * 
		 * @param type The type of event to be dispatched. This should be <code>CheckBoxEvent.CHANGE</code>.
		 * @param isOn The state of the check box.
		 * 
		 * @example
		 * <pre>
		 * var event:CheckBoxEvent = new CheckBoxEvent(CheckBoxEvent.CHANGE, boolean);
		 * dispatchEvent(event);
		 * </pre>
		 */		
		public function CheckBoxEvent(type:String, isOn:Boolean) {
			super(type);
			_isOn = isOn;
		}
		
		/**
		 * @return The state of the check box. If value is true, check box was checked.
		 */		
		public function get isOn():Boolean {
			return _isOn;
		}
		
		/**
		 * @return The cloned event.
		 */
		override public function clone():Event {
			return new CheckBoxEvent(type, _isOn);
		}
		
		override public function toString():String {
			return formatToString("CheckBoxEvent", "type", 
			                      "bubbles", "cancelable", "eventPhase", "_isOn");
		}				
	}
}

チェックボックスのチェックマーク

チェックマークの絵を描く。

package my.display 
{
	import flash.display.Shape;

	public class CheckBoxMark extends Shape {
		
		public function CheckBoxMark() {
			draw();
		}
		
		private function draw():void {
			graphics.beginFill(0x000000);
			graphics.lineStyle(1, 0x000000);
			graphics.moveTo(0, 10);
			graphics.lineTo(5, 15);
			graphics.lineTo(20, 0);
			graphics.lineTo(5, 20);
			graphics.lineTo(0, 10);
			graphics.endFill();
			scaleX = 0.7;
			scaleY = 0.7;
		}
	
}

チェックボックス

メインとなるクラス

package my.display 
{
	import my.events.CheckBoxEvent;	
	 
	import flash.display.Sprite;
	import flash.display.Shape;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.text.TextFormat;	
	 
	public class CheckBox extends Sprite {
		
		private const SIZE:uint = 20;
		private const TEXT_FORMAT:TextFormat = new TextFormat(null, 20);
		
		private var _mark:Shape;
		private var _isOn:Boolean;
		
		public function CheckBox(isOn:Boolean=false, text:String="") {
			_isOn = isOn;
			drawBox();
			createMark();
			createTextField(text);
			addEventListener(MouseEvent.MOUSE_DOWN, onMouseDownListener);
		}
		
		private function drawBox():void {
			graphics.beginFill(0xFFFFFF);
			graphics.lineStyle(1);
			graphics.lineTo(SIZE, 0);
			graphics.lineTo(SIZE, SIZE);
			graphics.lineTo(0, SIZE);
			graphics.lineTo(0, 0);
			graphics.endFill();			
		}		
		
		private function createMark():void {
			_mark = new CheckBoxMark();
			_mark.x = (width - _mark.width) / 2;
			_mark.y = (height - _mark.height) / 2;
			addChild(_mark);
			_mark.visible = _isOn;
		}
		
                private function createTextField(text:String):void {
			var field:TextField = new TextField();
			field.text = text;
			field.setTextFormat(TEXT_FORMAT);
			field.selectable = false;
			field.x = width;
			field.y = (height - field.textHeight) / 2 ;
			addChild(field);
		}
		
		private function onMouseDownListener(e:MouseEvent):void {
			_isOn = !_isOn;
			dispatchEvent(new CheckBoxEvent(CheckBoxEvent.CHANGE, _isOn));
			_mark.visible = _isOn;
		}				
	}
}

動作確認

ちゃんと動いているようだ。

package  
{
	import my.display.CheckBox;
	import my.events.CheckBoxEvent;
	 
	import flash.display.Sprite;	
	
	public class Main extends Sprite{
		
		public function Main() {
			var c:CheckBox = new CheckBox(true, "test");
			c.x = c.y = 100;
			addChild(c);
			c.addEventListener(CheckBoxEvent.CHANGE, changeListener);
		}
		
		private function changeListener(e:CheckBoxEvent):void {
			trace(e.isOn);
		}
	}

}

スライダーをつくる

カスタムイベントの勉強にスライダーを作ってみた。
とりあえず

  • イベントクラス
  • スライダーのつまみ部分
  • スライダー

の3つのクラスで作ってみた。

イベントクラス

  • 発生するイベント
    • SliderEvent.START => スライダーのドラッグを開始したときに発生
    • SliderEvent.CHANGE => スライダーをドラッグ中に発生
    • SliderEvent.END => ドラッグ終了時に発生
  • コンストラクタのpercentは、ドラッグしているスライダーの割合
package
{
	import flash.events.Event;
	 
	public class SliderEvent extends Event {
		
		public static const START:String = "SliderStart";
		public static const CHANGE:String = "SliderChange";
		public static const END:String = "SliderEnd";
		
		private var _percent:Number;
		
		public function SliderEvent(type:String, percent:Number) {
			super(type);
			_percent = percent;
		}
		
		public function percent():Number {
			return _percent;
		}
		
		
		override public function clone():Event {
			return new SliderEvent(type, _percent);
		}
		
		override public function toString():String {
			return formatToString("SliderEvent", "type", 
			                      "bubbles", "cancelable", "eventPhase", "_percent");
		}
	}
}

スライダーのつまみ部分

  • つまみの絵を描くだけ。
  • コンストラク
    • widthとheightでつまみの大きさを指定。fillColorで何色で塗るか指定。
package
{
	import flash.display.Sprite;
	internal class SliderHandle extends Sprite {
		
		public function SliderHandle(width:Number, height:Number, fillColor:uint = 0xFFFFFF) {
			draw(width, height, fillColor);
		}
		
		private function draw(width:Number, height:Number, fillColor:uint):void {
			graphics.beginFill(fillColor);
			graphics.lineStyle(1, 0x000000);
			graphics.lineTo(width / 2, height);
			graphics.lineTo( -width / 2, height);
			graphics.lineTo(0, 0);
			graphics.endFill();
		}
	}
}

スライダー

  • コンストラク
    • widthはバーの長さ、percentは初期値(0〜100を指定)
  • イベントの送出
    • getPercentで、現在のスライダーバーの割合を計算して、SliderEventをdispatchEventする

startDragにスライダーのつまみを動かせる範囲を指定できたので簡単にかけた。

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Rectangle;

	public class Slider extends Sprite {
		
		private const BAR_HEIGHT:uint = 10;
		private const SLIDER_HANDLE_WIDTH:uint = 10;
		private const SLIDER_HANDLE_HEIGHT:uint = 20;
		
		private var _handle:SliderHandle;
		private var _bar_width:uint;
		
		public function Slider(width:uint, percent:uint) {
			_bar_width = width;
			drawSlideLine();
			createSliderHandle();
			initStartPosition(percent);
		}
		
		private function drawSlideLine():void {
			graphics.beginFill(0xFFFFFF);
			graphics.lineStyle(1);
			graphics.lineTo(_bar_width, 0);
			graphics.lineTo(_bar_width, BAR_HEIGHT);
			graphics.lineTo(0, BAR_HEIGHT);
			graphics.lineTo(0, 0);
			graphics.endFill();			
		}
		
		private function createSliderHandle():void {
			_handle = new SliderHandle(SLIDER_HANDLE_WIDTH, SLIDER_HANDLE_HEIGHT);
			_handle.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownListener);
			_handle.y = BAR_HEIGHT / 2;
			addChild(_handle);			
		}
		
		private function initStartPosition(percent:uint):void {
			if ( 0 > percent || percent > 100) {
				throw Error("percent mast range 0 to 100:" + percent);
			}
			_handle.x = _bar_width * percent / 100;
		}
		
		private function draggableArea():Rectangle {
			return  new Rectangle(0, BAR_HEIGHT / 2, _bar_width, 0);
		}
		
		private function getPercent():Number {
			return _handle.x / _bar_width * 100;
		}
		
	    private function mouseDownListener(e:MouseEvent):void {
			stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveListener);
			stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpListener);
			dispatchEvent(new SliderEvent(SliderEvent.START, getPercent()));
			_handle.startDrag(false, draggableArea());
		}
		
		private function mouseMoveListener(e:MouseEvent):void {
			dispatchEvent(new SliderEvent(SliderEvent.CHANGE, getPercent()));			
		}
		
		private function mouseUpListener(e:MouseEvent):void {
			stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveListener);
			stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpListener);
			dispatchEvent(new SliderEvent(SliderEvent.END, getPercent()));
			_handle.stopDrag();
		}		
	}
}

使い方

下のようにして使う。

package
{
	import flash.display.Sprite;
	
	public class Main extends Sprite {
		public function Main() {
		    var slider:Slider = new Slider(500, 50);
			addChild(slider);
			slider.addEventListener(SliderEvent.START, f1);
			slider.addEventListener(SliderEvent.CHANGE, f2);
			slider.addEventListener(SliderEvent.END, f3);
		}
		
		private function f1(e:SliderEvent):void {
			trace("start:" + e.percent());
		}
		
		private function f2(e:SliderEvent):void {
			trace("change:" + e.percent());
		}
		
		private function f3(e:SliderEvent):void {
			trace("end:" + e.percent());
		}
	}
	
}