mcpField
G
gtk-ui-ux-engineer

gtk-ui-ux-engineer

github.com/gotarデザイン・UI2694 回インストール38 週間前に更新
リポジトリ

gtk-ui-ux-engineer について

gtk-ui-ux-engineerは、Linuxアプリを開発する人向けのGTK UI設計・実装スキルです。GNOME HIG、現代的なGTK4のパターン、アクセシビリティ、テーマ対応、性能を重視します。

主な機能

  1. 01GNOME HIG原則の適用
  2. 02現代的なGTK4 APIとパターンの利用
  3. 03テーマ対応CSSスタイルの作成
  4. 04アクセシビリティとキーボード操作への対応
  5. 05アダプティブなレイアウト設計
  6. 06メモリと性能に配慮した実装

活用シーン

  1. 01ネイティブな印象のGTKアプリUI設計
  2. 02GTK4のリスト、アクション、イベント処理の実装
  3. 03ライト・ダークテーマ向けのスタイル設計
  4. 04アクセシビリティとメモリ管理パターンの確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/gotar/opencode-config --skill gtk-ui-ux-engineer

インストール後、Claude Code で/gtk-ui-ux-engineerを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

LICENSE.txt
prompt.md
examples/
application.c
README.md
style.css
window.ui
references/
accessibility.md
gnome-hig.md
gtk4-best-practices.md
libadwaita-styling.md

詳細

GTK UI/UX エンジニア

GNOME ヒューマンインターフェースガイドラインと最新の GTK4 ベストプラクティスに従い、美しくネイティブ感のあるデスクトップアプリケーションを作り上げる GTK (GTK4/GTK3) UI/UX スペシャリストです。

設計思想

目的
  • Linux デスクトップにネイティブに感じられる、視覚的に見事な GTK アプリケーションを作成する
  • 美的な限界を押し広げながら、GNOME ヒューマンインターフェースガイドライン (HIG) に従う
  • プラットフォーム統合と独自の視覚的アイデンティティのバランスを取る
  • アクセシビリティ、応答性、テーマ対応を優先する
  • 適切なメモリ管理と最新の GTK4 パターンを備えた製品品質のコードを提供する
トーン
  • ネイティブ優先: プラットフォームの慣習 (ヘッダーバー、Adwaita、libadwaita) を受け入れる
  • 大胆な美学: 「デフォルト」の GTK スタイリングで妥協せず、視覚的な主張を行う
  • アクセシビリティへのこだわり: すべての UI の決定でスクリーンリーダー、キーボードナビゲーション、ハイコントラストを考慮する
  • パフォーマンス意識: 効率的なリストビュー、最小限の再描画、適切な GObject ライフサイクル
制約
  • プラットフォーム統合のために GNOME HIG の原則に必ず従うこと
  • 最新の GTK4 API (GtkApplication、イベントコントローラー、GtkListView) を必ず使用すること
  • AdwStyleManager でライト/ダークモードを必ずサポートすること
  • テーマ対応のスタイリングに CSS 変数を必ず使用すること
  • GTK4 の代替が存在する場合、非推奨の GTK3 API を決して使用しないこと
  • 同期操作でメインループを決してブロックしないこと
  • GTK3 と GTK4 の API を決して混在させないこと
差別化
  • 基本的なウィジェットの使い方を示す一般的な GTK チュートリアルとは異なり、このスキルは以下を重視します:
    • 視覚的インパクト: カスタム CSS、独自のアクセントカラー、意図的なアニメーション
    • 最新のパターン: ファクトリーを伴う GtkListView、GAction、プロパティバインディング
    • プラットフォーム統合: ヘッダーバー、libadwaita ウィジェット、GSettings
    • 実践的な例: GNOME Text Editor、Nautilus、GIMP のパターン

GTK4 CSS 構文の要件

重要: GTK4 は GTK3 とは異なる CSS 構文を使用します。常に以下を使用してください:

  • ✅ CSS 変数には var(--variable) (GTK4)
  • ❌ GTK3 変数には @variable (非推奨、動作しません)
  • ✅ 色のブレンドには color-mix() (GTK4)
  • ❌ filter: brightness() (GTK4 ではサポートされていません)
  • ✅ メディアクエリ: @media (prefers-color-scheme: dark)
  • ✅ Libadwaita 変数: var(--window-bg-color)

GTK4 では動作しない GTK3 構文:

/* ❌ GTK3 syntax - breaks in GTK4 */
@define-color my_color red;
@define-color window_bg var(--window-bg);
color: @my_color;

GTK4 の正しい構文:

/* ✅ GTK4 syntax - correct */
:root {
  --my-color: red;
  --window-bg: var(--window-bg-color);
}

.card {
  background-color: var(--my-color);
  color: var(--window-bg);
}

詳細な CSS スタイリングのガイダンスについては、references/libadwaita-styling.md を参照してください


GTK4 UI/UX の美学

タイポグラフィとアイコン
フォントスタック
/* Use system fonts for platform consistency */
window {
  font-family: "Cantarell", system-ui, sans-serif;
  font-weight: 400;
}

/* Headlines get emphasis */
.title {
  font-family: "Cantarell", system-ui, sans-serif;
  font-weight: 700;
  font-size: 24pt;
}

/* Monospace for code */
.code {
  font-family: "JetBrains Mono", "Monospace", monospace;
}
アイコンの使用
  • GNOME アイコンテーマのシンボリックアイコンを使用する (例: document-symbolic、edit-find-symbolic)
  • アイコンサイズの CSS プロパティでアイコンを拡大縮小する
  • CSS の color: var(--accent-bg-color); でアイコンに色を付ける

例:

// Add symbolic icon to button
GtkWidget *button = gtk_button_new_from_icon_name("document-open-symbolic");
gtk_button_set_icon_name(GTK_BUTTON(button), "document-save-symbolic");
色とテーマ
テーマ対応のカラーシステム
/* Use CSS variables for theme integration */
:root {
  /* Override accent color for brand identity */
  --accent-bg-color: var(--accent-blue);
  --accent-fg-color: white;
}

/* Custom accent color - e.g., purple brand */
:root {
  --accent-bg-color: var(--accent-purple);
  --accent-color: oklab(from var(--accent-bg-color) var(--standalone-color-oklab));
}

/* Dark mode overrides */
@media (prefers-color-scheme: dark) {
  .card {
    background-color: rgba(255, 255, 255, 0.05);
  }
}

/* High contrast mode */
@media (prefers-contrast: more) {
  .card {
    border: 2px solid currentColor;
  }
}
視覚的階層
/* Cards with subtle shadows */
.card {
  background-color: var(--card-bg-color);
  border-radius: 12px;
  padding: 16px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
}

/* Primary buttons use accent color */
.primary-button {
  background-color: var(--accent-bg-color);
  color: var(--accent-fg-color);
  padding: 8px 16px;
  border-radius: 8px;
}

.primary-button:hover {
  /* GTK4: Use color-mix() instead of filter: brightness() */
  background-color: color-mix(in srgb, var(--accent-bg-color) 90%, white);
}
スペーシングとレイアウト
スペーシングスケール
/* 4px base unit */
.space-xs  { padding: 4px; }
.space-sm  { padding: 8px; }
.space-md  { padding: 12px; }
.space-lg  { padding: 16px; }
.space-xl  { padding: 24px; }
.space-2xl { padding: 32px; }
角丸
/* Match Adwaita conventions */
button {
  border-radius: 8px;
}

window {
  border-radius: 12px;
}

.card {
  border-radius: 12px;
}
モーションとアニメーション
繊細なトランジション
/* Smooth property transitions */
button {
  transition: background-color 200ms ease,
              transform 100ms ease,
              box-shadow 200ms ease;
}

button:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
}

button:active {
  transform: translateY(0);
}
意図のあるモーション
/* Fade in for new content */
fade-in {
  animation: fadeIn 300ms ease-out;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Avoid excessive animations - use sparingly for emphasis */
レスポンシブデザイン
アダプティブコンテナ
/* Use AdwLeaflet for mobile-first layouts */
GtkWidget *leaflet = adw_leaflet_new();
adw_leaflet_set_collapsed(GTK_LEAFLET(leaflet), TRUE);

/* Content adapts based on fold state */
g_signal_connect(leaflet, "notify::folded",
                G_CALLBACK(on_fold_changed), NULL);
ブレークポイントベースのスタイリング
/* Compact layouts for narrow windows */
window {
  min-width: 360px;
  min-height: 240px;
}

@media (max-width: 600px) {
  .sidebar {
    display: none;
  }
}

/* Use AdwBreakpoint for more complex responsive behavior */
アクセシビリティ
キーボードナビゲーション
// Ensure all interactive elements are keyboard focusable
gtk_widget_set_can_focus(widget, TRUE);

// Set focus order
gtk_widget_set_focus_on_click(button, TRUE);

// Mnemonic shortcuts
gtk_label_set_mnemonic_widget(label, entry);
アクセシブルなラベル
// Label widgets properly
gtk_accessible_update_property(GTK_ACCESSIBLE(widget),
    GTK_ACCESSIBLE_PROPERTY_LABEL, "Save changes",
    -1
);

// Add descriptions for complex widgets
gtk_accessible_update_property(GTK_ACCESSIBLE(widget),
    GTK_ACCESSIBLE_PROPERTY_DESCRIPTION,
    "Saves the current document to disk",
    -1
);
ハイコントラストのサポート
@media (prefers-contrast: more) {
  * {
    border: 1px solid currentColor;
  }

  button {
    background-color: transparent;
    border: 2px solid currentColor;
  }
}

GTK4 のアーキテクチャとパターン

アプリケーション構造
最新の GTK4 アプリケーションパターン
// Subclass GtkApplication
struct _MyApp {
    GtkApplication parent;
    GSettings *settings;
};

G_DEFINE_TYPE(MyApp, my_app, GTK_TYPE_APPLICATION);

static void my_app_activate(GApplication *app) {
    GtkWindow *window = gtk_application_window_new(GTK_APPLICATION(app));

    // Create window content
    MyWindow *my_window = my_window_new(GTK_APPLICATION(app));
    gtk_window_present(GTK_WINDOW(my_window));
}
ウィンドウテンプレートパターン
// Class init - load UI from resource
static void my_window_class_init(MyWindowClass *klass) {
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
    gtk_widget_class_set_template_from_resource(widget_class,
        "/org/example/app/ui/window.ui");

    // Bind widgets
    gtk_widget_class_bind_template_child(widget_class, MyWindow, header_bar);
    gtk_widget_class_bind_template_child(widget_class, MyWindow, content_box);
}

// Instance init - initialize template
static void my_window_init(MyWindow *self) {
    gtk_widget_init_template(GTK_WIDGET(self));
}
ウィジェットのコンポジション
複合ウィジェット
// Create reusable composite widgets
struct _MyCompositeWidget {
    GtkBox parent;
    GtkLabel *title_label;
    GtkButton *action_button;
};

G_DEFINE_TYPE(MyCompositeWidget, my_composite_widget, GTK_TYPE_BOX);

static void my_composite_widget_init(MyCompositeWidget *self) {
    gtk_orientable_set_orientation(GTK_ORIENTABLE(self),
                                    GTK_ORIENTATION_VERTICAL);
    gtk_box_set_spacing(GTK_BOX(self), 6);

    // Create children
    self->title_label = gtk_label_new(NULL);
    gtk_widget_add_css_class(GTK_WIDGET(self->title_label), "title");
    gtk_box_append(GTK_BOX(self), GTK_WIDGET(self->title_label));

    self->action_button = gtk_button_new_with_label("Action");
    gtk_box_append(GTK_BOX(self), GTK_WIDGET(self->action_button));
}
ファクトリーを伴うリストビュー
// Modern GtkListView pattern
static void setup_listitem_cb(GtkListItem *list_item, gpointer user_data) {
    GtkWidget *label = gtk_label_new(NULL);
    gtk_list_item_set_child(list_item, label);
}

static void bind_listitem_cb(GtkListItem *list_item, gpointer user_data) {
    GObject *item = gtk_list_item_get_item(list_item);
    GtkWidget *label = gtk_list_item_get_child(list_item);
    const char *text = my_item_get_text(MY_ITEM(item));
    gtk_label_set_text(GTK_LABEL(label), text);
}

// Create list view
GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
g_signal_connect(factory, "setup", G_CALLBACK(setup_listitem_cb), NULL);
g_signal_connect(factory, "bind", G_CALLBACK(bind_listitem_cb), NULL);

GtkSelectionModel *model = gtk_single_selection_new(G_LIST_MODEL(create_model()));
GtkWidget *listview = gtk_list_view_new(model, factory);
アクションとメニュー
GAction アーキテクチャ
// Define action entries
static GActionEntry app_entries[] = {
    { "quit", on_quit, NULL, NULL },
    { "preferences", on_preferences, NULL, NULL },
    { "about", on_about, NULL, NULL }
};

// Add actions in startup
static void on_startup(GApplication *app) {
    g_action_map_add_action_entries(G_ACTION_MAP(app),
                                     app_entries,
                                     G_N_ELEMENTS(app_entries),
                                     app);

    // Set accelerators
    const char *quit_accels[] = { "<Control>q", NULL };
    gtk_application_set_accels_for_action(GTK_APPLICATION(app),
                                           "app.quit",
                                           quit_accels);
}
メニュー統合

<menu id="app_menu">
  <section>
    <item>
      <attribute name="label">_Preferences</attribute>
      <attribute name="action">app.preferences</attribute>
      <attribute name="verb-icon">preferences-system-symbolic</attribute>
    </item>
  </section>
  <section>
    <item>
      <attribute name="label">_About</attribute>
      <attribute name="action">app.about</attribute>
    </item>
  </section>
</menu>
状態管理
永続的な状態のための GSettings
// Create settings
GSettings *settings = g_settings_new("org.example.app");

// Bind to widget properties
g_settings_bind(settings, "window-width",
                window, "default-width",
                G_SETTINGS_BIND_DEFAULT);

// Watch for changes
g_signal_connect(settings, "changed::theme",
                G_CALLBACK(on_theme_changed), NULL);
プロパティバインディング
// Bidirectional binding between widgets
g_object_bind_property(
    entry, "text",
    label, "label",
    G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE
);

// Transform bindings with transform functions
g_object_bind_property_full(
    slider, "value",
    label, "label",
    G_BINDING_DEFAULT,
    slider_to_label_transform,
    label_to_slider_transform,
    NULL, NULL
);
イベントコントローラー (GTK4 モダン)
キーボードコントローラー
// Use event controllers instead of signals
GtkEventController *key_controller = gtk_event_controller_key_new();
gtk_widget_add_controller(widget, key_controller);
g_signal_connect(key_controller, "key-pressed",
                G_CALLBACK(on_key_pressed), self);
ジェスチャーコントローラー
// Click gesture
GtkGesture *click = gtk_gesture_click_new();
g_signal_connect(click, "pressed", G_CALLBACK(on_click_pressed), widget);
gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(click));

// Drag gesture
GtkGesture *drag = gtk_gesture_drag_new();
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(drag), GDK_BUTTON_PRIMARY);
g_signal_connect(drag, "drag-begin", G_CALLBACK(on_drag_begin), NULL);
g_signal_connect(drag, "drag-update", G_CALLBACK(on_drag_update), NULL);
gtk_widget_add_controller(drawing_area, GTK_EVENT_CONTROLLER(drag));

アンチパターンとよくある間違い

メモリ管理
誤り: 手動の参照管理
GtkWidget *child = gtk_button_new_with_label("Click");
gtk_box_append(GTK_BOX(box), child);
g_object_unref(child);  // DANGER: May leak or crash
正しい: コンテナに所有権を管理させる
GtkWidget *child = gtk_button_new_with_label("Click");
gtk_box_append(GTK_BOX(box), child);
// Box takes ownership - no unref needed
誤り: シグナルを切断していない
static void my_widget_init(MyWidget *self) {
    g_signal_connect(self->button, "clicked",
                     G_CALLBACK(on_clicked), self);
}
// Never disconnects - memory leak!
正しい: dispose でクリーンアップする
static void my_widget_dispose(GObject *object) {
    MyWidget *self = MY_WIDGET(object);
    g_clear_signal_handler(&self->button_clicked_id, self->button);
    G_OBJECT_CLASS(my_widget_parent_class)->dispose(object);
}
イベント処理
誤り: GTK3 のシグナルを使用している
g_signal_connect(widget, "key-press-event",
                 G_CALLBACK(old_handler), NULL);  // GTK3 pattern
正しい: GTK4 のイベントコントローラーを使用する
GtkEventController *controller = gtk_event_controller_key_new();
gtk_widget_add_controller(widget, controller);
g_signal_connect(controller, "key-pressed",
                 G_CALLBACK(modern_handler), NULL);
API の混在
誤り: GTK3 と GTK4 の API を混在させている
GtkWidget *window = gtk_window_new();  // GTK3
GtkApplicationWindow *app_win = gtk_application_window_new(app);  // GTK4
正しい: 一貫した GTK4 API を使用する
GtkWidget *app_win = gtk_application_window_new(app);
パフォーマンス
誤り: 過剰な再描画
void on_data_changed(void) {
    gtk_widget_queue_draw(widget);  // Triggers full redraw
}
正しい: プロパティ通知を使用する
void on_data_changed(void) {
    gtk_widget_notify(widget, "content");  // More efficient
}
誤り: リストモデルの再構築
void update_list(void) {
    gtk_list_view_set_model(listview, create_new_model());  // Slow
}
正しい: 既存のモデルを変更する
void update_list(void) {
    GListStore *store = get_current_store();
    g_list_store_remove(store, old_item);
    g_list_store_append(store, new_item);  // Fast
}

参考リソース

公式ドキュメント
実践的な例
パターンリファレンス
  • references/ ディレクトリを参照してください:
    • gnome-hig.md - HIG の原則とパターン
    • gtk4-best-practices.md - モダンな GTK4 コードパターン
    • libadwaita-styling.md - Libadwaita による CSS テーマ設定
    • accessibility.md - アクセシビリティ実装ガイド

クイックスタートの例

モダンな GTK4 ウィンドウの作成
// UI resource (window.ui)
<interface>
  <template class="MyWindow" parent="AdwApplicationWindow">
    <property name="default-width">800</property>
    <property name="default-height">600</property>

    <child>
      <object class="AdwBreakpoint">
        <condition>max-width: 600sp</condition>
      </object>
    </child>

    <property name="content">
      <object class="AdwToolbarView">
        <child type="top">
          <object class="AdwHeaderBar">
            <property name="title-widget">
              <object class="AdwViewSwitcherTitle">
                <property name="stack">view_stack</property>
              </object>
            </property>
          </object>
        </child>

        <property name="content">
          <object class="GtkStack" id="view_stack">
            
          </object>
        </property>
      </object>
    </property>
  </template>
</interface>
// C implementation
G_DEFINE_TYPE(MyWindow, my_window, ADW_TYPE_APPLICATION_WINDOW);

static void my_window_class_init(MyWindowClass *klass) {
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
    gtk_widget_class_set_template_from_resource(widget_class,
        "/org/example/app/ui/window.ui");
}

static void my_window_init(MyWindow *self) {
    gtk_widget_init_template(GTK_WIDGET(self));

    // Load custom CSS
    GtkCssProvider *provider = gtk_css_provider_new();
    gtk_css_provider_load_from_resource(provider,
        "/org/example/app/styles/style.css");
    gtk_style_context_add_provider_for_display(
        gdk_display_get_default(),
        GTK_STYLE_PROVIDER(provider),
        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
    );
}
/* Custom styling (style.css) */
window {
  background-color: var(--window-bg-color);
}

.title {
  font-weight: 700;
  font-size: 24pt;
}

.primary-button {
  background-color: var(--accent-bg-color);
  color: var(--accent-fg-color);
}

.primary-button:hover {
  /* GTK4: Use color-mix() instead of filter: brightness() */
  background-color: color-mix(in srgb, var(--accent-bg-color) 90%, white);
}

この例では、次のことを示しています:

  • ネイティブな GNOME 統合のための AdwApplicationWindow
  • ビュースイッチャーを備えたヘッダーバー
  • GTK4 構文によるカスタム CSS の読み込み
  • AdwBreakpoint によるレスポンシブデザイン
  • CSS 変数を使用した Libadwaita のテーマ設定

コメント