mirror of
https://github.com/slint-ui/slint.git
synced 2025-12-23 09:19:32 +00:00
* 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
388 lines
15 KiB
C++
388 lines
15 KiB
C++
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
|
|
|
#pragma once
|
|
#include <string_view>
|
|
#include "slint_color.h"
|
|
#include "slint_brush_internal.h"
|
|
#include "slint_string.h"
|
|
|
|
namespace slint {
|
|
|
|
namespace private_api {
|
|
|
|
using cbindgen_private::types::GradientStop;
|
|
|
|
/// \private
|
|
/// LinearGradientBrush represents a gradient for a brush that is a linear sequence of color stops,
|
|
/// that are aligned at a specific angle.
|
|
class LinearGradientBrush
|
|
{
|
|
public:
|
|
/// Constructs an empty linear gradient with no color stops.
|
|
LinearGradientBrush() = default;
|
|
/// Constructs a new linear gradient with the specified \a angle. The color stops will be
|
|
/// constructed from the stops array pointed to be \a firstStop, with the length \a stopCount.
|
|
LinearGradientBrush(float angle, const GradientStop *firstStop, int stopCount)
|
|
: inner(make_linear_gradient(angle, firstStop, stopCount))
|
|
{
|
|
}
|
|
|
|
/// Returns the linear gradient's angle in degrees.
|
|
float angle() const
|
|
{
|
|
// The gradient's first stop is a fake stop to store the angle
|
|
return inner[0].position;
|
|
}
|
|
|
|
/// Returns the number of gradient stops.
|
|
int stopCount() const { return int(inner.size()) - 1; }
|
|
|
|
/// Returns a pointer to the first gradient stop; undefined if the gradient has not stops.
|
|
const GradientStop *stopsBegin() const { return inner.begin() + 1; }
|
|
/// Returns a pointer past the last gradient stop. The returned pointer cannot be dereferenced,
|
|
/// it can only be used for comparison.
|
|
const GradientStop *stopsEnd() const { return inner.end(); }
|
|
|
|
private:
|
|
cbindgen_private::types::LinearGradientBrush inner;
|
|
|
|
friend class slint::Brush;
|
|
|
|
static SharedVector<private_api::GradientStop>
|
|
make_linear_gradient(float angle, const GradientStop *firstStop, int stopCount)
|
|
{
|
|
SharedVector<private_api::GradientStop> gradient;
|
|
gradient.push_back({ Color::from_argb_encoded(0).inner, angle });
|
|
for (int i = 0; i < stopCount; ++i, ++firstStop)
|
|
gradient.push_back(*firstStop);
|
|
return gradient;
|
|
}
|
|
};
|
|
|
|
/// \private
|
|
/// RadialGradientBrush represents a circular gradient centered in the middle
|
|
class RadialGradientBrush
|
|
{
|
|
public:
|
|
/// Constructs an empty linear gradient with no color stops.
|
|
RadialGradientBrush() = default;
|
|
/// Constructs a new circular radial gradient . The color stops will be
|
|
/// constructed from the stops array pointed to be \a firstStop, with the length \a stopCount.
|
|
RadialGradientBrush(const GradientStop *firstStop, int stopCount)
|
|
: inner(make_circle_gradient(firstStop, stopCount))
|
|
{
|
|
}
|
|
|
|
/// Returns the number of gradient stops.
|
|
int stopCount() const { return int(inner.size()); }
|
|
|
|
/// Returns a pointer to the first gradient stop; undefined if the gradient has not stops.
|
|
const GradientStop *stopsBegin() const { return inner.begin(); }
|
|
/// Returns a pointer past the last gradient stop. The returned pointer cannot be dereferenced,
|
|
/// it can only be used for comparison.
|
|
const GradientStop *stopsEnd() const { return inner.end(); }
|
|
|
|
private:
|
|
cbindgen_private::types::RadialGradientBrush inner;
|
|
|
|
friend class slint::Brush;
|
|
|
|
static SharedVector<private_api::GradientStop>
|
|
make_circle_gradient(const GradientStop *firstStop, int stopCount)
|
|
{
|
|
SharedVector<private_api::GradientStop> gradient;
|
|
for (int i = 0; i < stopCount; ++i, ++firstStop)
|
|
gradient.push_back(*firstStop);
|
|
return gradient;
|
|
}
|
|
};
|
|
|
|
/// \private
|
|
/// ConicGradientBrush represents a conic gradient that rotates around a center point
|
|
class ConicGradientBrush
|
|
{
|
|
public:
|
|
/// Constructs an empty conic gradient with no color stops.
|
|
ConicGradientBrush() = default;
|
|
/// Constructs a new conic gradient with the specified starting \a angle. The color stops will
|
|
/// be constructed from the stops array pointed to be \a firstStop, with the length \a
|
|
/// stopCount.
|
|
ConicGradientBrush(float angle, const GradientStop *firstStop, int stopCount)
|
|
: inner(make_conic_gradient(angle, firstStop, stopCount))
|
|
{
|
|
}
|
|
|
|
/// Returns the conic gradient's starting angle (rotation) in degrees.
|
|
float angle() const { return inner[0].position; }
|
|
|
|
/// Returns the number of gradient stops.
|
|
int stopCount() const { return int(inner.size()) - 1; }
|
|
|
|
/// Returns a pointer to the first gradient stop; undefined if the gradient has not stops.
|
|
const GradientStop *stopsBegin() const { return inner.begin() + 1; }
|
|
/// Returns a pointer past the last gradient stop. The returned pointer cannot be dereferenced,
|
|
/// it can only be used for comparison.
|
|
const GradientStop *stopsEnd() const { return inner.end(); }
|
|
|
|
private:
|
|
cbindgen_private::types::ConicGradientBrush inner;
|
|
|
|
friend class slint::Brush;
|
|
|
|
static SharedVector<private_api::GradientStop>
|
|
make_conic_gradient(float angle, const GradientStop *firstStop, int stopCount)
|
|
{
|
|
SharedVector<private_api::GradientStop> gradient;
|
|
// The gradient's first stop is a fake stop to store the angle (same pattern as
|
|
// LinearGradient)
|
|
gradient.push_back({ Color::from_argb_encoded(0).inner, angle });
|
|
for (int i = 0; i < stopCount; ++i, ++firstStop)
|
|
gradient.push_back(*firstStop);
|
|
|
|
// Normalize stops to [0, 1] range with proper boundary stops
|
|
cbindgen_private::types::slint_conic_gradient_normalize_stops(&gradient);
|
|
|
|
// Apply rotation if angle is non-zero
|
|
if (angle != 0.0f) {
|
|
cbindgen_private::types::slint_conic_gradient_apply_rotation(&gradient, angle);
|
|
}
|
|
return gradient;
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
/// Brush is used to declare how to fill or outline shapes, such as rectangles, paths or text. A
|
|
/// brush is either a solid color or a linear gradient.
|
|
class Brush
|
|
{
|
|
public:
|
|
/// Constructs a new brush that is a transparent color.
|
|
Brush() : Brush(Color {}) { }
|
|
/// Constructs a new brush that is of color \a color.
|
|
Brush(const Color &color) : data(Inner::SolidColor(color.inner)) { }
|
|
/// \private
|
|
/// Constructs a new brush that is the gradient \a gradient.
|
|
Brush(const private_api::LinearGradientBrush &gradient)
|
|
: data(Inner::LinearGradient(gradient.inner))
|
|
{
|
|
}
|
|
|
|
/// \private
|
|
/// Constructs a new brush that is the gradient \a gradient.
|
|
Brush(const private_api::RadialGradientBrush &gradient)
|
|
: data(Inner::RadialGradient(gradient.inner))
|
|
{
|
|
}
|
|
|
|
/// \private
|
|
/// Constructs a new brush that is the gradient \a gradient.
|
|
Brush(const private_api::ConicGradientBrush &gradient)
|
|
: data(Inner::ConicGradient(gradient.inner))
|
|
{
|
|
}
|
|
|
|
/// Returns the color of the brush. If the brush is a gradient, this function returns the color
|
|
/// of the first stop.
|
|
inline Color color() const;
|
|
|
|
/// Returns a new version of this brush that has the brightness increased
|
|
/// by the specified factor. This is done by calling Color::brighter on
|
|
/// all the colors of this brush.
|
|
[[nodiscard]] inline Brush brighter(float factor) const;
|
|
/// Returns a new version of this color that has the brightness decreased
|
|
/// by the specified factor. This is done by calling Color::darker on
|
|
/// all the colors of this brush.
|
|
[[nodiscard]] inline Brush darker(float factor) const;
|
|
|
|
/// Returns a new version of this brush with the opacity decreased by \a factor.
|
|
///
|
|
/// This is done by calling Color::transparentize on all the colors of this brush.
|
|
[[nodiscard]] inline Brush transparentize(float factor) const;
|
|
|
|
/// Returns a new version of this brush with the related color's opacities
|
|
/// set to \a alpha.
|
|
[[nodiscard]] inline Brush with_alpha(float alpha) const;
|
|
|
|
/// Returns true if \a a is equal to \a b. If \a a holds a color, then \a b must also hold a
|
|
/// color that is identical to \a a's color. If it holds a gradient, then the gradients must be
|
|
/// identical. Returns false if the brushes differ in what they hold or their respective color
|
|
/// or gradient are not equal.
|
|
friend bool operator==(const Brush &a, const Brush &b) { return a.data == b.data; }
|
|
/// Returns false if \a is not equal to \a b; true otherwise.
|
|
friend bool operator!=(const Brush &a, const Brush &b) { return a.data != b.data; }
|
|
|
|
private:
|
|
using Tag = cbindgen_private::types::Brush::Tag;
|
|
using Inner = cbindgen_private::types::Brush;
|
|
Inner data;
|
|
friend struct private_api::Property<Brush>;
|
|
};
|
|
|
|
Color Brush::color() const
|
|
{
|
|
Color result;
|
|
switch (data.tag) {
|
|
case Tag::SolidColor:
|
|
result.inner = data.solid_color._0;
|
|
break;
|
|
case Tag::LinearGradient:
|
|
if (data.linear_gradient._0.size() > 1) {
|
|
result.inner = data.linear_gradient._0[1].color;
|
|
}
|
|
break;
|
|
case Tag::RadialGradient:
|
|
if (data.radial_gradient._0.size() > 0) {
|
|
result.inner = data.radial_gradient._0[0].color;
|
|
}
|
|
break;
|
|
case Tag::ConicGradient:
|
|
if (data.conic_gradient._0.size() > 1) {
|
|
result.inner = data.conic_gradient._0[1].color;
|
|
}
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
inline Brush Brush::brighter(float factor) const
|
|
{
|
|
Brush result = *this;
|
|
switch (data.tag) {
|
|
case Tag::SolidColor:
|
|
cbindgen_private::types::slint_color_brighter(&data.solid_color._0, factor,
|
|
&result.data.solid_color._0);
|
|
break;
|
|
case Tag::LinearGradient:
|
|
for (std::size_t i = 1; i < data.linear_gradient._0.size(); ++i) {
|
|
cbindgen_private::types::slint_color_brighter(&data.linear_gradient._0[i].color, factor,
|
|
&result.data.linear_gradient._0[i].color);
|
|
}
|
|
break;
|
|
case Tag::RadialGradient:
|
|
for (std::size_t i = 0; i < data.radial_gradient._0.size(); ++i) {
|
|
cbindgen_private::types::slint_color_brighter(&data.radial_gradient._0[i].color, factor,
|
|
&result.data.radial_gradient._0[i].color);
|
|
}
|
|
break;
|
|
case Tag::ConicGradient:
|
|
for (std::size_t i = 1; i < data.conic_gradient._0.size(); ++i) {
|
|
cbindgen_private::types::slint_color_brighter(&data.conic_gradient._0[i].color, factor,
|
|
&result.data.conic_gradient._0[i].color);
|
|
}
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
inline Brush Brush::darker(float factor) const
|
|
{
|
|
Brush result = *this;
|
|
switch (data.tag) {
|
|
case Tag::SolidColor:
|
|
cbindgen_private::types::slint_color_darker(&data.solid_color._0, factor,
|
|
&result.data.solid_color._0);
|
|
break;
|
|
case Tag::LinearGradient:
|
|
for (std::size_t i = 1; i < data.linear_gradient._0.size(); ++i) {
|
|
cbindgen_private::types::slint_color_darker(&data.linear_gradient._0[i].color, factor,
|
|
&result.data.linear_gradient._0[i].color);
|
|
}
|
|
break;
|
|
case Tag::RadialGradient:
|
|
for (std::size_t i = 0; i < data.radial_gradient._0.size(); ++i) {
|
|
cbindgen_private::types::slint_color_darker(&data.radial_gradient._0[i].color, factor,
|
|
&result.data.radial_gradient._0[i].color);
|
|
}
|
|
break;
|
|
case Tag::ConicGradient:
|
|
for (std::size_t i = 1; i < data.conic_gradient._0.size(); ++i) {
|
|
cbindgen_private::types::slint_color_darker(&data.conic_gradient._0[i].color, factor,
|
|
&result.data.conic_gradient._0[i].color);
|
|
}
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
inline Brush Brush::transparentize(float factor) const
|
|
{
|
|
Brush result = *this;
|
|
switch (data.tag) {
|
|
case Tag::SolidColor:
|
|
cbindgen_private::types::slint_color_transparentize(&data.solid_color._0, factor,
|
|
&result.data.solid_color._0);
|
|
break;
|
|
case Tag::LinearGradient:
|
|
for (std::size_t i = 1; i < data.linear_gradient._0.size(); ++i) {
|
|
cbindgen_private::types::slint_color_transparentize(
|
|
&data.linear_gradient._0[i].color, factor,
|
|
&result.data.linear_gradient._0[i].color);
|
|
}
|
|
break;
|
|
case Tag::RadialGradient:
|
|
for (std::size_t i = 0; i < data.radial_gradient._0.size(); ++i) {
|
|
cbindgen_private::types::slint_color_transparentize(
|
|
&data.radial_gradient._0[i].color, factor,
|
|
&result.data.radial_gradient._0[i].color);
|
|
}
|
|
break;
|
|
case Tag::ConicGradient:
|
|
for (std::size_t i = 1; i < data.conic_gradient._0.size(); ++i) {
|
|
cbindgen_private::types::slint_color_transparentize(
|
|
&data.conic_gradient._0[i].color, factor,
|
|
&result.data.conic_gradient._0[i].color);
|
|
}
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
inline Brush Brush::with_alpha(float alpha) const
|
|
{
|
|
Brush result = *this;
|
|
switch (data.tag) {
|
|
case Tag::SolidColor:
|
|
cbindgen_private::types::slint_color_with_alpha(&data.solid_color._0, alpha,
|
|
&result.data.solid_color._0);
|
|
break;
|
|
case Tag::LinearGradient:
|
|
for (std::size_t i = 1; i < data.linear_gradient._0.size(); ++i) {
|
|
cbindgen_private::types::slint_color_with_alpha(
|
|
&data.linear_gradient._0[i].color, alpha,
|
|
&result.data.linear_gradient._0[i].color);
|
|
}
|
|
break;
|
|
case Tag::RadialGradient:
|
|
for (std::size_t i = 0; i < data.radial_gradient._0.size(); ++i) {
|
|
cbindgen_private::types::slint_color_with_alpha(
|
|
&data.radial_gradient._0[i].color, alpha,
|
|
&result.data.radial_gradient._0[i].color);
|
|
}
|
|
break;
|
|
case Tag::ConicGradient:
|
|
for (std::size_t i = 1; i < data.conic_gradient._0.size(); ++i) {
|
|
cbindgen_private::types::slint_color_with_alpha(
|
|
&data.conic_gradient._0[i].color, alpha,
|
|
&result.data.conic_gradient._0[i].color);
|
|
}
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
namespace private_api {
|
|
|
|
template<>
|
|
inline void Property<slint::Brush>::set_animated_value(
|
|
const slint::Brush &new_value,
|
|
const cbindgen_private::PropertyAnimation &animation_data) const
|
|
{
|
|
cbindgen_private::slint_property_set_animated_value_brush(&inner, &value, &new_value,
|
|
&animation_data);
|
|
}
|
|
|
|
} // namespace private_api
|
|
|
|
} // namespace slint
|