【Haskell】diagramsでgtkのDrawingAreaにお絵かき
HaskellのdiagramsパッケージでgtkのDrawingAreaに描画してみたので、 minimalなサンプルコードを以下に記しておきます。
手順
- 前回の記事に従って、gtkの開発環境を構築する
- diagrams-gtkのインストール
cabal update
cabal install diagrams-gtk
ソースコード
{-# LANGUAGE NoMonomorphismRestriction #-}
module Main where
import Graphics.UI.Gtk
import Graphics.Rendering.Cairo (liftIO)
import Diagrams.Prelude
import Diagrams.Backend.Gtk
import Diagrams.Backend.Cairo (Cairo)
main :: IO ()
main = do
initGUI
window <- windowNew
canvas <- drawingAreaNew
set window [windowDefaultWidth := 1000,
windowDefaultHeight := 1000,
containerBorderWidth := 10,
containerChild := canvas]
widgetShowAll window
-- Event
window `on` unrealize $ mainQuit
canvas `on` exposeEvent $ tryEvent $ do
liftIO $ defaultRender canvas signalImg
mainGUI
signalImg :: Diagram Cairo R2
signalImg = pad 1.1 $ hinge ||| housing
where housing = lights <> roundedRect 2 0.8 0.2 # fc gray
lights = centerXY $ hcat' (with & sep .~ 0.1) $ zipWith fc [red, yellow, green] (replicate 3 $ circle 0.2)
hinge = vrule 0.8 ||| hrule 0.4
実行結果
当たり判定をつける (7/19加筆)
diagramは、以下のように簡単に当たり判定をつけることができます。
{-# LANGUAGE NoMonomorphismRestriction #-}
module Main where
import Graphics.UI.Gtk
import Graphics.Rendering.Cairo (liftIO)
import Diagrams.Prelude hiding (Renderable)
import Diagrams.Backend.Gtk
import Diagrams.Backend.Cairo (Cairo)
import Control.Monad (when)
main :: IO ()
main = do
initGUI
window <- windowNew
canvas <- drawingAreaNew
widgetSetSizeRequest canvas 500 500
set window [windowDefaultWidth := 500,
windowDefaultHeight := 500,
containerBorderWidth := 10,
containerChild := canvas]
widgetShowAll window
-- Event
window `on` unrealize $ mainQuit
canvas `on` exposeEvent $ tryEvent $ do
drawin <- eventWindow
liftIO $ renderToGtk drawin c1
canvas `on` buttonPressEvent $ tryEvent $ do
button <- eventButton
(x,y) <- eventCoordinates
case sample c1 (p2 (x,y)) of
Any True -> do
liftIO $ putStrLn "inside"
liftIO $ putStrLn $ show (x, y)
return ()
Any False -> do
liftIO $ putStrLn "outside"
liftIO $ putStrLn $ show (x, y)
return ()
mainGUI
c1 :: Diagram Cairo R2
c1 = toGtkCoords $ (circle 100 ||| circle 50) # fc gray
実行結果
円の内側をクリックするとinside、 外側をクリックするとoutsideと表示されます。
gtk2hsを使ってみる
Haskellで、gtkを(久しぶりに)使ってみました。 とりあえず、昔書いたコードが動くところまで行ったので、手順と注意点を記録に残しておきます。
gtkとgtk3というパッケージがあるのですが、gtk3は上手く行かなかったので、そこらへんの記録も。
開発環境
手順
gtkパッケージの場合
- Haskell Platformをインストール
- ここからGTK+ 2.xの
all-in-one bundle
をダウンロード - 中身をパスにスペースを含まないディレクトリに解凍
- 例:
C:\gtk\
- 例:
- その中の
bin
にパスを通す- 例:
C:\gtk\bin
- 例:
- 以下のコマンドを実行
cabal update
cabal install gtk2hs-buildtools
cabal install gtk
module Main where
import Graphics.UI.Gtk
import Control.Monad.Trans ( liftIO )
main = do
initGUI
xml <- builderNew
builderAddFromFile xml "hellogtk2hs.glade"
window <- builderGetObject xml castToWindow "window1"
window `on` unrealize $ mainQuit
closeButton <- builderGetObject xml castToButton "button2"
closeButton `on` buttonPressEvent $ tryEvent $ do
liftIO $ widgetDestroy window
label <- builderGetObject xml castToLabel "label1"
entry <- builderGetObject xml castToEntry "entry1"
applyButton <- builderGetObject xml castToButton "button1"
applyButton `on` buttonPressEvent $ tryEvent $ do
name <- liftIO $ get entry entryText
liftIO $ set label [ labelText := "Hello " ++ name ]
widgetShowAll window
mainGUI
gtk3パッケージの場合
- Haskell Platformをインストール
- ここからGTK+ 3.xの
all-in-one bundle
をダウンロード - 中身をパスにスペースを含まないディレクトリに解凍
- 例:
C:\gtk3\
- 例:
- その中の
bin
にパスを通す- 例:
C:\gtk3\bin
- 例:
- 以下のコマンドを実行
cabal update
cabal install gtk2hs-buildtools
cabal install gtk3
- glade3.14をここからインストール
- 3.8はgtk2用なので注意
- 以下同様
注意点
<イベント>と<イベントモナド>
onClickedとかを使う以下の書き方はもう古いようです:
module Main where
import Graphics.UI.Gtk
import Graphics.UI.Gtk.Glade
main = do
initGUI
Just xml <- xmlNew "hellogtk2hs.glade"
window <- xmlGetWidget xml castToWindow "window1"
onDestroy window mainQuit
closeButton <- xmlGetWidget xml castToButton "button2"
onClicked closeButton $ do
widgetDestroy window
label <- xmlGetWidget xml castToLabel "label1"
entry <- xmlGetWidget xml castToEntry "entry1"
applyButton <- xmlGetWidget xml castToButton "button1"
onClicked applyButton $ do
name <- get entry entryText
set label [ labelText := "Hello " ++ name ]
widgetShowAll window
mainGUI
今どきの書き方は最初のコードを参考にしてください。 新しい書き方の方が、多少書くのが面倒くさくなってくれている分、より厳しい型によるチェックをしてくれます。
基本的な書き方は、
<オブジェクト> `on` <イベント> $ tryEvent $ do <イベントモナド>
と言った感じです。
主要な<イベント>はここのEventsの節にまとめられています。
<イベントモナド>はパラメタライズされており、各種イベントに対応するイベントモナドを設けることで、 チェックを厳格にしているようです。
例えば、<イベント>buttonPressEvent :: WidgetClass self => Signal self (EventM EButton Bool)
に対応する<イベントモナド>はEventM EButton
です。
このモナドの専用の関数としてeventButton :: EventM EButton MouseButton
などがあり、型によって、このコンテキストで使える関数を限定しています。
hoogleでEventM EButton a +gtk
などと検索すると、そのモナドで使える関数の一覧が出てくるので、 その点でも非常に便利です。
ちなみに、<イベントモナド> EventM t
はMonadIOのインスタンスなので、liftIO
を使えば、任意のIOアクションが行えまます(行わざる負えない場合がほとんどです)。
gladeを別にインストールする必要がない件
現在はgtkにgladeがデフォルトで入っているので、gladeパッケージを入れる必要がありません。
gladeパッケージを使うときと使わない時の変更点はだいたいこんな感じです:
gladeパッケージを使うとき(以前の書き方)
import Graphics.UI.Gtk
import Graphics.UI.Gtk.Glade
main = do
initGUI
Just xml <- xmlNew "hellogtk2hs.glade"
window <- xmlGetWidget xml castToWindow "window1"
gladeパッケージ使わないとき(今どきの書き方)
import Graphics.UI.Gtk
main = do
initGUI
xml <- builderNew
builderAddFromFile xml "hellogtk2hs.glade"
window <- builderGetObject xml castToWindow "window1"
gtk3
gtk3は試してみましたが、 どうやらexpose-event(gtk2hsではexposeEvent)がdeplicatedのようで、代用としてdraw eventというのがあるようです。
gtk3パッケージでexposeEvent :: WidgetClass self => Signal self (EventM EExpose Bool)
を使うと実行時エラーがでるので、注意です。
今のところ、私は、
-
gtk2hsにおいてdraw eventに対応する関数を見つけることができない
-
hoogleのAPI searchにgtk3パッケージが対応していない
などの理由でgtkパッケージの方(つまりgtk2へのバインディング)を使っています。
glade
gladeも、
- gtk2 -> glade3.8
- gtk3 -> glade 3.14
と使い分けなければ、上手く行きませんでした。
gtk2ではVBoxとかHBoxとWidgetが分かれていましたが、gtk3ではBoxの属性として、VとHがあるようで(?)、gtk3でglade3.8で作ったVBoxとかHBoxを読み込むとエラーがでました。
おわりに
今後、diagramsパッケージを使用してgtkアプリケーションのDrawingArea上にお絵かきをすることに挑戦する予定なので、 次回の記事はそのレポートの予定です。
xyzzy haskell-mode 改良
ghci-shell-modeに少し改良を加えました。
また、hs-modeからhaskell-modeへ名前を変更しました。
変更点
- ghciへのコマンド出力時のエンコーディングをutf8に変更
- 日本語のディレクトリでもghci-load-fileできるようになりました
- ghci-shell-first-errorの追加
- ghci-load-fileした後にF10ですぐにエラー行に飛べるようになりました
ghci-shell-mode
前回していなかったので少し説明を。
ghci-mode(マイナーモード)で、.hsファイルを編集中にC-c C-iで、 xyzzy上でghciを起動することができます。 (ghci起動と同時に現在編集中のファイルをロードします。)
ghci-shell-modeはghciとやりとりするためにghciと関連付けられたバッファで用いるモードです。
前回書き忘れていましたが、ghci-shell-modeのキーバインドは以下のとおりです。
だいたいcmdとかでghciを起動した時と同じように使えると思います。
キー | 関数名 | 説明 |
---|---|---|
RET | ghci-shell-send-input | ghciにコマンドを送信 |
C-c C-c | ghci-shell-send-interrupt | ghciにinterruptシグナルを送る |
Up | ghci-shell-up | ヒストリー前 |
Down | ghci-shell-down | ヒストリー後 |
Home | ghci-shell-home | カーソルをプロンプトの手前に移動 |
S-Home | ghci-shell-selection-beginning-of-line | カーソルをプロンプトの手前に移動して選択 |
TAB | ghci-dabbrev | dabbrev |
F10 | ghci-shell-first-error | 最初のエラー行へジャンプ |
TAB補完がdabbrevを使っておりショボイため、ゆくゆくは改良したいと考えています。 (ちゃんとgithubとか使って公開したほうがいいかなぁ)
xyzzy haskell-mode
windowsのhaskellユーザーが少ないこともあって、 今まで、haskellのプログラミング環境の構築には苦労して 来ました。
一時期、haskellerの中ではメインストリーム1な linux + vimに乗り換えようとも思いましたが、敷居が高すぎて、 断念したヘタレです。
結局、現時点ではwindows + xyzzyに落ち着いています:
OS | Windows 7 |
エディタ | xyzzy 0.2.2 系列 + haskell mode |
コンパイラ | ghc |
lispの文法すら把握できていないヘタレですが、 他人のコードのコピペで適当なhaskell modeを作ってみたので、 公開します:
インストール方法 2
-
hs-mode.l
、ghci.l
、hasktags.l
を\sitelisp
にいれて、キーワードファイルHaskell
を\etc
にいれます。 -
.xyzzy
(もしくはsite-init.l
)に以下を追加します。
(require "hasktags")
(require "hs-mode")
(require "ghci")
(push '("\\.hs$" . (lambda ()
(hs-mode)
(ghci-mode)
)) *auto-mode-alist*)
(push '("\\.lhs$" . (lambda ()
(hs-mode)
(ghci-mode)
)) *auto-mode-alist*)
使い方
hs-mode.l
キー | 関数名 | 説明 |
---|---|---|
TAB | hs-tab-indent-toggle | 適当にインデントの調整(トグル) |
RET | hs-newline-and-indent | 改行して手前の行にインデントをあわせる |
ghci.l
キー | 関数名 | 説明 |
---|---|---|
C-c C-i | ghci-load-file | カレントバッファをghciバッファにロード |
hasktags.l
関数名 | 説明 |
---|---|
hasktags-make-tags-file | tagsファイルを生成 |
hasktags-jump-tag | タグジャンプ |
hasktags-back-tag-jump | バックタグジャンプ |
.xyzzy
に適当にキーバインドを設定しておくとよいです3:
(global-set-key #\F4 'hasktags-jump-tag)
(global-set-key #\S-F4 'hasktags-back-tag-jump)
(global-set-key #\M-/ 'hasktags-make-tags-file)
色設定(おまけ)
おまけに画面例で使った私のxyzzy色設定を晒しておきます。 (個人的にはなかなかイケてると思っている。)
[Colors]
textColor=#c6c6c6
backColor=#211816
ctlColor=#8080
selectionTextColor=#f000000
selectionBackColor=#e800000
kwdColor1=#6dc79c
kwdColor2=#cd987e
kwdColor3=#219fad
stringColor=#5263d6
commentColor=#808080
tagColor=#face87
cursorColor=#800080
caretColor=#ff00
imeCaretColor=#ffff
linenum=#c6c6c6
reverse=#fcf8
unselectedModeLineFg=#13000000
unselectedModeLineBg=#10e8e8e8
modeLineFg=#d0d0d0
modeLineBg=#13000000
おわりに
現時点では、ghci.lでxyzzyからghciを操作するとき、ghciのTAB補完機能が使えません。
最新のghciでは:completeというコマンドが実装され、(emacsなどの)テキストエディタから TAB補完機能が使えるようになったらしいです。
そのうちxyzzyでもこの:completeコマンドを利用して、TAB補完を実装したいと思っています。
Dell XPS 8700のHDDをSSDに換装
先日パソコンを買い換えました。
昔からDellのパソコンが好きなのですが、SSDを搭載したモデルがないので、最近は Dell PCの購入をためらっていましたが、これまで、使用していたDospara Prime Galleria JFのファンの騒音に耐え切れず、
という運びとなりました。 (本当はなるべく面倒くさいことはしたくなかったんですが)
構成
before
製品名 | Dospara Prime Galleria JF |
---|---|
OS | Windows 7 |
CPU | Core-i5 2400 3.10GHz |
ドライブ | Intel SSD 510 |
GPU | NVIDIA GeForce GT440 |
after
製品名 | Dell XPS 8700 |
---|---|
OS | Windows 7 |
CPU | Core i7-4770 3.40GHz |
プライマリ ドライブ | Intel SSD 510 |
セカンダリ ドライブ | 1TB HDD |
GPU | NVIDIA GeForce GTX645 |
左がDell XPS 8700、右がDospara Prime Galleria JFです。
作業概要
実際は換装というよりは、
- Dospara PCからSSDを取り外す(データは入ったまま)
- Dell PCにSSDを装着
- リカバリーCDからDell PCを起動
- SSDにOSをインストール(HDDをセカンダリ、SSDをプライマリに設定)
- HDDをフォーマット
という作業を行っているので、HDDのほうもセカンダリドライブとして使えるような構成になっています。
準備するもの(パソコン以外で)
-
ドライバー(ネジ回す方の)
-
リソースCD(Dell PC買ったらついてくるドライバとかが入ってるやつ)
-
SSDのブラケット+取り付けネジ(HDDのベイにSSDを取り付けるのに必要なものです。別途手に入れる必要があります。1)
作業詳細
とりあえず、はじめに電源を抜き、両方のパソコンのカバーを取り外しました。2
Dospara PC上のSSDのデータケーブルと電源ケーブルをマザーボードから取り外しました。
Dospara PCからSSDを取り外しました。
SSDをブラケットに固定(ネジ4箇所)し、Dell PCのシャドーベイに装着(ネジ4箇所)しました。3 (肝心なところの写真を撮っていませんでしたorz)
次に、PCの電源を入れ、以下を行って、無事、換装完了しました。
-
CDドライブにOSのリカバリーCDを入れる
-
Windwosが立ち上がる前に、F12キーを連打->画面に従って、リカバリーCDから起動
-
マイコンピュータでHDDの方を認識しているかチェック
-
HDDをフォーマット(ここらへん参照)
-
必要に応じてリソースCDからドライバをインストール
ちなみに、Prime Galleriaくんは母が使うということで、実家に送り出しました。
Haskellの小技集1
忘れがちだけど、覚えていると役に立つHaskell小技集です。
リストから数を引く
リストの要素に適当な数を足すのは(+ 1)
のように部分適用を使えばよいですが、 (- 1)
は部分適用と判定されないので、以下のように書きます。
ls = map (subtract 1) [1,2,3]
実行結果
ls ==> [0,1,2]
ついsubtract
関数を忘れてしまいますよね。スペルも。。
特定のコンストラクタを持つ値だけ取り出す
data Temperature = C Int | F Int
ts = [C 1, F 13, C 12, F 43, F 12, C 11]
celsius = [a | C a <- ts]
実行結果
celsius ==> [1,12,11]
catMaybes
とかもこれを使って定義されてるんですね。
(-> a) がApplicativeな件
(-> Bool)
がApplicativeのインスタンスであることを利用して、リストls :: [a]
から 述語p1, p2 :: a -> Bool
の少なくとも一方を満たす要素だけ取り出したりできます。
例えば、String
から“大文字または数値”である要素を取り出すコードは以下のように書けます:
import Control.Applicative
import Data.Char (isUpper, isDigit)
aa = filter (liftA2 (||) isUpper isDigit) "dsUOIJfa8eOIJee49r8u389f"
実行結果
aa ==> "UOIJ8OIJ498389"
もちろん、(||)
を(&&)
に変えれば連言もいけます。
【Haskell】 Stateモナドで乱数生成用モジュールを作る
今日もHaskellネタ。
Haskellでは避けて通れないモナドの話をしてみようと思ったのですが
・・・やっぱり難しい。
結局のところ、
だよなぁと思ったので、モナドについて解説するのはやめにして、 実際にモナドを使ったコードを書いてみます。
Stateモナドを使った、簡単な乱数ライブラリ
使い方
ソースコード
module EasyRandom (
Random(..),
random,
uniform,
permutation,
normal,
runRandom
) where
import Control.Monad.State.Lazy
(state, State(..), runState)
import qualified System.Random as R
(random, randomR, getStdRandom, StdGen(..), Random)
import qualified Data.Random.Normal as N
(normal)
type Random a = State R.StdGen a
random :: Random Int
random = state R.random
normal :: Random Double
normal = state N.normal
constant :: a -> Random a
constant = return
uniform :: R.Random a => (a, a) -> Random a
uniform (x,y) = state $ R.randomR (x,y)
permutation :: [a] -> Random [a]
permutation ls = permutation' (length ls) ls
permutation' :: Int -> [a] -> Random [a]
permutation' _ = return
permutation' _ [x] = return [x]
permutation' n (x:xs) = do
ys <- permutation' (n - 1) xs
i <- uniform (0, n - 2)
return (ys !! i) : update i x ys
where
update :: Int -> a -> [a] -> [a]
update i x xs
| i < 0 = xs
| otherwise = let (ls, (r:rs)) = splitAt i xs in ls ++ (x:rs)
runRandom :: Random a -> IO a
runRandom = R.getStdRandom . runState
以上です(汗)