atlasを使って画像表示 / ドラッグで移動可能なウィジェット - Kivy Advent Calendar 2013

何となくカードゲームを作ってみたかったので、カードの画像が1枚に収まってる素材を探してきて、それをatlasで表示してみます。
カードはドラッグで移動させたいので、Scatterをあわせてみました。
ランダムにばらまいてみました。まだ全然ゲームにはなってません... いや神経衰弱はできるかな?


(/sdcard/kivy/cards/)

trump.gif カードの素材 (http://sozai.7gates.net/docs/trump/)
cards.atlas 素材の座標位置を示すファイル
main.py

(cards.atlas)

{
  "trump.gif":{
    "SA":[  0,540,60,90], "HA":[ 60,540,60,90], "DA":[120,540,60,90], "CA":[180,540,60,90],
    "S2":[  0,450,60,90], "H2":[ 60,450,60,90], "D2":[120,450,60,90], "C2":[180,450,60,90],
    "S3":[  0,360,60,90], "H3":[ 60,360,60,90], "D3":[120,360,60,90], "C3":[180,360,60,90],
    "S4":[  0,270,60,90], "H4":[ 60,270,60,90], "D4":[120,270,60,90], "C4":[180,270,60,90],
    "S5":[  0,180,60,90], "H5":[ 60,180,60,90], "D5":[120,180,60,90], "C5":[180,180,60,90],
    "S6":[  0, 90,60,90], "H6":[ 60, 90,60,90], "D6":[120, 90,60,90], "C6":[180, 90,60,90],
    "S7":[  0,  0,60,90], "H7":[ 60,  0,60,90], "D7":[120,  0,60,90], "C7":[180,  0,60,90],

    "S8":[240,540,60,90], "H8":[300,540,60,90], "D8":[360,540,60,90], "C8":[420,540,60,90],
    "S9":[240,450,60,90], "H9":[300,450,60,90], "D9":[360,450,60,90], "C9":[420,450,60,90],
    "ST":[240,360,60,90], "HT":[300,360,60,90], "DT":[360,360,60,90], "CT":[420,360,60,90],
    "SJ":[240,270,60,90], "HJ":[300,270,60,90], "DJ":[360,270,60,90], "CJ":[420,270,60,90],
    "SQ":[240,180,60,90], "HQ":[300,180,60,90], "DQ":[360,180,60,90], "CQ":[420,180,60,90],
    "SK":[240, 90,60,90], "HK":[300, 90,60,90], "DK":[360, 90,60,90], "CK":[420, 90,60,90],

    "J1":[240,  0,60,90], "J2":[300,  0,60,90], "N": [360,  0,60,90]
  }
}

(main.py)

from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.scatter import Scatter
from random import randint

from kivy.lang import Builder
Builder.load_string('''\
:
    canvas.before:
        Color:
            rgb: 0, 0.3, 0.1
        Rectangle:
            pos: self.pos
            size: self.size
:
    suit: 'N'
    rank: ''
    face: _face
    do_rotation: True
    do_scale: False
    auto_bring_to_front: True
    do_collide_after_children: True
    size: (120, 150)
    size_hint: (None, None)
    ToggleButton:
        id: _face
        size: (60, 90)
        size_hint: (None, None)
        background_normal: 'atlas://cards/N'
        background_down: 'atlas://cards/%s%s' % (self.parent.suit, self.parent.rank)
''')

class CardTable(RelativeLayout):
    pass

class Card(Scatter):

    def on_touch_up(self, touch):
        if self.collide_point(*touch.pos):
            self.face.state = 'down' if self.face.state == 'normal' else 'normal'

class CardTableApp(App):

    def build(self):
        self.table = CardTable()
        for suit in list('SHDC'):
             for rank in list('A23456789TJQK'):
                 card = Card()
                 card.suit = suit
                 card.rank = rank
                 card.pos = (randint(50, 1150), randint(50, 1870))
                 self.table.add_widget(card)
        return self.table

if __name__ == '__main__':
    CardTableApp().run()

追記

  • いろいろ調整してはいますが、さすがに小サイズのウィジェットに「カードへのタッチで開く/閉じるをトグルにする」と「ドラッグして動かす」の両方を持たせるのは無理がありました。本当にゲームを作るときは別のUIに出来ないか検討してみます...
  • 画面サイズを決めうちにしているところがあります。kivy.core.windowで取れるはずなので、後で修正すると思います...

別のアプリを使ってから復帰する - Kivy Advent Calendar 2013

KivyLauncherでテキスト入力だけのアプリを作り、何か入力してから別のアプリに切り替えてみます。しかしその後KivyLauncherに戻ってもランチャ画面になってしまい、入力した内容は失われてしまいます。


(main.py)

from kivy.app import App
from kivy.uix.textinput import TextInput

class Memo(TextInput):
    pass

class MemoApp(App):

    def build(self):
        self.memo = Memo()
        return self.memo

if __name__ == '__main__':
    MemoApp().run()

モバイルOSのアプリ切り替えは必ずしもメモリ上に残ることを保証していないため、アプリが自主的に状態を保存する必要があります。Kivyアプリが状態保存に対応していない場合は、(menuボタン等による)アプリ切り替えも(home/backボタンによる)終了と同様に終了処理が呼び出されます。KivyLauncher自体はメモリに残っていたとしても、すでにKivyアプリは終了しているため復帰が行われてもランチャ画面に戻ってしまいます。
Kivyアプリに復帰できるようにするにはon_pause、on_resumeメソッドを用意します。
on_pauseが最後にTrueを返すと、別のアプリに切り替える際に終了処理を呼び出さなくなるので、KivyLauncherがメモリ上に残っていればそのKivyアプリにすぐ復帰します。またOSがKivyLauncherのメモリを解放していた場合でも、ファイルに保存した状態をon_resumeでメモリに呼び戻して同じように復帰できます。


(main.py)

from kivy.app import App
from kivy.uix.textinput import TextInput

class Memo(TextInput):
    pass

class MemoApp(App):

    def on_pause(self):
        with open('memo.txt', 'w') as f:
            f.write(self.memo.text)
            return True

    def on_resume(self):
        with open('memo.txt') as f:
            self.memo.text = f.read()

    def build(self):
        self.memo = Memo()
        return self.memo

if __name__ == '__main__':
    MemoApp().run()

追記

さすがにbackボタンで終了した場合は内容はクリアされます (たぶんon_stopにフックをかければ保存対応可能かと思いますが...)

画面を転換する - Kivy Advent Calendar 2013

5日目にCarouselを使いましたが、これはユーザのスライド操作で複数のウィジェットを見せていくことができます。これに対しScreenManagerは、それ自体はスライド操作を受け付けませんが、フェードイン、ワイプなどのさまざまな効果を持たせた画面転換ができます。


(/sdcard/kivy/screen/)

android.txt
main.py

(android.txt)

title=screen
author=cheeseshop
orientation=portrait

(main.py)

from kivy.app import App
from kivy.properties import StringProperty
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.screenmanager import ShaderTransition, SlideTransition, FadeTransition, WipeTransition

FADEINOUT_TRANSITION_FS = '''$HEADER$
uniform float t;
uniform sampler2D tex_in;
uniform sampler2D tex_out;

void main(void) {
float t1 = 0.45;
float t2 = 0.55;
vec4 cin = vec4(texture2D(tex_in, tex_coord0.st));
vec4 cout = vec4(texture2D(tex_out, tex_coord0.st));
vec4 frag_col = t < t1 ? vec4*1
            on_text: app.settr(self.text)
        Button:
            pos_hint: {'center_x':0.5, 'top':0.2}
            size_hint: (None, None)
            size: (200, 50)
            text: 'Back to home'
            on_press: root.manager.transition = app.tr1; root.manager.current = 'home'
''')

class FadeInOutTransition(ShaderTransition):
    fs = StringProperty(FADEINOUT_TRANSITION_FS)

class HomeScreen(Screen):
    pass

class SettingScreen(Screen):
    pass

class ScreenApp(App):

    trs = {
        'Slide_H': (SlideTransition(direction='right'),
                    SlideTransition(direction='left')),
        'Slide_V': (SlideTransition(direction='up'),
                    SlideTransition(direction='down')),
        'Swap':     SwapTransition(),
        'Fade':     FadeTransition(),
        'Wipe':     WipeTransition(),
        'FadeInOut':   FadeInOutTransition(),
    }

    def settr(self, tr):
        trs = self.trs[tr]
        if isinstance(trs, tuple):
            self.tr0 = trs[0]
            self.tr1 = trs[1]
        else:
            self.tr0 = trs
            self.tr1 = trs

    def build(self):
        self.sm = ScreenManager()
        self.sm.add_widget(HomeScreen(name='home'))
        self.sm.add_widget(SettingScreen(name='setting'))
        self.settr('Fade')
        return self.sm

if __name__ == '__main__':
    ScreenApp().run()

解説

このサンプルでは、「home」と「setting」の2つの画面間の移動をボタンで行います。setting画面には、6種類の画面効果を切り替えるオプションを用意しています。
まずScreenManagerを作り、そこに複数の画面を追加(add_widget)しておきます。transitionプロパティで効果を指定してから、currentプロパティに画面のnameを指定することで転換が行われます。
6種類のうち5つは組み込みで持っている画面の転換方法です。

transition arg
SlideTransition 'right' 'left' 水平方向にスライドして切り替える
SlideTransition 'up' 'down' 垂直方向にスライドして切り替える
SwapTransition 前後を入れ替えるようにして切り替える
FadeTransition じわじわと切り替える
WipeTransition ワイプして切り替える

最後の1つ「FadeInOut」はこのスクリプトの中で作成したオリジナルの画面効果です。

FadeInOutTransition フェードアウトしてからフェードイン

Pythonスクリプトなのに「void main(vold) {...}」とかC言語っぽい文字列がありますが、これがシェーダ効果を記述する言語(GLSL)です。ShaderTransitionのプロパティにセットするとOpenGL APIを通じて自動的にコンパイルされ、画面効果として使えるようになります。

追記

このサンプルでは使っていませんが、Kivy 1.8からは次の効果も使えるようです。

FallOutTransition 中心へ古い画面が落ち込んでいく
RiseInTransition 中心から新しい画面が広がってくる

どちらもiOSAndroidの画面転換としてはおなじみのものですね。

*1:1.0 - t / t1) * cout) : t > t2 ? vec4((t - t2) / (1.0 - t2) * cin) : vec4(0.0); gl_FragColor = frag_col; } ''' from kivy.lang import Builder Builder.load_string(''' : canvas.before: Color: rgb: 0.4, 0.3, 0.2, 1 Rectangle: pos: self.pos size: self.size FloatLayout: Label: pos_hint: {'center_x':0.5, 'top':0.8} size_hint: (None, None) size: (200, 50) text: 'Home' Button: pos_hint: {'center_x':0.5, 'top':0.2} size_hint: (None, None) size: (200, 50) text: 'Goto setting' on_press: root.manager.transition = app.tr0; root.manager.current = 'setting' : canvas.before: Color: rgb: 0.2, 0.3, 0.4, 1 Rectangle: pos: self.pos size: self.size FloatLayout: Label: pos_hint: {'center_x':0.5, 'top':0.8} size_hint: (None, None) size: (200, 50) text: 'Setting' Spinner: pos_hint: {'center_x':0.5, 'top':0.5} size_hint: (None, None) size: (200, 50) text: 'Slide_H' values: sorted(app.trs.keys(

ポップアップ - Kivy Advent Calendar 2013

(※ビルドの話をしましたが、再びKivyLauncherで出来る範囲での話に戻ります)
5日目の「スライドっぽい...」でも使ったので、今回はポップアップについて解説します。
Kivyの中で、ポップアップに当たるものは次のようなものがあります。

Popup タイトルバーあり / 背景が暗くなる
ModalView タイトルバーなし / 背景が暗くなる
Bubble タイトルバーなし / ふきだし (背景は暗くならない)

元のウィンドウの上にオーバラップして表示され、ヘルプ・エラー・進捗を示すメッセージウィンドウとして、あるいはさまざまな選択を要求するメニューやダイアログ画面として使われます。いずれもイベント処理を追加しなければドラッグで動いたりはせず、ただ表示するだけです。
PopupとModalViewの違いは、タイトルバーがあるかないかだけです。これらは単体でもopen/dismissで表示・非表示ができます。attach_toで親ウィンドウを示すこともできますが、デフォルトで元のウィンドウが暗くなってしまうので、設定画面やウィザードのように、大きく画面を占める使い方が主で、ポップアップメニューのような使い方には適さないと思います(background_colorをアルファ値0にすれば背景を暗くさせないことも可能ですが)。
一方Bubbleは下のウィジェットを暗くすることはせず、またふきだしの向きをつけられるので「ウィジェットに対するアクション選択肢」を示すポップアップメニューとして適したものです。その代わり下のウィジェットが隠れてしまうほど大きく画面を占める使い方には向きません。
Bubbleの代表的なものが、TextInputでテキストがあるところを長押しして表示される「Cut-Copy-Paste」の3ボタンのポップアップです。Bubble自体に表示や非表示にするメソッドはなく、元となるウィジェットへadd_widget、remove_widgetして表示、非表示としていて、ウィジェットへのタップをきっかけに選択肢を開くといった形になります。
以下はBubbleのサンプルで、タッチした座標を出しています。


(/sdcard/kivy/bubbles/)

android.txt
main.py

(main.py)

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.bubble import Bubble

from kivy.lang import Builder
Builder.load_string('''
:
    canvas.before:
        Color:
            rgb: 0.0, 0.2, 0.2, 1
        Rectangle:
            pos: self.pos
            size: self.size
:
    label: _label
    arrow_pos: 'bottom_left'
    size_hint: (None, None)
    size: (200, 50)
    Label:
        id: _label
''')

class Result(Bubble):

    def __init__(self, **kwargs):
        super(Result, self).__init__(**kwargs)
        self.label.text = kwargs['text']

class Bubbles(FloatLayout):

    def on_touch_up(self, touch):
        result = Result(text='(%d, %d)'%touch.pos, pos=touch.pos)
        self.add_widget(result)

class BubblesApp(App):

    def build(self):
        return Bubbles()

if __name__ == '__main__':
    BubblesApp().run()

Python for Androidのビルド (2) - Kivy Advent Calendar 2013

VMも動いたのでいよいよビルド作業に入りますが、その前にやっておく作業があります。

ビルド環境の修正

基本的にこのVMイメージは初期状態でPython for Androidのビルドに必要なコマンドはすべてインストールされているため、apt-getやyumで環境構築する必要はありません。
ただ、現在(2013年11月)配布されているイメージは次の問題があり対応が必要になります。

(1) python-for-androidの再取得
ディスクイメージ内に配置してある /home/kivy/android/python-for-android/ は、パス名の問題でPython-2.7.2のコンパイルに失敗します。これはあらためてgithubから最新版を取得・展開すれば解決します。

$ cd python-for-android
$ git pull

(2) cythonのアップグレード
Cythonの処理中に「構文が違う」というエラーを返すことになりますので、pipを使って最新版にしておきます。

$ sudo pip install --upgrade cython

Python for Androidのビルド

/home/kivy/android/python-for-android/ に移動して distribute.sh を実行します。

$ ./distribute.sh -m "sqlite3 openssl pyopenssl lxml audiostream cymunk ffmpeg pil pyjnius twisted kivy"

必要なレシピがあればここで追加しておきます。上のコマンドラインはKivyLauncher 1.7.2と同じもので、最終的なAPKのサイズは12〜13MBくらいになります。
レシピの中には現在ビルドできないものもあるようです。この場合 dist/default/ フォルダがの中にファイルが1つもない状態で終了します。問題のレシピを取り除いて再度distribute.shを実行するときは dist/default/ フォルダを一旦削除する必要があります。

  • サイトからダウンロードできない (※URLを書き換えれば解決?)
    • gevent
    • libevent
  • ioモジュールがない等のエラー (※Pythonバージョンの不一致?)

distribute.shによってPythonと各種ライブラリがAndroid向け実行ファイルとしてビルドされれば、次はAPKパッケージを作る準備段階になります。念のため次のファイルが作成されていることを確認してください。
(src/ 以下)

  • default.properties
  • local.properties
  • objs/local/armeabi/*.a
  • objs/local/armeabi/*.so
  • objs/local/armeabi/objs/**
  • libs/armeabi/*.so

(dist/default/ 以下)

  • build.py

APKパッケージの作成

dist/default/ に移動して build.py を実行します。

$ ./build.py \
      --name "Kivy Launcher" \
      --package org.kivy.pygame \
      --version 1.7.2.1 \
      --launcher \
      --icon templates/launcher-icon.png \
      --presplash templates/launcher-presplash.jpg \
      --permission INTERNET \
      --permission BLUETOOTH \
      release

dist/default/bin/ の下に次のようなファイル名でAPKファイルが作成されます。

KivyLauncher-1.7.2.1-release-unsigned.apk

これをAndroid端末のDownloadsなどのフォルダに送って、ファイラなどで実行するとインストールが始まります。
(あらかじめ「提供元不明のアプリのインストール」を許可しておいてください)

Python for Androidのビルド (1) - Kivy Advent Calendar 2013

(※Python for Androidは後半の記事ですが、時間切れのため先行して公開します)
KivyLauncherではopensslやPILなどのモジュールが導入されていて、せいぜいWebサーバと連携して何かやる程度の軽いアプリならあまり困らないのですが、それでも自分でモジュールをインストールしたいなということはあります。
でもAndroidの保護機構のため、Google Playからインストールしたリリース版のKivyLauncherにはライブラリインストールはできません。また /sdcard/kivy/(プロジェクトフォルダ)/ の下に置けばimportで取り込む場合もPythonで記述されたライブラリに限られます。

Python for Android」というのは文字通りAndroid上で動くPythonコンパイルするためのキットで、ライブラリの組み込みやAndroidインストーラパッケージ (APK) を作るツールも含まれています。このビルドオプションに複数アプリを切り替えるランチャーを組み込むものがあり、それをセットしてビルドしたものがKivyLauncherとして公開されているわけです。
KivyLauncherをビルドし直せば次のことが可能になります。

  • リリースされていないバージョン、ブランチを使う
  • Androidパーミッションの追加
  • CまたはCythonで記述されたライブラリの組み込み
  • 実行方法の変更 (単体アプリ、ウィンドウ化など)

Kivyサイトでは、Android SDK/NDKやビルドに必要なツールをインストールしたLinuxベースの開発環境イメージを配布しています。既存のLinux環境に構築することもできますが、ここではこの開発環境イメージを使って動かすことにします。

Oracle VM VirtualBoxのインストール

LinuxWindowsMac OS XSolarisなど、VirtualBoxが動くマシンだったら、環境のセットアップを飛ばして手っ取り早くビルドできるのでオススメです。
まずはVirtualBoxのインストールから。プラットフォームにあったパッケージをダウンロードしてインストールします。
https://www.virtualbox.org/wiki/Downloads

VMの作成

次にVMのイメージを入手します。
http://kivy.org/#download
KivyサイトのVirtual Machinesの項目にある「Kivy Python for android VM」(Googleドライブで公開)をダウンロードします。7-zipという日本ではややマイナーな圧縮形式ですが、展開ツールはすぐ見つかると思います。
ユーザディレクトリ下など適当な場所に展開したら、Oracle VM VirtualBoxを実行して

  • VMを新規作成
  • Linux-Ubuntuを指定
  • メモリサイズを指定 (1024MB程度?)
  • 「すでにある仮想ハードドライブファイルを使用する」を選択して展開したファイルを指定

でセットアップは完了です。

Ubuntuの操作

VMの起動を行うとUbuntuKDEデスクトップが立ち上がります。

ここから次のようにテキスト端末を開いて、kivyユーザとしてログインします。このVMでのkivyユーザの初期パスワードは「kivy123」で設定されています。

  • Ctrl+Alt+F1を押せば全画面のCUIコンソールになり、ログイン待ちになる
    • 別のコンソールが必要なら、Ctrl+Alt+F2 〜 Ctrl+Alt+F6 で立ち上がる
    • Ctrl+Alt+F7でKDEのデスクトップに戻る
  • Ctrl+Alt+Tを押せばKDE上でターミナルを開く
    • (すでにkivyユーザでKDEデスクトップが立ち上がっているのでログインは不要)

ビルド作業はCUIかターミナルのどちらかで行うことになります。まあ各々好みがあるので問いませんが、速度的にいえばCUIの方がお薦めです。

共有フォルダの設定

ソースファイルや作成されたAPKパッケージなど、VMとホスト機の間でファイルのやり取りができるよう共有フォルダを作っておくといいでしょう。
まずホスト機の適当な場所に共有フォルダshareを作り、VirtualBoxマネージャの「フォルダ共有」でそのフォルダを設定します。
次にゲストOSでmount操作を行えば、shareフォルダを通じてファイルのやり取りができるようになります。

$ mkdir share
$ sudo mount -t vboxsf share share

【修正】スライドっぽいものを作る - Kivy Advent Calendar 2013

Carouselというウィジェットは、ドラッグしてウィジェットを次々と表示できます。すでに2日目 (Tofu issue) で何の解説もなく使ってますが...
これとRstDocumentを組み合わせればスライドっぽいものができますね。
 

ということで作ってみました。横方向にスライドすれば次の画面が出てきます。
(一部白い斑点がありますが、これはAndroidの開発者オプションでタップ位置を表示しているものです)


(/sdcard/kivy/slides/)

docutils.zip docutils-*.tar.gzの中の「docutils」ディレクトリをZIP圧縮したもの
android.txt Androidの場合必要
fonts_ja.py 2日目「Tofu issue」のfonts_ja.pyと同じ
main.py
slides.kv
title.txt スライド表紙
page01.rst 1ページ目
page02.rst 2ページ目
page03.rst 3ページ目



(android.txt)

title=slides
author=cheeseshop
orientation=landscape



(main.py)

# -*- coding: utf-8 -*-
import os, sys
sys.path.insert(0, os.path.join(os.getcwd(), 'docutils.zip'))

from kivy.app import App
from kivy.uix.carousel import Carousel
from kivy.uix.label import Label
from kivy.uix.rst import RstDocument
from kivy.uix.textinput import TextInput
from kivy.uix.popup import Popup
import codecs
import fonts_ja

class TLabel(Label):

    def __init__(self, **kwargs):
        if kwargs.has_key('source'):
            kwargs['text'] = codecs.open(kwargs['source'], 'r', 'utf-8').read()
            del kwargs['source']
        super(TLabel, self).__init__(**kwargs)

class Rst(RstDocument):

    source_encoding = 'utf-8'

    def on_touch_up(self, touch):
        if not self.parent.current_slide is self:
            return
        if touch.is_double_tap:
            popup = Popup(
                title='Source',
                content=TextInput(text=self.text),
                auto_dismiss=True,
                size_hint=(0.8, 0.8))
            popup.open()

    def __init__(self, **kwargs):
        if kwargs.has_key('source'):
            kwargs['text'] = codecs.open(kwargs['source'], 'r', 'utf-8').read()
            del kwargs['source']
        super(Rst, self).__init__(**kwargs)

class SlidesApp(App):

    def build(self):
        carousel = Carousel(direction='right')
        carousel.add_widget(TLabel(source='title.txt'))
        carousel.add_widget(Rst(source='page01.rst'))
        carousel.add_widget(Rst(source='page02.rst'))
        carousel.add_widget(Rst(source='page03.rst'))
        return carousel

if __name__ == '__main__':
    SlidesApp().run()



(slides.kv)

:
    font_size: '20pt'
    halign: 'center'
    valign: 'middle'
    color: 0.2, 0.2, 0.0, 1
    canvas.before:
        Color:
            rgb: 0.9, 0.9, 0.8
        Rectangle:
            pos: self.pos
            size: self.size

:
    font_size: '28pt'
    halign: 'center'
    valign: 'middle'
    color: 0.2, 0.2, 0.0, 1

:
    cols: 1
    content: content
    size_hint_y: None
    height: content.texture_size[1] + 30
    canvas:
        Color:
            rgb: parse_color('#cccccc')
        Rectangle:
            pos: self.x - 1, self.y - 1
            size: self.width + 2, self.height + 2
        Color:
            rgb: parse_color('#eeeeee')
        Rectangle:
            pos: self.pos
            size: self.size
    Label:
        id: content
        markup: True
        text_size: self.width - 10, None
        font_name: '/system/fonts/DroidSansFallback.ttf'
        color: (0, 0, 0, 1)



(title.txt)

Kivy / Python for Androidでプレゼンツール

2013.12.05
http://d.hatena.ne.jp/cheeseshop/



(page01.rst)

================================================
CarouselとScreenManagerの違い
================================================

Carousel
                                                                                              • -
スライドして次/前ページへ移動 + 上下スクロールと干渉しないよう左右にスライドさせることが多い + イベント記述なしで、スライド操作を受け付ける + 左右または上下方向のスライド表示しかできない ScreenManager
                                                                                              • -
別の画面へ遷移する + 画面転換に使う (ゲームオーバー、設定画面、ログイン要求) + ScreenManager自体はスライド操作は受けつけない + OpenGLのエフェクトが使え、シェーダー効果も自作できる - スライド (SlideTransition) - 前後を入替えるように (SwapTransition) - ワイプ (WipeTransition) フェード (FadeTransition) - 中央からズームで現れる (RiseInTransition) 中央に消えていく (FallOutTransition)



(page02.rst)

================================================
このプレゼンツールのポイント
================================================

Tofu issue

  + 等幅フォントのところだけ化けるのに対応

# コードsample print 'Hello!' ... docutilsが入ってない + ZIP圧縮していれる方法を採用 ダブルタップすると + まるで編集画面っぽくreSTテキストを開きます + でも保存できません... orz



(page03.rst)

================================================
まとめ
================================================

.. image:: .kivy/icon/kivy-icon-128.png

解説

docutils.zip

2日目でRstDocument使ったときは「docutilsをそのままプロジェクトにつっこんで」とか書きましたが、1MB以上あるんですね。Androidへの転送は面倒だし。
今回はライブラリ本体だけZIP圧縮しておいています。Pythonはこの手が使えるので便利ですね。
ただこれでも480KBくらいありますので、他のプロジェクトと共通で使うのなら、KivyLauncherをビルドしてdocutilsを最初から入れていた方がいいかもしれません。

source引数

TLabelとRstでsourceにファイル名を入れていますが、なぜかその場でファイルを読んでtextに入れ直しています。
これはRstDocumentウィジェットは「source_encoding='utf-8'」としているのに、全然設定が渡されておらず、docutilsは「input_encoding='ascii'」でrstファイルを読もうとしてUnicodeエラーに。
いろいろ試行錯誤しましたが時間切れのため、codecs.openで読み込んでtextにUnicode文字列をつっこむ方法を取っています(Unicode文字列を渡せば正しくレンダリングするのですが)。
ひょっとしたらSphinxの日本語パッチか何かで、スマートな解決方法をやっているのかもしれません。


ダブルタップ

ページをダブルタップをするとTextInputが開き、reSTソースが表示されます(グレーアウトした背景をタップすれば閉じます)。
本当は「プレゼン中にその場で編集→即時更新」なんてかっこいいこと考えたのですが、これも時間切れ。今はただ表示するだけです。
ちなみに最後のreturn Trueを忘れると挙動がおかしくなるようです。ダブルタップのイベントが他のページにも渡されてしまうのかもしれません。

(追記 2013-12-13) ごめんなさい。イベント処理についての知識が足りてなかったです。この件について後で解説します。
(追記 2013-12-21) http://d.hatena.ne.jp/cheeseshop/20131221 で解説を書きました
(追記 2013-12-21) 上のmain.pyを修正しました。今はスライド自身のreSTソースが表示されているはずです。

追記

あーやっと理解しました。
masterブランチでは直っていますが、KivyLaucher 1.7.2はdocutilsにエンコーディングを渡してないです(preloadメソッドにencoding引数がない!)
この問題も1.8.0リリース待ちですかね...

https://twitter.com/mathieuvirbel/status/407950627046948864

昨日、作者さんが「うまくいけば今週中にKivy 1.8出すよ、しかもPython 3コンパチだ」とか呟いてるし。
期待していいんだね? Python for Androidだけ遅れるとかないよね?