Skip to main content

orinium_browser/browser/core/ui/
renderer.rs

1//! ブラウザの描画機能。タブと chrome の描画リクエストを DrawCommand に変換し、
2//! プラットフォームの GPU レンダラへ送る。
3
4use std::collections::HashMap;
5
6use crate::browser::core::ui::TabId;
7use crate::engine::renderer_model::{AffineTransform, DrawCommand, FillRule, Rect, rect_path};
8use crate::platform::renderer::gpu::GpuRenderer;
9
10use super::{BasicChrome, BasicContextMenu, Chrome, ContextMenu, RenderState};
11use crate::browser::core::tab::Tab;
12
13/// BrowserRenderer は実際の描画を担当する。
14///
15/// 責務:
16/// - アクティブタブのページとブラウザ chrome から DrawCommand を生成する
17/// - DevTools ペインが開いている場合は分割ビューとして両ペインを生成する
18/// - 開いているコンテキストメニューを最前面オーバーレイとして生成する
19/// - DrawCommand をプラットフォームの GPU レンダラへ渡し、描画を実行する
20/// - ウィンドウのサイズ・スケール・タイトルなどの描画状態を保持する
21///
22/// BrowserApp / BrowserUi は直接の描画実装を持たず、このレンダラへ処理を委譲する。
23#[derive(Debug)]
24pub struct BrowserRenderer {
25    /// ウィンドウの描画状態(DrawCommand、サイズ、スケール、タイトル)。
26    pub render_state: RenderState,
27    /// ブラウザ chrome(ツールバーなどの UI 描画)。既定は [`BasicChrome`]。
28    pub chrome: Box<dyn Chrome>,
29    /// WebView 右クリックで開くコンテキストメニュー。既定は [`BasicContextMenu`]。
30    pub menu: Box<dyn ContextMenu>,
31}
32
33impl Default for BrowserRenderer {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl BrowserRenderer {
40    /// 既定の [`BasicChrome`] と [`BasicContextMenu`] でレンダラを生成する。
41    pub fn new() -> Self {
42        Self::with_chrome(Box::new(BasicChrome::new()))
43    }
44
45    /// 任意の chrome 実装と既定の [`BasicContextMenu`] でレンダラを生成する。
46    pub fn with_chrome(chrome: Box<dyn Chrome>) -> Self {
47        Self::with_chrome_and_menu(chrome, Box::new(BasicContextMenu::new()))
48    }
49
50    /// ウィンドウの初期サイズ・スケール・タイトルでレンダラを生成する。
51    pub fn with_window(window_size: (u32, u32), scale_factor: f64, window_title: String) -> Self {
52        Self {
53            render_state: RenderState::new(window_size, scale_factor, window_title),
54            chrome: Box::new(BasicChrome::new()),
55            menu: Box::new(BasicContextMenu::new()),
56        }
57    }
58
59    /// 任意の chrome 実装と任意のコンテキストメニュー実装でレンダラを生成する。
60    pub fn with_chrome_and_menu(chrome: Box<dyn Chrome>, menu: Box<dyn ContextMenu>) -> Self {
61        Self {
62            render_state: RenderState::default(),
63            chrome,
64            menu,
65        }
66    }
67
68    /// ウィンドウのサイズ・スケール・タイトルを設定する。
69    pub fn set_window(&mut self, window_size: (u32, u32), scale_factor: f64, window_title: String) {
70        self.render_state.window_size = window_size;
71        self.render_state.scale_factor = scale_factor;
72        self.render_state.window_title = window_title;
73    }
74
75    /// 指定されたアクティブタブのレイアウトを更新し、ページと chrome の
76    /// DrawCommand を再生成する。
77    pub fn rebuild(&mut self, tabs: &mut HashMap<TabId, Tab>, active_id: Option<TabId>) {
78        let (width, height) = self.render_state.viewport();
79
80        let Rect {
81            x,
82            y,
83            width: content_width,
84            height: content_height,
85        } = self.chrome.content_rect(width, height);
86
87        // Reuse allocation
88        let mut draw_commands = std::mem::take(&mut self.render_state.draw_commands);
89        draw_commands.clear();
90
91        // Page area: below the chrome, clipped so page content never overlaps it.
92        draw_commands.push(DrawCommand::PushClip {
93            path: rect_path(x, y, content_width, content_height),
94            rule: FillRule::NonZero,
95        });
96        draw_commands.push(DrawCommand::PushTransform {
97            transform: AffineTransform::translate(x, y),
98        });
99
100        let title = if let Some(active_tab) = active_id
101            && let Some(tab) = tabs.get_mut(&active_tab)
102        {
103            // Keep the chrome in sync with the active tab.
104            let url = tab.document_url().map(|url| url.to_string());
105            self.chrome.sync_url(url.as_deref());
106
107            tab.draw(&mut draw_commands, content_width, content_height);
108
109            tab.title()
110        } else {
111            None
112        };
113
114        draw_commands.push(DrawCommand::PopTransform);
115        draw_commands.push(DrawCommand::PopClip);
116
117        // Chrome drawn on top of the page area.
118        self.chrome.draw(&mut draw_commands, width, height);
119
120        // The context menu is drawn last: topmost overlay above the chrome.
121        self.menu.draw(&mut draw_commands, width, height);
122
123        // Return reused buffer
124        self.render_state.draw_commands = draw_commands;
125
126        if let Some(title) = title {
127            self.render_state.window_title = title;
128        }
129    }
130
131    /// 現在の DrawCommand を GPU レンダラへ送る。
132    pub fn apply_draw_commands(&self, gpu: &mut GpuRenderer) {
133        gpu.parse_draw_commands(&self.render_state.draw_commands);
134    }
135
136    /// DrawCommand を再生成して GPU に送り、実際の描画を実行する。
137    pub fn redraw(
138        &mut self,
139        tabs: &mut HashMap<TabId, Tab>,
140        active_id: Option<TabId>,
141        gpu: &mut GpuRenderer,
142    ) {
143        self.rebuild(tabs, active_id);
144        self.apply_draw_commands(gpu);
145        if let Err(e) = gpu.render() {
146            log::error!(target: "BrowserRenderer::redraw", "Render error occurred: {}", e);
147        }
148    }
149}