mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2025-01-06 02:40:07 +00:00
89020892a8
Some of the properties where currentcolor might make a difference between computed and used value are arrays, so we need to be able to resolve arrays of values. Change things around to make resolve a GtkCssValue vfunc and turn the existing resolve() implementations into implementations of that vfunc. Fixes: #6814
402 lines
12 KiB
C
402 lines
12 KiB
C
/*
|
|
* Copyright (C) 2023 GNOME Foundation Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors: Alice Mikhaylenko <alicem@gnome.org>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gtkcssreferencevalueprivate.h"
|
|
|
|
#include "gtkcssarrayvalueprivate.h"
|
|
#include "gtkcsscustompropertypoolprivate.h"
|
|
#include "gtkcssshorthandpropertyprivate.h"
|
|
#include "gtkcssstyleprivate.h"
|
|
#include "gtkcssunsetvalueprivate.h"
|
|
#include "gtkcssvalueprivate.h"
|
|
#include "gtkstyleproviderprivate.h"
|
|
|
|
#define GDK_ARRAY_NAME gtk_css_refs
|
|
#define GDK_ARRAY_TYPE_NAME GtkCssRefs
|
|
#define GDK_ARRAY_ELEMENT_TYPE gpointer
|
|
#define GDK_ARAY_PREALLOC 32
|
|
#define GDK_ARRAY_NO_MEMSET 1
|
|
#include "gdk/gdkarrayimpl.c"
|
|
|
|
#define MAX_TOKEN_LENGTH 65536
|
|
|
|
typedef enum {
|
|
RESOLVE_SUCCESS,
|
|
RESOLVE_INVALID,
|
|
RESOLVE_ANIMATION_TAINTED,
|
|
} ResolveResult;
|
|
|
|
struct _GtkCssValue {
|
|
GTK_CSS_VALUE_BASE
|
|
|
|
GtkStyleProperty *property;
|
|
GtkCssVariableValue *value;
|
|
GFile *file;
|
|
guint subproperty;
|
|
};
|
|
|
|
static void
|
|
gtk_css_value_reference_free (GtkCssValue *value)
|
|
{
|
|
gtk_css_variable_value_unref (value->value);
|
|
if (value->file)
|
|
g_object_unref (value->file);
|
|
}
|
|
|
|
static ResolveResult
|
|
resolve_references_do (GtkCssVariableValue *value,
|
|
guint property_id,
|
|
GtkCssVariableSet *style_variables,
|
|
GtkCssVariableSet *keyframes_variables,
|
|
gboolean root,
|
|
GtkCssRefs *refs,
|
|
gsize *out_length,
|
|
gsize *out_n_refs)
|
|
{
|
|
GtkCssCustomPropertyPool *pool = gtk_css_custom_property_pool_get ();
|
|
gsize i;
|
|
gsize length = value->length;
|
|
gsize n_refs = 0;
|
|
ResolveResult ret = RESOLVE_SUCCESS;
|
|
|
|
if (value->is_animation_tainted)
|
|
{
|
|
GtkCssStyleProperty *prop = _gtk_css_style_property_lookup_by_id (property_id);
|
|
|
|
if (!_gtk_css_style_property_is_animated (prop))
|
|
{
|
|
/* Animation-tainted variables make other variables that reference
|
|
* them animation-tainted too, so unlike regular invalid variables it
|
|
* propagates to the root. For example, if --test is animation-tainted,
|
|
* --test2: var(--test, fallback1); prop: var(--test2, fallback2); will\
|
|
* resolve to fallback2 and _not_ to fallback1. So we'll propagate it
|
|
* up until the root, and treat it same as invalid there instead. */
|
|
ret = RESOLVE_ANIMATION_TAINTED;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (value->is_invalid)
|
|
{
|
|
ret = RESOLVE_INVALID;
|
|
goto error;
|
|
}
|
|
|
|
if (!root)
|
|
{
|
|
n_refs += 1;
|
|
gtk_css_refs_append (refs, value);
|
|
}
|
|
|
|
for (i = 0; i < value->n_references; i++)
|
|
{
|
|
GtkCssVariableValueReference *ref = &value->references[i];
|
|
int id = gtk_css_custom_property_pool_lookup (pool, ref->name);
|
|
GtkCssVariableValue *var_value = NULL;
|
|
gsize var_length, var_refs;
|
|
GtkCssVariableSet *source = style_variables;
|
|
ResolveResult result = RESOLVE_INVALID;
|
|
|
|
if (keyframes_variables)
|
|
var_value = gtk_css_variable_set_lookup (keyframes_variables, id, NULL);
|
|
|
|
if (!var_value && style_variables)
|
|
var_value = gtk_css_variable_set_lookup (style_variables, id, &source);
|
|
|
|
if (var_value)
|
|
{
|
|
result = resolve_references_do (var_value, property_id, source,
|
|
keyframes_variables, FALSE,
|
|
refs, &var_length, &var_refs);
|
|
|
|
if (root && result == RESOLVE_ANIMATION_TAINTED)
|
|
result = RESOLVE_INVALID;
|
|
}
|
|
|
|
if (result == RESOLVE_INVALID)
|
|
{
|
|
if (ref->fallback)
|
|
{
|
|
result = resolve_references_do (ref->fallback, property_id,
|
|
style_variables, keyframes_variables,
|
|
FALSE, refs, &var_length, &var_refs);
|
|
|
|
if (root && result == RESOLVE_ANIMATION_TAINTED)
|
|
result = RESOLVE_INVALID;
|
|
}
|
|
|
|
if (result != RESOLVE_SUCCESS)
|
|
{
|
|
ret = result;
|
|
goto error;
|
|
}
|
|
}
|
|
else if (result == RESOLVE_ANIMATION_TAINTED)
|
|
{
|
|
ret = RESOLVE_ANIMATION_TAINTED;
|
|
goto error;
|
|
}
|
|
|
|
length += var_length - ref->length;
|
|
n_refs += var_refs;
|
|
|
|
if (length > MAX_TOKEN_LENGTH)
|
|
{
|
|
ret = RESOLVE_INVALID;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (out_length)
|
|
*out_length = length;
|
|
|
|
if (out_n_refs)
|
|
*out_n_refs = n_refs;
|
|
|
|
return ret;
|
|
|
|
error:
|
|
/* Remove the references we added as if nothing happened */
|
|
gtk_css_refs_splice (refs, gtk_css_refs_get_size (refs) - n_refs, n_refs, TRUE, NULL, 0);
|
|
|
|
if (out_length)
|
|
*out_length = 0;
|
|
|
|
if (out_n_refs)
|
|
*out_n_refs = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
resolve_references (GtkCssVariableValue *input,
|
|
guint property_id,
|
|
GtkCssStyle *style,
|
|
GtkCssVariableSet *keyframes_variables,
|
|
GtkCssRefs *refs)
|
|
{
|
|
if (resolve_references_do (input, property_id, style->variables,
|
|
keyframes_variables, TRUE, refs,
|
|
NULL, NULL) != RESOLVE_SUCCESS)
|
|
{
|
|
gtk_css_refs_clear (refs);
|
|
}
|
|
}
|
|
|
|
static void
|
|
parser_error (GtkCssParser *parser,
|
|
const GtkCssLocation *start,
|
|
const GtkCssLocation *end,
|
|
const GError *error,
|
|
gpointer user_data)
|
|
{
|
|
GtkStyleProvider *provider = user_data;
|
|
GError *new_error = NULL;
|
|
GtkCssVariableValue **vars;
|
|
char **names;
|
|
gsize n_vars;
|
|
|
|
gtk_css_parser_get_expanding_variables (parser, &vars, &names, &n_vars);
|
|
|
|
if (n_vars > 0)
|
|
{
|
|
for (int i = 0; i < n_vars; i++)
|
|
{
|
|
GtkCssSection *section;
|
|
|
|
if (names[i + 1])
|
|
g_set_error (&new_error,
|
|
error->domain, error->code,
|
|
"While expanding %s: %s", names[i + 1], error->message);
|
|
else
|
|
g_set_error_literal (&new_error,
|
|
error->domain, error->code,
|
|
error->message);
|
|
|
|
if (vars[i]->section == NULL)
|
|
section = gtk_css_section_new (gtk_css_parser_get_file (parser), start, end);
|
|
else
|
|
section = gtk_css_section_ref (vars[i]->section);
|
|
|
|
gtk_style_provider_emit_error (provider, section, new_error);
|
|
|
|
gtk_css_section_unref (section);
|
|
g_clear_error (&new_error);
|
|
}
|
|
|
|
for (int i = 0; i < n_vars; i++)
|
|
{
|
|
if (vars[i])
|
|
gtk_css_variable_value_unref (vars[i]);
|
|
g_free (names[i]);
|
|
}
|
|
|
|
g_free (vars);
|
|
g_free (names);
|
|
}
|
|
}
|
|
|
|
static GtkCssValue *
|
|
gtk_css_value_reference_compute (GtkCssValue *value,
|
|
guint property_id,
|
|
GtkCssComputeContext *context)
|
|
{
|
|
GtkCssValue *result = NULL, *computed;
|
|
GtkCssRefs refs;
|
|
guint shorthand_id = G_MAXUINT;
|
|
|
|
if (GTK_IS_CSS_SHORTHAND_PROPERTY (value->property))
|
|
{
|
|
shorthand_id = _gtk_css_shorthand_property_get_id (GTK_CSS_SHORTHAND_PROPERTY (value->property));
|
|
if (context->shorthands && context->shorthands[shorthand_id])
|
|
{
|
|
result = gtk_css_value_ref (context->shorthands[shorthand_id]);
|
|
goto pick_subproperty;
|
|
}
|
|
}
|
|
|
|
gtk_css_refs_init (&refs);
|
|
|
|
resolve_references (value->value, property_id, context->style, context->variables, &refs);
|
|
|
|
if (gtk_css_refs_get_size (&refs) > 0)
|
|
{
|
|
const GtkCssToken *token;
|
|
|
|
GtkCssParser *value_parser =
|
|
gtk_css_parser_new_for_token_stream (value->value,
|
|
value->file,
|
|
(GtkCssVariableValue **) refs.start,
|
|
gtk_css_refs_get_size (&refs),
|
|
parser_error, context->provider,
|
|
NULL);
|
|
|
|
result = _gtk_style_property_parse_value (value->property, value_parser);
|
|
token = gtk_css_parser_peek_token (value_parser);
|
|
if (!gtk_css_token_is (token, GTK_CSS_TOKEN_EOF))
|
|
{
|
|
char *junk;
|
|
|
|
junk = gtk_css_token_to_string (token);
|
|
gtk_css_parser_error_syntax (value_parser,
|
|
"Junk at end of %s value: %s",
|
|
value->property->name, junk);
|
|
g_free (junk);
|
|
|
|
g_clear_pointer (&result, gtk_css_value_unref);
|
|
}
|
|
gtk_css_parser_unref (value_parser);
|
|
}
|
|
|
|
gtk_css_refs_clear (&refs);
|
|
|
|
if (result == NULL)
|
|
result = _gtk_css_unset_value_new ();
|
|
|
|
if (shorthand_id != G_MAXUINT)
|
|
{
|
|
GtkCssValue *sub;
|
|
|
|
if (context->shorthands)
|
|
{
|
|
g_assert (context->shorthands[shorthand_id] == NULL);
|
|
context->shorthands[shorthand_id] = gtk_css_value_ref (result);
|
|
}
|
|
|
|
pick_subproperty:
|
|
sub = gtk_css_value_ref (_gtk_css_array_value_get_nth (result, value->subproperty));
|
|
gtk_css_value_unref (result);
|
|
result = sub;
|
|
}
|
|
|
|
computed = gtk_css_value_compute (result, property_id, context);
|
|
computed->is_computed = TRUE;
|
|
|
|
gtk_css_value_unref (result);
|
|
|
|
return computed;
|
|
}
|
|
|
|
static gboolean
|
|
gtk_css_value_reference_equal (const GtkCssValue *value1,
|
|
const GtkCssValue *value2)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
static GtkCssValue *
|
|
gtk_css_value_reference_transition (GtkCssValue *start,
|
|
GtkCssValue *end,
|
|
guint property_id,
|
|
double progress)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
gtk_css_value_reference_print (const GtkCssValue *value,
|
|
GString *string)
|
|
{
|
|
gtk_css_variable_value_print (value->value, string);
|
|
}
|
|
|
|
static const GtkCssValueClass GTK_CSS_VALUE_REFERENCE = {
|
|
"GtkCssReferenceValue",
|
|
gtk_css_value_reference_free,
|
|
gtk_css_value_reference_compute,
|
|
NULL,
|
|
gtk_css_value_reference_equal,
|
|
gtk_css_value_reference_transition,
|
|
NULL,
|
|
NULL,
|
|
gtk_css_value_reference_print
|
|
};
|
|
|
|
GtkCssValue *
|
|
_gtk_css_reference_value_new (GtkStyleProperty *property,
|
|
GtkCssVariableValue *value,
|
|
GFile *file)
|
|
{
|
|
GtkCssValue *result;
|
|
|
|
result = gtk_css_value_new (GtkCssValue, >K_CSS_VALUE_REFERENCE);
|
|
result->property = property;
|
|
result->value = gtk_css_variable_value_ref (value);
|
|
result->contains_variables = TRUE;
|
|
|
|
if (file)
|
|
result->file = g_object_ref (file);
|
|
else
|
|
result->file = NULL;
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
_gtk_css_reference_value_set_subproperty (GtkCssValue *value,
|
|
guint property)
|
|
{
|
|
g_assert (GTK_IS_CSS_SHORTHAND_PROPERTY (value->property));
|
|
|
|
value->subproperty = property;
|
|
}
|
|
|