• RustGUI学习(iced)之小部件(一):如何使用按钮和文本标签部件


    前言
    本专栏是学习Rust的GUI库iced的合集,将介绍iced涉及的各个小部件分别介绍,最后会汇总为一个总的程序。
    iced是RustGUI中比较强大的一个,目前处于发展中(即版本可能会改变),本专栏基于版本0.12.1.

    概述
    这是本专栏的第一篇,主要讲述按钮(button)和文本标签(text)两个部件的使用,会结合实例来说明。

    环境配置:
    系统:windows
    平台:visual studio code
    语言:rust
    库:iced

    在这里插入图片描述
    注:iced是一个受Elm启发而编写,适用于rust语言的跨平台的GUI库。

    本篇内容:
    1、button
    2、text

    一、按钮部件

    按钮部件在iced中的定义如下:

    /// Creates a new [`Button`] with the provided content.  
    ///
    /// [`Button`]: crate::Button
    pub fn button<'a, Message, Theme, Renderer>(
        content: impl Into<Element<'a, Message, Theme, Renderer>>,
    ) -> Button<'a, Message, Theme, Renderer>
    where
        Renderer: core::Renderer,
        Theme: button::StyleSheet,
    {
        Button::new(content)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上面是官方的代码,可以知道,button其实是Button来创建的。而Button来自于iced_widget库:

    #[allow(missing_debug_implementations)]
    pub struct Button<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>      
    where
        Theme: StyleSheet,
        Renderer: crate::core::Renderer,
    {
        content: Element<'a, Message, Theme, Renderer>,
        on_press: Option<Message>,
        width: Length,
        height: Length,
        padding: Padding,
        clip: bool,
        style: Theme::Style,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    我们可以看到,一个新建的button,拥有以下属性或者方法:
    content、on_press、width、height、padding、clip、style
    其中,content按照定义,是一个Element,即可以是任何元素,但一般情况下,我们会给content赋为纯文本(即按钮名称)或者文本部件(即text)。
    width、height这两个属性好理解,即按钮的宽、高,宽、高属性既可以设置为预设值,也可以设置为自定义的大小。
    padding是指的按钮的content与按钮整个框架的间距。
    clip是指当按钮尺寸小于文字时,多出的文字是显示还是隐藏。
    style是设置按钮的外观样式,可以使用库预设的样式,也可以使用自定义的样式,下文将会详述。
    我们先来看一下button实际的代码:

    button("按钮1").clip(false).width(80).height(40).style(style6).padding(4)    
                .on_press(Message::Clicked),
    
    • 1
    • 2

    这是一个典型的button实例,上面的代码中,“按钮1”即content,其他属性则显而易见,其中style的参数style6如下:

    let style6=theme::Button::Custom(Box::new(MyButtonStyle));
    
    • 1

    此处使用的是自定义外观,库预设的样式如下:

    let style1=theme::Button::Primary;
            let style2=theme::Button::Secondary;
            let style3=theme::Button::Positive;
            let style4=theme::Button::Destructive;
            let style5=theme::Button::Text;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    看一下这五种预设的按钮样式实际是什么样的:
    在这里插入图片描述
    如果预设样式足够使用,那么直接使用即可,如果预设样式不满足,那么还可以使用自定义样式,比如要设置按钮边框,添加圆角,改变颜色等,我们来看一下如何设置自定义按钮样式:
    我们查看iced源码可知,按钮的样式是在StyleSheet这个特性下设置的:

    pub trait StyleSheet {
        /// The supported style of the [`StyleSheet`].
        type Style: Default;
    
        /// Produces the active [`Appearance`] of a button.
        fn active(&self, style: &Self::Style) -> Appearance;
    
        /// Produces the hovered [`Appearance`] of a button.
        fn hovered(&self, style: &Self::Style) -> Appearance {
            let active = self.active(style);
    
            Appearance {
                shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0),
                ..active
            }
        }
    
        /// Produces the pressed [`Appearance`] of a button.
        fn pressed(&self, style: &Self::Style) -> Appearance {
            Appearance {
                shadow_offset: Vector::default(),
                ..self.active(style)
            }
        }
    
        /// Produces the disabled [`Appearance`] of a button.
        fn disabled(&self, style: &Self::Style) -> Appearance {
            let active = self.active(style);
    
            Appearance {
                shadow_offset: Vector::default(),
                background: active.background.map(|background| match background {
                    Background::Color(color) => Background::Color(Color {
                        a: color.a * 0.5,
                        ..color
                    }),
                    Background::Gradient(gradient) => {
                        Background::Gradient(gradient.mul_alpha(0.5))
                    }
                }),
                text_color: Color {
                    a: active.text_color.a * 0.5,
                    ..active.text_color
                },
                ..active
            }
        }
    }
    
    • 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

    如果我们要设置自定义的样式,那么新建一个样式结构体,然后将StyleSheet赋予我们新建的样式,在其中修改即可,如下:
    1、新建样式:

    struct MyButtonStyle;
    
    • 1

    2、实现StyleSheet特性

    impl  StyleSheet for MyButtonStyle{      
        type Style = Theme;
        //激活时外观
        fn active(&self, style: &Self::Style) -> button::Appearance {
            Appearance{
                shadow_offset:Vector{x:0.0,y:0.0},
                background:Some(style.extended_palette().primary.base.color.into()),
                text_color:style.extended_palette().background.base.text,
                border:Border{
                    color:Color::BLACK,
                    width:1.0,
                    radius:[3.0;4].into(),},
                shadow:Shadow { 
                    color: Color::WHITE, 
                    offset: Vector{x:0.0,y:0.0},
                    blur_radius:0.0 },
    
            }
        }
        //悬停时外观
        fn hovered(&self, style: &Self::Style) -> Appearance {
            Appearance{
                shadow_offset:Vector{x:0.0,y:0.0},
                background:Some(style.extended_palette().primary.weak.color.into()),
                text_color:style.extended_palette().primary.weak.text,
                border:Border{
                    color:Color::BLACK,
                    width:1.0,
                    radius:[3.0;4].into(),
                },
                shadow:Shadow {
                    color: Color::WHITE,
                    offset: Vector{x:0.0,y:0.0},
                    blur_radius:0.0,
                }
            }
        }
        //按下时外观
        fn pressed(&self, style: &Self::Style) -> Appearance {
            Appearance{
                shadow_offset:Vector{x:0.0,y:0.0},
                background:Some(style.extended_palette().primary.strong.color.into()),
                text_color:style.extended_palette().primary.weak.text,
                border:Border{
                    color:Color::BLACK,
                    width:1.0,
                    radius:[3.0;4].into(),
                },
                shadow:Shadow {
                    color: Color::BLACK,
                    offset: Vector{x:0.0,y:0.0},
                    blur_radius:0.0,
                }
            }
        }
    }
    
    • 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

    可以看到,我们在此处设置了按钮的激活、悬停、按压三种状态下的外观,具体来说,就是修改Appearance这个属性,其子属性如下:

    pub struct Appearance {     
        /// The amount of offset to apply to the shadow of the button.
        pub shadow_offset: Vector,
        /// The [`Background`] of the button.
        pub background: Option<Background>,
        /// The text [`Color`] of the button.
        pub text_color: Color,
        /// The [`Border`] of the buton.
        pub border: Border,
        /// The [`Shadow`] of the butoon.
        pub shadow: Shadow,
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这是官方定义的按钮的Appearance属性,我们只要一一修改,就可以得到我们自己的Appearance。如上面的示例代码中,我们修改了背景色、圆角半径等,其中shadow可以设置按钮的阴影,有兴趣的可以去尝试,可以做出不同的效果,当然,对于普通的UI界面来说,不需要那么复杂的效果,那么只要修改背景色、文字颜色、边框、圆角半径等即可。
    当我们新建样式完成后,就可以在创建按钮时调用了:

    let style6=theme::Button::Custom(Box::new(MyButtonStyle));
    
    • 1

    此处,Custom的参数形式,在官方定义中如下:

    Custom(Box<dyn button::StyleSheet<Style = Theme>>), 
    
    • 1

    来看一下自定义样式和预设样式有什么不一样:
    在这里插入图片描述
    按钮的样式说的差不多了,下面说一下按钮最重要的一个属性,on_press,这是按钮能够进行交互的基础,当用户点击按钮时,就会触发on_press,然后将on_press的消息Message传递给后台,根据按钮传递的消息,运行相应的逻辑,然后再更新UI。
    示例代码中如下:

     button("按钮1").clip(false).width(80).height(40).style(style1).padding(4)
                .on_press(Message::Clicked),
    
    • 1
    • 2

    on_press的参数为Message::Clicked,此处的Message::Clicked需要自己创建,是enum枚举:

    #[derive(Debug,Clone,Copy)]
    enum Message{  
        Clicked,
    }
    
    • 1
    • 2
    • 3
    • 4

    当按钮被点击时,Message::Clicked就会被触发,然后在iced的update函数中,我们可以处理这个message:

     fn update(&mut self,message:Message){   
            match message{
                Message::Clicked => {
                    self.value +=1;
                }    
            }
    
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    即,当Message::Clicked触发一次,就会执行一次当前Message::Clicked下对应的程序,本例中即将value这个值加1.

    那么,当我点击了按钮,value值也加1了,该如何将这个变化反应到UI界面,使其直观显示出来呢?

    我们会用到iced的view函数:

    fn view(&self) -> Element<Message>{           
             
        }
    
    • 1
    • 2
    • 3

    view函数实时显示当前UI,所以,我们要将部件都写在view函数中,比如,本例中我添加一个按钮、一个文本标签,然后当点击按钮时,文本的值加1.

    fn view(&self) -> Element<Message>{     
         
            let style6=theme::Button::Custom(Box::new(MyButtonStyle));
            let  style7=theme::Text::Color(Color::BLACK);
            column![
        
                button("按钮6").clip(false).width(80).height(40).style(style6).padding(4)
                .on_press(Message::Clicked),
                text(format!("value:{}",self.value)).size(15)
                .width(40).height(40).style(style7),
            ].padding(20)
            .spacing(20)
            .into()
            
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    如上,我们添加了按钮,这不用多说,又添加了一个text部件,其中,text部件的text属性部分,我们将其设置为与value值绑定,这样,每当value值变化,view函数都会刷新text部件的文本部分。
    这其中,column是一种排列方式,即区域内的部件按照纵向排列,另一种方式是row,即横向排列。
    看一下实际效果:
    在这里插入图片描述

    二、文本标签

    再来说一下文本标签的使用,相比于按钮,文本标签要简单许多,在官方的定义中,text部件如下:

    /// Creates a new [`Text`] widget with the provided content. 
    ///
    /// [`Text`]: core::widget::Text
    pub fn text<'a, Theme, Renderer>(
        text: impl ToString,
    ) -> Text<'a, Theme, Renderer>
    where
        Theme: text::StyleSheet,
        Renderer: core::text::Renderer,
    {
        Text::new(text.to_string())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可以看到,其来自于Text:

    /// A paragraph of text.                  
    #[allow(missing_debug_implementations)]
    pub struct Text<'a, Theme, Renderer>
    where
        Theme: StyleSheet,
        Renderer: text::Renderer,
    {
        content: Cow<'a, str>,
        size: Option<Pixels>,
        line_height: LineHeight,
        width: Length,
        height: Length,
        horizontal_alignment: alignment::Horizontal,
        vertical_alignment: alignment::Vertical,
        font: Option<Renderer::Font>,
        shaping: Shaping,
        style: Theme::Style,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    其中content是文本要显示的字符,size是文字的尺寸,width和height是宽、高,horizontal_alignment是设置文字的水平样式,有左、中、右可选,而vertical_alignment是设置文字的垂直样式,顶部、中间、底部可选,font是设置字体,如果不设置,则和主窗体保持一致,shaping应该指的是文字的显示质量。style是指文字样式,在text的style中,只有颜色可以修改,如下:

    /// The style sheet of some text. 
    pub trait StyleSheet {
        /// The supported style of the [`StyleSheet`].
        type Style: Default + Clone;
    
        /// Produces the [`Appearance`] of some text.
        fn appearance(&self, style: Self::Style) -> Appearance;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    /// The apperance of some text.   
    #[derive(Debug, Clone, Copy, Default)]
    pub struct Appearance {
        /// The [`Color`] of the text.
        ///
        /// The default, `None`, means using the inherited color.
        pub color: Option<Color>,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    可以看到,text的样式就是修改颜色。
    我们在上面的示例中如下:

    text(format!("value:{}",self.value)).size(15).shaping(Shaping::Advanced) 
                .width(40).height(40).style(style7),
    
    • 1
    • 2

    如果要设置文字的排列样式,可以添加:

    text(format!("value:{}",self.value)).size(15).shaping(Shaping::Advanced)     
                .horizontal_alignment(alignment::Horizontal::Center)
                .vertical_alignment(alignment::Vertical::Center)
                .width(40).height(40).style(style7),
    
    • 1
    • 2
    • 3
    • 4

    如上代码,文字就会一直保持在中心位置。

    下面再来简单说一下窗体的属性问题,iced建立窗体,有两种样式,一种是比较简单的呈现,是sandbox,另一种是application。
    在前期,我们会使用sandbox来演示。

    pub fn main() -> iced::Result {    
       Counter::run(Settings::default())
    }
    
    • 1
    • 2
    • 3

    以上是官方的默认主程序,此处运行主窗体,其中的参数设置皆为默认,但默认的参数不一定适用,所以需要对其进行修改。
    如果要修改默认参数,需要先导入:

    use iced::font::Family; 
    use iced::window::{self,icon,Position};
    
    • 1
    • 2

    以及:

    extern crate image;
    use image::GenericImageView;
    
    • 1
    • 2

    此处的image库是用来处理图像的。

    接下来看一下如何修改窗口参数:

    pub fn main() -> iced::Result {
       //Counter::run(Settings::default())
       let myfont="微软雅黑";
       let ico=image_to_icon("..\\gui-btn\\img\\ico1.png");
       Counter::run(Settings{
           window:window::Settings{
               size:Size{width:800.0,height:600.0},
               position:Position::Specific(Point{x:100.0,y:20.0}),
               icon:Some(ico),
               ..window::Settings::default()
           },
           default_font:Font { 
            family:Family::Name(myfont),
            ..Font::DEFAULT
         },
         ..Settings::default()
       })
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    如上代码,可以看到,我们设置了窗体尺寸、窗体显示位置、窗体图标、字体,事实上程序参数如下:

    /// The settings of an application.    
    #[derive(Debug, Clone)]
    pub struct Settings<Flags> {
        /// The identifier of the application.
        ///
        /// If provided, this identifier may be used to identify the application or
        /// communicate with it through the windowing system.
        pub id: Option<String>,
    
        /// The window settings.
        ///
        /// They will be ignored on the Web.
        pub window: window::Settings,
    
        /// The data needed to initialize the [`Application`].
        ///
        /// [`Application`]: crate::Application
        pub flags: Flags,
    
        /// The fonts to load on boot.
        pub fonts: Vec<Cow<'static, [u8]>>,
    
        /// The default [`Font`] to be used.
        ///
        /// By default, it uses [`Family::SansSerif`](crate::font::Family::SansSerif).
        pub default_font: Font,
    
        /// The text size that will be used by default.
        ///
        /// The default value is `16.0`.
        pub default_text_size: Pixels,
    
        /// If set to true, the renderer will try to perform antialiasing for some
        /// primitives.
        ///
        /// Enabling it can produce a smoother result in some widgets, like the
        /// [`Canvas`], at a performance cost.
        ///
        /// By default, it is disabled.
        ///
        /// [`Canvas`]: crate::widget::Canvas
        pub antialiasing: bool,
    }
    
    • 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

    其中,window参数又如下:

    /// The window settings of an application.    
    #[derive(Debug, Clone)]
    pub struct Settings {
        /// The initial logical dimensions of the window.
        pub size: Size,
    
        /// The initial position of the window.
        pub position: Position,
    
        /// The minimum size of the window.
        pub min_size: Option<Size>,
    
        /// The maximum size of the window.
        pub max_size: Option<Size>,
    
        /// Whether the window should be visible or not.
        pub visible: bool,
    
        /// Whether the window should be resizable or not.
        pub resizable: bool,
    
        /// Whether the window should have a border, a title bar, etc. or not.
        pub decorations: bool,
    
        /// Whether the window should be transparent.
        pub transparent: bool,
    
        /// The window [`Level`].
        pub level: Level,
    
        /// The icon of the window.
        pub icon: Option<Icon>,
    
        /// Platform specific settings.
        pub platform_specific: PlatformSpecific,
    
        /// Whether the window will close when the user requests it, e.g. when a user presses the
        /// close button.
        ///
        /// This can be useful if you want to have some behavior that executes before the window is
        /// actually destroyed. If you disable this, you must manually close the window with the
        /// `window::close` command.
        ///
        /// By default this is enabled.
        pub exit_on_close_request: bool,
    }
    
    • 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

    我们并不是每一个参数都需要修改,这里我们只是修改了尺寸、位置、图标以及字体这种常用的参数,其余的参数可以全部默认。
    在我们上面的代码中,有一句处理图像的程序:

    let ico=image_to_icon("..\\gui-btn\\img\\ico1.png");
    
    • 1

    其中的image_to_icon是我们自定义的函数,用于将图像转为icon,函数如下:

    ///
    /// 将图片转为窗口图标
    /// 
    fn image_to_icon(img:&str)-> icon::Icon {           
        let img=image::open(img).unwrap();
        let img_de=img.dimensions();
        //println!("img_de is {:?}",img_de);
        let w1=img_de.0;
        let h1=img_de.1;
        let img_file=img.to_rgba8();
        let ico=icon::from_rgba(img_file.to_vec(),w1,h1);
        let ico_file=match ico{
            Ok(file)=>file,
            Err(e)=>panic!("error is {}",e),
        };
        ico_file
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这里用到image库,需要在toml中添加:

    image="0.25.1"
    
    • 1

    以上就是本篇的内容,其中涉及窗口的内容,在以后的篇章中将不再赘述。

  • 相关阅读:
    std::make_shared和new初始化智能指针的区别
    【Pandas数据处理100例】(九十):Pandas使用period_range()生成固定间隔日期
    memcached的大key存储与slab钙化问题踩坑
    前端开发:CSS属性选择器
    RibbitMQ学习笔记之交换机实战
    【控制台】报错:Uncaught ReferenceError: process is not defined
    使用控制台方式部署sentinel
    Targeting S+ (version 31 and above) requires that an explicit value for android
    2023年共享按摩椅分析:随着对健康和舒适生活的需求不断增长[图]
    JAVA计算机毕业设计电子病历系统Mybatis+系统+数据库+调试部署
  • 原文地址:https://blog.csdn.net/normer123456/article/details/138120925