「はじめてのJavaFX」(工学社刊)サポートページ

本書に記載されなかったサンプルプログラムの解説

本書では、掲載予定のサンプルプログラムのいくつかを、ページ数の制限上本書では割愛しました。でもサンプルプログラムまで割愛してしまうのもナンですからそのまま付録CDに残しました。
それらのプログラムについて、ここで解説します。 本来はサンプルフォルダ中の「どのプログラムが」割愛されていたかリストアップしてさしあげるのが親切とは思いますが、プログラムの一部だけというものもあるので、あいにくですが勉強のつもりで、みなさんで「コレだな」と見当つけてみてください。

第5章

第7章


第5章


「RedBlue.fx」中に、「無効専用アイコン」を指定する(本書pp.102-107の補足情報)

RedBlue.fxでは、Redボタンが無効になった場合アイコンも含めて退色しましたから、イヤでも無効であることがわかりますが、さらに無効のときに別のアイコンを与えることもできます。 図1のようなアイコンにしてみましょう。名前は「disable.png」です。実はこのアイコンもサンプル画像フォルダ中に残っています。




図1「disable.png」

Redボタンの内容を記述している最初のButton{...}の中に、リスト1を追加してみましょう。



リスト1 Buttonに特性「disabledIcon」を追加する


disabledIcon:Image{

	url:"file:///C:/Users/javafx/work/img/disable.png"

}



実行してみます。




図2 無効度がより強まったかどうか?



Redボタンが有効になったとき、このバツアイコンが再び赤丸アイコンになることも確認してください。


チェックボックスを「クリックしたときの動作」(本書pp.141-148の補足情報)

実はチェックボックスにも「action」特性があります。これは、

そのチェックボックスが選択された・されないに関係なく、とにかくクリックされたときに何か特別な動作をさせたい

という場合に使えます。

リスト2のような例はどうでしょうか。



リスト2 CheckBoxAction.fx

import javafx.ui.*;

class FishModel{
  attribute name:String;
  attribute selected:Boolean;
}

class FishGroupModel{
  attribute group:FishModel*;
  attribute message:String; //表示させるメッセージ
}

var fishgroup=FishGroupModel{

	var maguro=FishModel{
	name:"まぐろ"
	selected:false}

	var katuo=FishModel{
	name:"かつお"
	selected:false
	}

	var iwashi=FishModel{
	name:"いわし"
	selected:false
	}

	 group:[maguro,katuo,iwashi]
	 
	 message:"どれになさいますか"

};


Frame{

  width:250
  height:100
  content:BorderPanel{
  
    top:GridPanel{
        rows:1
        columns:sizeof fishgroup.group
        cells:foreach (fish in fishgroup.group)
          CheckBox{
              text:fish.name
              selected:bind fish.selected

              //アクションを記述
              action:operation(){

               if(fish.selected){
                  fishgroup.message="{fish.name}がお好みですか";
                }
               else{
                  fishgroup.message="{fish.name}はお嫌いですか";
                }
              }
          }
      }

     bottom:Label{text: bind fishgroup.message}

    }

  visible:true
}



リスト2では、ひとつひとつの「FishModel」ではなく、全体の「FishGroupModel」に、特性「message」を与えています。アクションの結果を表示させるための文字列です。
リスト2では、簡単のため、「今どの要素が選択状態にあるか」を表示するようにはしていません。
そのかわり、その要素が選択状態にあるか、ないかで、異なる動作をさせています。

if( fish.selected)

というのがそれです。 チェックボックスがクリックされたときに、

クリックによって選択された場合は「お好みですか」、
クリックによって選択が解除された場合は「お嫌いですか」

と、表示するようにするのです。

実行してみましょう。他のチェックボックスがオンかオフかは、このアプリケーションでは関係しません。




図3 今クリックしたチェックボックスの動作に反応してメッセージが表示される


タブ画面に普通のウィジェットを置く(本書pp.198-199の補足情報)

2枚のタブ画面にそれぞれ「プログレスバー」と「スライダ」を記述して表示させるプログラム「TwoTabs.fx」は、本書には掲載しないのでサンプルプログラムを御覧ください、と書きましたが、せっかくのサポートページですからズバーッとお見せしましょう。リスト3のとおりです。
ひとつのプログラムに2枚のタブ画面の記述を全て入れるので、元のプログラム「SimpleSlider.fx」と「ProgressBar.fx」に記述した全てのモデル、変数等を一括して記述することになります。



リスト3 TwoTabs.fx

import javafx.ui.*;

//Sliderのためのモデルと変数
class SliderValueModel{
  attribute value:Number;
}

var slidervalue=SliderValueModel{
  value:0
};

//ProgressBarのためのモデルと変数

class ClickNumModel{
  attribute num:Integer;
}

var clicknum=ClickNumModel{
  num:0
};


Frame{

  width:250
  height:200

  content:TabbedPane{
  
    tabPlacement:TOP
    
    tabs:[	//具体的なTabの配列なので[...]で囲む
    
     Tab{
      title:"SimpleSlider"
      content:GridPanel{
        border:EmptyBorder{top:10 bottom:10 right:20 left:20}
        rows:2 columns:1
        cells:[
            Slider{
              value:bindslidervalue.value
              min:0
              max:100
              minorTickSpacing:10
              majorTickSpacing:20
              paintTicks:true    

            },
           Label{
                text:bind slidervalue.value.toString()
            }
        ]
       }          
    },

    Tab{
      title:"ProgressBar"
      content:GridPanel{
        border:EmptyBorder{top:10 bottom:10 right:20 left:20}
        rows:2 columns:1
        cells:[
           ProgressBar{
            max:50
            min:0
            value:bind clicknum.num
            string:bind "{clicknum.num.toString()}回"
            stringPainted:true
            borderPainted:true
          },
          Button{
            text:"ここをクリック"
            action:operation(){
              clicknum.num++;
            }
          }
        ]
      }
    }

    ]
  }

  visible:true

}



実行してみましょう。どちらも、タブ上でちゃんと動作することを確認してください。




図4「SimpleSlide」と「ProgressBar」のタブを切り替える




第7章


曲線(本書pp.231-232の補足情報)

本書では、両端点と制御点を指定して描く「ベジェ曲線」を紹介しましたが、同様のセオリーで「CubicCurve(3次曲線)」「QuadCurve(2次曲線)」書くことができます。
ただし、「ベジェ曲線」が「d」という配列の中に「CurveTo」という過程を書く方式になっているのに対し、これらの曲線は端点や制御点を「特性」にもつひとつのShapeとして一気に描くことができるので、簡単といえば簡単です。
と言っても、端点や制御点を自分で考えて決めなければないのは同じです。

・3次曲線

まずは3次曲線です。リスト4は、開始点・終点・制御点をいろいろに変えた3種類の3次曲線を表示したものです。「3次」などと偉そうに書きましたが、要するにS字(あるいはその裏返し)のような曲線です。



リスト4 MyCubicCurves.fx

import javafx.ui.*;
import javafx.ui.canvas.*;

Frame{

  width:300
  height:150

  content:GridPanel{
   rows:1
   columns:3

   cells:[

      Canvas{
        content:CubicCurve{
         x1:10 y1:10
         x2:90 y2:90
         ctrlx1:40 ctrly1:20
         ctrlx2:60 ctrly2:80
         stroke: blue
         strokeWidth:2
       }
      },
     Canvas{
        content:CubicCurve{
          x1:10 y1:10
          x2:90 y2:90
          ctrlx1:50 ctrly1:10
          ctrlx2:50 ctrly2:80
          stroke: purple
          strokeWidth:2
       }
      },
      Canvas{
        content:CubicCurve{
          x1:10 y1:10
          x2:70 y2:90
          ctrlx1:90 ctrly1:30
          ctrlx2:10 ctrly2:70
          stroke: orange
          strokeWidth:2
       }
      }

    ]
  }
  visible:true
}



実行してみましょう。




図5 いろいろな3次曲線



CubicCurveのとる主な特性を表1に示します。



表1 CubicCurveの主な特性
x1, y1 曲線の開始点のX座標、Y座標
x2, y2 曲線の終点のX座標、Y座標
ctrlx1, ctrly1 最初の制御点のX座標、Y座標
ctrlx2, ctrly2 2番目の制御点のX座標、Y座標



「制御点」とは、実は筆者もよくわかりません。おそらく本書に示した「ベジェ曲線」の「制御点」と同じような考え方でしょう。
両制御点が曲線の外側に遠く離れていると、曲線のグンニャリ度が増す、ぐらいに考えておきましょう。

・QuadCurve

QuadCurveは山(逆さにすれば谷)が1つだけの曲線です。Javaの世界では「2次曲線」とされているようですが、「Quad」は「4」じゃないのかなと筆者は密かに思っています。でも2次曲線は4次曲線の仲間ですし、こだわってもしょうがありませんね。
QuadCurveでは「制御点」は一つです。



表2 QuadCurveの主な特性
x1, y1 曲線の開始点のX座標、Y座標
x2, y2 曲線の終点のX座標、Y座標
ctrlx, ctrly 制御点のX座標、Y座標


これもリスト5のように、開始点・終点・制御点をいろいろに変えて、どのような結果が出るか見てみましょう。



リスト5 MyQuadCurves.fx

import javafx.ui.*;
import javafx.ui.canvas.*;


Frame{
  width:300
  height:150

  content:GridPanel{
  rows:1
  columns:3
    cells:[
    
      Canvas{
       content:QuadCurve{
          x1:10
          y1:100
          ctrlx:50
          ctrly:10
          x2:90
          y2:100
         stroke:blue
         strokeWidth:2
        }
      },
      Canvas{
       content:QuadCurve{
          x1:10
          y1:100
          ctrlx:50
          ctrly:50
          x2:90
          y2:100
         stroke:brown
         strokeWidth:2
        }
      },
     Canvas{
       content:QuadCurve{
          x1:30
          y1:10
          ctrlx:10
          ctrly:100
          x2:90
          y2:10
         stroke:magenta
         strokeWidth:2
        }
      }

    ]
  }
  visible:true
}



図6 リスト5の実行結果

QuadCurveでは、制御点が開始点と終了点の中点にあると、対称的な曲線になります。また、制御点が端点から垂直方向に離れると、凹凸が深くなります。

本書ですでに紹介した「ベジェ曲線」や、ここで追加の紹介をした「何次曲線」というものを、制御点をひねくり出しながら描く必要はそう多くはないでしょうが、「こういう描き方もできたな」と覚えがあれば何らかの役には立つかも知れません。


グラデーションの高度な設定(本書pp.236-238の補足情報)

・両端の処理

本章ではグラデーションの塗り方を紹介していますが、図形イッパイではなく、途中から途中までを塗ることもできます。
この場合、今まで使わなかった特性x1,y1及びx2,y2を用います。
リストでは、「LinearGradient」の特性にこれらを加え、長方形の左から0.5-0.75の範囲をグラデーションにしています。



リスト6「LinearGradient」に特性を追加

 fill:LinearGradient{
 
    x1:0.5,y1:0, x2:0.75,y2:0 //X方向の両端を残す

    stops:[
          Stop{offset:0.0 color:blue},
          Stop{offset:1.0 color:lightblue}

          ]

   //リスト7あるいはリスト8をここに追加

 }


本書掲載のプログラム「SimpleGrad.fx」の「fill:LinearGradient{...}」の部分を、リスト6のように書き換えたら、「PartialGrad.fx」という別名で保存し、実行してみましょう。




図7「SimpleGrad.fx」の一部をリスト6に修正後(グラデーションの範囲を狭めた)

図7では、グラデーションの領域の外側では、それぞれの基本色がベタ塗りされることになります(デフォルト)。しかし、「LinearGradient」の特性「spreadMethod」を指定することにより、別の処理のしかたもできます。

リスト7のように「REFLECT」を指定することで、グラデーションを指定しない部分には、グラデーションが「対称的」に繰り返されます。



リスト7 LinearGradient の特性「spreadMethod」を「REFLECT」に

spreadMethod:REFLECT 


リスト6にさらにリスト7を追加したら、Reflect.fxという別名で保存し(サンプルフォルダにあります)、実行してみましょう。




図8 リスト7を追加後(対称配置)

リスト7に代えてリスト8のように「REPEAT」に指定すれば、同じ向きにグラデーションが繰り返されます。



リスト8 LinearGradientの特性「spreadMethod」を「REPEAT」に

spreadMethod:REPEAT


リスト7をリスト8に書き換えたら、Repeat.fxという別名で保存し、実行してみましょう。




図9 リスト7をやめてリスト8にする(繰り返し)

・放射状グラデーションの中心をずらす

放射状グラデーションでも直線状グラデーションと同じようにstop、spreadMethodの指定ができますが、それよりもう一組大事な特性があります。「」色の放射の中心を領域の中心から少しずらすことができます。



リスト9 focusX, focusYを設定

focusX:40 focusY:40


放射状グラデーションのサンプルプログラム「RadialGrad.fx」に、さらにリスト9を追加したら、「RadialShear.fx」という別の名前で保存し、実行してみましょう。




図10 リスト9を追加後(放射の中心を斜めに置く)

これはなかなかシブい効果を与える設定ですね。


陰影の特性を設定する(本書pp.252-253の補足情報)

「ぼかし」や「ノイズ」とともに「フィルタ」の一種である、「陰影」にも、いろいろな特性があります。本書で紹介した「Shadow.fx」を別のファイル「MoreShadow.fx」にして編集してみましょう。
「ShadowFilter」に、次の設定を入れます。



リスト10 ShadowFilterの設定を変える例

ShadowFilter{
   angle:30
   distance:5
   opacity:0.7
   shadowColor:white
}


リスト10では、shadowColorつまり影の色を白にしました。そこで、フレームの背景色は黒にしましょう。
Frameの特性「background」を指定します。



リスト11 フレームの背景色を黒にする

Frame{
  width:200
  height:200
  background:black  //背景色を指定

  ..........
  


テキストそのものも白にします。



リスト12 Textのfillやstrokeを白にする

 content:Text{

         ..........

           fill:white
           stroke:white
           strokeWidth:1
     } //Textの閉じカッコ


実行するとこんな感じです。




図11 ShadowFilterの特性を調整した結果

本書でボツになったわけが、なんとなくわかりますね。


Transformか、位置情報か(本書pp.253-257の補足情報)

本書をここまで読み進んだら、図形の移動やサイズ変更を、二通りの手法で書くことができるようになっています。それを確かめておきましょう。
「Transform」で移動させる方法と、図形の「x,y」など位置情報の特性を指定する方法です。

平行移動を例にとりましょう。図形を、「X」のボタンでX方向、「Y」のボタンでY方向に移動させるプログラムです。実は本書のどこかにさりげなく、絵だけ表示されているんです。




図12 X方向、Y方向に分けて動かす

ひとつは、リスト13のように、図形の位置を表す特性を変更する方法です。



リスト13 XYPosition.fx

import javafx.ui.*;
import javafx.ui.canvas.*;

class PositionModel{
  attribute x: Number;
  attribute y: Number;
}

var change=PositionModel{
  x:0 y:0
};


Frame{
    width:250
    height:200

    content:BorderPanel{

      center:Canvas{
        content:Rect{
          x:bind 10*change.x
          y:bind 10*change.y
          width:50
          height:30
          fill:red
       }
    }
 
    bottom:GridPanel{
      rows:1
      columns:3
      
      cells:[
      
        Button{
          text:"X"
          action:operation(){
            change.x++;
          }
        },
        Button{
          text:"Y"
          action:operation(){
            change.y++;
          }
        },
        Button{
          text:"クリア"
          action:operation(){
            change.x=0;
            change.y=0;
          }
        },

      ]
    }
  }

  visible:true

}



もうひとつは、Transformのうちの、translateを用いる方法です。



リスト14 XYTranslate.fx

import javafx.ui.*;
import javafx.ui.canvas.*;

class TransformModel{
  attribute transform:Transform*;

}
var dotransform=TransformModel{};

Frame{
    width:250
    height:200
    
    content:BorderPanel{

    center:Canvas{
      content:Rect{
        x:0
        y:0
        width:50
        height:30
        fill:red
        transform:bind dotransform.transform
     }
    }

    bottom:GridPanel{
      rows:1
      columns:3

      cells:[

        Button{
          text:"X"
          action:operation(){
            var movex:Transform=translate(10,0);
            insert movex into dotransform.transform;
          }
        },
        Button{
          text:"Y"
          action:operation(){
            var movey:Transform=translate(0,10);
            insert movey into dotransform.transform;
          }
        },
        Button{
          text:"クリア"
          action:operation(){
            delete dotransform.transform;
          }
        },

      ]
    }

  }
  visible:true
}


いずれも、本書を一通り学んでいれば、特に解説の必要もないでしょう。
このように、「動かす」というだけなら、どちらが良い悪いということもないと思います。
「移動量」あるいは「移動の過程」が大事なときはTransform、
「現在の位置や、他の図形との相対位置」が大事なときは、座標の値を変化させるのが妥当です。
また、JavaFXにもまだまだ不確定要素がありますから、いずれかを行って何か不具合が出たときに、もう一方の方法を試して見ると問題を回避できることがあるかも知れません。


グループで動かしたり個別に動かしたりする(本書pp.257-259の補足情報)

本書では、図形をグループ化する方法を学びました。しかし、グループ化したからといって、個々の図形に対する操作ができなくなるわけではありません。
本書のプログラム「MyGroup.fx」では、ボタンを押すと二つの図形が一緒に動くように指定してあります。これを改良して、押すボタンによって個々の図形が動いたり、全体が動いたりするようにしてみましょう。
本書に掲載した「MyGroup.fx」の一部をリスト15に示します。



リスト15 MyGroup.fx(本書に掲載したものを抜粋)


 ..............

class TransformModel{
  attribute transform:Transform*;
}

var grouptransform=TransformModel{};

//リスト16を追記

Frame{

  .................

  content:BorderPanel{

    center:Canvas{
      content:Group{
        transform:bind grouptransform.transform
        content:[

          Rect{

           //リスト17を追記

            ...............

          },

          Circle{

           //リスト18を追記

              .................

           }

       ]
    }//Groupの閉じ括弧
  }//Canvasの閉じ括弧

   bottom: //リスト19のように書く
         
     ...................



まず、変数grouptransformの他に、リスト16のように同じTransformModel型の変数recttransformとcircletransformも作ります。特性は指定しません。



リスト16 recttransformとcircletransformを追加

var recttransform=TransformModel{};
var circletransform=TransformModel{};


Rectの{...}の中に、特性transformの指定を追記し、リスト16で追加したrecttransformの特性transformにバインドします。 リスト17 Rect{...} の中に追記

transform:bind
recttransform.transform


Circle{...}の中にも特性transformの指定を追記し、こちらはcircletransformの特性transformにバインドします。 リスト18 Circle{...} の中に追記

transform:bind
circletransform.transform


「MyGroup.fx」ではボタンが「回転」ひとつでしたので、BorderPanel(全体の配置を決めるパネル)のbottomに直接置いていましたが、今度はボタンが増えるので、bottomにはGridPanelを置いて、さらにその上にボタンを置きます。



リスト19 bottomの内容を書き換える

  bottom:GridPanel{
    rows:1
    columns:4
     cells:[

      Button{
        text:"四角"
        action:operation(){
          var rotate:Transform=rotate(45,200,200);
          insert rotate into recttransform.transform;
        }
      },
     Button{
        text:"丸"
        action:operation(){
          var rotate:Transform=rotate(60,80,80);
          insert rotate into circletransform.transform;
        }
      },
      Button{
        text:"御一緒に"
        action:operation(){
          var rotate:Transform=rotate(30,150,150);
          insert rotate into grouptransform.transform;
        }
      },
     Button{
        text:"クリア"
        action:operation(){
          delete recttransform.transform;
          delete circletransform.transform;
          delete grouptransform.transform;
        }
      }

     ]
    } //GridPanelの閉じカッコ
   } //BorderPanelの閉じカッコ


リスト19では、「四角」のボタンを押したときにはrecttransformに、「丸」のボタンを押したときにはcircletransformに、回転の行程が入るようになっています。これらの場合、それぞれの図形が個別に回転することになります。
「御一緒に」のボタンをクリックすると、円も四角もそれぞれの位置から、同じ中心と同じ回転角で回り始めます。
なお、「クリア」ボタンを押したときは、recttransformもcircletransformも要素を全部消して、全く元に戻します。

実行して、丸や四角を個別に回したり、全体を回したりしてみましょう。




図13 丸や四角を個別に回して位置を変えたあと、全体を回しているところ


キャンバス内に画像を置く(本書pp.262-263の補足情報)

Canvas上に画像を置くには、まず「ImageView」という「画像を置く入れ物」を置いてそのcontentとして画像を指定する、というやり方を本書で学びました。
本書では、その利用法として「図形のパターン」に画像を使うというちょっと凝った例を示しましたが、もちろん画像を丸や四角のように移動させたり変形させたりという使い方もできます。
その実際例を示しておきましょう。他の図形にかぶさったりして画像が自由に変形することを確かめてください。なお、画像は本書でも何度か用いているものを使用しています。



リスト20 JpgCanvas.fx

import javafx.ui.*;
import javafx.ui.canvas.*;

class TransformModel{
  attribute transform:Transform*;
}
var dotransform=TransformModel{};


Frame{
  width:400
  height:400
  content:BorderPanel{
  
    center:Canvas{
      content:[

       Circle{
          cx:200
          cy:200
          radius:120
          fill:RadialGradient{
            cx:200
            cy:200
          stops:[
           Stop{ offset:0.0, color:lightgreen},
           Stop{ offset:1.0,color:green}
          ]
         }
       },
       ImageView{
         image:Image{
           url: "file:///C:/Users/javafx/work/img/testimg.jpg"
         }
         transform:bind dotransform.transform
            //ImageViewだけにtransformを指定
       }

     ]
    }

  right:GridPanel{
    columns:1
    rows:5

    cells:[

      Button{
        text:"移動"
        action:operation(){
          var move:Transform=translate(100,50);
          insert move into dotransform.transform;
        }
      },
      Button{
        text:"回転"
        action:operation(){
          var rotate:Transform=rotate(30,0,0);
          insert rotate into dotransform.transform;
        }
      },
      Button{
        text:"拡大"
        action:operation(){
          var scale:Transform=scale(2.0,3.0);
          insert scale into dotransform.transform;
        }
      },
      Button{
        text:"傾斜"
        action:operation(){
          var skew:Transform=skew(5,5);
          insert skew into dotransform.transform;
        }
      },
      Button{
        text:"クリア"
        action:operation(){
          delete dotransform.transform;
        }
      }

    ]
  }

 }
  visible:true
}



図14 図形にかぶさって画像が移動しているところ


3つ以上の図形を複合する(本書pp.263-268の補足情報)

本書では「Area」により「二つの図形」を複合する例を示しました。
それぞれの図形はshape1とshape2で指定しますが、shape3は加えられないのでしょうか?
shape3という特性はありません。でも、shape1あるいはshape2自体にAreaのいずれかを指定すれば、「入れ子」的に複合図形を作成することができます。
リスト21は、本書掲載のサンプル「MyArea.fx」で用いた「Add」の図形から、さらに星形で切り抜きをやったものです。



リスト21 AreaThree.fx

import javafx.ui.*;
import javafx.ui.canvas.*;

var mycircle=Circle{
  cx:50 cy:100
  radius:30
  fill:lightblue
  stroke:blue
};
var myrect=Rect{
  x:50 y:40
  width:100 height:60
  fill:lightyellow
  stroke:yellow
};
var mystar=Star{
  cx:60 cy:80
  rin:10
  rout:20
  points:6
  fill:black
};

Frame{
  width:400
  height:200

  content:GridPanel{
   rows:1
   columns:2

   cells:[

    Canvas{
      content:[myrect, mycircle,mystar]
    },
    Canvas{
       content:Subtract{
        shape1:Add{
          shape1:myrect
          shape2:mycircle
          //最終的に指定されるので、塗りの指定は不要
        }
        shape2:mystar
        fill:red
       stroke:blue
     }
   }

  ]
  
 }
  visible:true
}


リスト21では、shape1に「Add{...}」、shape2にmystarを指定しています。
shape1に指定された「Add{...}」では、Subtractで最終的に塗りが指定されているので、ここでの塗りの指定は不要です(指定してもエラーにはならないが、結局キャンセルされます)。
実行結果は、実は本書にひそかに掲載されています。探してみてください。


「半径情報」か「Scale」か(本書pp.278-279の補足情報)

図形の変形を記述する「Transform」の中の「scale」は、「拡大・縮小」を記述すると書きましたが、円の場合の「radius」など、大きさの情報を直接指定するのとは異なります。
それは、リスト22を実行してみればわかります。本書の「OnPress.fx」と同様の書き方ですが、マウスボタンを押したときに円のscaleをX,Y方向ともに「2.0」にするというものです。」



リスト22 OnPressScale.fx

import javafx.ui.canvas.*;
import javafx.ui.*;

class TransformModel{
  attribute transform:Transform*;
}
var dotransform=TransformModel{};

Frame{
 width:200
 height:200
 
 content:
   Canvas{
     content:
      Circle{
         transform:bind dotransform.transform
         cx:50
         cy:50
         radius:20
         fill:red
         onMousePressed:operation(e:CanvasMouseEvent){
           var large:Transform=scale(2.0,2.0);
           insert large into dotransform.transform;
         }
         onMouseReleased:operation(e:CanvasMouseEvent){
           var small:Transform=scale(0.5,0.5);
           insert small into dotransform.transform;
         }
      }
   }
visible:true
}


リスト22では、マウスボタンを放すとscaleが「元に戻る」ようにしたいのですが、マウスボタンはその前に押さなければ放すという動作が成立しませんから、必ず事前に2倍に拡大されているはずです。そこで、放したときには逆に半分に縮小しています。

実行してみると、確かに大きさは変わりますが、赤丸が飛び出してくるように見えます。




図15 リスト22で、マウスボタンを押さないとき(左)と、押したとき (右)

なぜでしょうか。「scale」を指定すると、円の中心座標もそれぞれ拡大されるからだと考えられます。
これはこれで面白いプログラムですが、本書のプログラムのような「半径情報」を直接変化させるものと、場合により使い分けが必要ですね。