1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
//! This module contains all the important structs and implementations to create, configure
//! and run a webview window.

use std::sync::{Arc, Mutex};
#[cfg(target_os = "linux")]
use std::rc::Rc;

use include_dir::Dir;

use crate::{application::Application, bounds::Bounds, params::Params, request::Request};

#[cfg(target_os = "linux")]
use crate::linux::{webview::WebView as WebViewImpl, webkitview::WebViewHandle as WebViewHandleImpl};
#[cfg(target_os = "windows")]
use crate::windows::webview::{WebView as WebViewImpl, WebViewHandle as WebViewHandleImpl};

/// WebView is a Window running as program including a web view
/// 
/// WebView has to be built with the help of the ```WebView::builder``` function
#[derive(Clone)]
pub struct WebView {
    pub(crate) webview: WebViewImpl
}

/// With the help of this WebViewHandle you can evaluate script in the WebView (via WebView::eval)
/// 
/// You can retrieve a WebViewHandle via WebView::get_handle
#[derive(Clone)]
pub struct WebViewHandle {
    pub(crate) handle: WebViewHandleImpl
}

impl WebView {
    /// Creates a ```WebViewBuilder``` to construct a WebView.
    /// 
    /// Call several WebViewBuilder functions and create the WebView with ```build()```
    /// 
    /// # Example
    /// 
    /// ```
    /// #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
    /// Allows console to show up in debug build but not release build.
    ///
    /// use webview_app::webview::WebView;
    /// 
    /// fn on_activate(app: &Application)->WebView {
    ///     let webview = 
    ///         WebView::builder()
    ///             .appid("de.uriegel.hello")
    ///             .title("Rust Web View 🦞")
    ///             .url("https://crates.io/crates/webview_app")
    ///             .build();
    ///     webview
    /// }
    /// ```
    pub fn builder(app: &Application)->WebViewBuilder {
            WebViewBuilder { 
            title: None,
            app: app.clone(),
            url: None,
            debug_url: None,
            query_string: None,
            width: None,
            height: None,
            save_bounds: false,
            #[cfg(target_os = "linux")]
            builder_path: None,
            #[cfg(target_os = "linux")]
            with_builder: None,
            without_native_titlebar: false,
            devtools: false,
            default_contextmenu : true,
            webroot: None,
        }
    }

    /// Sets a callback which is invoked on closing the app
    /// 
    /// You can prevent closing the app when returning false
    /// 
    /// # Example
    /// 
    /// ```
    /// let can_close = true;
    /// ...
    /// webview.can_close(move ||can_close);
    /// ```
    pub fn can_close(&self, val: impl Fn()->bool + 'static) {
        self.webview.can_close(val);
    }

    /// When the webview is created, this callback is being called. You can then install WebView requests.
    /// 
    /// The Callback function has the following parameters: a request object, the request id, the command id (the Webview method) and the 
    /// JSON payload.
    /// 
    /// A true return value signals that the request is being processed by this callback. 
    pub fn connect_request<F: Fn(&Request, String, String, String) -> bool + 'static>(
        &self,
        on_request: F,
    ) {
        self.webview.connect_request(on_request);
    }   

    /// Retrieving a WebViewHandle to evaluate script in the WebView
    pub fn get_handle(&self)->WebViewHandle {
        WebViewHandle {
            handle: self.webview.get_handle()
        }
    }

    /// Evaluates script in the WebView
    /// 
    /// You need a WebViewHandle which you can retrieve via WebView::get_handle
    /// 
    /// You do not need a reference to the WebView handle!
    pub fn eval(handle: WebViewHandle, script: &str) {
        WebViewImpl::start_evaluate_script(handle, script);
    }

    #[cfg(target_os = "windows")]
    /// Execute script in webview
    pub fn execute_javascript(script: &str) {
        WebViewImpl::execute_javascript(script);        
    }
}

/// Builder to construct a WebView
pub struct WebViewBuilder<'a> {
    title: Option<&'a str>,
    app: Application,
    url: Option<&'a str>,
    debug_url: Option<&'a str>,
    query_string: Option<&'a str>,
    width: Option<i32>,
    height: Option<i32>,
    save_bounds: bool,
    #[cfg(target_os = "linux")]    
    with_builder: Option<Rc<dyn Fn(&gtk::Builder)>>,
    #[cfg(target_os = "linux")]    
    builder_path: Option<&'a str>,
    without_native_titlebar: bool,
    devtools: bool,
    default_contextmenu: bool,
    webroot: Option<Dir<'static>>,
}

impl <'a> WebViewBuilder<'a> {
    /// Builds the WebView.
    /// 
    /// Call this function when all settings are set.
    pub fn build(self)->WebView {
        let bounds = Bounds {
            x: None,
            y: None,
            width: self.width, 
            height: self.height, 
            is_maximized: false
        };

        let webroot = self.webroot.map(|webroot| Arc::new(Mutex::new(webroot)));

        let params = Params {
            title: self.title,
            app: &self.app,
            bounds,
            save_bounds: self.save_bounds,
            url: self.url,
            debug_url: self.debug_url,
            query_string: self.query_string,
            #[cfg(target_os = "windows")]
            without_native_titlebar: self.without_native_titlebar,
            devtools: self.devtools,
            default_contextmenu: self.default_contextmenu,
            webroot,
            #[cfg(target_os = "linux")]
            builder_path: self.builder_path,
            #[cfg(target_os = "linux")]#[cfg(target_os = "linux")]
            with_builder: self.with_builder
        };

        WebView { 
            webview: WebViewImpl::new(params)
        }
    }

    /// Sets the title of the window containing the web view.
    pub fn title(mut self, val: &'a str)->WebViewBuilder<'a> {
        self.title = Some(val);
        self
    }

    /// With the help of this method you can initialize the size of the window with custom values.
    /// In combination with "save_bounds()" this is the initial width and heigth of the window at first start,
    /// otherwise the window is always starting with these values.
    pub fn initial_bounds(mut self, w: i32, h: i32)->WebViewBuilder<'a> {
        self.width = Some(w);
        self.height = Some(h);
        self
    }

    /// Saves window bounds after closing app.
    /// 
    /// When you call save_bounds, then windows location and width and height and normal/maximized state is saved on close. 
    /// After restarting the app the webview is displayed at these settings again.
    pub fn save_bounds(mut self)->WebViewBuilder<'a> {
        self.save_bounds = true;
        self
    }

    /// Hides the native window titlebar
    /// 
    /// Only working on Windows
    /// 
    pub fn without_native_titlebar(mut self)->WebViewBuilder<'a> {
        self.without_native_titlebar = true;
        self
    }

    #[cfg(target_os = "linux")]
    /// Callback to create the Web View Window via a UI resource
    pub fn with_builder(mut self, builder_path: &'a str, on_build: impl Fn(&gtk::Builder) + 'static)->WebViewBuilder<'a> {
        self.builder_path = Some(builder_path);
        self.with_builder = Some(Rc::new(on_build));
        self
    }        

    /// Sets the Web View's url
    /// 
    /// You can use 
    /// * ```http(s)://``` 
    /// * ```file://```
    pub fn url(mut self, val: &'a str)->WebViewBuilder<'a> {
        self.url = Some(val);
        self
    }

    /// Sets the Web View's url when debugging
    /// 
    /// This url is used when the app is being debugged. For example, if you use a react website you can set
    /// 
    /// * ```debug_url(http://localhost:5173```)
    /// 
    /// and for the release version you set an url to the published web site
    /// 
    /// You can use 
    /// * ```http(s)://``` 
    /// * ```file://```
    pub fn debug_url(mut self, val: &'a str)->WebViewBuilder<'a> {
        if cfg!(debug_assertions) {
            self.debug_url = Some(val);
        }
        self
    }

    /// If you want your web site be included as a resource in the binary file, call this method.
    /// 
    /// You must not call the ```url()``` method. It is set automatically to ```res://webroot/index.html```
    /// 
    /// ```index.html``` has to be present in the webroot directory. All dependant web site resources have to be relatively referenced. 
    /// The complete web site can be included.
    /// 
    /// For this purpose you have to add the crate ```https://crates.io/crates/include_dir```.
    /// 
    /// # Hints
    /// * The path to webroot is relative to the crates root directory
    /// * If you set ```debug_url(...)``` then this url is loaded in debug version (for example a react website hosted by vite in comparison to 
    /// the published react website included in the binary)
    /// 
    /// # example
    /// 
    /// ```
    /// use include_dir::{include_dir};
    /// use webview_app::webview::WebView;
    ///
    /// fn main() {
    ///     let webview = 
    ///         WebView::builder()
    ///             .appid("de.uriegel.hello")
    ///             .title("Website form custom resources 🦞")
    ///             .webroot(include_dir!("webroots/custom_resources"))
    ///             .build();
    ///     webview.run();
    /// }
    /// ```
    pub fn webroot(mut self, webroot: Dir<'static>)->WebViewBuilder<'a> {
        self.webroot = Some(webroot);
        self
    }

    /// Sets the query string to the final webroot's url
    /// 
    /// # example
    /// 
    /// ```
    /// use include_dir::{include_dir};
    /// use webview_app::webview::WebView;
    ///
    /// fn on_activate(app: &Application)->WebView {
    ///     let webview = 
    ///         WebView::builder()
    ///             .appid("de.uriegel.hello".to_string())
    ///             .title("Website form custom resources 🦞")
    ///             .webroot(include_dir!("webroots/custom_resources"))
    ///             .query_string("?param1=test&param2=somthing")
    ///             .build();
    ///     webview
    /// }
    /// ```
    pub fn query_string(mut self, query_string: &'a str)->WebViewBuilder<'a> {
        self.query_string = Some(query_string);
        self
    }

    /// Enable (not to show) the developer tools.
    /// 
    /// Used to enable the developer tools. Otherwise it is not possible to open these tools. 
    /// The developer tools can be shown by default context menu or by calling the javascript method WebView.showDevtools()
    pub fn devtools(mut self, only_when_debugging: bool)->WebViewBuilder<'a> {
        self.devtools = true;
        if cfg!(not(debug_assertions)) {
            self.devtools = !only_when_debugging;
        }
        self
    }

    /// Disable the default context menu.
    /// 
    /// If you set ```default_contextmenu()```, the web view's default context menu is not being displayed when you right click the mouse.    
    pub fn default_contextmenu_disabled(mut self)->WebViewBuilder<'a> {
        self.default_contextmenu = false;
        self
    }
}