Qt;Athon 2025: Meet the Winners of Our Global Student Coding Competition

In the fall of 2025, Qt Group hosted its second Qt;Athon—an online global coding competition for students. This time Qt;Athon was hosted in collaboration with Extenly. The event brought together 138 student teams from 32 countries, all challenged to push their creativity and technical skills. 

2D Rendering - Introducing Qt Canvas Painter

Let’s talk about Qt Canvas Painter — a new imperative 2D rendering API aiming to combine performance, productivity and modern features. This blog post brings you up to date with some 2D rendering history, the current status and future possibilities.

To get us started, here's a short introduction video of 2D rendering with the Qt Canvas Painter.

Qt 6.10.2 Released

Qt 6.10.2 is now available for download. As a patch release, Qt 6.10.2 doesn’t introduce new features, but it delivers around 300 bug fixes, security improvements, and quality enhancements on top of Qt 6.10.1. For a full overview of the most notable changes, take a look at the Qt 6.10.2 release notes.

Qt Widgets to Qt Quick, An Application Journey Part 1

- A Tale of Two Software Architectures

I've been working with Qt on and off for more than 20 years and during that time I've seen Qt Widgets grow and then the advent of Qt Quick. This means having two different graphics stack available at your fingertips in Qt.

Qt Creator 18.0.2 released

We are happy to announce the release of Qt Creator 18.0.2!

This release fixes a range of smaller issues like the persistence of the "Always save files before build" option and an issue that occurred when using some custom toolchains with vcpkg. It also updates the wizard templates for the Qt Safe Renderer.

C++26 Reflection 💚 QRangeModel

In the Qt Company's R&D organization we have made it a tradition to start the year with a Hackathon where anyone can work on anything they find interesting. It's a great opportunity to work on or with something else than usual, to try out new technologies, or to generally scratch whatever might have been itching. We started with a pitching session the week before so that people that are looking for inspiration or projects to join know what's on the buffet. And then on Wednesday morning we kicked off the hacking, giving everyone two full days to work on their project before the presentations on Friday noon.

REST API Development with Qt 6

This post describes an experiment using Qt 6.7’s REST APIs to explore Stripe’s payment model, and what I learned building a small desktop developer tool.

Recent Qt releases have included several conveniences for developing clients of remote REST APIs. I recently tried it out with the Stripe payments REST API to get to grips with the Qt REST API in the real world. The overloading of the term API is unhelpful, I find, but hopefully not too confusing here.

As with almost everything I try out, I created Qt desktop tooling as a developer aid to exploring the Stripe API and its behavior. Naming things is hard, but given that I want to put a “Q” in the name, googling “cute stripes” gives lots of hits about fashion, and the other too-obvious-to-say pun, I’ve pushed it to GitHub as “Qashmere“:

setAlternatingRowColors(true);

Developers using REST APIs will generally be familiar with existing tooling such as Postman and Bruno, for synthesizing calls to collections of REST APIs. Indeed, Qashmere uses the Stripe Postman JSON definition to present the collection of APIs and parameters. Such tools have scripting interfaces and state to create workflows that a client of the REST API needs to support, like “create a payment, get the id of the payment back from the REST API and then cancel the payment with the id”, or “create a payment, get the id of the payment back from the REST API and then confirm it by id with a given credit card”.

So why create Qashmere? In addition to REST APIs, Stripe maintains objects which change state over time. The objects remain at REST until acted on by an external force, and when such an action happens a notification is sent to clients about those state changes, giving them a chance to react. I wanted to be able to collect the REST requests/responses and the notified events and present them as they relate to the Stripe objects. Postman doesn’t know about events or about Stripe objects in particular, except that it is possible to write a script in Postman to extract the object which is part of a JSON payload. Postman also doesn’t know that if a Payment Intent is created, there are a subset of next steps which could be in a workflow, such as cancel, capture or confirm payment etc.

Something that I discovered in the course of trying this out is that when I confirm a Payment Intent, a new Charge object is created and sent to me with the event notification system. Experimental experiences like that help build intuition.

Stripe operates with real money, but it also provides for sandboxes where synthetic payments, customers etc can be created and processed with synthetic payment methods and cards. As Qashmere is only useful as a developer tool or learning aid, it only works with Stripe sandboxes.

Events from Stripe are sent to pre-configured web servers owned by the client. The web servers need to have a public IP address, which is obviously not appropriate for a desktop application. A WebSocket API would be more suitable and indeed the stripe cli tool uses a WebSocket to receive events, but the WebSocket protocol is not documented or stable. Luckily the stripe cli tool can be used to relay events to another HTTP server, so Qashmere runs a QHttpServer for that purpose.

Implementation with Qt REST API

The QRestReply wraps a QNetworkReply pointer and provides convenience API for accessing the HTTP return code and for creating a QJsonDocument from the body of the response. It must be created manually if using QNetworkAccessManager directly. However the new QRestAccessManager wraps a QNetworkAccessManager pointer, again to provide convenience APIs and overloads for making requests that are needed in REST APIs (though some less common verbs like OPTIONS and TRACE are not built-in). The QRestAccessManager has conveniences like overloads that provide a way to supply callbacks which already take the QRestReply wrapper object as a parameter. If using a QJsonDocument request overload, the “application/jsonContent-Type is automatically set in the header.

One of the inconveniences of QRestAccessManager is that in Qashmere I use an external definition of the REST API from the Postman definition which includes the HTTP method. Because the QRestAccessManager provides strongly typed API for making requests I need to do something like:

if (method == "POST") {
rest.post(request, requestData, this, replyHandler);
} else if (method == "GET") {
rest.get(request, this, replyHandler);
} else if (method == "DELETE") {
rest.deleteResource(request, this, replyHandler);
}

There is a sendCustomRequest class API which can be used with a string, but it does not have an overload for QJsonDocument, so the convenience of having the Content-Type header set is lost. This may be an oversight in the QRestAccessManager API.

Another missing feature is URL parameter interpolation. Many REST APIs are described as something like /v1/object/:object_id/cancel, and it would be convenient to have a safe way to interpolate the parameters into the URL, such as:

QUrl result = QRestAccessManager::interpolatePathParameters(
"/v1/accounts/:account_id/object/:object_id/cancel", {
{"account_id", "acc_1234"},
{"object_id", "obj_5678"}
}
);

This is needed to avoid bugs such as a user-supplied parameter containing a slash for example.

Coding Con Currency

In recent years I’ve been writing and reading more Typescript/Angular code which consumes REST services, and less C++. I’ve enjoyed the way Promises work in that environment, allowing sequences of REST requests, for example, to be easy to write and read. A test of a pseudo API could await on requests to complete and invoke the next one with something like:

requestFactory.setBaseURL("http://some_service.com");
async testWorkflow(username: string, password: string) {
const loginRequest = requestFactory.makeRequest("/login");
const loginRequestData = new Map();
loginRequestData.setParam("username", username);
loginRequestData.setParam("password", password);
const loginResponse = await requestAPI.post(
loginRequest, loginRequestData);
const bearerToken = loginResponse.getData();
requestAPI.setBearerToken(bearerToken);
const listingRequest = requestFactory.makeRequest("/list_items");
const listingResponse = await requestAPI.get(listingRequest);
const listing = JSON.parse(listingResponse.getData());
const firstItemRequest = requestFactory.makeRequest(
"/retrieve_item/:item_id",
 
{
item_id: listing[0].item_id
}
);
const firstItem = await requestAPI.get(firstItemRequest);
}

The availability of async functions and the Promise to await on make a test like this quite easy to write, and the in-application use of the API uses the same Promises, so there is little friction between application code and test code.

I wanted to see if I can recreate something like that based on the Qt networking APIs. I briefly tried using C++20 coroutines because they would allow a style closer to async/await, but the integration friction with existing Qt types was higher than I wanted for an experiment.

Using the methods in QtFuture however, we already have a way to create objects representing the response from a REST API. The result is similar to the Typescript example, but with different ergonomics, using .then instead of the async and await keywords.

struct RestRequest
{
QString method;
QString requestUrl;
QHttpHeaders headers;
QHash<QString, QString> urlParams;
QUrlQuery queryParams;
std::variant<QUrlQuery, QJsonDocument> requestData;
};
struct RestResponse
{
QJsonDocument jsonDoc;
QHttpHeaders headers;
QNetworkReply::NetworkError error;
QUrl url;
int statusCode;
};
QFuture<RestResponse> makeRequest(RestRequest restRequest)
{
auto url = interpolatePathParameters(
restRequest.requestUrl,
 
restRequest.urlParams);
auto request = requestFactory.createRequest(url);
auto requestBodyDoc = extractRequestContent(restRequest.requestData);
auto requestBody = requestBodyDoc.toJson(QJsonDocument::Compact);
auto reply = qRestManager.sendCustomRequest(request,
restRequest.method.toUtf8(),
requestBody,
&qnam,
[](QRestReply &) {});
return QtFuture::connect(reply, &QNetworkReply::finished).then(
[reply]() {
QRestReply restReply(reply);
auto responseDoc = restReply.readJson();
if (!responseDoc) {
throw std::runtime_error("Failed to read response");
}
RestResponse response;
response.jsonDoc = *responseDoc;
response.statusCode = restReply.httpStatus();
response.error = restReply.error();
response.headers = reply->headers();
response.url = reply->url();
return response;
}
);
}

The QRestAccessManager API requires the creation of a dummy response function when creating a custom request because it is not really designed to be used this way. The result is an API accepting a request and returning a QFuture with the QJsonDocument content. While it is possible for a REST endpoint to return something else, we can follow the Qt philosophy of making the most expected case as easy as possible, while leaving most of the rest possible another way. This utility makes writing unit tests relatively straightforward too:

RemoteAPI remoteApi;
remoteApi.setBaseUrl(QUrl("https://dog.ceo"));
auto responseFuture = remoteApi.makeRequest(
{"GET",
"api/breed/:breed/:sub_breed/images/random",
{},
{
{"breed", "wolfhound"},
 
{"sub_breed", "irish"}
}});
QFutureWatcher<RestResponse> watcher;
QSignalSpy spy(&watcher, &QFutureWatcherBase::finished);
watcher.setFuture(responseFuture);
QVERIFY(spy.wait(10000));
auto jsonObject = responseFuture.result().jsonDoc.object();
QCOMPARE(jsonObject["status"], "success");
QRegularExpression regex(
R"(https://images\.dog\.ceo/breeds/wolfhound-irish/[^.]+.jpg)");
QVERIFY(regex.match(jsonObject["message"].toString()).hasMatch());

The result is quite similar to the Typescript above, but only because we can use spy.wait. In application code, we still need to use .then with a callback, but we can additionally use .onFailed and .onCanceled instead of making multiple signal/slot connections.

With the addition of QtFuture::whenAll, it is easy to make multiple REST requests at once and react when they are all finished, so perhaps something else has been gained too, compared to a signal/slot model:

RemoteAPI remoteApi;
remoteApi.setBaseUrl(QUrl("https://dog.ceo"));
auto responseFuture = remoteApi.requestMultiple({
{
"GET",
"api/breeds/list/all",
},
{"GET",
"api/breed/:breed/:sub_breed/images/random",
{},
{{"breed", "german"}, {"sub_breed", "shepherd"}}},
{"GET",
"api/breed/:breed/:sub_breed/images/random/:num_results",
{},
{{"breed", "wolfhound"},
 
{"sub_breed", "irish"},
 
{"num_results", "3"}}},
{"GET", "api/breed/:breed/list", {}, {{"breed", "hound"}}},
});
QFutureWatcher<QList<RestResponse>> watcher;
QSignalSpy spy(&watcher, &QFutureWatcherBase::finished);
watcher.setFuture(responseFuture);
QVERIFY(spy.wait(10000));
auto four_responses = responseFuture.result();
QCOMPARE(four_responses.size(), 4);
QCOMPARE(four_responses[0].jsonDoc.object()["status"], "success");
QVERIFY(four_responses[0].jsonDoc.object()["message"].
toObject()["greyhound"].isArray());
QRegularExpression germanShepherdRegex(
R"(https://images.dog.ceo/breeds/german-shepherd/[^.]+.jpg)");
QCOMPARE(four_responses[1].jsonDoc.object()["status"], "success");
QVERIFY(germanShepherdRegex.match(
four_responses[1].jsonDoc.object()["message"].toString()).hasMatch());
QRegularExpression irishWolfhoundRegex(
R"(https://images.dog.ceo/breeds/wolfhound-irish/[^.]+.jpg)");
QCOMPARE(four_responses[2].jsonDoc.object()["status"], "success");
auto irishWolfhoundList =
 
four_responses[2].jsonDoc.object()["message"].toArray();
QCOMPARE(irishWolfhoundList.size(), 3);
QVERIFY(irishWolfhoundRegex.match(irishWolfhoundList[0].toString()).
hasMatch());
QVERIFY(irishWolfhoundRegex.match(irishWolfhoundList[1].toString()).
hasMatch());
QVERIFY(irishWolfhoundRegex.match(irishWolfhoundList[2].toString()).
hasMatch());
QCOMPARE(four_responses[3].jsonDoc.object()["status"], "success");
auto houndList = four_responses[3].jsonDoc.object()["message"].toArray();
QCOMPARE_GE(houndList.size(), 7);
QVERIFY(houndList.contains("afghan"));
QVERIFY(houndList.contains("basset"));
QVERIFY(houndList.contains("blood"));
QVERIFY(houndList.contains("english"));
QVERIFY(houndList.contains("ibizan"));
QVERIFY(houndList.contains("plott"));
QVERIFY(houndList.contains("walker"));

setAutoDeleteReplies(false);

I attempted to use new API additions in recent Qt 6 versions to interact with a few real-world REST services. The additions are valuable, but it seems that there are a few places where improvements might be possible. My attempt to make the API feel closer to what developers in other environments might be accustomed to had some success, but I’m not sure QFuture is really intended to be used this way.
Do readers have any feedback? Would using QCoro improve the coroutine experience? Is it very unusual to create an application with QWidgets instead of QML these days? Should I have used PyQt and the python networking APIs?

Debugging Qt WebAssembly - DWARF

 

One of the most tedious tasks a developer will do is debugging a nagging bug. It's worse when it's a web app, and even worse when its a webassembly web app.


The easiest way to debug Qt Webassembly is by configuring using the -g argument, or CMAKE_BUILD_TYPE=Debug . Emscripten embeds DWARF symbols in the wasm binaries. 

NOTE: Debugging wasm files with DWARF works only in the Chrome browser with the help of a browser extension. 

C/C++ DevTools Support (DWARF) browser extension. If you are using Safari or Firefox, or do not want to or cannot install a browser extension, you will need to generate source maps, which I will look at in my next blog post. 

DWARF debugging

You need to also enable DWARF in the browser developer tools settings, but you do not need symlinks to the source directories, as you would need to using source maps, as the binaries are embedded with the full directory path. Like magic!

 Emscripten embeds DWARF symbols into the binaries built with -g by default, so re-building Qt or your application in debug mode is all you need to do.

Qt builds debug libraries by default using the optimized argument -g2, which produces less debugging info, but results in faster link times. To preserve debug symbols you need to build Qt debug using the -g or -g3 argument. Both of these do the same thing.

Using DWARF debugger



Open Chome with the extention mentioned before installed, and open the console tools. Navigate to the Qt for WebAssembly web application you need to debug. Once it opens, it may take a few seconds for all the symbols and files to get parsed. If you are debugging into Qt, this will take quite a few seconds - just keep waiting. 
The javascript console will soon contain file source file paths and sources. You can find your file to debug, and set breakpoints. Just reload the page and once it hits a breakpoint, it will stop execution and highlight the current line in the source view.  It also will show variable names and values.


You can then step though your code as you would debugging a desktop application.

A taxonomy of modern user interfaces

We implemented Google’s Material 3 and Microsoft’s Fluent 2 design systems for QSkinny, as shown in the screenshots below (still an ongoing effort). This required some new features to be implemented, some of which hinted at new general paradigms within many other design systems.

Contrarily, some features often found in commercial design systems seem to have been deliberately left out of these two ‘generic’ ones above.

Material 3 Google’s Material 3 design system

Fluent 2 Microsoft’s Fluent 2 design system

Here is a subjective list of requirements for new design systems:

1. Subtle shadows everywhere

Of course the days of the 90s style thick drop shadows are gone, but apparently so is the complete flat style of the first iPhone. Shadows (and other ways of elevation) are used as a subtle way to draw attention and show levels of visual hierarchy.

For instance, a hovered or focused button will have a higher elevation than a resting button, and a dialog or a popup an even higher one. Typically these shadows have a minimal x/y offset, and a semitransparent color.

Material has a guide on elevation that lists shadows as one element, and colors as another one. Interestingly the usage of shadows in Material 3 was reduced in favor of coloring in comparison to its predecessor Material 2.

Fluent 2 similarly lists shadows and box strokes (i.e. box borders) as elements of elevation.

As a consequence, shadows should be 1st class citizens in UI toolkits: Defining a shadow on a control should be as easy as setting a color or a font.

Here is how QSkinny defines the gradient and shadow of the QskMenu class for Fluent 2:

    using Q = QskMenu;
    
    setBoxBorderColors( Q::Panel, pal.strokeColor.surface.flyout );
    setGradient( Q::Panel, pal.background.flyout.defaultColor );
    
    setShadowMetrics( Q::Panel, theme.shadow.flyout.metrics );
    setShadowColor( Q::Panel, theme.shadow.flyout.color );

Fluent 2 menu

Fluent 2 menus in QSkinny

Side note: Inner shadows (as opposed to drop shadows) seem to be very rare and have only been observed in sophisticated controls for commercial design systems.

2. Gradients are gone… or aren’t they?

Material 3 comes with an incredibly sophisticated color/palette system (see below); but surfaces like button panels etc. consist of one color only (Sometimes two colors need to be “flattened” into one, e.g. a semi-transparent hover color and an opaque button panel color).

Same for Fluent 2: The design system lists several shades of its iconic blue color and more flavors of gray without any gradient.

However, many commercial design systems still use gradients for “2.5D-style” effects. Of course they are applied as subtly as the aforementioned shadows, but are still being used a lot.

The QSkinny example below shows two small buttons on the left with subtle linear gradients (45 degrees) in the inner panel and a reversed gradient on the outer button ring. This has both an elevation and a “texture” effect without actually using textures (i.e. images). The shadows add to the depth, but even with only gradients would the sense of elevation still be there.

On the right is a gauge with conical gradients: The inner green one going from dark green to lighter green, and the outer one trying to mimic a chromatic effect. Radial gradiants are not shown here, but are equally usable.

gradients example

Gradients add depth and elevation

Outlook: For more sophistication there might be the need in the future of multi-dimensional gradients like the one below: a conical gradient (from purple to blue) and a radial one (transparent to full hue):

2-dimensional gradients

A gauge generated by AI showing multi-dimensional gradients

3. Those modern palettes aren’t what they used to be

Google created a new color system called HCT (hue, chroma, tone) built on older ones like HSL (hue, saturation, lightness). The difference is that HCT is “perceptually accurate”, i.e. colors that feel lighter are actually recognized as lighter within HCT with a higher tone value.

By changing only the chroma (~ saturation) and tone (~ lightness) of a color, one can generate a whole palette to be used within a UI that matches the input color nicely; as an example, see the Material 3 palette on Figma.

In terms of accessibility, by choosing e.g. background and font colors with a big tone difference, one can be reasonably sure that text is comfortly legible.

So even developers without design experience can create a visibly appealing UI, without even knowing the main input color. Creating the palette depending on an input color is used on e.g. Android, where the user can choose a color or just upload an image, and all controls adapt to the main color.

Side note from the Qt world: QColor::darker() and QColor::lighter() are not fine-grained enough for this purpose.

With the additional support of changing colors dynamically (see below), this means that palettes can easily be generated for both day and night mode from one or two input colors; one could also easily change between different brand colors.

The QSkinny tractor example shows how to extend the Material 3 palette with more colors and how to change between day/night mode as well as different brands easily, as switching colors and other hints is supported directly in the framework.

Ikea colors QSkinny tractor example in day/night mode with “Ikea colors”

John Deere colors QSkinny tractor example in day/night mode with “John Deere colors”

For an online demo of this app, see the tractor example on the main page.

So while changing between branding themes as above might not be a day-to-day use case, changing between day and night mode certainly is. This means that a UI toolkit should be able to change skin colors dynamically.

Note: Ideally not only colors can be changed, but any type of skin definition like box roundings, fonts etc. QSkinny treats every definition token, or so called “skin hint”, the same and can therefore exchange any of those at runtime.

4. Graphics: Write once, change all the time

Graphics like the button icons and the bigger tractor icon in the example above should be flexible in several ways:

They should adapt to color changes, as they are typically part of a definition that might change with the current skin; i.e. if the font and background colors change, then so need the icons.

Equally important, they should be flexible in size. With the Material 3 skin, e.g. buttons can have a different size in pixels depending on the physical size of the screen used: On smartphones that have a high resolution and a low physical size, buttons tend to have a bigger size in pixels than on tablets or TVs. This means that also the icons might have to grow with the buttons; when using layouts, they might even have to grow dynamically at runtime when resizing a window.

For the use case of different physical sizes, Material 3 supports so-called “density-independent pixels”; Fluent 2 has a similar concept named “device-independent pixels”.

These two points of course hint at using raster graphics like SVGs rather than JPGs/PNGs for icons; at least in Qt they have made kind of a revival recently.

In QSkinny flexible graphics have been supported from the start, and fit nicely with the use of icons in Material 3 and Fluent 2.

Lastly, at some point those graphics might also need to be animated with e.g. lottie. Material 3 and Fluent 2 don’t seem to have an urgent need for this, but other design systems might have this requirement.

5. Conclusion

Implementing Material 3 and Fluent 2 for QSkinny was a painful but solid way to future-proof our UI toolkit.

Modern toolkits should be following the paradigms of modern design systems: They should allow for easy definition of shadows, or generally speaking: They should allow for styling all aspects of a “box” like gradients, border colors, box shapes, and shadows.

In addition they should allow for dynamically changing all aspects of the current style like colors, fonts and the aforementioned box aspects.

Talking in QSkinny terms, we believe that styling aspects of boxes and changing skin hints dynamically is well covered after having implemented bot Material 3 and Fluent 2.

Of course there is more work to do when it comes to more advanced features for other design systems like inner shadows, multi-dimensional gradients or multiple shadow colors.

6. Quiz

Can you guess the “brand” from the colors being used in the screenshot below? Let me know and the first one to get it right will receive a drink of his/her choice next time we meet in person!

Which brand is this? Which brand do these colors belong to? Hint: It is not really a company.