iced源码分析

前言

iced是一个比较流行的UI库,设计思路还是挺有意思的,不过因为rust复杂的语法,这个库确实很难让一个不精通rust的开发者那么容易理解。这里记录下这几天的阅读源码心得。

正文

iced核心包括四个模块。

  1. iced库,主要控制应用状态、核心是application。
  2. iced core这是真正的iced的核心库,主要功能包括三部分
  1. 通过winit创建窗口,以及处理窗口的点击事件
  2. 在窗口回调杨中,使用widget库绘制图像
  3. 而widget的绘制则是通过tiny_skia或者wgup提供的renderer绘制图像。
  1. tiny_skia或者wgup提提供的renderer绘制图像
  2. 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额绘制过程没有详细介绍,这里暂时不详细介绍了,等待以后有空再补充把。

相关推荐
Htht1112 小时前
【Qt】之【Bug】点击按钮(ui->pushButton)触发非本类设置的槽函数
qt·ui·bug
zhuziheniaoer3 小时前
rust-candle学习笔记12-实现因果注意力
笔记·学习·自然语言处理·rust
无名之逆3 小时前
Hyperlane: Unleash the Power of Rust for High-Performance Web Services
java·开发语言·前端·后端·http·rust·web
Source.Liu3 小时前
【typenum】 0 配置文件(Cargo.toml)
rust
CodeCraft Studio13 小时前
报表控件stimulsoft教程:使用 JoinType 关系参数创建仪表盘
前端·ui
明月看潮生15 小时前
青少年编程与数学 02-019 Rust 编程基础 01课题、环境准备
开发语言·青少年编程·rust·编程与数学
白熊18817 小时前
【图像大模型】Stable Diffusion Web UI:深度解析与实战指南
ui·stable diffusion
OJAC近屿智能18 小时前
英伟达发布Llama-Nemotron系列新模型,性能超越DeepSeek-R1
大数据·人工智能·ui·aigc·llama
UI设计兰亭妙微1 天前
未来设计新篇章!2025 年 UX/UI 设计趋势,技术与体验的全新结合!
ui·ux
液态不合群2 天前
rust程序静态编译的两种方法总结
开发语言·后端·rust