1use std::collections::HashMap;
4
5use freya_winit::{
6 plugins::{
7 FreyaPlugin,
8 PluginEvent,
9 PluginHandle,
10 },
11 winit::window::Window,
12};
13use torin::prelude::Area;
14use wry::{
15 Rect,
16 WebView as WryWebView,
17 WebViewBuilder,
18};
19
20use crate::{
21 lifecycle::{
22 WebViewEvents,
23 WebViewLifecycleEvent,
24 },
25 registry::{
26 WebViewConfig,
27 WebViewId,
28 },
29};
30
31struct WebViewState {
33 webview: WryWebView,
34}
35
36pub struct WebViewPlugin {
41 webviews: HashMap<WebViewId, WebViewState>,
42 events: WebViewEvents,
43}
44
45impl Default for WebViewPlugin {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl WebViewPlugin {
52 pub fn new() -> Self {
54 Self {
55 webviews: HashMap::new(),
56 events: WebViewEvents::default(),
57 }
58 }
59
60 #[cfg(target_os = "linux")]
61 fn ensure_gtk_initialized(&mut self) {
62 if !gtk::is_initialized() {
64 if gtk::init().is_ok() {
65 tracing::debug!("WebViewPlugin: GTK initialized");
66 } else {
67 tracing::error!("WebViewPlugin: Failed to initialize GTK");
68 }
69 }
70 }
71
72 #[cfg(target_os = "linux")]
73 fn advance_gtk_event_loop(&self) -> bool {
74 if gtk::is_initialized() {
75 let mut processed = false;
77 while gtk::events_pending() {
78 gtk::main_iteration_do(false);
79 processed = true;
80 }
81 processed
82 } else {
83 false
84 }
85 }
86
87 fn create_webview(
88 &mut self,
89 window: &Window,
90 id: WebViewId,
91 config: &WebViewConfig,
92 area: &Area,
93 ) {
94 tracing::debug!(
95 "WebViewPlugin: Creating WebView {:?} with URL {}",
96 id,
97 config.url
98 );
99
100 let mut builder = WebViewBuilder::new();
101
102 if !config.url.is_empty() {
103 builder = builder.with_url(&config.url);
104 }
105
106 if config.transparent {
107 builder = builder.with_transparent(true);
108 }
109
110 if let Some(ref user_agent) = config.user_agent {
111 builder = builder.with_user_agent(user_agent);
112 }
113
114 if let Some(ref on_created) = config.on_created {
115 builder = (on_created)(builder)
116 }
117
118 let bounds = Rect {
120 position: wry::dpi::Position::Logical(area.origin.to_f64().to_tuple().into()),
121 size: wry::dpi::Size::Logical(area.size.to_f64().to_tuple().into()),
122 };
123 tracing::info!(
124 "WebViewPlugin: Creating WebView with bounds: pos=({}, {}), size=({}, {})",
125 area.min_x(),
126 area.min_y(),
127 area.width(),
128 area.height()
129 );
130 builder = builder.with_bounds(bounds).with_visible(true);
131
132 match builder.build_as_child(window) {
134 Ok(webview) => {
135 let _ = webview.set_visible(true);
137
138 self.webviews.insert(id, WebViewState { webview });
139 tracing::debug!("WebViewPlugin: WebView {:?} created successfully", id);
140 }
141 Err(e) => {
142 tracing::error!("WebViewPlugin: Failed to create WebView {:?}: {}", id, e);
143 }
144 }
145 }
146}
147
148impl FreyaPlugin for WebViewPlugin {
149 fn on_event(&mut self, event: &mut PluginEvent, _handle: PluginHandle) {
150 match event {
151 PluginEvent::RunnerCreated { runner, .. } => {
152 runner.provide_root_context(|| self.events.clone());
153 }
154 PluginEvent::WindowCreated { .. } => {
155 tracing::debug!("WebViewPlugin: Window created");
157 #[cfg(target_os = "linux")]
158 self.ensure_gtk_initialized();
159 }
160
161 PluginEvent::FinishedMeasuringLayout { window, .. } => {
162 let events: Vec<WebViewLifecycleEvent> =
164 self.events.lock().unwrap().drain(..).collect();
165
166 for event in events {
168 match event {
169 WebViewLifecycleEvent::Resized { id, area, config } => {
170 tracing::debug!("WebViewPlugin: Received Resized event for {:?}", id);
171 if let Some(state) = self.webviews.get_mut(&id) {
173 if let Err(e) = state.webview.set_visible(true) {
174 tracing::error!(
175 "WebViewPlugin: Failed to set visible {:?}: {}",
176 id,
177 e
178 );
179 }
180 let rect = Rect {
181 position: wry::dpi::Position::Logical(
182 area.origin.to_f64().to_tuple().into(),
183 ),
184 size: wry::dpi::Size::Logical(
185 area.size.to_f64().to_tuple().into(),
186 ),
187 };
188
189 let resize = match state.webview.bounds() {
190 Ok(r) => r != rect,
191 _ => true,
192 };
193 if resize && let Err(e) = state.webview.set_bounds(rect) {
194 tracing::error!(
195 "WebViewPlugin: Failed to set bounds for {:?}: {}",
196 id,
197 e
198 );
199 }
200 } else {
201 self.create_webview(window, id, &config, &area);
202 }
203 }
204 WebViewLifecycleEvent::Close { id } => {
205 tracing::debug!("WebViewPlugin: Received Close event for {:?}", id);
206 self.webviews.remove(&id);
207 }
208 WebViewLifecycleEvent::Hide { id } => {
209 tracing::debug!("WebViewPlugin: Received Hide event for {:?}", id);
210 if let Some(state) = self.webviews.get_mut(&id) {
211 let _ = state.webview.set_visible(false);
212 }
213 }
214 }
215 }
216 }
217
218 PluginEvent::WindowClosed { .. } => {
219 tracing::debug!(
221 "WebViewPlugin: Window closed, cleaning up {} WebViews",
222 self.webviews.len()
223 );
224 self.webviews.clear();
225 }
226
227 #[cfg(target_os = "linux")]
228 PluginEvent::AfterRedraw { window, .. } => {
229 self.advance_gtk_event_loop();
231 if !self.webviews.is_empty() {
233 window.request_redraw();
234 }
235 }
236
237 PluginEvent::BeforeRender { .. } => {
238 #[cfg(target_os = "linux")]
240 {
241 let _ = self.advance_gtk_event_loop();
242 }
243 }
244
245 _ => {}
246 }
247 }
248}