0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Textualで作る新世代PythonターミナルUIアプリ開発入門

Last updated at Posted at 2025-08-05

はじめに

こんにちは!本記事では、Pythonで美しくインタラクティブなターミナルユーザーインターフェース(TUI)を構築できる「Textual」ライブラリについて、全12章にわたって詳しくご紹介します。Textualは、従来のCLIとは一線を画す表現力と、Webアプリのようなウィジェット設計、そして高速開発を兼ね備えた今注目のフレームワークです。各章は全て動作可能なPythonコードを掲載しています。さあ、最先端のターミナルアプリ開発を一緒に始めましょう!

第1章:Textualとは?新時代のターミナルUI

Textualは、ターミナル上でGUIライクなインターフェースを気軽に構築できるPython製のフレームワークです。レイアウト設計やイベント処理、カスタムウィジェットなど、Webやモバイル開発で得られる快適さを、ローカルやリモートのターミナルでも再現できます。まずはインストールから始めましょう。

# インストール
!pip install textual

第2章:最小限の“Hello, Textual!”

Textualのパワフルさを感じる最もシンプルな方法は、最小限のアプリを試すことです。この章では、1つのウィジェットのみを表示するhelloアプリを作成し、UIの起動や終了など基本挙動を体験します。Appクラスを継承し、composeでウィジェットをyieldするだけでOKです。

from textual.app import App
from textual.widgets import Static

class HelloTextualApp(App):
    def compose(self):
        yield Static("Hello, Textual!")

if __name__ == "__main__":
    HelloTextualApp().run()

第3章:複数ウィジェットとレイアウト

Textualでは複数のウィジェットを同時に配置し、柔軟な画面設計が可能です。HeaderやFooter、複数のStatic要素を重ねてターミナルアプリの骨組みを作成します。composeメソッドに複数yieldすれば、並べて表示されます。

from textual.app import App, ComposeResult
from textual.widgets import Static, Header, Footer

class MultiWidgetApp(App):
    def compose(self) -> ComposeResult:
        yield Header()
        yield Static("メインコンテンツ")
        yield Footer()

if __name__ == "__main__":
    MultiWidgetApp().run()

第4章:ボタンとイベント処理

Textualの大きな魅力は、イベント駆動型の開発です。ボタンウィジェットへのクリックイベントをon_ で検知し、状態を動的に変更できます。ここではカウントアップするシンプルな操作を実装します。

from textual.app import App
from textual.widgets import Button, Static

class CounterApp(App):
    def __init__(self):
        super().__init__()
        self.count = 0

    def compose(self):
        self.display = Static(f"現在の値: {self.count}")
        yield self.display
        yield Button("+1", id="inc_btn")

    def on_button_pressed(self, event):
        if event.button.id == "inc_btn":
            self.count += 1
            self.display.update(f"現在の値: {self.count}")

if __name__ == "__main__":
    CounterApp().run()

第5章:インタラクティブな入力フォーム

Textualはテキスト入力や選択肢、ラジオボタンなど、実用的なフォーマットがすぐに作れます。ここでは名前を入力し、ボタン押下時に挨拶を表示するフォームを紹介します。

from textual.app import App
from textual.widgets import Input, Button, Static

class InputApp(App):
    def compose(self):
        self.msg = Static("")
        self.input = Input(placeholder="お名前を入力")
        yield self.input
        yield Button("送信")
        yield self.msg

    def on_button_pressed(self, event):
        self.msg.update(f"こんにちは、{self.input.value} さん!")

if __name__ == "__main__":
    InputApp().run()

第6章:レイアウト管理とグリッドシステム

TextualはターミナルUIにもかかわらず、CSS風のレイアウトやグリッド配置ができます。複数のウィジェットのサイズ比や位置を柔軟に指定することで、見やすく整理された画面を作れます。

from textual.app import App, ComposeResult
from textual.containers import Grid
from textual.widgets import Static

class GridApp(App):
    def compose(self) -> ComposeResult:
        with Grid():
            yield Static("左パネル", id="left")
            yield Static("右パネル", id="right")

if __name__ == "__main__":
    GridApp().run()

第7章:テーマとスタイリング

Textualでは、各ウィジェットやアプリ全体にCSS類似のスタイル設定が可能。文字色や背景色、ボーダーやフォントサイズなど、視認性や美観を柔軟に調整できます。好みのテーマを自作することも容易です。

from textual.app import App
from textual.widgets import Static

class StyleApp(App):
    CSS = """
    Screen {
        background: #282c34;
        color: #d7d7d7;
    }
    Static {
        border: round #5fd7ff;
        padding: 1;
        background: #44475a;
        color: #f1fa8c;
    }
    """

    def compose(self):
        yield Static("美しくカスタマイズできるターミナルUI")

if __name__ == "__main__":
    StyleApp().run()

第8章:入力と出力の非同期処理

非同期の操作もTextualなら簡単です。データ取得や長時間処理をasync/awaitで実現しつつ、UIが固まることなく操作できます。ここでは擬似的に待ち時間を加えたデータ更新を行います。

from textual.app import App
from textual.widgets import Button, Static
import asyncio

class AsyncApp(App):
    def compose(self):
        self.stat = Static("データ待ち")
        yield self.stat
        yield Button("データ取得")

    async def on_button_pressed(self, event):
        self.stat.update("取得中...")
        await asyncio.sleep(2)
        self.stat.update("取得完了!")

if __name__ == "__main__":
    AsyncApp().run()

第9章:タブ切替とダイナミック画面遷移

Tabやページ遷移のような操作も、Textualなら画面を切り替えるだけ。アプリケーションに複数セクションがある場合も、ボタンやイベントで柔軟に切り替えられます。

from textual.app import App
from textual.widgets import Button, Static

class TabApp(App):
    def compose(self):
        self.page = Static("ホーム画面")
        yield self.page
        yield Button("次へ", id="next")
        yield Button("戻る", id="back")

    def on_button_pressed(self, event):
        if event.button.id == "next":
            self.page.update("次のページへ移動しました")
        elif event.button.id == "back":
            self.page.update("ホーム画面へ戻りました")

if __name__ == "__main__":
    TabApp().run()

第10章:リスト表示とスクロール管理

リスト表示・スクロール対応もTextualの得意分野です。大量のデータやログを、ウィジェットのスクロールで快適に閲覧可能にします。ここでは20行のリストを自動生成してスクロール表示します。

from textual.app import App
from textual.widgets import ListView, ListItem, Static

class ListApp(App):
    def compose(self):
        items = [ListItem(Static(f"項目{i}")) for i in range(1, 21)]
        yield ListView(*items)

if __name__ == "__main__":
    ListApp().run()

第11章:カスタムウィジェットの作成

Textualでは独自のウィジェットコンポーネント設計もできます。自分だけの複雑な挙動や専用部品をクラスとしてまとめ、複数アプリ間で再利用できます。ここではクリックで色が変わる独自Staticウィジェットを作成します。

from textual.app import App
from textual.widgets import Static

class ColorStatic(Static):
    def on_click(self, event):
        self.styles.background = "#50fa7b" if self.styles.background != "#50fa7b" else "#ff5555"

class CustomWidgetApp(App):
    def compose(self):
        yield ColorStatic("クリックして色を変えよう", id="color_sw")

if __name__ == "__main__":
    CustomWidgetApp().run()

第12章:アプリケーションの本番運用と拡張性

Textualで作ったアプリはクロスプラットフォームで動作し、本格業務アプリへも容易にスケールアップできます。バージョン管理・CI/CD導入・Web展開(今後対応予定)など、本番運用にも最適です。最後に全要素を盛り込んだダッシュボード例です。

from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, Button, Static
from textual.containers import Grid

class DashboardApp(App):
    CSS = """
    Static {border: round #bd93f9; padding: 1;}
    Button {margin: 1;}
    """

    def compose(self) -> ComposeResult:
        yield Header(show_clock=True)
        with Grid():
            yield Static("売上: ¥100,000")
            yield Static("ユーザ数: 2,340")
            yield Static("在庫: 450個")
        yield Button("更新")
        yield Footer()

if __name__ == "__main__":
    DashboardApp().run()

まとめ

Textualは、Pythonで気軽に高品質なTUIアプリを実現するための革新的なライブラリです。GUIとCLIのいいとこ取りで、業務ツールや趣味開発、アイデアのプロトタイプ作成まで幅広く活用できます。本記事の12章を通し、ぜひ最先端のターミナルアプリ開発を体験し、新たな可能性に触れてみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?