The Rust analog to Qt's uic.
cargo install ruic
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: ]
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:
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.
By default, ruic ignores QButtonBoxes, QFrames, QGroupBoxes, QLabels, QLayouts, and QWidgets if they follow Qt Designer's default naming scheme for fields that are left unnamed: "<type>", "<type>_*" or "_*". 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()),
}
}
}
}