Dashboardウィジットの作り方大まかな構造
1、以下ようにのバンドルを用意する(+はディレクトリ、・はファイル)+ウィジェット名 ・ウィジェット名.html ←HTML:初期設定を記述する ・ウィジェット名.js ←JavaScript:動作を定義する ・ウィジェット名.css ←スタイルシート:テキストや画像の配置やフォントなどを定義する ・Icon.png ←アイコンの画像 ・Default.png ←背景画像 ・Info.plist ←インフォメーション:ウィジェット名や作者情報などを記述する +images 画像1.png ←必要なとき 画像2.png
2、「+ウィジェット名」に 「.wdgt」 をつけて「ウィジェット名.wdgt」にするサンプル(05017.widget)の説明
05017.widget(zip)(864K)のダウンロード 05017.widget は簡単なアニメーションを表示するプログラムです ウィジェットの右下に表示されるボタンを押す事でアニメーションの切り替えが可能です だれでも簡単に拡張(アニメーションの種類をふやしたり)できるように作ったつもりです ここでは、この05017.widget を例にDashboardウィジットの作り方を説明します まずはディレクトリの構造です+05017.wdgt ・05017.html ←HTML:初期設定を記述する ・05017.js ←JavaScript:動作を定義する ・05017.css ←スタイルシート:テキストや画像の配置やフォントなどを定義する ・Icon.png ←アイコンの画像 ・Default.png ←背景画像 ・Info.plist ←インフォメーション:ウィジェット名や作者情報などを記述する +images ・Default_reverse.png ←裏面の画像 ・done.png ←doneボタンの画像 ・done_pressed.png ←doneボタンが押された時の画像 +Chica ←アニメーション「Chica」用の画像 +Chiyo ←アニメーション「Chiyo」用の画像 +Haduki ←アニメーション「Haduki」用の画像 +Hituji ←アニメーション「Hituji」用の画像 +Mika ←アニメーション「Mika」用の画像 +Minaduki ←アニメーション「Minaduki」用の画像 +Satsuki ←アニメーション「Satsuki」用の画像
注意 1、使用する画像は全てpng形式に変換しましょう 2、「Icon.png」は名前を変えては行けません 3、ダウンロードしたファイルのコメントはすべて英語(しかも文法めちゃくちゃ)です05017.html 〜初期設定を記述〜
ではさっそく、HTML文書から作ります<HTML> <HEAD> <!-- スタイルシートを読み込む--> <STYLE type="text/css"> @import "05017.css"; </STYLE> <!-- JavaScriptを読み込む --> <SCRIPT type='text/javascript' src='05017.js' charset='utf-8'/> </HEAD> <!-- 設定は05017.css内の「body」に従う -- ウィジェトが起動したら05017.js内の「setup()」を呼び出す --> <BODY onload='setup();'> <!-- ウィジェットの表面の定義 表面の名前を「front」にする -- 設定は05017.css内の「#front」に従う -- 表面でマウスが動いたら05017.js内の「mousemove()」を呼び出す -- 表面からマウスが出たら05017.js内の「mouseexit()」を呼び出す --> <DIV id="front" onmousemove='mousemove(event);' onmouseout='mouseexit(event);'> <!-- ウィジェット表面の背景画像を指定 設定は05017.css内の「.backgroundImage」に従う --> <IMG span="backgroundImage" src="Default.png"> <!-- テキストの初期設定 テキストの名前を「worldText」にする -- 設定は05017.css内の「#worldText」に従う -- 初期のテキストを「Mika」にする --> <DIV id="worldText">Mika</DIV> <!-- 画像の初期設定 画像の名前を「pict」にする -- 設定は05017.css内の「.image」に従う --初期の画像を「images」内の「Mika」内の「Mika_1.png」にする --> <IMG id="pict" src="images/Mika/Mika_1.png" class="image" /> <!-- 右下に表示される infoボタンの設定, -- 設定は05017.css内の「#fliprollie 」に従う --> <DIV class='flip' id='fliprollie'></DIV> <!-- 右下に表示される infoボタンの設定, -- 設定は05017.css内の「#flip」と「.flip 」に従う -- ボタンの上にマウスが動いたら05017.js内の「showPrefs()」 -- 表面からマウスが出たら05017.js内の「exitflip()」を呼び出す --> <DIV class='flip' id='flip' onclick='showPrefs(event);' onmouseover='enterflip(event);' onmouseout='exitflip(event)';></DIV> </DIV> <!-- ウィジェットの裏面の定義 裏面の名前を「back」にする -- 設定は05017.css内の「#back」に従う --> <DIV id="back"> <!-- 裏面の背景画像を設定 --> <IMG span="backgroundImage" src="Images/Default_reverse.png"> <!-- 選択リストの設定 選択リストの名前を「worldPopup」にする -- リストが選択されたら05017.js内の「changeWorld()」を呼び出す --> <SELECT id='worldPopup' onchange='changeWorld(this);'> <OPTION value=1>Mika</OPTION> <OPTION value=2>Chica</OPTION> <OPTION value=3>Chiyo</OPTION> <OPTION value=4>Hituji</OPTION> <OPTION value=5>Haduki</OPTION> <OPTION value=6>Minaduki</OPTION> <OPTION value=7>Satsuki</OPTION> <OPTION value=8>J05017</OPTION> </SELECT> <!-- Doneボタンの設定 --設定は05017.css内の「.doneButton 」に従う -- クリックされたら05017.js内の「hidePrefs()」を呼び出す --> <IMG class="doneButton" src="Images/done.png" onclick='hidePrefs()' /> </DIV> </BODY> </HTML>
05017.css 〜テキストや画像の配置やフォントなどを定義〜
HTML文書ではテキストや画像、ボタンなどの初期設定をおこないました 次はスタイルシート「05017.css」を使ってテキストや画像の配置を決めます スタイルシートの書式 〜スタイルの設定〜スタイル名{} HTMLタグのスタイルを設定します 例、body {} #スタイル名{} id名ごとのスタイルを設定します 例、#worldText {} .スタイル名{} class='スタイル名' で呼び出されるスタイルです 例、.backgroundImage
〜フォントの設定〜font: ピクセル数px "フォント名"; テキストの大きさとフォントを指定します 例、font: 20px "Lucida Grande"; font-weight: フォントの形式; テキストの形式を指定します 例、font-weight: bold;(太字) text-align: テキストの位置; テキストの位置を指定します 例、text-align: left;(左寄せ) color: テキストの色; テキストの色を指定します 例、color: white;(白)
〜位置とサイズの設定〜margin: ピクセル数; 余白の設定 position: absolute; 位置を自分で設定するという意味 top: ピクセル数px; left: ピクセル数px; height: ピクセル数; width: ピクセル数;
スタイルシートまで設定したら、HTML文書をブラウザで表示したときに計画通りに表示されているか確かめましょう05017.js 〜動作の定義〜
全部説明するのは面倒なので、それぞれのメソッドの大まかな説明に留めますsetup(): ウィジェットが開かれたときに呼び出される ウィジェットの初期設定を行う changeWorld(): 裏面のリストが選択されたときに呼び出される 選択肢によって表示テキスト、アニメーションの画像名、画像の枚数を変える 最後に選択したテキストを記憶させる changeImage(): setup()またはchangeWorld()から呼び出される アニメーションの設定 showPrefs(): 表面の右下のボタンがクリックされたときに呼び出される 裏面を表示 hidePrefs(): 裏面のdoneボタンがクリックされたときに呼び出される 表面を表示 makeKey(): changeWorld()から呼び出される Keyを作る removed(): ウィジェットがDashboardから削除されたときに呼び出される focused(): フォーカスされたときに呼び出される blurred(): フォーカスされなくなったときに呼び出される mousemove (): 表面でマウスが動いたときに呼び出される animate()を使って右下のフェードイン(表示)を表示する mouseexit (): 表面からマウスが出たときに呼び出される animate()を使って右下のボタンをフェードアウト(非表示)する animate(): mousemove ()やmouseexit ()から呼び出される フェードアウトやフェードイン時のアニメーションを提供する limit_3 (): animate()から呼び出される enterflip(): 右下のボタンの上にマウスが乗ったときに呼び出される exitflip(): 右下のボタンの上からマウスが出たときに呼び出される
/***********************************/ // SAVING AND RETRIEVING PREFERENCES /***********************************/ // setup() is run when the body loads. It checks to see if there is a preference for this widget // and if so, applies the preference to the widget. function setup() { if(window.widget) // always check to make sure that you are running in Dashboard { // The preferences are retrieved: var worldString = widget.preferenceForKey(makeKey("worldString")); if (worldString && worldString.length > 0) // if the retrieved preferences are not empty, { // they are restored. document.getElementById("worldText").innerText = worldString; var imgname = worldString; document.getElementById("pict").src = "images/" + imgname + "/"+ imgname + "_1.png"; if( worldString == "Mika") // if Goodbye is the retreived value... { document.getElementById("worldPopup").selectedIndex = 0; // set the popup to reflect that. } } setInterval(changeImage,150); } } // changeWorld() is called whenever a menu item is chosen in the widget's preferences. It queries the // menu to find out which option was chosen, applies the change to the widget, and saves the preference. var imgname = "Mika"; var imgpage = 6; function changeWorld(elem) { var world = document.getElementById("worldText"); switch( parseInt(elem.options[elem.selectedIndex].value) ) // find out which option was chosen { case 1: // if option #1 world.innerText="Mika"; // change the front text if(window.widget) { widget.setPreferenceForKey("Mika",makeKey("worldString")); } imgname = "Mika"; imgpage = 6; changeImage() break; case 2: // if option #2 world.innerText="Chica"; // change the front text if(window.widget) { widget.setPreferenceForKey("Chica",makeKey("worldString")); } imgname = "Chica"; imgpage = 6; changeImage() break; case 3: // if option #3 world.innerText="Chiyo"; // change the front text if(window.widget) { widget.setPreferenceForKey("Chiyo",makeKey("worldString")); } imgname = "Chiyo"; imgpage = 2; changeImage() break; case 4: // if option #4 world.innerText="Hituji"; // change the front text if(window.widget) { widget.setPreferenceForKey("Hituji",makeKey("worldString")); } imgname = "Hituji"; imgpage = 13; changeImage() break; case 5: // if option #5 world.innerText="Haduki"; // change the front text if(window.widget) { widget.setPreferenceForKey("Haduki",makeKey("worldString")); } imgname = "Haduki"; imgpage = 12; changeImage() break; case 6: // if option #6 world.innerText="Minaduki"; // change the front text if(window.widget) { widget.setPreferenceForKey("Minaduki",makeKey("worldString")); } imgname = "Minaduki"; imgpage = 17; changeImage() break; case 7: // if option #7 world.innerText="Satsuki"; // change the front text if(window.widget) { widget.setPreferenceForKey("Satsuki",makeKey("worldString")); } imgname = "Satsuki"; imgpage = 14; changeImage() break; case 8: // if option #8 world.innerText="J05017"; // change the front text if(window.widget) { widget.setPreferenceForKey("J05017",makeKey("worldString")); } imgname = "J05017"; imgpage = 6; changeImage() break; } } //changeImage() changing pict's img var imgnum = 0; function changeImage() { var img = imgname; var imgend = imgpage + 1; imgnum++; if(imgnum == imgend) imgnum = 1; document.getElementById("pict").src = "images/" + img + "/"+ img + "_" + imgnum + ".png"; } /*********************************/ // HIDING AND SHOWING PREFERENCES /*********************************/ // showPrefs() clicked preferences flipper show back function showPrefs() { var front = document.getElementById("front"); var back = document.getElementById("back"); if (window.widget) widget.prepareForTransition("ToBack"); // freezes the widget so that you can change it without the user noticing front.style.display="none"; // hide the front back.style.display="block"; // show the back if (window.widget) setTimeout ('widget.performTransition();', 0); // and flip the widget over document.getElementById('fliprollie').style.display = 'none'; // clean up the front side - hide the circle behind the info button } // hidePrefs() clicked done button show front function hidePrefs() { var front = document.getElementById("front"); var back = document.getElementById("back"); if (window.widget) widget.prepareForTransition("ToFront"); // freezes the widget and prepares it for the flip back to the front back.style.display="none"; // hide the back front.style.display="block"; // show the front if (window.widget) setTimeout ('widget.performTransition();', 0); // and flip the widget back to the front } // makeKey() makes the widget multi-instance aware function makeKey(key) { return (widget.identifier + "-" + key); } /***************/ // WIDGET EVENTS /***************/ // removed() when widget is removed from the Dashboard function removed() { widget.setPreferenceForKey(null,makeKey("worldString")); } // focused() when the widget gets key focus function focused() { document.getElementById('worldText').style.color = 'white'; } // blurred() when the widget looses key focus function blurred() { document.getElementById('worldText').style.color = 'gray'; } // Here we register for some widget events if(window.widget) { widget.onremove = removed; window.onfocus = focused; window.onblur = blurred; } // PREFERENCE BUTTON ANIMATION (- the pref flipper fade in/out) var flipShown = false; // a flag used to signify if the flipper is currently shown or not. // A structure that holds information that is needed for the animation to run. var animation = {duration:0, starttime:0, to:1.0, now:0.0, from:0.0, firstElement:null, timer:null}; // mousemove() when mousemove on the front function mousemove (event) { if (!flipShown) // if the preferences flipper is not already showing... { if (animation.timer != null) // reset the animation timer value, in case a value was left behind { clearInterval (animation.timer); animation.timer = null; } var starttime = (new Date).getTime() - 13; // set it back one frame animation.duration = 500; // animation time, in ms animation.starttime = starttime; // specify the start time animation.firstElement = document.getElementById ('flip'); // specify the element to fade animation.timer = setInterval ("animate();", 13); // set the animation function animation.from = animation.now; // beginning opacity (not ness. 0) animation.to = 1.0; // final opacity animate(); // begin animation flipShown = true; // mark the flipper as animated } } // mousemove() when mouseexit from the front function mouseexit (event) { if (flipShown) { // fade in the flip widget if (animation.timer != null) { clearInterval (animation.timer); animation.timer = null; } var starttime = (new Date).getTime() - 13; animation.duration = 500; animation.starttime = starttime; animation.firstElement = document.getElementById ('flip'); animation.timer = setInterval ("animate();", 13); animation.from = animation.now; animation.to = 0.0; animate(); flipShown = false; } } // animate() preferences flipper's fade animation function animate() { var T; var ease; var time = (new Date).getTime(); T = limit_3(time-animation.starttime, 0, animation.duration); if (T >= animation.duration) { clearInterval (animation.timer); animation.timer = null; animation.now = animation.to; } else { ease = 0.5 - (0.5 * Math.cos(Math.PI * T / animation.duration)); animation.now = computeNextFloat (animation.from, animation.to, ease); } animation.firstElement.style.opacity = animation.now; } // limit_3() for animate() function limit_3 (a, b, c) { return a < b ? b : (a > c ? c : a); } // computeNextFloat() for animate() function computeNextFloat (from, to, ease) { return from + (to - from) * ease; } // enterflip() when the info button itself receives onmouseover function enterflip(event) { document.getElementById('fliprollie').style.display = 'block'; } // exitflip() when the info button itself receives onmouseout function exitflip(event) { document.getElementById('fliprollie').style.display = 'none'; }
Info.plist 〜ウィジェットや作者の情報を記述する〜
最後にInfo.plistにウィジェットや作者の情報を記述します Info.plistはXML形式で記述します<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleIdentifier</key> <string>j05017.u-ryukyu.05017</string> ←ウィジェットを一意に識別する文字列、ドメイン名を逆にした形式で表します <key>CFBundleName</key> <string>05017</string> ←ウィジェット名 <key>CFBundleDisplayName</key> <string>05017</string> ←ウィジェットバーと「Finder」に表示される名称 <key>CFBundleShortVersionString</key> <string>1.0</string> ←ウィジェットのバージョン <key>CFBundleVersion</key> <string>1.0</string> ←ウィジェットのバージョン <key>CloseBoxInsetX</key> <integer>0</integer> ←閉じるボタンのx座標 <key>CloseBoxInsetY</key> <integer>0</integer> ←閉じるボタンのy座標 <key>MainHTML</key> <string>05017.html</string> ←ウィジェットを実装するメインの HTMLファイル名 </dict> </plist>
ここまで終わればDashboardで実行する事が出来ます あとは実行結果を見ながら様々な修正を加えてください練習問題
練習代わりに05017.wdgtで表示できるアニメーションの数を増やしてみてください JavaScriptとHTMLの記述をよ〜くみたらわかるようにしてあるつもりです 「そんなの楽勝ッス!!」という人は実際にウィジェットを自作して動かしてみましょう 分からない人は下記の「アニメーションの追加法」をご覧下さいアニメーションの追加法
1、アニメーションに使う画像を用意します このとき、画像はpng形式でなければいけません 2、画像に連番を振り、画像名と同じ名前のディレクトリにいれます このとき、画像名は英語にして下さい また、画像名と連番の間にはかならず「_」を入れ、連番は1、2、3、4〜のように振って下さい つまり「画像名_連番.png」という感じにして下さい このとき連番を01、02〜 や 001,002〜のようにすると表示できません 例「Mika」というアニメーションを追加するとき+Mika ・Mika_1.png ・Mika_2.png ・Mika_3.png ・Mika_4.png
3、ディレクトリを 05017.wdgt 内の images ディレクトリ内に入れます 4、次にHTML文書内の「<SElECT>」タグ内に次の記述を追加します<OPTION value=番号>画像名</OPTION>
つまりリストの5番目に「Mika」を追加する場合は 「<OPTION value=5>Mika</OPTION>」を追加します 5、あとはJavaScript内の「changeWorld()」メソッドに次のような記述を追加しますcase 番号: // if option #番号 world.innerText="画像名"; // change the front text if(window.widget) { widget.setPreferenceForKey("画像名",makeKey("worldString")); } imgname = "画像名"; imgpage = 画像の枚数; changeImage() break;
例 全部で6枚の「Mika」をリストの5番目に追加する場合case 5: // if option #5 world.innerText="Mika"; // change the front text if(window.widget) { widget.setPreferenceForKey("Mika",makeKey("worldString")); } imgname = "Mika"; imgpage = 6; changeImage() break;