TUI Realm
GitHub概要
veeso/tui-realm
👑 A ratatui framework to build stateful applications with a React/Elm inspired approach
スター743
ウォッチ2
フォーク32
作成日:2021年4月7日
言語:Rust
ライセンス:MIT License
トピックス
consolecrosstermguiratatuiruststatefulstateful-tuiterminalterminal-appterminal-graphicstuitui-frameworktui-realm
スター履歴
データ取得日時: 2025/7/25 11:09
TUI Realm
TUI Realmは、React/Elm風のアーキテクチャを採用したコンポーネントベースのTUIフレームワークです。Ratatui上に構築され、宣言的な状態管理と型安全なイベント処理を提供します。Model-View-Update (MVU) パターンにより、複雑なTUIアプリケーションを整理された方法で構築できます。
特徴
MVUアーキテクチャ
- Model: アプリケーション状態の定義
- View: UIコンポーネントの描画
- Update: イベントに基づく状態更新
- Subscription: 外部イベントの監視
コンポーネントシステム
- Component: 再利用可能なUIパーツ
- MockComponent: テスト用のモック実装
- Props/State: コンポーネントの属性と内部状態
- Event Handling: 型安全なイベント処理
豊富な機能
- フォーカス管理: 自動的なフォーカス制御
- グローバルイベント: アプリ全体のイベント処理
- サブスクリプション: タイマーや外部イベント
- アニメーション: 組み込みアニメーション機能
開発者体験
- 型安全: Rustの型システムを活用
- テストサポート: モック機能付き
- デバッグ機能: イベントトレーシング
- 詳細なドキュメント: 豊富な例とガイド
基本的な使用方法
インストール
[dependencies]
tuirealm = "2.0"
ratatui = "0.30"
crossterm = "0.27"
基本的なアプリケーション
use tuirealm::application::{PollStrategy, Update};
use tuirealm::{Application, Component, Event, EventListenerCfg, Sub, SubClause, SubEventClause};
use tuirealm::props::{Alignment, BorderType, Borders, Color, Props, TextSpan};
use tuirealm::command::{Cmd, CmdResult};
use tuirealm::terminal::TerminalBridge;
use tuirealm::tui::layout::{Constraint, Direction, Layout};
// メッセージ定義
#[derive(Debug, PartialEq)]
enum Msg {
AppClose,
IncreaseCounter,
DecreaseCounter,
}
// コンポーネントID
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
enum Id {
Counter,
IncButton,
DecButton,
}
// モデル定義
struct Model {
app: Application<Id, Msg, NoUserEvent>,
counter: isize,
quit: bool,
redraw: bool,
terminal: TerminalBridge,
}
impl Update<Msg> for Model {
fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
if let Some(msg) = msg {
self.redraw = true;
match msg {
Msg::AppClose => {
self.quit = true;
None
}
Msg::IncreaseCounter => {
self.counter += 1;
self.app
.attr(&Id::Counter, Attribute::Text, AttrValue::String(
format!("Counter: {}", self.counter)
));
None
}
Msg::DecreaseCounter => {
self.counter = self.counter.saturating_sub(1);
self.app
.attr(&Id::Counter, Attribute::Text, AttrValue::String(
format!("Counter: {}", self.counter)
));
None
}
}
} else {
None
}
}
}
コンポーネント定義
use tuirealm::command::{Cmd, CmdResult};
use tuirealm::props::{Alignment, BorderType, Borders, Color, Props, Style};
use tuirealm::tui::widgets::{Block, Paragraph};
use tuirealm::{AttrValue, Attribute, Component, Event, MockComponent, State};
#[derive(MockComponent)]
pub struct Label {
props: Props,
}
impl Default for Label {
fn default() -> Self {
Self {
props: Props::default(),
}
}
}
impl Component<Msg, NoUserEvent> for Label {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: KeyCode::Esc, ..
}) => Some(Msg::AppClose),
_ => None,
}
}
fn attr(&mut self, attr: Attribute, value: AttrValue) {
self.props.set(attr, value);
}
fn state(&self) -> State {
State::None
}
fn perform(&mut self, _: Cmd) -> CmdResult {
CmdResult::None
}
}
// レンダリング実装
impl Component<Msg, NoUserEvent> for Label {
fn render(&self, render: &mut Frame, area: Rect) {
let text = self
.props
.get_or(Attribute::Text, AttrValue::String(String::default()))
.unwrap_string();
let widget = Paragraph::new(text)
.block(
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
)
.alignment(Alignment::Center);
render.render_widget(widget, area);
}
}
フォームの実装
use tuirealm::props::{InputType, TextSpan};
use tuirealm::command::{Cmd, Direction, Position};
#[derive(MockComponent)]
pub struct Input {
props: Props,
states: OwnStates,
}
#[derive(Default)]
struct OwnStates {
input: String,
cursor: usize,
}
impl Component<Msg, NoUserEvent> for Input {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent {
code: KeyCode::Char(ch),
..
}) => {
self.states.input.insert(self.states.cursor, ch);
self.states.cursor += 1;
Some(Msg::TextChanged(self.states.input.clone()))
}
Event::Keyboard(KeyEvent {
code: KeyCode::Backspace,
..
}) => {
if self.states.cursor > 0 {
self.states.cursor -= 1;
self.states.input.remove(self.states.cursor);
Some(Msg::TextChanged(self.states.input.clone()))
} else {
None
}
}
Event::Keyboard(KeyEvent {
code: KeyCode::Left,
..
}) => {
self.states.cursor = self.states.cursor.saturating_sub(1);
None
}
Event::Keyboard(KeyEvent {
code: KeyCode::Right,
..
}) => {
if self.states.cursor < self.states.input.len() {
self.states.cursor += 1;
}
None
}
_ => None,
}
}
fn state(&self) -> State {
State::One(StateValue::String(self.states.input.clone()))
}
}
高度な機能
サブスクリプション
// タイマーサブスクリプション
let sub = Sub::new(
SubEventClause::Tick,
SubClause::Always
);
app.mount(
Id::Clock,
Box::new(ClockComponent::default()),
vec![sub],
)?;
// ポート監視サブスクリプション
let sub = Sub::new(
SubEventClause::User(UserEvent::PortData),
SubClause::Always
);
グローバルリスナー
// グローバルキーバインド設定
app.active_global_listener(
Id::GlobalListener,
Box::new(GlobalListener::default()),
Sub::new(
SubEventClause::Keyboard(KeyCode::Char('q')),
SubClause::Always
)
)?;
アニメーション
use tuirealm::props::Color;
use std::time::Duration;
// アニメーション付きプログレスバー
#[derive(MockComponent)]
pub struct AnimatedProgress {
props: Props,
progress: f64,
animation_step: f64,
}
impl Component<Msg, NoUserEvent> for AnimatedProgress {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Tick => {
if self.progress < 1.0 {
self.progress += self.animation_step;
self.props.set(
Attribute::Value,
AttrValue::Number(self.progress)
);
Some(Msg::Redraw)
} else {
Some(Msg::AnimationComplete)
}
}
_ => None,
}
}
}
エコシステム
標準コンポーネント
- tuirealm-stdlib: 標準的なUIコンポーネント集
- カスタムコンポーネント: 独自コンポーネントの作成が容易
実例プロジェクト
- termscp: ターミナルSCP/SFTPファイル転送クライアント
- rustmission: Transmission BitTorrentクライアント
- music-player: ターミナル音楽プレーヤー
設計原則
- 宣言的UI: UIの状態を宣言的に記述
- 型安全: コンパイル時にエラーを検出
- コンポーネント指向: 再利用可能なUI部品
- テスタビリティ: モックとテストのサポート
利点
- 構造化: MVUパターンによる整理された設計
- 型安全: Rustの型システムを最大限活用
- 再利用性: コンポーネントベースの設計
- テスト容易性: モック機能による単体テスト
- 拡張性: カスタムコンポーネントの作成が簡単
制約事項
- 学習曲線: MVUパターンの理解が必要
- ボイラープレート: コンポーネント定義に多くのコード
- パフォーマンス: 抽象化レイヤーのオーバーヘッド
- 依存関係: Ratatuiに依存
他のライブラリとの比較
項目 | TUI Realm | Ratatui | Cursive |
---|---|---|---|
アーキテクチャ | MVU | イミディエート | リテインド |
抽象度 | 高 | 中 | 高 |
学習コスト | 高 | 中 | 低〜中 |
再利用性 | 非常に高 | 中 | 高 |
テスト性 | 非常に高 | 中 | 中 |
まとめ
TUI Realmは、React/Elmの開発経験を活かしてRustでTUIアプリケーションを構築したい開発者に最適です。MVUパターンとコンポーネントシステムにより、複雑なアプリケーションでも管理しやすい構造を実現できます。特に、大規模なTUIアプリケーションや、チーム開発において真価を発揮します。