1use iced::advanced::{
2 self,
3 graphics::core::event,
4 layout,
5 renderer::{self},
6 widget::Tree,
7 Clipboard, Layout, Shell, Widget,
8};
9use iced::event::Status;
10use iced::keyboard;
11use iced::mouse::{self, Interaction};
12use iced::widget::image::{Handle, Image};
13use iced::{theme::Theme, Event, Length, Rectangle};
14use iced::{Element, Point, Size, Task};
15use url::Url;
16
17use crate::{engines, ImageInfo, PageType, ViewId};
18
19#[allow(missing_docs)]
20#[derive(Debug, Clone, PartialEq)]
21pub enum Action {
23 ChangeView(u32),
25 CloseCurrentView,
27 CloseView(u32),
29 CreateView(PageType),
31 GoBackward,
32 GoForward,
33 GoToUrl(Url),
34 Refresh,
35 SendKeyboardEvent(keyboard::Event),
36 SendMouseEvent(mouse::Event, Point),
37 Update,
39 Resize(Size<u32>),
40}
41
42pub struct WebView<Engine, Message>
44where
45 Engine: engines::Engine,
46{
47 engine: Engine,
48 view_size: Size<u32>,
49 current_view_index: Option<usize>, view_ids: Vec<ViewId>, on_close_view: Option<Message>,
52 on_create_view: Option<Message>,
53 on_url_change: Option<Box<dyn Fn(String) -> Message>>,
54 url: String,
55 on_title_change: Option<Box<dyn Fn(String) -> Message>>,
56 title: String,
57}
58
59impl<Engine: engines::Engine + Default, Message: Send + Clone + 'static> WebView<Engine, Message> {
60 fn get_current_view_id(&self) -> ViewId {
61 *self
62 .view_ids
63 .get(self.current_view_index.expect(
64 "The current view index is not currently set. Ensure you call the Action prior",
65 ))
66 .expect("Could find view index for current view. Maybe its already been closed?")
67 }
68
69 fn index_as_view_id(&self, index: u32) -> usize {
70 *self
71 .view_ids
72 .get(index as usize)
73 .expect("Failed to find that index, maybe its already been closed?")
74 }
75}
76
77impl<Engine: engines::Engine + Default, Message: Send + Clone + 'static> Default
78 for WebView<Engine, Message>
79{
80 fn default() -> Self {
81 WebView {
82 engine: Engine::default(),
83 view_size: Size {
84 width: 1920,
85 height: 1080,
86 },
87 current_view_index: None,
88 view_ids: Vec::new(),
89 on_close_view: None,
90 on_create_view: None,
91 on_url_change: None,
92 url: String::new(),
93 on_title_change: None,
94 title: String::new(),
95 }
96 }
97}
98
99impl<Engine: engines::Engine + Default, Message: Send + Clone + 'static> WebView<Engine, Message> {
100 pub fn new() -> Self {
102 Self::default()
103 }
104
105 pub fn on_create_view(mut self, on_create_view: Message) -> Self {
107 self.on_create_view = Some(on_create_view);
108 self
109 }
110
111 pub fn on_close_view(mut self, on_close_view: Message) -> Self {
113 self.on_close_view = Some(on_close_view);
114 self
115 }
116
117 pub fn on_url_change(mut self, on_url_change: impl Fn(String) -> Message + 'static) -> Self {
119 self.on_url_change = Some(Box::new(on_url_change));
120 self
121 }
122
123 pub fn on_title_change(
125 mut self,
126 on_title_change: impl Fn(String) -> Message + 'static,
127 ) -> Self {
128 self.on_title_change = Some(Box::new(on_title_change));
129 self
130 }
131
132 pub fn update(&mut self, action: Action) -> Task<Message> {
134 let mut tasks = Vec::new();
135
136 if self.current_view_index.is_some() {
137 if let Some(on_url_change) = &self.on_url_change {
138 let url = self.engine.get_url(self.get_current_view_id());
139 if self.url != url {
140 self.url = url.clone();
141 tasks.push(Task::done(on_url_change(url)))
142 }
143 }
144 if let Some(on_title_change) = &self.on_title_change {
145 let title = self.engine.get_title(self.get_current_view_id());
146 if self.title != title {
147 self.title = title.clone();
148 tasks.push(Task::done(on_title_change(title)))
149 }
150 }
151 }
152
153 match action {
154 Action::ChangeView(index) => {
155 {
157 self.view_size.width += 10;
158 self.view_size.height -= 10;
159 self.engine.resize(self.view_size);
160 self.view_size.width -= 10;
161 self.view_size.height += 10;
162 self.engine.resize(self.view_size);
163 self.engine
164 .request_render(self.index_as_view_id(index), self.view_size);
165 }
166 self.current_view_index = Some(index as usize);
167 }
168 Action::CloseCurrentView => {
169 self.engine.remove_view(self.get_current_view_id());
170 self.view_ids.remove(self.get_current_view_id());
171 if let Some(on_view_close) = &self.on_close_view {
172 tasks.push(Task::done(on_view_close.clone()));
173 }
174 }
175 Action::CloseView(index) => {
176 self.engine.remove_view(self.index_as_view_id(index));
177 self.view_ids.remove(self.index_as_view_id(index));
178
179 if let Some(on_view_close) = &self.on_close_view {
180 tasks.push(Task::done(on_view_close.clone()))
181 }
182 }
183 Action::CreateView(page_type) => {
184 let id = self.engine.new_view(self.view_size, Some(page_type));
185 self.view_ids.push(id);
186
187 if let Some(on_view_create) = &self.on_create_view {
188 tasks.push(Task::done(on_view_create.clone()))
189 }
190 }
191 Action::GoBackward => {
192 self.engine.go_back(self.get_current_view_id());
193 }
194 Action::GoForward => {
195 self.engine.go_forward(self.get_current_view_id());
196 }
197 Action::GoToUrl(url) => {
198 self.engine
199 .goto(self.get_current_view_id(), PageType::Url(url.to_string()));
200 }
201 Action::Refresh => {
202 self.engine.refresh(self.get_current_view_id());
203 }
204 Action::SendKeyboardEvent(event) => {
205 self.engine
206 .handle_keyboard_event(self.get_current_view_id(), event);
207 }
208 Action::SendMouseEvent(point, event) => {
209 self.engine
210 .handle_mouse_event(self.get_current_view_id(), event, point);
211 }
212 Action::Update => {
213 self.engine.update();
214 if self.current_view_index.is_some() {
215 self.engine
216 .request_render(self.get_current_view_id(), self.view_size);
217 }
218 return Task::batch(tasks);
219 }
220 Action::Resize(size) => {
221 self.view_size = size;
222 self.engine.resize(size);
223 }
224 };
225
226 if self.current_view_index.is_some() {
227 self.engine
228 .request_render(self.get_current_view_id(), self.view_size);
229 }
230
231 Task::batch(tasks)
232 }
233
234 pub fn view(&self) -> Element<Action> {
236 WebViewWidget::new(
237 self.engine.get_view(self.get_current_view_id()),
238 self.engine.get_cursor(self.get_current_view_id()),
239 )
240 .into()
241 }
242}
243
244struct WebViewWidget<'a> {
245 image_info: &'a ImageInfo,
246 cursor: Interaction,
247}
248
249impl<'a> WebViewWidget<'a> {
250 fn new(image_info: &'a ImageInfo, cursor: Interaction) -> Self {
251 Self { image_info, cursor }
252 }
253}
254
255impl<Renderer> Widget<Action, Theme, Renderer> for WebViewWidget<'_>
256where
257 Renderer: iced::advanced::image::Renderer<Handle = iced::advanced::image::Handle>,
258{
259 fn size(&self) -> Size<Length> {
260 Size {
261 width: Length::Fill,
262 height: Length::Fill,
263 }
264 }
265
266 fn layout(
267 &self,
268 _tree: &mut Tree,
269 _renderer: &Renderer,
270 limits: &layout::Limits,
271 ) -> layout::Node {
272 layout::Node::new(limits.max())
273 }
274
275 fn draw(
276 &self,
277 tree: &Tree,
278 renderer: &mut Renderer,
279 theme: &Theme,
280 style: &renderer::Style,
281 layout: Layout<'_>,
282 cursor: mouse::Cursor,
283 viewport: &Rectangle,
284 ) {
285 <Image<Handle> as Widget<Action, Theme, Renderer>>::draw(
286 &self.image_info.as_image(),
287 tree,
288 renderer,
289 theme,
290 style,
291 layout,
292 cursor,
293 viewport,
294 )
295 }
296
297 fn on_event(
298 &mut self,
299 _state: &mut Tree,
300 event: Event,
301 layout: Layout<'_>,
302 cursor: mouse::Cursor,
303 _renderer: &Renderer,
304 _clipboard: &mut dyn Clipboard,
305 shell: &mut Shell<'_, Action>,
306 _viewport: &Rectangle,
307 ) -> event::Status {
308 let size = Size::new(layout.bounds().width as u32, layout.bounds().height as u32);
309 if self.image_info.width != size.width || self.image_info.height != size.height {
310 shell.publish(Action::Resize(size));
311 }
312
313 match event {
314 Event::Keyboard(event) => {
315 shell.publish(Action::SendKeyboardEvent(event));
316 }
317 Event::Mouse(event) => {
318 if let Some(point) = cursor.position_in(layout.bounds()) {
319 shell.publish(Action::SendMouseEvent(event, point));
320 }
321 }
322 _ => (),
323 }
324 Status::Ignored
325 }
326
327 fn mouse_interaction(
328 &self,
329 _state: &Tree,
330 layout: Layout<'_>,
331 cursor: mouse::Cursor,
332 _viewport: &Rectangle,
333 _renderer: &Renderer,
334 ) -> mouse::Interaction {
335 if cursor.is_over(layout.bounds()) {
336 self.cursor
337 } else {
338 mouse::Interaction::Idle
339 }
340 }
341}
342
343impl<'a, Message: 'a, Renderer> From<WebViewWidget<'a>> for Element<'a, Message, Theme, Renderer>
344where
345 Renderer: advanced::Renderer + advanced::image::Renderer<Handle = advanced::image::Handle>,
346 WebViewWidget<'a>: Widget<Message, Theme, Renderer>,
347{
348 fn from(widget: WebViewWidget<'a>) -> Self {
349 Self::new(widget)
350 }
351}