1个不稳定版本

0.0.1 2020年9月21日

#8 in #direct2d

MIT许可证

2KB

Native Windows GUI

欢迎来到Native Windows GUI(简称NWG)。一个用于在Microsoft Windows桌面开发原生GUI应用程序的Rust库。

NWG是在WINAPI之上的一个非常轻量级的包装器。它通过提供一个简单、安全且类似Rust的接口,让您作为开发者处理API的怪癖和粗糙边缘。

NWG保持简单。这意味着编译时间短,资源使用最少,查找文档时间更少,您开发应用程序的时间更多。

当然,您不必听我的,请查看展示示例

这是NWG的第三版和最终版。它被认为是“成熟”的,或者说,正如我所说,“待办事项列表已清空,它很可能保持这种状态”。这个版本实现了开发Windows应用程序所需的大部分内容。不要使用较旧版本,因为它们有“不可调和的设计决策”并且无法支持一些关键功能。未来的开发将在其他库中完成。

如果您已经读完了这个介绍,您应该知道我的twitter账号是#gdube_dev,您可以通过GitHub Sponsors支持这个项目。

任何支持都将受到极大的欢迎。

安装

要在项目中使用NWG,请将其添加到cargo.toml

[dependencies]
native-windows-gui = "1.0.12"
native-windows-derive = "1.0.3" # Optional. Only if the derive macro is used.

然后,在main.rs或lib.rs中

extern crate native_windows_gui as nwg;
extern crate native_windows_derive as nwd;  // Optional. Only if the derive macro is used.

Rust 2018别名

您可以通过在Cargo.toml中添加以下代码来跳过源代码中的extern crate定义:Cargo.toml 注意,过程宏仍然需要一个extern crate定义,所以这不会与native-windows-derive一起工作。

[dependencies]
nwg = {version = "^1.0.12", package = "native-windows-gui"}

试一下

亲自看看。NWG有很多示例和一个完整的交互式测试套件。您需要做的只是

git clone [email protected]:gabdube/native-windows-gui.git

cd native-windows-gui/native-windows-gui # Running the tests from the workspace screws up the features

cargo test everything --features "all"  # For the test suite
cargo run --example basic
cargo run --example calculator
cargo run --example message_bank
cargo run --example image_decoder_d --features "extern-canvas"
cargo run --example partials --features "listbox frame combobox"
cargo run --example system_tray --features "tray-notification message-window menu cursor"
cargo run --example dialog_multithreading_d --features "notice"
cargo run --example image_decoder_d --features "image-decoder file-dialog"
cargo run --example month_name_d --features "winnls textbox"
cargo run --example splash_screen_d --features "image-decoder"
cargo run --example drop_files_d --features "textbox"

cd examples/opengl_canvas
cargo run

# The closest thing to a real application in the examples
cd ../examples/sync-draw
cargo run

# Requires the console to be run as Admin because of the embed resource
cd ../examples/embed_resources
cargo run

从Ubuntu进行交叉编译

要求:MinGW编译器

sudo apt install gcc-mingw-w64-x86-64

要求:支持Rust

rustup target add x86_64-pc-windows-gnu

编译和运行基本示例

cargo build --release --target=x86_64-pc-windows-gnu
cargo build --release --target=x86_64-pc-windows-gnu --example basic
wine target/x86_64-pc-windows-gnu/release/examples/basic.exe

项目结构

这是主项目git。它被分为多个部分

  • native-windows-gui
    • 基础库。包含交互式测试套件和大量示例
  • native-windows-derive
    • 一个过程宏,它可以从Rust结构生成GUI应用程序(在我看来,这很酷)
  • docs/native-windows-docs 在线阅读
    • 详尽的文档,涵盖了您需要了解的关于NWG的所有内容
  • 展示
    • 示例的图片。如果您已经制作了一个NWG应用程序并想在这里分享,请发给我消息或打开一个PR。这里是免费的空间。

支持的功能

  • 整个winapi控制库 (参考)
    • 一些非常专门的控件不支持:平面滚动条、IP控件、Rebar和分页器。
  • 菜单和菜单栏
  • 图像和字体资源
    • BMP
    • ICO
    • CUR
    • PNG*
    • GIF*
    • JPG*
    • TIFF*
    • DDS*
    • *:使用Windows Imaging Component (WIC)的扩展图像格式。
  • 本地化支持
    • 使用Windows国家语言支持内部 (参考)
  • 工具提示
  • 系统托盘通知
  • 光标处理
  • 完整的剪贴板包装器
  • 部分模板支持
    • 将大型应用程序分割成块
  • 动态控件支持
    • 在运行时添加/删除控件
    • 在运行时绑定或解绑新事件
  • 支持多线程应用程序
    • 从另一个线程与GUI线程通信
    • 在不同的线程上运行多个窗口
  • 简单的布局配置
    • FlexboxLayout
    • GridLayout
  • 拖放
    • 从桌面拖放文件到窗口
  • 最常见的对话框
    • 文件对话框(保存、打开、打开文件夹)
    • 字体对话框
    • 颜色对话框
  • 可以由外部渲染API使用的画布
  • 高DPI感知
  • 支持无障碍功能
    • 标签导航
  • 支持低级系统消息捕获(HWND、MSG、WPARAM、LPARAM)
  • 使用Wine和mingw从Linux交叉编译和测试Windows。
    • 并非所有功能都受支持(但大多数都受支持,感谢WINE!)
    • 有关操作步骤,请参阅 https://zork.net/~st/jottings/rust-windows-and-debian.html

性能

这在一个 Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz, 3401 Mhz, 4 Core(s), 8 Logical Processor() 上进行测量

在发布模式下,basic 示例在磁盘上的大小为 163kb,在内存中为 900kb。启动时间是瞬间的。

交互式测试套件(具有每个功能和100多个测试)在磁盘上的大小为 931 kb,在内存中为 8MB。启动时间仍然是瞬间的。

基本应用程序的初始构建时间大约为 22秒。这主要是由于 winapi-rs 初始编译时间。后续编译时间大约为 0.7秒

开发

该库的开发被认为“已完成”。这意味着API将不会发生任何变化。如果发现错误或在文档的某些区域不明确,可以提出问题。如果我遗漏了非常重要的功能,它很可能会被添加。

许可证

NWG使用MIT许可证

代码示例

使用原生Windows派生

#![windows_subsystem = "windows"]
/*!
    A very simple application that shows your name in a message box.
    Unlike `basic_d`, this example uses layout to position the controls in the window
*/


extern crate native_windows_gui as nwg;
extern crate native_windows_derive as nwd;

use nwd::NwgUi;
use nwg::NativeUi;


#[derive(Default, NwgUi)]
pub struct BasicApp {
    #[nwg_control(size: (300, 115), position: (300, 300), title: "Basic example", flags: "WINDOW|VISIBLE")]
    #[nwg_events( OnWindowClose: [BasicApp::say_goodbye] )]
    window: nwg::Window,

    #[nwg_layout(parent: window, spacing: 1)]
    grid: nwg::GridLayout,

    #[nwg_control(text: "Heisenberg", focus: true)]
    #[nwg_layout_item(layout: grid, row: 0, col: 0)]
    name_edit: nwg::TextInput,

    #[nwg_control(text: "Say my name")]
    #[nwg_layout_item(layout: grid, col: 0, row: 1, row_span: 2)]
    #[nwg_events( OnButtonClick: [BasicApp::say_hello] )]
    hello_button: nwg::Button
}

impl BasicApp {

    fn say_hello(&self) {
        nwg::modal_info_message(&self.window, "Hello", &format!("Hello {}", self.name_edit.text()));
    }
    
    fn say_goodbye(&self) {
        nwg::modal_info_message(&self.window, "Goodbye", &format!("Goodbye {}", self.name_edit.text()));
        nwg::stop_thread_dispatch();
    }

}

fn main() {
    nwg::init().expect("Failed to init Native Windows GUI");
    nwg::Font::set_global_family("Segoe UI").expect("Failed to set default font");
    let _app = BasicApp::build_ui(Default::default()).expect("Failed to build UI");
    nwg::dispatch_thread_events();
}

基本示例。如果只需要简单的静态UI,则适用

#![windows_subsystem = "windows"]
/**
    A very simple application that show your name in a message box.

    This demo shows how to use NWG without the NativeUi trait boilerplate.
    Note that this way of doing things is alot less extensible and cannot make use of native windows derive.

    See `basic` for the NativeUi version and `basic_d` for the derive version
*/
extern crate native_windows_gui as nwg;
use std::rc::Rc;

fn main() {
    nwg::init().expect("Failed to init Native Windows GUI");
    nwg::Font::set_global_family("Segoe UI").expect("Failed to set default font");

    let mut window = Default::default();
    let mut name_edit = Default::default();
    let mut hello_button = Default::default();
    let layout = Default::default();

    nwg::Window::builder()
        .size((300, 115))
        .position((300, 300))
        .title("Basic example")
        .build(&mut window)
        .unwrap();

    nwg::TextInput::builder()
        .text("Heisenberg")
        .focus(true)
        .parent(&window)
        .build(&mut name_edit)
        .unwrap();

    nwg::Button::builder()
        .text("Say my name")
        .parent(&window)
        .build(&mut hello_button)
        .unwrap();

    nwg::GridLayout::builder()
        .parent(&window)
        .spacing(1)
        .child(0, 0, &name_edit)
        .child_item(nwg::GridLayoutItem::new(&hello_button, 0, 1, 1, 2))
        .build(&layout)
        .unwrap();

    let window = Rc::new(window);
    let events_window = window.clone();

    let handler = nwg::full_bind_event_handler(&window.handle, move |evt, _evt_data, handle| {
        use nwg::Event as E;

        match evt {
            E::OnWindowClose => 
                if &handle == &events_window as &nwg::Window {
                    nwg::modal_info_message(&events_window.handle, "Goodbye", &format!("Goodbye {}", name_edit.text()));
                    nwg::stop_thread_dispatch();
                },
            E::OnButtonClick => 
                if &handle == &hello_button {
                    nwg::modal_info_message(&events_window.handle, "Hello", &format!("Hello {}", name_edit.text()));
                },
            _ => {}
        }
    });

    nwg::dispatch_thread_events();
    nwg::unbind_event_handler(&handler);
}

使用NativeUi样板

#![windows_subsystem = "windows"]
/*!
    A very simple application that shows your name in a message box.
    Uses layouts to position the controls in the window
*/

extern crate native_windows_gui as nwg;
use nwg::NativeUi;


#[derive(Default)]
pub struct BasicApp {
    window: nwg::Window,
    layout: nwg::GridLayout,
    name_edit: nwg::TextInput,
    hello_button: nwg::Button
}

impl BasicApp {

    fn say_hello(&self) {
        nwg::modal_info_message(&self.window, "Hello", &format!("Hello {}", self.name_edit.text()));
    }
    
    fn say_goodbye(&self) {
        nwg::modal_info_message(&self.window, "Goodbye", &format!("Goodbye {}", self.name_edit.text()));
        nwg::stop_thread_dispatch();
    }

}

//
// ALL of this stuff is handled by native-windows-derive
//
mod basic_app_ui {
    use native_windows_gui as nwg;
    use super::*;
    use std::rc::Rc;
    use std::cell::RefCell;
    use std::ops::Deref;

    pub struct BasicAppUi {
        inner: Rc<BasicApp>,
        default_handler: RefCell<Option<nwg::EventHandler>>
    }

    impl nwg::NativeUi<BasicAppUi> for BasicApp {
        fn build_ui(mut data: BasicApp) -> Result<BasicAppUi, nwg::NwgError> {
            use nwg::Event as E;
            
            // Controls
            nwg::Window::builder()
                .flags(nwg::WindowFlags::WINDOW | nwg::WindowFlags::VISIBLE)
                .size((300, 115))
                .position((300, 300))
                .title("Basic example")
                .build(&mut data.window)?;

            nwg::TextInput::builder()
                .text("Heisenberg")
                .parent(&data.window)
                .focus(true)
                .build(&mut data.name_edit)?;

            nwg::Button::builder()
                .text("Say my name")
                .parent(&data.window)
                .build(&mut data.hello_button)?;

            // Wrap-up
            let ui = BasicAppUi {
                inner: Rc::new(data),
                default_handler: Default::default(),
            };

            // Events
            let evt_ui = Rc::downgrade(&ui.inner);
            let handle_events = move |evt, _evt_data, handle| {
                if let Some(ui) = evt_ui.upgrade() {
                    match evt {
                        E::OnButtonClick => 
                            if &handle == &ui.hello_button {
                                BasicApp::say_hello(&ui);
                            },
                        E::OnWindowClose => 
                            if &handle == &ui.window {
                                BasicApp::say_goodbye(&ui);
                            },
                        _ => {}
                    }
                }
            };

           *ui.default_handler.borrow_mut() = Some(nwg::full_bind_event_handler(&ui.window.handle, handle_events));

           // Layouts
           nwg::GridLayout::builder()
            .parent(&ui.window)
            .spacing(1)
            .child(0, 0, &ui.name_edit)
            .child_item(nwg::GridLayoutItem::new(&ui.hello_button, 0, 1, 1, 2))
            .build(&ui.layout)?;

            return Ok(ui);
        }
    }

    impl Drop for BasicAppUi {
        /// To make sure that everything is freed without issues, the default handler must be unbound.
        fn drop(&mut self) {
            let handler = self.default_handler.borrow();
            if handler.is_some() {
                nwg::unbind_event_handler(handler.as_ref().unwrap());
            }
        }
    }

    impl Deref for BasicAppUi {
        type Target = BasicApp;

        fn deref(&self) -> &BasicApp {
            &self.inner
        }
    }
}

fn main() {
    nwg::init().expect("Failed to init Native Windows GUI");
    nwg::Font::set_global_family("Segoe UI").expect("Failed to set default font");
    let _ui = BasicApp::build_ui(Default::default()).expect("Failed to build UI");
    nwg::dispatch_thread_events();
}

归属

对于测试套件中使用的图标(仅限那里)

无运行时依赖