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?

科学と神々株式会社Advent Calendar 2024

Day 13

~GUIシステムの設計とウィジェット体系~

Last updated at Posted at 2024-12-12

第13回:Realé - GUI/OSフレームワーク(前編)

~GUIシステムの設計とウィジェット体系~

はじめに

Realéは、クロスプラットフォームに対応したGUI/OSフレームワークです。今回は基本的なGUIシステムの設計とウィジェット体系について解説します。

GUIシステムの設計

// GUIシステムのコア
pub struct GuiSystem {
    renderer: Renderer,
    event_system: EventSystem,
    window_manager: WindowManager,
    theme_manager: ThemeManager,
}

impl GuiSystem {
    pub async fn new(config: GuiConfig) -> Result<Self> {
        let renderer = Renderer::new(&config.renderer_config)?;
        let event_system = EventSystem::new();
        let window_manager = WindowManager::new(&config.window_config)?;
        let theme_manager = ThemeManager::new(&config.theme_config)?;
        
        Ok(Self {
            renderer,
            event_system,
            window_manager,
            theme_manager,
        })
    }
    
    pub async fn run(&mut self) -> Result<()> {
        loop {
            // イベントの処理
            self.process_events().await?;
            
            // ウィンドウの更新
            self.window_manager.update().await?;
            
            // 描画
            self.render_frame().await?;
            
            // フレーム同期
            self.synchronize_frame().await?;
        }
    }
}

// レンダリングシステム
pub struct Renderer {
    graphics_context: GraphicsContext,
    render_queue: RenderQueue,
    shader_cache: ShaderCache,
}

impl Renderer {
    pub async fn render_frame(&mut self, scene: &Scene) -> Result<()> {
        // シーンの準備
        let render_commands = self.prepare_scene(scene)?;
        
        // コマンドのキューイング
        for cmd in render_commands {
            self.render_queue.push(cmd)?;
        }
        
        // 描画の実行
        self.graphics_context.execute_commands(&self.render_queue).await?;
        
        // キューのクリア
        self.render_queue.clear();
        
        Ok(())
    }
}

ウィジェット体系

// ウィジェットの基本トレイト
pub trait Widget: Send + Sync {
    fn id(&self) -> WidgetId;
    fn bounds(&self) -> Rect;
    fn parent(&self) -> Option<WidgetId>;
    fn children(&self) -> &[WidgetId];
    
    fn layout(&mut self, constraints: BoxConstraints) -> Size;
    fn paint(&self, context: &mut PaintContext);
    fn handle_event(&mut self, event: &Event) -> bool;
}

// ウィジェットツリー
pub struct WidgetTree {
    root: WidgetId,
    nodes: HashMap<WidgetId, Box<dyn Widget>>,
    layout_cache: LayoutCache,
}

impl WidgetTree {
    pub fn layout(&mut self) -> Result<()> {
        // レイアウトの再計算が必要なウィジェットを特定
        let dirty_widgets = self.layout_cache.get_dirty_widgets();
        
        // レイアウトの更新
        for widget_id in dirty_widgets {
            self.update_layout(widget_id)?;
        }
        
        Ok(())
    }
    
    fn update_layout(&mut self, widget_id: WidgetId) -> Result<Size> {
        let widget = self.nodes.get_mut(&widget_id)
            .ok_or(Error::WidgetNotFound(widget_id))?;
            
        // 子ウィジェットのレイアウトを先に計算
        let mut child_sizes = Vec::new();
        for &child_id in widget.children() {
            let child_size = self.update_layout(child_id)?;
            child_sizes.push(child_size);
        }
        
        // 親ウィジェットのレイアウトを計算
        let constraints = self.get_constraints(widget_id)?;
        let size = widget.layout(constraints);
        
        // キャッシュの更新
        self.layout_cache.update(widget_id, size);
        
        Ok(size)
    }
}

// 基本的なウィジェットの実装
pub struct Container {
    id: WidgetId,
    bounds: Rect,
    parent: Option<WidgetId>,
    children: Vec<WidgetId>,
    style: ContainerStyle,
}

impl Widget for Container {
    fn layout(&mut self, constraints: BoxConstraints) -> Size {
        // コンテナのサイズを計算
        let size = if let Some(fixed_size) = self.style.fixed_size {
            constraints.constrain(fixed_size)
        } else {
            constraints.max
        };
        
        self.bounds = Rect::new(Point::origin(), size);
        size
    }
    
    fn paint(&self, context: &mut PaintContext) {
        // 背景の描画
        if let Some(background) = &self.style.background {
            context.fill_rect(self.bounds, background);
        }
        
        // 境界線の描画
        if let Some(border) = &self.style.border {
            context.stroke_rect(self.bounds, border);
        }
    }
    
    fn handle_event(&mut self, event: &Event) -> bool {
        match event {
            Event::MouseDown(position) if self.bounds.contains(*position) => {
                // クリックイベントの処理
                true
            }
            _ => false,
        }
    }
}

イベント処理

// イベントシステム
pub struct EventSystem {
    listeners: HashMap<EventType, Vec<Box<dyn EventListener>>>,
    event_queue: EventQueue,
}

impl EventSystem {
    pub async fn process_events(&mut self) -> Result<()> {
        while let Some(event) = self.event_queue.pop() {
            // イベントタイプに対応するリスナーを取得
            let listeners = self.listeners.get(&event.event_type())
                .ok_or(Error::NoListenerFound)?;
                
            // リスナーに通知
            for listener in listeners {
                listener.handle_event(&event).await?;
            }
        }
        Ok(())
    }
    
    pub fn register_listener<L: EventListener + 'static>(
        &mut self,
        event_type: EventType,
        listener: L,
    ) {
        self.listeners.entry(event_type)
            .or_default()
            .push(Box::new(listener));
    }
}

// イベントリスナーの実装例
pub struct ButtonListener {
    button_id: WidgetId,
    callback: Box<dyn Fn() + Send + Sync>,
}

impl EventListener for ButtonListener {
    async fn handle_event(&self, event: &Event) -> bool {
        match event {
            Event::MouseClick(position) => {
                if self.is_within_bounds(*position) {
                    (self.callback)();
                    true
                } else {
                    false
                }
            }
            _ => false,
        }
    }
}

実装例:カスタムウィジェットの実装

// カスタムウィジェットの実装例
pub struct CustomButton {
    id: WidgetId,
    bounds: Rect,
    label: String,
    state: ButtonState,
    style: ButtonStyle,
}

impl Widget for CustomButton {
    fn layout(&mut self, constraints: BoxConstraints) -> Size {
        // テキストサイズに基づいたレイアウト計算
        let text_size = self.measure_text(&self.label);
        let padding = self.style.padding;
        
        let size = Size::new(
            text_size.width + padding.horizontal(),
            text_size.height + padding.vertical(),
        );
        
        let constrained_size = constraints.constrain(size);
        self.bounds = Rect::new(Point::origin(), constrained_size);
        constrained_size
    }
    
    fn paint(&self, context: &mut PaintContext) {
        // 背景の描画
        let background_color = match self.state {
            ButtonState::Normal => self.style.normal_color,
            ButtonState::Hovered => self.style.hover_color,
            ButtonState::Pressed => self.style.pressed_color,
        };
        
        context.fill_rounded_rect(self.bounds, self.style.corner_radius, background_color);
        
        // テキストの描画
        let text_layout = context.create_text_layout(&self.label, self.style.font)?;
        let text_position = self.calculate_text_position(&text_layout);
        context.draw_text(&text_layout, text_position, self.style.text_color);
    }
    
    fn handle_event(&mut self, event: &Event) -> bool {
        match event {
            Event::MouseEnter => {
                self.state = ButtonState::Hovered;
                true
            }
            Event::MouseLeave => {
                self.state = ButtonState::Normal;
                true
            }
            Event::MouseDown if self.bounds.contains(event.position()) => {
                self.state = ButtonState::Pressed;
                true
            }
            Event::MouseUp if self.state == ButtonState::Pressed => {
                self.state = ButtonState::Hovered;
                // クリックイベントの発火
                true
            }
            _ => false,
        }
    }
}

// 使用例
fn create_custom_ui() -> Result<WidgetTree> {
    let mut tree = WidgetTree::new();
    
    // ルートコンテナの作成
    let root = Container::new()
        .with_style(ContainerStyle {
            background: Some(Color::WHITE),
            padding: Padding::all(16.0),
            ..Default::default()
        });
    
    // カスタムボタンの追加
    let button = CustomButton::new()
        .with_label("Click me!")
        .with_style(ButtonStyle {
            normal_color: Color::BLUE,
            hover_color: Color::LIGHT_BLUE,
            pressed_color: Color::DARK_BLUE,
            text_color: Color::WHITE,
            corner_radius: 8.0,
            padding: Padding::symmetric(16.0, 8.0),
            font: Font::default(),
        });
    
    tree.add_widget(button, Some(root.id()))?;
    
    Ok(tree)
}

今回のまとめ

  1. 柔軟なGUIシステムの基本設計
  2. 拡張可能なウィジェット体系
  3. 効率的なイベント処理システム
  4. カスタムウィジェットの実装例

次回予告

第14回では、realeのOS抽象化レイヤーとプロセス制御について解説します。クロスプラットフォーム対応とリソース管理の実装について詳しく見ていきます。

参考資料

  • GUI Framework Design
  • Widget System Architecture
  • Event-Driven Programming
  • Cross-Platform UI Development
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?