zoukankan      html  css  js  c++  java
  • Rust GUI编程

    1.可以用iced框架,star比较多,而且在快速发展中,源码跨平台;

    2.在main方法文件最上面加上:#![windows_subsystem = "windows"],这样Windows平台运行编译好的gui程序时就不会弹出控制台框;

    对于Mac OS,Linux可以自己写个图标配置(Linux是.desktop文件)就可以直接双击图标来打开程序而非必须先开一个控制台,所以这两个平台没有这个全局宏。

    3.iced使用示例:根据官网示例写一个加减的按钮和显示:

    Cargo.toml:(这个配置兼容性不好,换成下面代码块里的)

    [package]
    name = "demo-iced"
    version = "0.1.0"
    authors = ["silentdoer <1010993610@qq.com>"]
    edition = "2018"
    
    # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
    
    [dependencies]
    iced = { git = "https://github.com/hecrj/iced", features = ["async-std", "debug"] } # debug考虑不要
    iced_web = "0.2" serde
    = { version = "1.0", features = ["derive"] } serde_json = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] async-std = "1" directories = "2" [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys = { version = "0.3", features = ["Window", "Storage"] } wasm-timer = "0.2" [package.metadata.deb] assets = [ ["target/release/todos", "usr/bin/iced-todos", "755"], ["iced-todos.desktop", "usr/share/applications/", "644"], ]

    上面的Cargo.toml配置有几个问题,第一是必需是vs2017或以上,第二就是debug模式编译的程序运行有问题,第三个就是需要编译的模块很多,第四个是win10编译出的release程序(debug是win10都运行有问题)win10可以用,但是win7运行失败(经过测试win7编译出的win10可以运行,但是win10编译出的在win7上仍然运行失败(2017,2015,gnu三个都失败了,等会再换2013和2012【2012直接编译失败,哪怕是OpenGL的】的C++如果还失败那只能在win7上编译Windows全平台了【或者测试下Ubuntu跨平台编译的gui程序是否可以同时在win10和win7上运行】));(当然也有好处,上面的配置用的GUI后端是DX或vulkan,而接下来的配置用的是OpenGL效率没上面的高【注意,不需要安装OpenGL,win10和win7里都有】)

    【这种方式编译出来的程序也有问题,就是它放到一个没有安装过C++环境的系统里运行不了。。。坑啊,目前没有比较好的解决方案,暂时还是用第一种算了,然后win10和win7都以release编译一下(刚才又查了下说也可能是显卡驱动的原因。。)】

    [package]
    name = "demo-iced"
    version = "0.1.0"
    authors = ["silentdoer <1010993610@qq.com>"]
    edition = "2018"
    
    # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
    
    [dependencies]
    iced = { git = "https://github.com/hecrj/iced", default-features = false, features = ["async-std", "debug", "glow"] }
    serde = { version = "1.0", features = ["derive"] }
    serde_json = "1.0"
    
    [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
    async-std = "1"
    directories = "2"
    
    [target.'cfg(target_arch = "wasm32")'.dependencies]
    web-sys = { version = "0.3", features = ["Window", "Storage"] }
    wasm-timer = "0.2"
    
    [package.metadata.deb]
    assets = [
        ["target/release/todos", "usr/bin/iced-todos", "755"],
        ["iced-todos.desktop", "usr/share/applications/", "644"],
    ]

    在项目根目录添加fonts目录,里面将Windows的simkai.ttf字体复制进去

    main.rs:

    #![windows_subsystem = "windows"]
    use iced::{
        button, scrollable, text_input, Align, Application, Button, Checkbox,
        Column, Command, Container, Element, Font, HorizontalAlignment, Length,
        Row, Scrollable, Settings, Text, TextInput,
    };
    use serde::{Deserialize, Serialize};
    
    pub fn main() {
        Todos::run(Settings {
            default_font: Some(include_bytes!("../fonts/simkai.ttf")),
            ..Settings::default()
        })
    }
    
    #[derive(Debug)]
    enum Todos {
        Loading,
        Loaded(State),
    }
    
    #[derive(Debug, Default)]
    struct State {
        scroll: scrollable::State,
        input: text_input::State,
        input_value: String,
        filter: Filter,
        tasks: Vec<Task>,
        controls: Controls,
        dirty: bool,
        saving: bool,
    }
    
    #[derive(Debug, Clone)]
    enum Message {
        Loaded(Result<SavedState, LoadError>),
        Saved(Result<(), SaveError>),
        InputChanged(String),
        CreateTask,
        FilterChanged(Filter),
        TaskMessage(usize, TaskMessage),
    }
    
    impl Application for Todos {
        type Executor = iced::executor::Default;
        type Message = Message;
        type Flags = ();
    
        fn new(_flags: ()) -> (Todos, Command<Message>) {
            (
                Todos::Loading,
                Command::perform(SavedState::load(), Message::Loaded),
            )
        }
    
        fn title(&self) -> String {
            let dirty = match self {
                Todos::Loading => false,
                Todos::Loaded(state) => state.dirty,
            };
    
            format!("Todos{} - Iced", if dirty { "*" } else { "" })
        }
    
        fn update(&mut self, message: Message) -> Command<Message> {
            match self {
                Todos::Loading => {
                    match message {
                        Message::Loaded(Ok(state)) => {
                            *self = Todos::Loaded(State {
                                input_value: state.input_value,
                                filter: state.filter,
                                tasks: state.tasks,
                                ..State::default()
                            });
                        }
                        Message::Loaded(Err(_)) => {
                            *self = Todos::Loaded(State::default());
                        }
                        _ => {}
                    }
    
                    Command::none()
                }
                Todos::Loaded(state) => {
                    let mut saved = false;
    
                    match message {
                        Message::InputChanged(value) => {
                            state.input_value = value;
                        }
                        Message::CreateTask => {
                            if !state.input_value.is_empty() {
                                state
                                    .tasks
                                    .push(Task::new(state.input_value.clone()));
                                state.input_value.clear();
                            }
                        }
                        Message::FilterChanged(filter) => {
                            state.filter = filter;
                        }
                        Message::TaskMessage(i, TaskMessage::Delete) => {
                            state.tasks.remove(i);
                        }
                        Message::TaskMessage(i, task_message) => {
                            if let Some(task) = state.tasks.get_mut(i) {
                                task.update(task_message);
                            }
                        }
                        Message::Saved(_) => {
                            state.saving = false;
                            saved = true;
                        }
                        _ => {}
                    }
    
                    if !saved {
                        state.dirty = true;
                    }
    
                    if state.dirty && !state.saving {
                        state.dirty = false;
                        state.saving = true;
    
                        Command::perform(
                            SavedState {
                                input_value: state.input_value.clone(),
                                filter: state.filter,
                                tasks: state.tasks.clone(),
                            }
                            .save(),
                            Message::Saved,
                        )
                    } else {
                        Command::none()
                    }
                }
            }
        }
    
        fn view(&mut self) -> Element<Message> {
            match self {
                Todos::Loading => loading_message(),
                Todos::Loaded(State {
                    scroll,
                    input,
                    input_value,
                    filter,
                    tasks,
                    controls,
                    ..
                }) => {
                    let title = Text::new("todos")
                        .width(Length::Fill)
                        .size(100)
                        .color([0.5, 0.5, 0.5])
                        .horizontal_alignment(HorizontalAlignment::Center);
    
                    let input = TextInput::new(
                        input,
                        "What needs to be done?",
                        input_value,
                        Message::InputChanged,
                    )
                    .padding(15)
                    .size(30)
                    .on_submit(Message::CreateTask);
    
                    let controls = controls.view(&tasks, *filter);
                    let filtered_tasks =
                        tasks.iter().filter(|task| filter.matches(task));
    
                    let tasks: Element<_> = if filtered_tasks.count() > 0 {
                        tasks
                            .iter_mut()
                            .enumerate()
                            .filter(|(_, task)| filter.matches(task))
                            .fold(Column::new().spacing(20), |column, (i, task)| {
                                column.push(task.view().map(move |message| {
                                    Message::TaskMessage(i, message)
                                }))
                            })
                            .into()
                    } else {
                        empty_message(match filter {
                            Filter::All => "You have not created a task yet...",
                            Filter::Active => "All your tasks are done! :D",
                            Filter::Completed => {
                                "You have not completed a task yet..."
                            }
                        })
                    };
    
                    let content = Column::new()
                        .max_width(800)
                        .spacing(20)
                        .push(title)
                        .push(input)
                        .push(controls)
                        .push(tasks);
    
                    Scrollable::new(scroll)
                        .padding(40)
                        .push(
                            Container::new(content).width(Length::Fill).center_x(),
                        )
                        .into()
                }
            }
        }
    }
    
    #[derive(Debug, Clone, Serialize, Deserialize)]
    struct Task {
        description: String,
        completed: bool,
    
        #[serde(skip)]
        state: TaskState,
    }
    
    #[derive(Debug, Clone)]
    pub enum TaskState {
        Idle {
            edit_button: button::State,
        },
        Editing {
            text_input: text_input::State,
            delete_button: button::State,
        },
    }
    
    impl Default for TaskState {
        fn default() -> Self {
            TaskState::Idle {
                edit_button: button::State::new(),
            }
        }
    }
    
    #[derive(Debug, Clone)]
    pub enum TaskMessage {
        Completed(bool),
        Edit,
        DescriptionEdited(String),
        FinishEdition,
        Delete,
    }
    
    impl Task {
        fn new(description: String) -> Self {
            Task {
                description,
                completed: false,
                state: TaskState::Idle {
                    edit_button: button::State::new(),
                },
            }
        }
    
        fn update(&mut self, message: TaskMessage) {
            match message {
                TaskMessage::Completed(completed) => {
                    self.completed = completed;
                }
                TaskMessage::Edit => {
                    self.state = TaskState::Editing {
                        text_input: text_input::State::focused(),
                        delete_button: button::State::new(),
                    };
                }
                TaskMessage::DescriptionEdited(new_description) => {
                    self.description = new_description;
                }
                TaskMessage::FinishEdition => {
                    if !self.description.is_empty() {
                        self.state = TaskState::Idle {
                            edit_button: button::State::new(),
                        }
                    }
                }
                TaskMessage::Delete => {}
            }
        }
    
        fn view(&mut self) -> Element<TaskMessage> {
            match &mut self.state {
                // 重要
                TaskState::Idle { edit_button } => {
                    let checkbox = Checkbox::new(
                        self.completed,
                        &format!("{}{}", self.description, "***").to_string(),
                        TaskMessage::Completed,
                    )
                    .width(Length::Fill);
    
                    Row::new()
                        .spacing(20)
                        .align_items(Align::Center)
                        // 空闲状态时,左侧一个checkbox,右侧的button样式是Icon
                        .push(checkbox)
                        .push(
                            // edit_button是state数据,edit_icon()返回的是一个Unicode字符作为显示字符(浏览器上不一致应该是fonts的原因)
                            Button::new(edit_button, edit_icon())
                                .on_press(TaskMessage::Edit)
                                .padding(10)
                                .style(style::Button::Icon),
                        )
                        .into()
                }
                // 当变为Editing状态时的view变化(要变成红色Delete)
                TaskState::Editing {
                    text_input,
                    delete_button,
                } => {
                    let text_input = TextInput::new(
                        text_input,
                        "Describe your task...",
                        &self.description,
                        TaskMessage::DescriptionEdited,
                    )
                    .on_submit(TaskMessage::FinishEdition)
                    .padding(10);
    
                    Row::new()
                        .spacing(20)
                        .align_items(Align::Center)
                        // editing状态下左侧没有了checkbox
                        .push(text_input)
                        .push(
                            // delete_button是state数据
                            Button::new(
                                delete_button,
                                Row::new()
                                    .spacing(10)
                                    .push(delete_icon())
                                    .push(Text::new("Delete")),
                            )
                            .on_press(TaskMessage::Delete)
                            .padding(10)
                            // 可以看到Editing状态时的button的样式是这个
                            .style(style::Button::Destructive),
                        )
                        .into()
                }
            }
        }
    }
    
    #[derive(Debug, Default, Clone)]
    pub struct Controls {
        all_button: button::State,
        active_button: button::State,
        completed_button: button::State,
    }
    
    impl Controls {
        fn view(&mut self, tasks: &[Task], current_filter: Filter) -> Row<Message> {
            let Controls {
                all_button,
                active_button,
                completed_button,
            } = self;
    
            let tasks_left = tasks.iter().filter(|task| !task.completed).count();
    
            let filter_button = |state, label, filter, current_filter| {
                let label = Text::new(label).size(16);
                let button =
                    Button::new(state, label).style(style::Button::Filter {
                        selected: filter == current_filter,
                    });
    
                button.on_press(Message::FilterChanged(filter)).padding(8)
            };
    
            Row::new()
                .spacing(20)
                .align_items(Align::Center)
                .push(
                    Text::new(&format!(
                        "{} {} left",
                        tasks_left,
                        if tasks_left == 1 { "task" } else { "tasks" }
                    ))
                    .width(Length::Fill)
                    .size(16),
                )
                .push(
                    Row::new()
                        .width(Length::Shrink)
                        .spacing(10)
                        .push(filter_button(
                            all_button,
                            "All",
                            Filter::All,
                            current_filter,
                        ))
                        .push(filter_button(
                            active_button,
                            "Active",
                            Filter::Active,
                            current_filter,
                        ))
                        .push(filter_button(
                            completed_button,
                            "Completed",
                            Filter::Completed,
                            current_filter,
                        )),
                )
        }
    }
    
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
    pub enum Filter {
        All,
        Active,
        Completed,
    }
    
    impl Default for Filter {
        fn default() -> Self {
            Filter::All
        }
    }
    
    impl Filter {
        fn matches(&self, task: &Task) -> bool {
            match self {
                Filter::All => true,
                Filter::Active => !task.completed,
                Filter::Completed => task.completed,
            }
        }
    }
    
    fn loading_message() -> Element<'static, Message> {
        Container::new(
            Text::new("Loading...")
                .horizontal_alignment(HorizontalAlignment::Center)
                .size(50),
        )
        .width(Length::Fill)
        .height(Length::Fill)
        .center_y()
        .into()
    }
    
    fn empty_message(message: &str) -> Element<'static, Message> {
        Container::new(
            Text::new(message)
                .width(Length::Fill)
                .size(25)
                .horizontal_alignment(HorizontalAlignment::Center)
                .color([0.7, 0.7, 0.7]),
        )
        .width(Length::Fill)
        .height(Length::Units(200))
        .center_y()
        .into()
    }
    
    // Fonts
    const ICONS: Font = Font::External {
        name: "Icons",
        bytes: include_bytes!("../fonts/simkai.ttf"),
    };
    
    fn icon(unicode: char) -> Text {
        Text::new(&unicode.to_string())
            .font(ICONS)
            .width(Length::Units(20))
            .horizontal_alignment(HorizontalAlignment::Center)
            .size(20)
    }
    
    // 这个字符在web和原生应用中的显示比较一致
    fn edit_icon() -> Text { icon('') } // 'u{F1F8}'是Unicode字符〇 fn delete_icon() -> Text { icon('') } // Persistence #[derive(Debug, Clone, Serialize, Deserialize)] struct SavedState { input_value: String, filter: Filter, tasks: Vec<Task>, } #[derive(Debug, Clone)] enum LoadError { FileError, FormatError, } #[derive(Debug, Clone)] enum SaveError { DirectoryError, FileError, WriteError, FormatError, }
    // 这个应用可以编译为Web应用,但是这里保存数据在web和普通应用间有点区别,所以这里通过这个宏来描述web编译时用哪个实现,非web应用时用哪种实现 #[cfg(not(target_arch
    = "wasm32"))] impl SavedState { fn path() -> std::path::PathBuf { let mut path = if let Some(project_dirs) = directories::ProjectDirs::from("rs", "Iced", "Todos") { project_dirs.data_dir().into() } else { std::env::current_dir().unwrap_or(std::path::PathBuf::new()) }; path.push("todos.json"); path } async fn load() -> Result<SavedState, LoadError> { use async_std::prelude::*; let mut contents = String::new(); let mut file = async_std::fs::File::open(Self::path()) .await .map_err(|_| LoadError::FileError)?; file.read_to_string(&mut contents) .await .map_err(|_| LoadError::FileError)?; serde_json::from_str(&contents).map_err(|_| LoadError::FormatError) } async fn save(self) -> Result<(), SaveError> { use async_std::prelude::*; let json = serde_json::to_string_pretty(&self) .map_err(|_| SaveError::FormatError)?; let path = Self::path(); if let Some(dir) = path.parent() { async_std::fs::create_dir_all(dir) .await .map_err(|_| SaveError::DirectoryError)?; } { let mut file = async_std::fs::File::create(path) .await .map_err(|_| SaveError::FileError)?; file.write_all(json.as_bytes()) .await .map_err(|_| SaveError::WriteError)?; } // This is a simple way to save at most once every couple seconds async_std::task::sleep(std::time::Duration::from_secs(2)).await; Ok(()) } } #[cfg(target_arch = "wasm32")] impl SavedState { fn storage() -> Option<web_sys::Storage> { let window = web_sys::window()?; window.local_storage().ok()? } async fn load() -> Result<SavedState, LoadError> { let storage = Self::storage().ok_or(LoadError::FileError)?; let contents = storage .get_item("state") .map_err(|_| LoadError::FileError)? .ok_or(LoadError::FileError)?; serde_json::from_str(&contents).map_err(|_| LoadError::FormatError) } async fn save(self) -> Result<(), SaveError> { let storage = Self::storage().ok_or(SaveError::FileError)?; let json = serde_json::to_string_pretty(&self) .map_err(|_| SaveError::FormatError)?; storage .set_item("state", &json) .map_err(|_| SaveError::WriteError)?; let _ = wasm_timer::Delay::new(std::time::Duration::from_secs(2)).await; Ok(()) } } // 设置样式,重点关注 mod style { use iced::{button, Background, Color, Vector}; pub enum Button { Filter { selected: bool }, Icon, Destructive, } impl button::StyleSheet for Button { fn active(&self) -> button::Style { match self { Button::Filter { selected } => { if *selected { button::Style { background: Some(Background::Color( Color::from_rgb(0.2, 0.2, 0.7), )), border_radius: 10, text_color: Color::WHITE, ..button::Style::default() } } else { button::Style::default() } }, Button::Icon => button::Style { text_color: Color::from_rgb(0.5, 0.5, 0.5), ..button::Style::default() }, Button::Destructive => button::Style { // 红色,0.8*255要这么对应CSS里的配置 background: Some(Background::Color(Color::from_rgb( 0.8, 0.2, 0.2, ))), border_radius: 5, text_color: Color::WHITE, shadow_offset: Vector::new(1.0, 1.0), ..button::Style::default() }, } } fn hovered(&self) -> button::Style { let active = self.active(); button::Style { text_color: match self { Button::Icon => Color::from_rgb(0.2, 0.2, 0.7), Button::Filter { selected } if !selected => { Color::from_rgb(0.2, 0.2, 0.7) } _ => active.text_color, }, shadow_offset: active.shadow_offset + Vector::new(0.0, 1.0), ..active } } } }

    4.Windows10配置gui开发环境,待写;

    5.Ubuntu20.04开发gui开发环境;

    5.1.build时如果出现failed to run custom build command for `x11 v2.18.2`需要安装pkg-config和org-dev两个依赖(用apt即可安装)

  • 相关阅读:
    Codeforces Round #605 (Div. 3)E
    Codeforces Round #628 (Div. 2)
    Codeforces Round #627 (Div. 3)
    AC自动机,知识点+hdu模板题
    Tire树,hdu2846,hdu4825
    Educational Codeforces Round 83 (Rated for Div. 2)
    分层最短路
    初入mysql的学习
    赛后总结
    Codeforces Round #625 (Div. 2, based on Technocup 2020 Final Round) D
  • 原文地址:https://www.cnblogs.com/silentdoer/p/13056165.html
Copyright © 2011-2022 走看看