Urwid

Pythonでコンソールユーザーインターフェースライブラリ。豊富なウィジェットセットとイベント駆動型のプログラミングモデルを提供する老舗のTUIライブラリです。

pythontuilibraryconsolewidgetsevent-driven

GitHub概要

urwid/urwid

Console user interface library for Python (official repo)

ホームページ:urwid.org
スター2,930
ウォッチ56
フォーク323
作成日:2010年2月25日
言語:Python
ライセンス:GNU Lesser General Public License v2.1

トピックス

なし

スター履歴

urwid/urwid Star History
データ取得日時: 2025/7/25 06:23

ライブラリ

Urwid

概要

UrwidはPythonでコンソールユーザーインターフェースを構築するための成熟したライブラリです。豊富なウィジェットセットとイベント駆動型のプログラミングモデルを提供し、複雑なTUIアプリケーションの開発を可能にします。

詳細

Urwidは2004年にIan Wardによって開発され、Python TUIライブラリの草分け的存在として長年にわたって多くのプロジェクトで使用されています。安定性と成熟度が高く、企業レベルのアプリケーション開発でも信頼されています。

主要な特徴

  • 豊富なウィジェット: ボタン、テキストボックス、リストボックス、メニューなど50以上のウィジェット
  • レイアウトシステム: フレキシブルなレイアウト管理
  • イベント駆動: キーボード、マウス、タイマーイベントの処理
  • カスタムウィジェット: 独自ウィジェットの作成が容易
  • テーマシステム: カラーパレット設定
  • 非同期処理: ノンブロッキング処理のサポート
  • Unicode対応: 多言語テキスト表示
  • マウスサポート: マウスイベントの処理

アーキテクチャ

Urwidの設計は以下の主要コンポーネントで構成されています:

  • Widget: UI要素の基底クラス
  • MainLoop: イベントループとアプリケーション制御
  • Pile/Columns: レイアウトコンテナ
  • Frame: ヘッダー、フッター、ボディを持つレイアウト
  • Palette: カラーテーマ管理
  • Canvas: 描画サーフェス

メリット・デメリット

メリット

  • 長年の実績と安定性
  • 豊富なウィジェットライブラリ
  • 詳細なドキュメント
  • フレキシブルなレイアウトシステム
  • マウスとキーボードの完全サポート
  • 比較的軽量
  • 成熟したコミュニティ
  • 企業での採用実績

デメリット

  • やや古風なAPI設計
  • 学習コストが高い
  • モダンなUI要素の不足
  • 限定的なスタイリングオプション
  • 新機能の追加が遅い

主要リンク

書き方の例

import urwid

class TodoApp:
    def __init__(self):
        self.todos = []
        self.current_id = 0
        
        # ヘッダー
        header = urwid.Text("📝 Todo App")
        header = urwid.AttrMap(header, 'header')
        
        # 入力フィールド
        self.edit = urwid.Edit("New todo: ")
        
        # ボタン
        add_btn = urwid.Button("Add")
        urwid.connect_signal(add_btn, 'click', self.add_todo)
        
        clear_btn = urwid.Button("Clear All")
        urwid.connect_signal(clear_btn, 'click', self.clear_todos)
        
        # ボタンレイアウト
        buttons = urwid.Columns([
            ('weight', 1, add_btn),
            ('weight', 1, clear_btn)
        ])
        
        # Todo リスト
        self.todo_list = urwid.SimpleFocusListWalker([])
        self.listbox = urwid.ListBox(self.todo_list)
        
        # レイアウト
        pile = urwid.Pile([
            ('pack', self.edit),
            ('pack', buttons),
            urwid.Divider(),
            self.listbox
        ])
        
        # フレーム
        self.frame = urwid.Frame(
            pile,
            header=header,
            footer=urwid.Text("Enter: Add | Space: Toggle | d: Delete")
        )
    
    def add_todo(self, button):
        text = self.edit.get_edit_text().strip()
        if text:
            todo_item = TodoItem(self.current_id, text, self.on_delete)
            self.todo_list.append(todo_item)
            self.todos.append({'id': self.current_id, 'text': text, 'done': False})
            self.current_id += 1
            self.edit.set_edit_text("")
    
    def clear_todos(self, button):
        self.todo_list.clear()
        self.todos.clear()
    
    def on_delete(self, todo_id):
        # リストから該当のTodoを削除
        for i, item in enumerate(self.todo_list):
            if hasattr(item, 'todo_id') and item.todo_id == todo_id:
                del self.todo_list[i]
                break
        
        # データからも削除
        self.todos = [t for t in self.todos if t['id'] != todo_id]
    
    def unhandled_input(self, key):
        if key in ('q', 'Q'):
            raise urwid.ExitMainLoop()

class TodoItem(urwid.WidgetWrap):
    def __init__(self, todo_id, text, delete_callback):
        self.todo_id = todo_id
        self.done = False
        self.delete_callback = delete_callback
        
        self.checkbox = urwid.CheckBox("", state=False)
        self.text = urwid.Text(text)
        
        # レイアウト
        columns = urwid.Columns([
            ('fixed', 4, self.checkbox),
            self.text
        ])
        
        super().__init__(columns)
    
    def keypress(self, size, key):
        if key == ' ':
            self.checkbox.toggle_state()
            self.done = not self.done
            style = 'done' if self.done else None
            self.text.set_text(('done' if self.done else None, self.text.text))
            return None
        elif key == 'd':
            self.delete_callback(self.todo_id)
            return None
        
        return super().keypress(size, key)

def main():
    # カラーパレット
    palette = [
        ('header', 'white', 'dark blue', 'bold'),
        ('done', 'dark gray', 'default', 'strikethrough'),
    ]
    
    app = TodoApp()
    
    # メインループ
    loop = urwid.MainLoop(
        app.frame,
        palette=palette,
        unhandled_input=app.unhandled_input
    )
    
    loop.run()

if __name__ == "__main__":
    main()