#qt #load #ui #analog #path #designer #uic

bin+lib ruic

Load Qt Designer .ui files into Rust code at compile time

1 unstable release

0.1.1 Jul 6, 2021

#1618 in Development tools

MIT license

16KB
208 lines

ruic

The Rust analog to Qt's uic.

Installation

cargo install ruic

Usage

USAGE:
    ruic [FLAGS] [OPTIONS] [path]

FLAGS:
        --all             Load objects that would ordinarily be ignored
    -f, --format          Run rustfmt on output
    -h, --help            Prints help information
        --no-recursive    Do not recursively scan directories
    -V, --version         Prints version information

ARGS:
    <path>    Directory or file to scan [default: .]

OPTIONS:
    -o, --out <out>          Output file [default: path + "/uic.rs"]
    -s, --suffix <suffix>    Suffix to append to widget names, e.g. "Ui" to turn "App" into "AppUi" [default: ]

How It Works

ruic generates a single .rs file out of one or more Qt Designer .ui files. It does this by loading all the files into source code and generating a load method for each one. Generated files require a few dependencies:

Example

Suppose you use Qt Designer to create the following file:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>HelloWorld</class>
 <widget class="QDialog" name="HelloWorld">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Dialog</string>
  </property>
  <layout class="QHBoxLayout" name="horizontalLayout">
   <item>
    <widget class="QLabel" name="label_HelloWorld">
     <property name="text">
      <string>Hello world!</string>
     </property>
    </widget>
   </item>
   <item>
    <widget class="QLineEdit" name="SayHi"/>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>

Running ruic on it will output the following .rs file:

// This file is automatically generated.
use cpp_core::{CastInto, Ptr};
use qt_core::{QBox, QPtr};
use qt_ui_tools::QUiLoader;
use qt_widgets::*;

#[derive(Debug)]
pub struct HelloWorld {
    pub widget: QBox<QDialog>,
    pub say_hi: QPtr<QLineEdit>,
}
impl HelloWorld {
    pub fn load<P: CastInto<Ptr<QWidget>>>(parent: P) -> Self {
        unsafe {
            let loader = QUiLoader::new_0a();
            loader.set_language_change_enabled(true);
            let bytes = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><ui version=\"4.0\"><class>HelloWorld</class><widget class=\"QDialog\" name=\"HelloWorld\"><property name=\"geometry\"><rect><x>0</x><y>0</y><width>400</width><height>300</height></rect></property><property name=\"windowTitle\"><string>Dialog</string></property><layout class=\"QHBoxLayout\" name=\"horizontalLayout\"><item><widget class=\"QLabel\" name=\"label_HelloWorld\"><property name=\"text\"><string>Hello world!</string></property></widget></item><item><widget class=\"QLineEdit\" name=\"SayHi\"/></item></layout></widget><resources/><connections/></ui>".as_bytes();
            let widget = loader.load_bytes_with_parent(bytes, parent);
            assert!(!widget.is_null(), "invalid ui file");
            Self {
                say_hi: widget.find_child("SayHi").unwrap(),
                widget: QBox::from_q_ptr(widget.into_q_ptr().dynamic_cast()),
            }
        }
    }
}

Note: load's safety is ensured by loading the entire content of the .ui file into source code.

To make use of this file, you could then write a Rust+Qt module along these lines:

use std::rc::Rc;

use cpp_core::{CastInto, Ptr};
use qt_widgets::QWidget;

use crate::uic;

pub struct HelloWorld {
    ui: uic::HelloWorld,
}

impl HelloWorld {
    fn new<P: CastInto<Ptr<QWidget>>>(parent: P) -> Rc<Self> {
        let this = Rc::new(Self {
            ui: uic::HelloWorld::load(parent),
        });
        unsafe { this.init() };
        this
    }

    unsafe fn init(self: &Rc<Self>) {
        /* add slot + signal connectors, etc. */
    }
}

Alternatively, you could pass something like --suffix=Ui to ruic in order to turn crate::uic::HelloWorld into crate::uic::HelloWorldUi, allowing you to import it directly without a name clash.

Note that in Rust+Qt, the way to create a parentless widget is to pass NullPtr as the parent.

Ignored Fields

By default, some fields are not loaded as variables into the struct:

  • QLabels that are named "label", start with "label_", or end with "_label"
  • QGroupBoxes
  • QLayouts with children

If you want to include such fields, pass --all to ruic on the command line. With --all, the generated file from the example above would instead be:

// This file is automatically generated.
use cpp_core::{CastInto, Ptr};
use qt_core::{QBox, QPtr};
use qt_ui_tools::QUiLoader;
use qt_widgets::*;

#[derive(Debug)]
pub struct HelloWorld {
    pub widget: QBox<QDialog>,
    pub horizontal_layout: QPtr<QHBoxLayout>,
    pub label_hello_world: QPtr<QLabel>,
    pub say_hi: QPtr<QLineEdit>,
}
impl HelloWorld {
    pub fn load<P: CastInto<Ptr<QWidget>>>(parent: P) -> Self {
        unsafe {
            let loader = QUiLoader::new_0a();
            loader.set_language_change_enabled(true);
            let bytes = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><ui version=\"4.0\"><class>HelloWorld</class><widget class=\"QDialog\" name=\"HelloWorld\"><property name=\"geometry\"><rect><x>0</x><y>0</y><width>400</width><height>300</height></rect></property><property name=\"windowTitle\"><string>Dialog</string></property><layout class=\"QHBoxLayout\" name=\"horizontalLayout\"><item><widget class=\"QLabel\" name=\"label_HelloWorld\"><property name=\"text\"><string>Hello world!</string></property></widget></item><item><widget class=\"QLineEdit\" name=\"SayHi\"/></item></layout></widget><resources/><connections/></ui>".as_bytes();
            let widget = loader.load_bytes_with_parent(bytes, parent);
            assert!(!widget.is_null(), "invalid ui file");
            Self {
                horizontal_layout: widget.find_child("horizontalLayout").unwrap(),
                label_hello_world: widget.find_child("label_HelloWorld").unwrap(),
                say_hi: widget.find_child("SayHi").unwrap(),
                widget: QBox::from_q_ptr(widget.into_q_ptr().dynamic_cast()),
            }
        }
    }
}

Dependencies

~7MB
~127K SLoC