前言
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的构造过程。我们参考一下计数器的实例代码,分析一下构造过程。
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。便于用户使用具体构造函数我们看下
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
whereState: 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接口。
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
whereT: 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
whereT: 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。
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>>
whereState: '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
pub trait Renderer: crate::Renderer {/// The image Handle to be displayed. Iced exposes its own default implementation of a [`Handle`]////// [`Handle`]: Self::Handletype 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的特殊绘制接口,比如:
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。
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的函数是:
fn run(self,settings: Settings,window_settings: Option<window::Settings>,) -> ResultwhereSelf: '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,) -> ResultwhereSelf: '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。
pub fn run<P, C>(settings: Settings,graphics_settings: graphics::Settings,window_settings: Option<window::Settings>,flags: P::Flags,
) -> Result<(), Error>whereP: Program + 'static,C: Compositor<Renderer = P::Renderer> + 'static,P::Theme: DefaultStyle,
{}
这个东西就比较复杂,这里主要核心是runtime模块通过userinterface管理关键的绘制工具,以及winit的库的消息进行绘制。
这个东西比较复杂,不再详细介绍。
后记
这里最关键的完整绘制winit申请绘制,以及绘制模块比如skia额绘制过程没有详细介绍,这里暂时不详细介绍了,等待以后有空再补充把。