第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)
}
今回のまとめ
- 柔軟なGUIシステムの基本設計
- 拡張可能なウィジェット体系
- 効率的なイベント処理システム
- カスタムウィジェットの実装例
次回予告
第14回では、realeのOS抽象化レイヤーとプロセス制御について解説します。クロスプラットフォーム対応とリソース管理の実装について詳しく見ていきます。
参考資料
- GUI Framework Design
- Widget System Architecture
- Event-Driven Programming
- Cross-Platform UI Development