slint/api/cpp
Tasuku Suzuki f24ad34a03
Some checks are pending
autofix.ci / format_fix (push) Waiting to run
autofix.ci / lint_typecheck (push) Waiting to run
autofix.ci / ci (push) Blocked by required conditions
Add support for CSS conic-gradient 'from <angle>' syntax (#9830)
* Add support for CSS conic-gradient 'from <angle>' syntax

Implement rotation support for conic gradients by adding the 'from <angle>'
syntax, which rotates the entire gradient by the specified angle.

- Add `from_angle` field to ConicGradient expression
- Parse 'from <angle>' syntax in compiler (defaults to 0deg when omitted)
- Normalize angles to 0-1 range (0.0 = 0°, 1.0 = 360°)
- Add `ConicGradientBrush::rotated_stops()` method that:
  * Applies rotation by adding from_angle to each stop position
  * Adds boundary stops at 0.0 and 1.0 with interpolated colors
  * Handles stops outside [0, 1] range for boundary interpolation
- Update all renderers (Skia, FemtoVG, Qt, Software) to use rotated_stops()

The rotation is applied at render time by the rotated_stops() method,
which ensures all renderers consistently handle the gradient rotation.

* Add screenshot to rotated conic gradient docs example

Wraps the rotated conic gradient example in CodeSnippetMD to automatically
generate and display a visual screenshot of the gradient rotation effect.
This makes it easier for users to understand how the 'from' parameter rotates
the gradient.

* Make ConicGradientBrush fields private

The from_angle and stops fields don't need to be pub since:
- Rust code in the same module can access them without pub
- C++ FFI access works through cbindgen-generated struct (C++ struct members are public by default)

* Optimize ConicGradientBrush::rotated_stops to avoid allocations

- Changed return type from Vec to SharedVector
- When from_angle is zero, returns a clone of internal SharedVector
  (only increments reference count instead of allocating new Vec)
- Removed break from duplicate position separation loop to handle
  all duplicate pairs, not just the first one
- Updated documentation to match actual implementation

* Remove automatic sorting in ConicGradientBrush::new() to match CSS spec

- CSS conic-gradient does not automatically sort color stops
- Stops are processed in the order specified by the user
- Changed boundary stop interpolation logic to use max_by/min_by
  instead of relying on sorted order
- This allows CSS-style hard transitions when stops are out of order

* Move conic gradient rotation processing to construction time

Major changes:
- ConicGradientBrush::new() now applies rotation and boundary stop
  processing immediately, instead of deferring to rotated_stops()
- Removed rotated_stops() method - backends now use stops() directly
- Changed to LinearGradientBrush pattern: store angle in first dummy stop
- Added angle() method to retrieve the stored angle
- Maintained #[repr(transparent)] by removing from_angle field
- All backends updated: rotated_stops() -> stops()
  - Qt backend
  - Skia renderer
  - femtovg renderer
  - Software renderer

C++ API changes:
- Added FFI function slint_conic_gradient_new() for C++ to call Rust's new()
- Updated make_conic_gradient() to call FFI function instead of manually
  constructing SharedVector
- Ensures C++-created gradients get full rotation processing

Benefits:
- Eliminates per-frame rotation calculations
- Reduces memory usage (no from_angle field)
- Consistent with LinearGradientBrush design
- C++ and Rust APIs now produce identical results

* Change ConicGradientBrush::new() from_angle parameter to use degrees

- Changed from_angle parameter from normalized form (0.0-1.0) to degrees
- Matches LinearGradientBrush API convention (angle in degrees)
- Updated internal conversion: from_angle / 360.0 for normalization
- Stores angle as-is in degrees in the first dummy stop
- FFI function slint_conic_gradient_new() passes degrees directly

Example usage:
  ConicGradientBrush::new(90.0, stops)  // 90 degrees
  LinearGradientBrush::new(90.0, stops) // 90 degrees (consistent)

* Fix ConicGradient color transformation methods to preserve angle

Changed brighter(), darker(), transparentize(), and with_alpha() methods
to clone and modify the gradient in-place instead of calling new().

- Clones the existing gradient (preserves angle and rotation)
- Modifies only color stops (skips first stop which contains angle)
- Avoids re-running expensive rotation processing
- Maintains the original angle information

Before: ConicGradientBrush::new(0.0, ...) // Lost angle information
After:  Clone + modify colors in-place     // Preserves angle

* Use premultiplied alpha interpolation for conic gradient colors

- Changed interpolate_color() to use premultiplied RGBA interpolation
- Updated signature to match Color::mix convention (&Color, factor)
- Added documentation explaining why we can't use Color::mix() here
  (Sass algorithm vs CSS gradient color interpolation)
- Reference: https://www.w3.org/TR/css-images-4/#color-interpolation

This ensures correct visual interpolation of semi-transparent colors
in gradient boundaries, following CSS gradient specification.

* Run rustfmt on conic gradient code

* Fix ConicGradientBrush edge cases and add comprehensive tests

- Handle stops that are all below 0.0 or all above 1.0
- Add default transparent gradient when no valid stops remain
- Add 7 unit tests covering basic functionality and edge cases

* Apply clippy suggestion: use retain() instead of filter().collect()

* Fix radial-gradient parsing to allow empty gradients

Allow @radial-gradient(circle) without color stops, fixing syntax test
regression from commit 820ae2b04.

The previous logic required a comma after 'circle', but it should only
error if there's something that is NOT a comma.

* Fix conic-gradient syntax test error markers

Update error markers to match actual compiler error positions.
The 'from 2' case produces two errors:
- One at the @conic-gradient expression level
- One at the literal '2' position

Auto-updated using SLINT_SYNTAX_TEST_UPDATE=1.

* Refactor ConicGradientBrush epsilon adjustment and update tests

- Move epsilon adjustment for first stop into rotation block
  (only needed when rotation is applied)
- Update property_view test to reflect boundary stops added by
  ConicGradientBrush::new()

* Update conic-gradient screenshot reference image

Update the reference screenshot to match the current rendering output.
The small pixel differences (1% different pixels, max color diff 3.46)
are due to minor rounding differences in the conic gradient implementation.

* Fix ConicGradientBrush C++ FFI to avoid C-linkage return type error

Refactored ConicGradientBrush construction to match LinearGradientBrush
pattern, fixing macOS Clang error about returning C++ types from extern "C"
functions.

Changes:
- Rust: Split ConicGradientBrush::new into simple construction + separate
  normalize_stops() and apply_rotation() methods
- Rust: Added FFI functions slint_conic_gradient_normalize_stops() and
  slint_conic_gradient_apply_rotation() that take pointers (no return value)
- C++: Construct SharedVector directly in make_conic_gradient(), then call
  Rust functions via pointer (matching LinearGradientBrush pattern)
- Optimized both methods to only copy when changes are needed

This resolves the macOS Clang error:
"'slint_conic_gradient_new' has C-linkage specified, but returns incomplete
type 'ConicGradientBrush' which could be incompatible with C"

The new approach maintains ABI compatibility while keeping complex gradient
processing logic in Rust.

* Fix C++ header generation to avoid GradientStop redefinition error

Resolves the macOS CI compilation error where GradientStop and
ConicGradientBrush were being defined in multiple headers
(slint_color_internal.h, slint_image_internal.h, and slint_brush_internal.h).

Changes:
- cbindgen.rs: Add ConicGradientBrush and FFI functions to slint_brush_internal.h include list
- cbindgen.rs: Add GradientStop, ConicGradientBrush, and FFI functions to exclude list for other headers
- slint_color.h: Add forward declaration for ConicGradientBrush
- slint_color.h: Add friend declaration for ConicGradientBrush to allow access to Color::inner

Root cause: After adding extern "C" functions in graphics/brush.rs,
cbindgen automatically detects and tries to include them in all headers
that use graphics/brush.rs as a source. The exclude list + filter logic
ensures these types only appear in slint_brush_internal.h.

This fixes the C++ compilation errors:
- "redefinition of 'GradientStop'"
- "ConicGradientBrush does not name a type"
- "Color::inner is private within this context"

* Prepare ConicGradientBrush FFI for Rust 2024 edition

Update FFI functions to use the new `#[unsafe(no_mangle)]` attribute
syntax and safe function signatures in preparation for Rust 2024 edition.

- Add `#![allow(unsafe_code)]` to graphics module for `#[unsafe(no_mangle)]`
- Add `#[cfg(feature = "ffi")]` to conditionally compile FFI functions
- Change from raw pointers to safe references (&mut)
- Remove manual null checks and unsafe blocks
2025-11-13 16:05:16 +01:00
..
cmake slint-compiler: Guess output format from file extension if not present 2025-07-11 08:32:03 +02:00
docs Bump version number to 1.15.0 2025-10-24 14:28:17 +00:00
esp-idf/slint Bump version number to 1.15.0 2025-10-24 14:28:17 +00:00
include Add support for CSS conic-gradient 'from <angle>' syntax (#9830) 2025-11-13 16:05:16 +01:00
tests Updgrade Catch2 dependency (#8846) 2025-07-05 08:54:24 +02:00
build.rs Auto-fixed clippy::needless_borrow 2025-02-07 09:02:45 +01:00
Cargo.toml Rename "Live Reload" feature to "Live Preview for Rust/C++" 2025-08-28 17:26:01 +02:00
cbindgen.rs Add support for CSS conic-gradient 'from <angle>' syntax (#9830) 2025-11-13 16:05:16 +01:00
CMakeLists.txt Bump version number to 1.15.0 2025-10-24 14:28:17 +00:00
lib.rs rich text: add an open-url function (#9936) 2025-11-05 04:10:19 +13:00
platform.rs C++: Remove unsafe keyword when it is not needed 2025-11-04 14:05:30 +01:00
README.md Fixup dead links to the old tutorial 2024-10-03 10:15:52 +02:00

Slint-cpp

A C++ UI toolkit

Slint is a UI toolkit that supports different programming languages. Slint.cpp is the C++ API to interact with a Slint UI from C++.

The complete C++ documentation can be viewed online at https://slint.dev/docs/cpp/.

Installing or Building Slint

Slint comes with a CMake integration that automates the compilation step of the .slint markup language files and offers a CMake target for convenient linkage.

Note: We recommend using the Ninja generator of CMake for the most efficient build and .slint dependency tracking. Install Ninja and select the CMake Ninja backend by passing -GNinja or setting the CMAKE_GENERATOR environment variable to Ninja.

Building from Sources

The recommended and most flexible way to use the C++ API is to build Slint from sources.

First you need to install the prerequisites:

  • Install Rust by following the Rust Getting Started Guide. Once this is done, you should have the rustc compiler and the cargo build system installed in your path.
  • cmake (3.21 or newer)
  • A C++ compiler that supports C++20 (e.g., MSVC 2019 16.6 on Windows)

You can include Slint in your CMake project using CMake's FetchContent feature. Insert the following snippet into your CMakeLists.txt to make CMake download the latest release, compile it and make the CMake integration available:

include(FetchContent)
FetchContent_Declare(
    Slint
    GIT_REPOSITORY https://github.com/slint-ui/slint.git
    # `release/1` will auto-upgrade to the latest Slint >= 1.0.0 and < 2.0.0
    # `release/1.0` will auto-upgrade to the latest Slint >= 1.0.0 and < 1.1.0
    GIT_TAG release/1
    SOURCE_SUBDIR api/cpp
)
FetchContent_MakeAvailable(Slint)

If you prefer to treat Slint as an external CMake package, then you can also build Slint from source like a regular CMake project, install it into a prefix directory of your choice and use find_package(Slint) in your CMakeLists.txt.

Cross-compiling

It is possible to cross-compile Slint to a different target architecture when building with CMake. In order to complete that, you need to make sure that your CMake setup is ready for cross-compilation. You can find more information about how to set this up in the upstream CMake documentation. If you are building against a Yocto SDK, it is sufficient to source the SDK's environment setup file.

Since Slint is implemented using the Rust programming language, you need to determine which Rust target matches the target architecture that you're compiling to. Please consult the upstream Rust documentation to find the correct target name. Now you need to install the Rust toolchain:

rustup target add <target-name>

Then you're ready to invoke CMake and you need to add -DRust_CARGO_TARGET=<target name> to the CMake command line. This ensures that the Slint library is built for the correct architecture.

For example if you are building against an embedded Linux Yocto SDK targeting an ARM64 board, the following commands show how to compile:

Install the Rust targe toolchain once:

rustup target add aarch64-unknown-linux-gnu

Set up the environment and build:

. /path/to/yocto/sdk/environment-setup-cortexa53-crypto-poky-linux
cd slint
mkdir build
cd build
cmake -DRust_CARGO_TARGET=aarch64-unknown-linux-gnu -DCMAKE_INSTALL_PREFIX=/slint/install/path ..
cmake --build .
cmake --install .

Binary Packages

We also provide binary packages of Slint for use with C++, which eliminates the need to have Rust installed in your development environment.

You can download one of our pre-built binaries for Linux or Windows on x86-64 architectures:

  1. Open https://github.com/slint-ui/slint/releases
  2. Click on the latest release
  3. From "Assets" download either slint-cpp-XXX-Linux-x86_64.tar.gz for a Linux x86-64 archive or slint-cpp-XXX-win64.exe for a Windows x86-64 installer. ("XXX" refers to the version of the latest release)
  4. Uncompress the downloaded archive or run the installer.

After extracting the artifact or running the installer, you can place the installation directory into your CMAKE_PREFIX_PATH and find_package(Slint) should succeed in locating the package.

Usage via CMake

A typical example looks like this:

cmake_minimum_required(VERSION 3.21)
project(my_application LANGUAGES CXX)

# Note: Use find_package(Slint) instead of the following three commands, if you prefer the package
# approach.
include(FetchContent)
FetchContent_Declare(
    Slint
    GIT_REPOSITORY https://github.com/slint-ui/slint.git
    # `release/1` will auto-upgrade to the latest Slint >= 1.0.0 and < 2.0.0
    # `release/1.0` will auto-upgrade to the latest Slint >= 1.0.0 and < 1.1.0
    GIT_TAG release/1
    SOURCE_SUBDIR api/cpp
)
FetchContent_MakeAvailable(Slint)

add_executable(my_application main.cpp)
target_link_libraries(my_application PRIVATE Slint::Slint)
slint_target_sources(my_application my_application_ui.slint)
# On Windows, copy the Slint DLL next to the application binary so that it's found.
if (WIN32)
    add_custom_command(TARGET my_application POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:my_application> $<TARGET_FILE_DIR:my_application> COMMAND_EXPAND_LISTS)
endif()

The slint_target_sources cmake command allows you to add .slint files to your build. Finally it is necessary to link your executable or library against the Slint::Slint target.

Tutorial

Let's make a UI for a todo list application using the Slint UI description language. Hopefully this should be self explanatory. Check out the documentation of the language for help

// file: my_application_ui.slint
import { CheckBox, Button, ListView, LineEdit } from "std-widgets.slint";

export struct TodoItem {
    title: string,
    checked: bool,
}

export component MainWindow {
    callback todo_added(string);
    property <[TodoItem]> todo_model;

    GridLayout {
        Row {
            text_edit := LineEdit {
                accepted(text) => { todo_added(text); }
            }
            Button {
                text: "Add Todo";
                clicked => {
                    todo_added(text_edit.text);
                }
            }
        }
        list_view := ListView {
            rowspan: 2;
            row: 2;
            for todo in todo_model: Rectangle {
                height: 20px;
                GridLayout {
                    CheckBox {
                        text: todo.title;
                        checked: todo.checked;
                        toggled => {
                            todo.checked = self.checked;
                        }
                    }
                }
            }
        }
    }
}

We can compile this code using the slint-compiler binary:

slint-compiler my_application_ui.slint > my_application_ui.h

Note: You would usually not type this command yourself, this is done automatically by the build system. (that's what the slint_target_sources cmake function does)

This will generate a my_application_ui.h header file. It basically contains the following code (edited for brevity)

#include <slint.h>

struct TodoItem {
    slint::SharedString title;
    bool checked;
};

struct MainWindow {
 public:
    inline auto create () -> slint::ComponentHandle<MainWindow>;

    inline auto get_todo_model () const -> std::shared_ptr<slint::Model<TodoItem>>;
    inline void set_todo_model (const std::shared_ptr<slint::Model<TodoItem>> &value) const;

    inline void invoke_todo_added (slint::SharedString arg_0) const;
    template<typename Functor> inline void on_todo_added (Functor && callback_handler) const;

    //...
}

We can then use this from out .cpp file

// include the generated file
#include "my_application_ui.h"

int main() {
    // Let's instantiate our window
    auto todo_app = MainWindow::create();

    // let's create a model:
    auto todo_model = std::make_shared<slint::VectorModel<TodoItem>>(std::vector {
        TodoItem { false, "Write documentation" },
    });
    // set the model as the model of our view
    todo_app->set_todo_model(todo_model);

    // let's connect our "add" button to add an item in the model
    todo_app->on_todo_added([todo_model](const slint::SharedString &s) {
         todo_model->push_back(TodoItem { false, s} );
    });

    // Show the window and run the event loop
    todo_app->run();
}

That's it.

For more details, check the Online documentation. We also have a Getting Started Template repository with the code of a minimal C++ application using Slint that can be used as a starting point to your program.