package net.javainthebox.flickrviewer; import javafx.scene.Scene; import javafx.scene.paint.Color; import javafx.stage.Stage; var imagePane = ImagePane { translateX: 120 } var thumbnailPane = ThumbnailPane { update: imagePane.update } var reader = AtomReadWorker { url: "http://api.flickr.com/services/feeds/photos_public.gne?id=57085156@N00&lang=en-us&format=atom" thumbnailPane: thumbnailPane } reader.execute(); Stage { title: "Flickr Searcher" scene: Scene { width: 660 height: 660 fill: Color.BLACK content: [ imagePane, thumbnailPane ] } }
package net.javainthebox.flickrviewer; import javafx.scene.CustomNode; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.effect.DropShadow; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; public class Thumbnail extends CustomNode { // 選択された時にコールされるコールバック関数 public var action: function(thumbnail: Thumbnail): Void; // イメージのタイトル public var title: String; // サムネイルイメージの URL // URLが変更したら、イメージをロードする public var thumbnailUrl: String on replace { if (thumbnailUrl != null) { thumbnail = Image { url: thumbnailUrl } } } // サムネイルイメージ var thumbnail: Image; // イメージのURL public var imageUrl: String; public-read def width: Number = 110.0; public-read def height: Number = 110.0; // 選択状態を表すフラグ public var selected: Boolean = false; // クリックされたら選択状態とし、コールバック関数をコールする override var pressed on replace { if (not selected and pressed) { selected = true; action(this); } } public override function create(): Node { Group { content: [ Rectangle { // 角丸四角にする arcHeight: 10 arcWidth: 10 x: 0 y: 0 width: 109 height: 109 // 選択状態ではグレー、非選択状態では黒 fill: bind if (selected) Color.web("#666666") else Color.web("#222222") stroke: Color.web("#666666") // ドロップシャドウを施すことにより浮きでるような効果を出す effect: DropShadow { offsetX: 1 offsetY: 1 radius: 1 color: Color.LIGHTGRAY } }, ImageView { // イメージは中央に配置する x: bind (width - thumbnail.width)/2 y: bind (height - thumbnail.height)/2 image: bind thumbnail } ] } } }
package net.javainthebox.flickrviewer; import javafx.scene.CustomNode; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; import javafx.scene.shape.Rectangle; // 三角形 サムネイルのページ送りに使用する class Triangle extends CustomNode { var x: Integer; var y: Integer; var width: Integer; var height: Integer; var isUpward: Boolean; var action: function(): Void; override var onMouseClicked = function(event: MouseEvent) { if (not disable) { action(); } } override function create(): Node { Group { content: [ Rectangle { x: x y: y width: width, height: height stroke: Color.web("#666666") fill: bind if (not disable) { if (pressed) { Color.GRAY } else if (hover) { Color.web("#666666") } else { Color.BLACK; } } else { Color.BLACK; } }, if (isUpward) { Polygon { points: [ x + width/2, y + height*.2, x + width/2 + height*.3, y + height*.8, x + width/2 - height*.3, y + height*.8 ] fill: bind if (disable) Color.GRAY else Color.WHITE } } else { Polygon { points: [ x + width/2, y + height*.8, x + width/2 + height*.3, y + height*.2, x + width/2 - height*.3, y + height*.2 ] fill: bind if (disable) Color.GRAY else Color.WHITE } } ] } } } // サムネイルを表示するペイン public class ThumbnailPane extends CustomNode { // サムネイルの選択状態が変更したらコールするコールバック関数 public-init var update: function(title: String, url: String); // サムネイル // サムネイルの個数に応じて、ページ送りのフラグをセットする public var thumbnails: Thumbnail[] on replace { if (calcIndex(page) < sizeof thumbnails) { forwardRemainsFlag = true; } for (thumbnail in thumbnails) { thumbnail.action = thumbnailSelected; } updateVisibleThumbnails(); } // 現在表示しているサムネイル var visibleThumbnails: Thumbnail[]; // 選択されているサムネイル var selectedThumbnail: Thumbnail; // サムネイルのページ数 var page: Integer = 0; // ページ送りができるかどうかを示すフラグ var forwardRemainsFlag = false; var backwardRemainsFlag = false; def numPerVertical = 5; def thumbnailSize = 120; public-read def width: Integer = 120; public-read def height: Integer = 660; def arrowHeight = 25; // サムネイルの選択状態が変更したらコールされる関数 function thumbnailSelected(thumbnail: Thumbnail): Void { if (selectedThumbnail != null) { selectedThumbnail.selected = false; } selectedThumbnail = thumbnail; update(selectedThumbnail.title, selectedThumbnail.imageUrl); } // ページ送りされた場合に表示を更新する関数 function updateVisibleThumbnails() { visibleThumbnails = for (y in [0.. numPerVertical - 1]) { var index = page * numPerVertical + y; var thumbnail = thumbnails[index]; thumbnail.translateX = 5; thumbnail.translateY = y * thumbnailSize + 35; thumbnail; } } function calcIndex(page: Integer): Integer { (page + 1) * numPerVertical; } public override function create(): Node { Group{ content: bind [ Rectangle { x: 0 y:0 width: width height: height fill: null stroke: Color.web("#666666") } Triangle { x: 0 y: 0 width: width height: arrowHeight isUpward: true disable: bind not backwardRemainsFlag action: function(): Void { page--; if (page == 0) { backwardRemainsFlag = false; } if (forwardRemainsFlag == false and calcIndex(page) < sizeof thumbnails) { forwardRemainsFlag = true; } updateVisibleThumbnails(); } } visibleThumbnails, Triangle { x: 0 y: height - arrowHeight width: width height: arrowHeight isUpward: false disable: bind not forwardRemainsFlag action: function(): Void { page++; if (calcIndex(page) >= sizeof thumbnails) { forwardRemainsFlag = false; } if (backwardRemainsFlag == false) { backwardRemainsFlag = true; } updateVisibleThumbnails(); } } ] } } }
package net.javainthebox.flickrviewer; import javafx.scene.CustomNode; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.effect.Reflection; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.Text; // イメージを表示するペイン public class ImagePane extends CustomNode { public-read var width = 540; public-read var height = 660; // 選択されたサムネイルが変更された時にコールされる関数 public function update(title: String, url: String): Void { // イメージ var image = Image { url: url // バックグラウンドでイメージをロードする backgroundLoading: true } imageView = ImageView { translateX: bind (width - image.width) / 2 translateY: bind (height - image.height) / 2 // 下にイメージを反射させるエフェクト effect: Reflection { fraction: 0.4 } image: image } // イメージのタイトル label = Text { translateX: 10 translateY: 30 font: Font { size: 18 } fill: Color.WHITE content: title } } var imageView: ImageView; var label: Text; public override function create(): Node { Group { content: bind [label, imageView] } } }
package net.javainthebox.flickrviewer; import javafx.data.pull.Event; import javafx.data.pull.PullParser; import javafx.data.xml.QName; import javafx.io.http.HttpRequest; import java.io.InputStream; import java.lang.Exception; import java.net.URLEncoder; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.SwingWorker; public class AtomReadWorker extends SwingWorker { public-init var url: String; public-init var thumbnailPane: ThumbnailPane; // サムネイルのURLを取り出すための正規表現 def pattern = java.util.regex.Pattern.compile("http://farm.*_m.jpg"); // 非同期に行われる処理 public override function doInBackground(): Object { // HTTP通信を行う var request = HttpRequest { location: url onInput: parseAtom; onException: function(ex: Exception) { println("exception: {ex.getMessage()}"); } }; request.enqueue(); } // Atomのパース function parseAtom(stream: InputStream) { var thumbnail: Thumbnail; var inEntry: Boolean; var parser = PullParser { documentType: PullParser.XML; input: stream onEvent: function(event: Event) { if (event.type == PullParser.START_ELEMENT) { if (event.qname.name == "entry") { // <entry>の開始 // 新たなThumbnailオブジェクトを生成する inEntry = true; thumbnail = Thumbnail {}; } } if (event.type == PullParser.END_ELEMENT) { if (event.qname.name == "entry") { // </entry>の場合 // 作成したThumbnailオブジェクトをイベントディスパッチスレッドで // 取得できるように、publishする inEntry = false; this.publish(thumbnail as Object); } else if (inEntry and event.qname.name == "title") { // </title>の場合 // Thumbnailオブジェクトのtitleを設定 thumbnail.title = event.text; } else if (inEntry and event.qname.name == "content") { // </content>の場合 // 正規表現を用いて、サムネイルのURLを取り出し、 // thumbnailUrlに設定 var matcher = pattern.matcher(event.text); if (matcher.find()) { var url = matcher.group(); thumbnail.thumbnailUrl = url.replaceAll("_m", "_t"); // 大きいイメージのURLを作成 thumbnail.imageUrl = url.replaceAll("_m", ""); } } } } } // パースの開始 parser.parse(); stream.close(); } // イベントディスパッチスレッドで処理される関数 // publishされたオブジェクトが引数となる override function process(thumbnails: List) { for (thumbnail in thumbnails) { insert thumbnail as Thumbnail into thumbnailPane.thumbnails; } } }