Qt and QML
The Qt software development platform has been available for more than twenty-five years. Since the first releases, there have been graphics design tools to ease development. From 2009, Qt 4.7, QML (Qt modeling language), has been a preferred UI creation method written manually using the QtCreator, Qt programming IDE.
Today QML is part of the QtQuick framework. In 2018, Qt published Qt Design Studio to let QML be more approachable for graphics designers and other developers who prefer alternatives to programming code writing. Within Qt Design Studio, Qt provides a Photoshop bridge tool that allows bringing graphics from Adobe Photoshop to be used on QtQuick UIs.
Figma is a collaborative vector graphics application. It has become very popular for UI design as it lets designs be exported directly as CSS, Android, or iOS code.
Last June, my colleague wrote a blog post about his experiment using Figma REST API for QML generation. He wrote a proof of concept script to read JSON from the server and compose a simple QML file. That inspired me to write FigmaQML.
FigmaQML is a full-featured tool to interpret Figma designs to QML. Any design can be transformed to be used in a Qt application. FigmaQML is not a Figma bridge for Qt Design Studio as the Photoshop bridge is; FigmaQML output can be directly imported as resources in QtCreator.
FigmaQML aims to produce an exact interpretation of the original design. There is no need to programmatically recode visuals as seen on Figma. It must be noted, though, that FigmaQML is a tool to generate UI visuals. Any functionality and interaction must be implemented in the generated code. For example, buttons look right, but actions need to be bound.
FigmaQML can be used as an application or from the command line. The selected mode is dependent on given command line parameters. By default, FigmaQML starts with the UI.
For the command line, the basic syntax is <USER_TOKEN> <PROJECT_TOKEN> <OUTPUT_DIR>. See --help and README for further information.
Here I will elaborate on details of FigmaQML that may help understand certain aspects of the generated output. The presentation of Figma has a pretty different paradigm compared to QML, and thus certain things are tricky to be presented as QML items.
Later I will use terms page and view, and those may need some explanation: The Figma design contains one or more pages. Pages are reflected in FigmaQML, but furthermore it considers top-level visuals as views. A QML file is generated per view on the page. It is assumed that each page on design contains multiple frames that represent a single UI view.
The Qt Photoshop bridge (see above) generates images from designs, and those images are used to compose a UI. Analogously it would be possible to request Figma Server to render any part of the UI and compose the UI from bitmaps. That would provide very exact visual accuracy but is very inflexible for a dynamic UI.
FigmaQML uses mainly QtQuick Text and Shapes for visual UI construction blocks. With the exception of gradients, there are some differences between Figma and QML gradients, and thus all gradients are pre-rendered to images on the Figma Server.
QtQuick Shapes are constructed on SVG paths provided by the Figma Server. Shapes are rendered using a given border and fill color and style. The complication is that Figma lets you define any border to be aligned outside, center, or inside its vertices. SVG, and so Qt, supports only the center, meaning the border line goes on the polygon edge, whereas in Figma, it can also stay completely outside or inside the polygon.
The difference is resolved using render masks, and therefore using either outside or inside border alignment, the generated QML code ends up being quite complex.
The Figma concept of components is very different from QtQuick Component items. A Figma design is presented as a tree of nodes. A component is a prototype tree that may refer to an instance in the same tree or another tree. Instances are copies of the component node trees that override node values. A change in a component means the changes in its node values are reflected in all corresponding instance nodes. For example, the position of an instance is an overridden coordinate node compared to its component node.
As the instances are complete trees, it is possible to render an instance without considering the corresponding component at all. However, especially as components are not just collections of properties, they may also contain inner components. Without components, the generated QML code will be unbearably redundant. And we also want to generate reusable components, not just a monolithic implementation.
Qt Components encapsulate an inner implementation, its inherited and declared properties defining the interface. It basically means that child properties cannot be modified unless declared as property aliases.
Since Figma design is presented as property trees, there are no restrictive interfaces. Hence instances can have different properties or even completely different child components, regardless of their position in the node tree.
The basic properties can just be overridden, but as the child structure between a component and its instance can be possibly different, I ended up introducing each child as a QtQuick Component property. Then, if a child between a component and an instance differs, the child can be overridden.
To avoid unnecessary overrides, position and size properties that are common for each QML Item, are provided as aliases. This recursive structure lets instances override any of their own and children's properties, and therefore provides similar flexibility as Figma's tree presentation does.
When rendered, Qt is using local fonts and Figma is using fonts on the server (Figma Desktop may use local fonts). Therefore QtQuick and Figma rendered results may differ a lot, regarding if the same font, almost the same, or completely different font is used. There are some heuristics to provide an alternative font if precisely the same is not available. FigmaQML provides a set of tools to manage and map used fonts.
FigmaQML has been tested against a wide set of Figma designs. To confirm visual quality, it is a tedious task to run FigmaQML for each design manually. For some time, subjective watching was the method to estimate rendering maturity. FigmaQML does not produce a pixel-perfect clone from the original design: for example, fonts are rendered slightly differently on different platforms.
To support testing, FigmaQML can provide images from the Figma Server rendered and the generated QML rendering views. This makes automated testing easy by applying fuzzy image comparison. Structural similarity provides a snappy algorithm to evaluate image differences. See README how that is used for comparison. I have used a method that determines if an algorithm provides over 90% proximity; the testing is considered passed. Current test outcome results in between 91-100%.