前言
iced是一个比较流行的UI库,设计思路还是挺有意思的,不过因为rust复杂的语法,这个库确实很难让一个不精通rust的开发者那么容易理解。这里记录下这几天的阅读源码心得。
正文
iced核心包括四个模块。
- iced库,主要控制应用状态、核心是application。
- iced core这是真正的iced的核心库,主要功能包括三部分
- 通过winit创建窗口,以及处理窗口的点击事件
- 在窗口回调杨中,使用widget库绘制图像
- 而widget的绘制则是通过tiny_skia或者wgup提供的renderer绘制图像。
- tiny_skia或者wgup提提供的renderer绘制图像
- winit提供窗口管理
所以iced_core才是真的库核心。管理所有业务逻辑,而iced源码基本上只是提供用户最基本的基本调用
下面跟踪一下applaction的构造过程。我们参考一下计数器的实例代码,分析一下构造过程。
rust
pub fn main() -> iced::Result {
iced::run("A cool counter", Counter::update, Counter::view)
}
#[derive(Default)]
struct Counter {
value: i64,
}
#[derive(Debug, Clone, Copy)]
enum Message {
Increment,
Decrement,
}
impl Counter {
fn update(&mut self, message: Message) {
match message {
Message::Increment => {
self.value += 1;
}
Message::Decrement => {
self.value -= 1;
}
}
}
fn view(&self) -> Column<Message> {
column![
button("Increment").on_press(Message::Increment),
text(self.value).size(50),
button("Decrement").on_press(Message::Decrement)
]
.padding(20)
.align_x(Center)
}
}t
iced::run
提供静态函数,用于创建applaction。便于用户使用具体构造函数我们看下
rust
pub fn run<State, Message, Theme, Renderer>(
title: impl application::Title<State> + 'static,
update: impl application::Update<State, Message> + 'static,
view: impl for<'a> application::View<'a, State, Message, Theme, Renderer>
+ 'static,
) -> Result
where
State: Default + 'static,
Message: std::fmt::Debug + Send + 'static,
Theme: Default + program::DefaultStyle + 'static,
Renderer: program::Renderer + 'static,
{
application(title, update, view).run()
}
这段代码主要是str实现了titile接口,Fn实现了Update。Fn实现了View接口。
rust
impl<State> Title<State> for &'static str {
fn title(&self, _state: &State) -> String {
self.to_string()
}
}
impl<T, State, Message, C> Update<State, Message> for T
where
T: Fn(&mut State, Message) -> C,
C: Into<Task<Message>>,
{
fn update(
&self,
state: &mut State,
message: Message,
) -> impl Into<Task<Message>> {
self(state, message)
}
}
impl<'a, T, State, Message, Theme, Renderer, Widget>
View<'a, State, Message, Theme, Renderer> for T
where
T: Fn(&'a State) -> Widget,
State: 'static,
Widget: Into<Element<'a, Message, Theme, Renderer>>,
{
fn view(
&self,
state: &'a State,
) -> impl Into<Element<'a, Message, Theme, Renderer>> {
self(state)
}
}
关于返回值,因为Element都为每种widget实现了From接口,所以都会自动转化位Element。返回值是编译器自动转化。
最终我们得到了Applaction。
rust
pub fn application<State, Message, Theme, Renderer>(
title: impl Title<State>,
update: impl Update<State, Message>,
view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>,
) -> Application<impl Program<State = State, Message = Message, Theme = Theme>>
where
State: 'static,
Message: Send + std::fmt::Debug + 'static,
Theme: Default + DefaultStyle,
Renderer: program::Renderer,
{
......
}
注意这里的Theme是通过外层widget确认的。而renderer却比较奇葩,这是一个非常奇怪的东西。他的根trait是core模块的render.rs 定义的。在不同的绘制的目的,有不同的嘴trait。比如core目录下的image.rs 下的renderer
rust
pub trait Renderer: crate::Renderer {
/// The image Handle to be displayed. Iced exposes its own default implementation of a [`Handle`]
///
/// [`Handle`]: Self::Handle
type Handle: Clone;
/// Returns the dimensions of an image for the given [`Handle`].
fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
/// Draws an [`Image`] inside the provided `bounds`.
fn draw_image(&mut self, image: Image<Self::Handle>, bounds: Rectangle);
}
最终是在不同绘制库,有不同的具体实现。比如tiny_skia。则实现了image的特殊绘制接口,比如:
rust
impl core::image::Renderer for Renderer {
type Handle = core::image::Handle;
fn measure_image(&self, handle: &Self::Handle) -> crate::core::Size<u32> {
self.engine.raster_pipeline.dimensions(handle)
}
fn draw_image(&mut self, image: core::Image, bounds: Rectangle) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_raster(image, bounds, transformation);
}
}
下面开始分析一下程序运行的流程。
applaction是一个包装,核心是 raw: 的program。
rust
struct Instance<State, Message, Theme, Renderer, Update, View> {
update: Update,
view: View,
_state: PhantomData<State>,
_message: PhantomData<Message>,
_theme: PhantomData<Theme>,
_renderer: PhantomData<Renderer>,
}
这正run的函数是:
rust
fn run(
self,
settings: Settings,
window_settings: Option<window::Settings>,
) -> Result
where
Self: 'static,
Self::State: Default,
{
self.run_with(settings, window_settings, || {
(Self::State::default(), Task::none())
})
}
/// Runs the [`Program`] with the given [`Settings`] and a closure that creates the initial state.
fn run_with<I>(
self,
settings: Settings,
window_settings: Option<window::Settings>,
initialize: I,
) -> Result
where
Self: 'static,
I: FnOnce() -> (Self::State, Task<Self::Message>) + 'static,
{
use std::marker::PhantomData;
struct Instance<P: Program, I> {
program: P,
state: P::State,
_initialize: PhantomData<I>,
}
impl<P: Program, I: FnOnce() -> (P::State, Task<P::Message>)>
shell::Program for Instance<P, I>
{
type Message = P::Message;
type Theme = P::Theme;
type Renderer = P::Renderer;
type Flags = (P, I);
type Executor = P::Executor;
fn new(
(program, initialize): Self::Flags,
) -> (Self, Task<Self::Message>) {
let (state, task) = initialize();
(
Self {
program,
state,
_initialize: PhantomData,
},
task,
)
}
fn title(&self, window: window::Id) -> String {
self.program.title(&self.state, window)
}
fn update(
&mut self,
message: Self::Message,
) -> Task<Self::Message> {
self.program.update(&mut self.state, message)
}
fn view(
&self,
window: window::Id,
) -> crate::Element<'_, Self::Message, Self::Theme, Self::Renderer>
{
self.program.view(&self.state, window)
}
fn subscription(&self) -> Subscription<Self::Message> {
self.program.subscription(&self.state)
}
fn theme(&self, window: window::Id) -> Self::Theme {
self.program.theme(&self.state, window)
}
fn style(&self, theme: &Self::Theme) -> Appearance {
self.program.style(&self.state, theme)
}
fn scale_factor(&self, window: window::Id) -> f64 {
self.program.scale_factor(&self.state, window)
}
}
#[allow(clippy::needless_update)]
let renderer_settings = crate::graphics::Settings {
default_font: settings.default_font,
default_text_size: settings.default_text_size,
antialiasing: if settings.antialiasing {
Some(crate::graphics::Antialiasing::MSAAx4)
} else {
None
},
..crate::graphics::Settings::default()
};
Ok(shell::program::run::<
Instance<Self, I>,
<Self::Renderer as compositor::Default>::Compositor,
>(
Settings {
id: settings.id,
fonts: settings.fonts,
default_font: settings.default_font,
default_text_size: settings.default_text_size,
antialiasing: settings.antialiasing,
}
.into(),
renderer_settings,
window_settings,
(self, initialize),
)?)
}
注意这里是program是在iced目录下。这里核心是调用winit的program,最关键的参数是(self, initialize)这个元组。这个参数是构成winit的program。
rust
pub fn run<P, C>(
settings: Settings,
graphics_settings: graphics::Settings,
window_settings: Option<window::Settings>,
flags: P::Flags,
) -> Result<(), Error>
where
P: Program + 'static,
C: Compositor<Renderer = P::Renderer> + 'static,
P::Theme: DefaultStyle,
{
}
这个东西就比较复杂,这里主要核心是runtime模块通过userinterface管理关键的绘制工具,以及winit的库的消息进行绘制。
这个东西比较复杂,不再详细介绍。
后记
这里最关键的完整绘制winit申请绘制,以及绘制模块比如skia额绘制过程没有详细介绍,这里暂时不详细介绍了,等待以后有空再补充把。