Rust GUI编程

   2023-02-09 学习力0
核心提示:1.可以用iced框架,star比较多,而且在快速发展中,源码跨平台;2.在main方法文件最上面加上:#![windows_subsystem = "windows"],这样Windows平台运行编译好的gui程序时就不会弹出控制台框;对于Mac OS,Linux可以自己写个图标配置(Linux是.desktop文件)

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即可安装)

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • bloom-server 基于 rust 编写的 rest api cache 中间件
    bloom-server 基于 rust 编写的 rest api cache
    bloom-server 基于 rust 编写的 rest api cache 中间件,他位于lb 与api worker 之间,使用redis 作为缓存内容存储, 我们需要做的就是配置proxy,同时他使用基于share 的概念,进行cache 的分布存储,包含了请求端口(proxy,访问数据) 以及cache 控制端口(
    03-08
  • #新闻拍一拍# Oracle 调研如何避免让 Java 开发者投奔 Rust 和 Kotlin | Linux 中国
    #新闻拍一拍# Oracle 调研如何避免让 Java 开发
     导读:• 英特尔对迟迟不被 Linux 主线接受的 SGX Enclave 进行了第 38 次修订 • ARM 支持开源的 Panfrost Gallium3D 驱动本文字数:977,阅读时长大约:1分钟作者:硬核老王Oracle 调研如何避免让 Java 开发者投奔 Rust 和 KotlinOracle 委托分析公司 Omd
    03-08
  • Linux系统下Rust快速安装:国内镜像加速
    Linux系统下Rust快速安装:国内镜像加速
    官方网址和方法Install Rust - Rust Programming Language然而速度慢得让人难以置信。利用国内镜像进行windows的Linux子系统的Rust安装。rust 使用国内镜像,快速安装方法参考:RUST安装慢怎么办,使用镜像方式安装_网络_为中华之崛起而编程-CSDN博客我的操作
    03-08
  • Rust到底值不值得学--Rust对比、特色和理念
    前言其实我一直弄不明白一点,那就是计算机技术的发展,是让这个世界变得简单了,还是变得更复杂了。当然这只是一个玩笑,可别把这个问题当真。然而对于IT从业者来说,这可不是一个玩笑。几乎每一次的技术发展,都让这个生态变得更为复杂。“英年早秃”已经成
    03-08
  • 超33000行新代码,为Linux内核添加Rust支持的补丁已准备就绪
    超33000行新代码,为Linux内核添加Rust支持的补
    https://mp.weixin.qq.com/s/oKw9aBJSdmRoO6-rbLAkNw7 月 4 日,一套修订后的补丁被提交至 Linux 内核的邮件列表中,该补丁为在 Linux 内核中以 Rust 作为辅助编程语言提供了支持,借助 Rust 可以提高 Linux 内核和内存的安全。整套补丁包含 17 个子项,不光
    03-08
  • 【译】Rust 的 Result 类型入门
    【译】Rust 的 Result 类型入门
    A Primer on Rust’s Result Type 译文原文链接:https://medium.com/@JoeKreydt/a-primer-on-rusts-result-type-66363cf18e6a原文作者:Joe Kreydt译文出处:https://github.com/suhanyujie/article-transfer-rs译者:suhanyujietips:水平有限,翻译不当之
    03-08
  • Rust实战系列-基本语法
    Rust实战系列-基本语法
    主要介绍 Rust 的语法、基本类型和数据结构,通过实现一个简单版 grep 命令行工具,来理解 Rust 独有的特性。本文是《Rust in action》学习总结系列的第二部分,更多内容请看已发布文章:一、Rust实战系列-Rust介绍“主要介绍 Rust 的语法、基本类型和数据结
    03-08
  • 全栈程序员的新玩具Rust(三)板条箱
    上次用到了stdout,这次我们来写一个更复杂一点的游戏rust的标准库叫做std,默认就会引入。这次我们要用到一个随机数函数,而随机数比较尴尬的一点是这玩意不在标准库中,我们要额外依赖一个库。很多编程方案都有自己的模块化库系统,rust也不例外,不过rust
    02-10
  • 全栈程序员的新玩具Rust(六)第一个WASM程序
    全栈程序员的新玩具Rust(六)第一个WASM程序
    先上代码https://gitee.com/lightsever/rust_study/tree/master/wasm_hello01webassembly就不用再赘述了,耳朵里面快磨出茧子来了。rustwasm是火狐自家的玩具,让我们来继续做实验,让rust飞起来吧。环境安装安装好rust环境之后仍然需要 一个 wasm 工具包carg
    02-10
  • 【Rust】标准库-Result rust数据库
    环境Rust 1.56.1VSCode 1.61.2概念参考:https://doc.rust-lang.org/stable/rust-by-example/std/result.html示例main.rsmod checked {#[derive(Debug)]pub enum MathError {DivisionByZero,NonPositiveLogarithm,NegativeSquareRoot,}pub type MathResult =
    02-09
点击排行