Skip to content

Commit 3a4431a

Browse files
author
zhangzhuang08
committed
add blog about tauri;
1 parent a95a743 commit 3a4431a

File tree

1 file changed

+257
-0
lines changed

1 file changed

+257
-0
lines changed

docs/blog/tauri.md

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
---
2+
title: "Tauri"
3+
page: true
4+
aside: true
5+
---
6+
7+
# Tauri
8+
Tauri is toolkit helping you develop cross-platform desktop GUI app. We'll talk about how Tauri does cross-platform work.
9+
10+
## Window
11+
There're two questions Tauri has to answer:
12+
1. how to build window
13+
2. how to build page inside window
14+
15+
In this chapter, we talk about first question, in next chapter, we talk about the latter.
16+
17+
Keep in mind that if we want to build a window, we have to depend on capbilities of operating system. On macOS, when you develop an app, you normally use `swiftUI` or other frameworks like that. These frameworks are developed by `Objective-C` or `Swift`. But there're ways to leverage frameworks with other languages, like `Rust` which Tauri uses. To reach it, we should take 2 steps:
18+
1. use `Rust bingdings` for `Objective-C` or `Swift`, when we build a window
19+
2. link `Objective-C` or `Swift` frameworks when we compile `Rust` code
20+
21+
`winit`, a Rust crate, has already done these work for us. Tauri has its own crate `Tao` which is a fork of `winit` and brings more features like menu and system tray.Note that menu and system tray are not part of window, so `winit` doesn't implement them; In early time, `Tao` implements them, but now move them into other crates, `muda` for menu, `tray-icon` for system tray.
22+
23+
Here're code snippet from `Tao`, exploring how to build window.
24+
25+
On macOS:
26+
```rs
27+
use objc2::{
28+
msg_send,
29+
rc::Retained,
30+
runtime::{AnyClass as Class, AnyObject as Object, ClassBuilder as ClassDecl, Sel},
31+
};
32+
33+
let ns_window = msg_send![WINDOW_CLASS.0, alloc];
34+
let ns_window: Option<Retained<NSWindow>> = msg_send![
35+
ns_window,
36+
initWithContentRect: frame,
37+
styleMask: masks,
38+
backing: NSBackingStoreType::Buffered,
39+
defer: NO,
40+
];
41+
```
42+
As you can see, these code is based on `Rust bindings` for `Objective-C` and its runtime. `msg_send`, `WINDOW_CLASS.0`, are concepts from `Objective-C`.
43+
44+
On ios:
45+
```rs
46+
let frame = match window_attributes.inner_size {
47+
Some(dim) => {
48+
let scale_factor = msg_send![screen, scale];
49+
let size = dim.to_logical::<f64>(scale_factor);
50+
CGRect {
51+
origin: screen_bounds.origin,
52+
size: CGSize {
53+
width: size.width as _,
54+
height: size.height as _,
55+
},
56+
}
57+
}
58+
None => screen_bounds,
59+
};
60+
61+
let view = view::create_view(&window_attributes, &platform_attributes, frame);
62+
63+
let gl_or_metal_backed = {
64+
let view_class: *const AnyClass = msg_send![view, class];
65+
let layer_class: *const AnyClass = msg_send![view_class, layerClass];
66+
let is_metal: bool = msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
67+
let is_gl: bool = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
68+
is_metal || is_gl
69+
};
70+
71+
let view_controller =
72+
view::create_view_controller(&window_attributes, &platform_attributes, view);
73+
let window = view::create_window(
74+
&window_attributes,
75+
&platform_attributes,
76+
frame,
77+
view_controller,
78+
);
79+
80+
// requires main thread
81+
// view::create_window
82+
pub unsafe fn create_window(
83+
window_attributes: &WindowAttributes,
84+
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
85+
frame: CGRect,
86+
view_controller: id,
87+
) -> id {
88+
let class = get_window_class();
89+
90+
let window: id = msg_send![class, alloc];
91+
assert!(!window.is_null(), "Failed to create `UIWindow` instance");
92+
let window: id = msg_send![window, initWithFrame: frame];
93+
assert!(
94+
!window.is_null(),
95+
"Failed to initialize `UIWindow` instance"
96+
);
97+
let () = msg_send![window, setRootViewController: view_controller];
98+
match window_attributes.fullscreen {
99+
Some(Fullscreen::Exclusive(ref video_mode)) => {
100+
let uiscreen = video_mode.monitor().ui_screen() as id;
101+
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
102+
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
103+
}
104+
Some(Fullscreen::Borderless(ref monitor)) => {
105+
let uiscreen: id = match &monitor {
106+
Some(monitor) => monitor.ui_screen() as id,
107+
None => {
108+
let uiscreen: id = msg_send![window, screen];
109+
uiscreen
110+
}
111+
};
112+
113+
msg_send![window, setScreen: uiscreen]
114+
}
115+
None => (),
116+
}
117+
118+
window
119+
}
120+
```
121+
As you can see, ios also depends on `Rust bindings` for `Objective-C`. Here's a question, we only have bindings code, but we also need to link `Objective-C` library at compile time, how to do ? The answer is `objc2` crate helps us. Take a look at `objc2` source code:
122+
```rs
123+
// Link to libobjc
124+
#[cfg_attr(not(feature = "unstable-objfw"), link(name = "objc", kind = "dylib"))]
125+
// Link to libobjfw-rt
126+
#[cfg_attr(feature = "unstable-objfw", link(name = "objfw-rt", kind = "dylib"))]
127+
extern "C" {}
128+
```
129+
When we compile project which depends on `objc2`, Cargo will link `objc` dylib.
130+
131+
132+
On Windows:
133+
```rs
134+
use windows::{
135+
core::PCWSTR,
136+
}
137+
let handle = CreateWindowExW(
138+
ex_style,
139+
PCWSTR::from_raw(class_name.as_ptr()),
140+
PCWSTR::from_raw(title.as_ptr()),
141+
style,
142+
position.0,
143+
position.1,
144+
adjusted_size.0,
145+
adjusted_size.1,
146+
parent,
147+
pl_attribs.menu,
148+
GetModuleHandleW(PCWSTR::null()).map(Into::into).ok(),
149+
Some(Box::into_raw(Box::new(window_flags)) as _),
150+
)?;
151+
```
152+
As you can see, building window on Windows depends on `windows`. `windows` is a crate provides `Rust bindings` for windows API, you're familiar if you use [`c++/winrt`](https://github.com/microsoft/cppwinrt) project, which provides you c++ function to allow you enjoy Windows API.
153+
154+
155+
156+
On Linux:
157+
```rs
158+
use gtk::{
159+
gdk::WindowState,
160+
glib::{self, translate::ToGlibPtr},
161+
prelude::*,
162+
CssProvider, Settings,
163+
};
164+
165+
let mut window_builder = gtk::ApplicationWindow::builder()
166+
.application(app)
167+
.accept_focus(attributes.focused);
168+
169+
if let Parent::ChildOf(parent) = pl_attribs.parent {
170+
window_builder = window_builder.transient_for(&parent);
171+
}
172+
173+
let window = window_builder.build();
174+
```
175+
As you can see, building window on Linux depends on `GTK`. `gtk` is a crate provides `Rust bindings` for `GTK `, like `objc2`. Unlike macOS and Windows, `GTK` might be not installed out of box on Linux, you have to install `GTK`, `GLib` and `Cairo` development files first.
176+
177+
## Webview
178+
Since we have known how to build a window, but how do we build page inside window ? To make better use of traditional web development tech, Tauri decides to render page with `webview` .
179+
180+
`webview` is a GUI component loading html,css,javascript like browser. You can focus on frontend development.
181+
182+
Tauri creates a crate `wry`, providing cross-platform `webview`. Like `Tao`, `wry` uses `Rust bindings` for platform standard programming language.
183+
184+
On macOS, `wry` uses [`wkwebview`](https://developer.apple.com/documentation/webkit/wkwebview?language=objc).
185+
```rs
186+
#[cfg(target_os = "macos")]
187+
let webview = {
188+
let window = ns_view.window().unwrap();
189+
190+
let scale_factor = window.backingScaleFactor();
191+
let (x, y) = attributes
192+
.bounds
193+
.map(|b| b.position.to_logical::<f64>(scale_factor))
194+
.map(Into::into)
195+
.unwrap_or((0, 0));
196+
let (w, h) = if is_child {
197+
attributes
198+
.bounds
199+
.map(|b| b.size.to_logical::<u32>(scale_factor))
200+
.map(Into::into)
201+
} else {
202+
None
203+
}
204+
.unwrap_or_else(|| {
205+
if is_child {
206+
let frame = NSView::frame(ns_view);
207+
(frame.size.width as u32, frame.size.height as u32)
208+
} else {
209+
(0, 0)
210+
}
211+
});
212+
213+
let frame = CGRect {
214+
origin: if is_child {
215+
window_position(ns_view, x, y, h as f64)
216+
} else {
217+
CGPoint::new(x as f64, (0 - y - h as i32) as f64)
218+
},
219+
size: CGSize::new(w as f64, h as f64),
220+
};
221+
let webview: Retained<WryWebView> =
222+
objc2::msg_send![super(webview), initWithFrame: frame, configuration: &**config];
223+
webview
224+
};
225+
226+
#[cfg(target_os = "ios")]
227+
let webview = {
228+
let frame = ns_view.frame();
229+
let webview: Retained<WryWebView> =
230+
objc2::msg_send![super(webview), initWithFrame: frame, configuration: &**config];
231+
if let Some((red, green, blue, alpha)) = attributes.background_color {
232+
// This is required first since the webview color is applied too late.
233+
webview.setOpaque(false);
234+
235+
let color = objc2_ui_kit::UIColor::colorWithRed_green_blue_alpha(
236+
red as f64 / 255.0,
237+
green as f64 / 255.0,
238+
blue as f64 / 255.0,
239+
alpha as f64 / 255.0,
240+
);
241+
242+
if !is_child {
243+
ns_view.setBackgroundColor(Some(&color));
244+
}
245+
// This has to be monitored as it may clash with isOpaque = true.
246+
// The webview background color may also applied too late so actually not that useful.
247+
webview.setBackgroundColor(Some(&color));
248+
}
249+
webview
250+
};
251+
```
252+
253+
On windows, `wry` uses `webview2` which is wrapped by crate `webview2-com`.
254+
255+
On linux, `wry` uses `webkitgtk` which is wrapped by crate `webkit2gtk`. Read more about GTK `webkitview` from [this](https://webkitgtk.org/reference/webkitgtk/stable/ctor.WebView.new.html).
256+
257+

0 commit comments

Comments
 (0)