diff --git a/gtk/css/gtkcssparser.c b/gtk/css/gtkcssparser.c index 418368369a..3adddd398d 100644 --- a/gtk/css/gtkcssparser.c +++ b/gtk/css/gtkcssparser.c @@ -27,12 +27,13 @@ #include "gtkcsslocationprivate.h" typedef struct _GtkCssParserBlock GtkCssParserBlock; +typedef struct _GtkCssParserTokenStreamData GtkCssParserTokenStreamData; struct _GtkCssParser { volatile int ref_count; - GtkCssTokenizer *tokenizer; + GPtrArray *tokenizers; GFile *file; GFile *directory; GtkCssParserErrorFunc error_func; @@ -42,6 +43,11 @@ struct _GtkCssParser GArray *blocks; GtkCssLocation location; GtkCssToken token; + + GtkCssVariableValue **refs; + gsize n_refs; + gsize next_ref; + gboolean var_fallback; }; struct _GtkCssParserBlock @@ -52,6 +58,12 @@ struct _GtkCssParserBlock GtkCssTokenType alternative_token; }; +static inline GtkCssTokenizer * +get_tokenizer (GtkCssParser *self) +{ + return g_ptr_array_index (self->tokenizers, self->tokenizers->len - 1); +} + static GtkCssParser * gtk_css_parser_new (GtkCssTokenizer *tokenizer, GFile *file, @@ -64,7 +76,11 @@ gtk_css_parser_new (GtkCssTokenizer *tokenizer, self = g_new0 (GtkCssParser, 1); self->ref_count = 1; - self->tokenizer = gtk_css_tokenizer_ref (tokenizer); + + self->tokenizers = g_ptr_array_new (); + g_ptr_array_add (self->tokenizers, gtk_css_tokenizer_ref (tokenizer)); + g_ptr_array_set_free_func (self->tokenizers, (GDestroyNotify) gtk_css_tokenizer_unref); + if (file) { self->file = g_object_ref (file); @@ -117,19 +133,46 @@ gtk_css_parser_new_for_bytes (GBytes *bytes, return result; } +GtkCssParser * +gtk_css_parser_new_for_token_stream (GtkCssVariableValue *value, + GFile *file, + GtkCssVariableValue **refs, + gsize n_refs, + GtkCssParserErrorFunc error_func, + gpointer user_data, + GDestroyNotify user_destroy) +{ + GtkCssTokenizer *tokenizer; + GtkCssParser *result; + + tokenizer = gtk_css_tokenizer_new_for_range (value->bytes, value->offset, + value->end_offset - value->offset); + result = gtk_css_parser_new (tokenizer, file, error_func, user_data, user_destroy); + gtk_css_tokenizer_unref (tokenizer); + + result->refs = refs; + result->n_refs = n_refs; + result->next_ref = 0; + + return result; +} + static void gtk_css_parser_finalize (GtkCssParser *self) { if (self->user_destroy) self->user_destroy (self->user_data); - g_clear_pointer (&self->tokenizer, gtk_css_tokenizer_unref); + g_clear_pointer (&self->tokenizers, g_ptr_array_unref); g_clear_object (&self->file); g_clear_object (&self->directory); if (self->blocks->len) g_critical ("Finalizing CSS parser with %u remaining blocks", self->blocks->len); g_array_free (self->blocks, TRUE); + if (self->refs) + g_free (self->refs); + g_free (self); } @@ -239,7 +282,7 @@ gtk_css_parser_get_start_location (GtkCssParser *self) const GtkCssLocation * gtk_css_parser_get_end_location (GtkCssParser *self) { - return gtk_css_tokenizer_get_location (self->tokenizer); + return gtk_css_tokenizer_get_location (get_tokenizer (self)); } /** @@ -273,12 +316,14 @@ static void gtk_css_parser_ensure_token (GtkCssParser *self) { GError *error = NULL; + GtkCssTokenizer *tokenizer; if (!gtk_css_token_is (&self->token, GTK_CSS_TOKEN_EOF)) return; - self->location = *gtk_css_tokenizer_get_location (self->tokenizer); - if (!gtk_css_tokenizer_read_token (self->tokenizer, &self->token, &error)) + tokenizer = get_tokenizer (self); + self->location = *gtk_css_tokenizer_get_location (tokenizer); + if (!gtk_css_tokenizer_read_token (tokenizer, &self->token, &error)) { /* We ignore the error here, because the resulting token will * likely already trigger an error in the parsing code and @@ -286,6 +331,64 @@ gtk_css_parser_ensure_token (GtkCssParser *self) */ g_clear_error (&error); } + + if (self->tokenizers->len > 1 && gtk_css_token_is (&self->token, GTK_CSS_TOKEN_EOF)) + { + g_ptr_array_remove_index_fast (self->tokenizers, self->tokenizers->len - 1); + gtk_css_parser_ensure_token (self); + return; + } + + /* Resolve var(--name): skip it and insert the resolved reference instead */ + if (self->n_refs > 0 && gtk_css_token_is_function (&self->token, "var") && self->var_fallback == 0) + { + GtkCssVariableValue *ref; + GtkCssTokenizer *ref_tokenizer; + + gtk_css_parser_start_block (self); + + if (gtk_css_parser_has_token (self, GTK_CSS_TOKEN_IDENT)) + { + char *var_name = gtk_css_parser_consume_ident (self); + + if (var_name[0] != '-' || var_name[1] != '-') + { + g_free (var_name); + + self->var_fallback++; + gtk_css_parser_skip (self); + gtk_css_parser_end_block (self); + self->var_fallback--; + return; + } + + g_free (var_name); + } + else + { + self->var_fallback++; + gtk_css_parser_skip (self); + gtk_css_parser_end_block (self); + self->var_fallback--; + return; + } + + /* If we encounter var() in a fallback when we can already resolve the + * actual variable, skip it */ + self->var_fallback++; + gtk_css_parser_skip (self); + gtk_css_parser_end_block (self); + self->var_fallback--; + + g_assert (self->next_ref < self->n_refs); + + ref = self->refs[self->next_ref++]; + ref_tokenizer = gtk_css_tokenizer_new_for_range (ref->bytes, ref->offset, + ref->end_offset - ref->offset); + g_ptr_array_add (self->tokenizers, ref_tokenizer); + + gtk_css_parser_ensure_token (self); + } } const GtkCssToken * @@ -1112,3 +1215,274 @@ gtk_css_parser_consume_any (GtkCssParser *parser, return result; } + +gboolean +gtk_css_parser_has_references (GtkCssParser *self) +{ + GtkCssTokenizer *tokenizer = get_tokenizer (self); + gboolean ret = FALSE; + int inner_blocks = 0, i; + + gtk_css_tokenizer_save (tokenizer); + + do { + const GtkCssToken *token; + + token = gtk_css_parser_get_token (self); + + if (inner_blocks == 0) + { + if (gtk_css_token_is (token, GTK_CSS_TOKEN_EOF)) + break; + + if (gtk_css_token_is (token, GTK_CSS_TOKEN_CLOSE_PARENS) || + gtk_css_token_is (token, GTK_CSS_TOKEN_CLOSE_SQUARE)) + { + goto error; + } + } + + if (gtk_css_token_is_preserved (token, NULL)) + { + if (inner_blocks > 0 && gtk_css_token_is (token, GTK_CSS_TOKEN_EOF)) + { + gtk_css_parser_end_block (self); + inner_blocks--; + } + else + { + gtk_css_parser_consume_token (self); + } + } + else + { + gboolean is_var = gtk_css_token_is_function (token, "var"); + + inner_blocks++; + gtk_css_parser_start_block (self); + + if (!ret && is_var) + { + token = gtk_css_parser_get_token (self); + + if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT)) + { + const char *var_name = gtk_css_token_get_string (token); + + if (var_name[0] != '-' || var_name[1] != '-') + goto error; + + gtk_css_parser_consume_token (self); + + if (!gtk_css_parser_has_token (self, GTK_CSS_TOKEN_EOF) && + !gtk_css_parser_has_token (self, GTK_CSS_TOKEN_COMMA)) + { + goto error; + } + + ret = TRUE; + } + } + } + } + while (!gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SEMICOLON) && + !gtk_css_parser_has_token (self, GTK_CSS_TOKEN_CLOSE_CURLY)); + + if (inner_blocks > 0) + goto error; + + gtk_css_tokenizer_restore (tokenizer); + self->location = *gtk_css_tokenizer_get_location (tokenizer); + gtk_css_tokenizer_read_token (tokenizer, &self->token, NULL); + + return ret; + +error: + for (i = 0; i < inner_blocks; i++) + gtk_css_parser_end_block (self); + + gtk_css_tokenizer_restore (tokenizer); + self->location = *gtk_css_tokenizer_get_location (tokenizer); + gtk_css_tokenizer_read_token (tokenizer, &self->token, NULL); + + return FALSE; +} + +static void +clear_ref (GtkCssVariableValueReference *ref) +{ + g_free (ref->name); + if (ref->fallback) + gtk_css_variable_value_unref (ref->fallback); +} + +GtkCssVariableValue * +gtk_css_parser_parse_value_into_token_stream (GtkCssParser *self) +{ + GBytes *bytes = gtk_css_tokenizer_get_bytes (get_tokenizer (self)); + const GtkCssToken *token; + gsize offset; + gsize length = 0; + GArray *refs; + GtkCssVariableValueReference *out_refs; + gsize n_refs; + int inner_blocks = 0, i; + gboolean is_initial = FALSE; + + refs = g_array_new (FALSE, TRUE, sizeof (GtkCssVariableValueReference)); + + g_array_set_clear_func (refs, (GDestroyNotify) clear_ref); + + for (token = gtk_css_parser_peek_token (self); + gtk_css_token_is (token, GTK_CSS_TOKEN_WHITESPACE); + token = gtk_css_parser_peek_token (self)) + { + gtk_css_parser_consume_token (self); + } + + offset = self->location.bytes; + + do { + token = gtk_css_parser_get_token (self); + + if (length == 0 && gtk_css_token_is_ident (token, "initial")) + is_initial = TRUE; + + if (gtk_css_token_is (token, GTK_CSS_TOKEN_BAD_STRING) || + gtk_css_token_is (token, GTK_CSS_TOKEN_BAD_URL)) + { + gtk_css_parser_error_syntax (self, "Invalid property value"); + goto error; + } + + if (inner_blocks == 0) + { + if (gtk_css_token_is (token, GTK_CSS_TOKEN_EOF)) + break; + + if (gtk_css_token_is (token, GTK_CSS_TOKEN_CLOSE_PARENS) || + gtk_css_token_is (token, GTK_CSS_TOKEN_CLOSE_SQUARE)) + { + gtk_css_parser_error_syntax (self, "Invalid property value"); + goto error; + } + } + + if (gtk_css_token_is_preserved (token, NULL)) + { + if (inner_blocks > 0 && gtk_css_token_is (token, GTK_CSS_TOKEN_EOF)) + { + length++; + gtk_css_parser_end_block (self); + + inner_blocks--; + } + else + { + length++; + gtk_css_parser_consume_token (self); + } + } + else + { + gboolean is_var = gtk_css_token_is_function (token, "var"); + + length++; + inner_blocks++; + + gtk_css_parser_start_block (self); + + if (is_var) + { + token = gtk_css_parser_get_token (self); + + if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT)) + { + GtkCssVariableValueReference ref; + char *var_name = g_strdup (gtk_css_token_get_string (token)); + + if (var_name[0] != '-' || var_name[1] != '-') + { + gtk_css_parser_error_value (self, "Invalid variable name: %s", var_name); + g_free (var_name); + goto error; + } + + length++; + gtk_css_parser_consume_token (self); + + if (!gtk_css_parser_has_token (self, GTK_CSS_TOKEN_EOF) && + !gtk_css_parser_has_token (self, GTK_CSS_TOKEN_COMMA)) + { + gtk_css_parser_error_syntax (self, "Invalid property value"); + g_free (var_name); + goto error; + } + + ref.name = var_name; + + if (gtk_css_parser_has_token (self, GTK_CSS_TOKEN_EOF)) + { + ref.length = 3; + ref.fallback = NULL; + } + else + { + length++; + gtk_css_parser_consume_token (self); + + ref.fallback = gtk_css_parser_parse_value_into_token_stream (self); + + if (ref.fallback == NULL) + { + gtk_css_parser_error_value (self, "Invalid fallback for: %s", var_name); + g_free (var_name); + goto error; + } + + ref.length = 4 + ref.fallback->length; + length += ref.fallback->length; + } + + g_array_append_val (refs, ref); + } + } + } + } + while (!gtk_css_parser_has_token (self, GTK_CSS_TOKEN_SEMICOLON) && + !gtk_css_parser_has_token (self, GTK_CSS_TOKEN_CLOSE_CURLY)); + + if (inner_blocks > 0) + { + gtk_css_parser_error_syntax (self, "Invalid property value"); + goto error; + } + + if (is_initial && length == 1) + { + g_array_unref (refs); + + return gtk_css_variable_value_new_initial (bytes, + offset, + self->location.bytes); + } + else + { + out_refs = g_array_steal (refs, &n_refs); + + return gtk_css_variable_value_new (bytes, + offset, + self->location.bytes, + length, + out_refs, + n_refs); + } + +error: + for (i = 0; i < inner_blocks; i++) + gtk_css_parser_end_block (self); + + g_array_unref (refs); + + return NULL; +} diff --git a/gtk/css/gtkcssparserprivate.h b/gtk/css/gtkcssparserprivate.h index c886c5f487..3d837ca283 100644 --- a/gtk/css/gtkcssparserprivate.h +++ b/gtk/css/gtkcssparserprivate.h @@ -22,6 +22,7 @@ #include "gtkcssenums.h" #include "gtkcsstokenizerprivate.h" +#include "gtkcssvariablevalueprivate.h" #include @@ -58,6 +59,13 @@ GtkCssParser * gtk_css_parser_new_for_bytes (GBytes GtkCssParserErrorFunc error_func, gpointer user_data, GDestroyNotify user_destroy); +GtkCssParser * gtk_css_parser_new_for_token_stream (GtkCssVariableValue *value, + GFile *file, + GtkCssVariableValue **refs, + gsize n_refs, + GtkCssParserErrorFunc error_func, + gpointer user_data, + GDestroyNotify user_destroy); GtkCssParser * gtk_css_parser_ref (GtkCssParser *self); void gtk_css_parser_unref (GtkCssParser *self); @@ -150,5 +158,9 @@ gsize gtk_css_parser_consume_any (GtkCssParser gsize n_options, gpointer user_data); +gboolean gtk_css_parser_has_references (GtkCssParser *parser); + +GtkCssVariableValue * gtk_css_parser_parse_value_into_token_stream (GtkCssParser *parser); + G_END_DECLS diff --git a/gtk/css/gtkcsstokenizer.c b/gtk/css/gtkcsstokenizer.c index 6ac778193a..7a75a9633d 100644 --- a/gtk/css/gtkcsstokenizer.c +++ b/gtk/css/gtkcsstokenizer.c @@ -575,6 +575,14 @@ gtk_css_token_init_dimension (GtkCssToken *token, GtkCssTokenizer * gtk_css_tokenizer_new (GBytes *bytes) +{ + return gtk_css_tokenizer_new_for_range (bytes, 0, g_bytes_get_size (bytes)); +} + +GtkCssTokenizer * +gtk_css_tokenizer_new_for_range (GBytes *bytes, + gsize offset, + gsize length) { GtkCssTokenizer *tokenizer; @@ -583,8 +591,8 @@ gtk_css_tokenizer_new (GBytes *bytes) tokenizer->bytes = g_bytes_ref (bytes); tokenizer->name_buffer = g_string_new (NULL); - tokenizer->data = g_bytes_get_data (bytes, NULL); - tokenizer->end = tokenizer->data + g_bytes_get_size (bytes); + tokenizer->data = g_bytes_get_region (bytes, 1, offset, length); + tokenizer->end = tokenizer->data + length; gtk_css_location_init (&tokenizer->position); tokenizer->saved_states = g_array_new (FALSE, FALSE, sizeof (GtkCssTokenizerSavedState)); @@ -613,6 +621,12 @@ gtk_css_tokenizer_unref (GtkCssTokenizer *tokenizer) g_free (tokenizer); } +GBytes * +gtk_css_tokenizer_get_bytes (GtkCssTokenizer *tokenizer) +{ + return tokenizer->bytes; +} + const GtkCssLocation * gtk_css_tokenizer_get_location (GtkCssTokenizer *tokenizer) { diff --git a/gtk/css/gtkcsstokenizerprivate.h b/gtk/css/gtkcsstokenizerprivate.h index 95630b54ad..7b6fe8bfdb 100644 --- a/gtk/css/gtkcsstokenizerprivate.h +++ b/gtk/css/gtkcsstokenizerprivate.h @@ -138,10 +138,14 @@ void gtk_css_token_print (const GtkCssTok char * gtk_css_token_to_string (const GtkCssToken *token); GtkCssTokenizer * gtk_css_tokenizer_new (GBytes *bytes); +GtkCssTokenizer * gtk_css_tokenizer_new_for_range (GBytes *bytes, + gsize offset, + gsize length); GtkCssTokenizer * gtk_css_tokenizer_ref (GtkCssTokenizer *tokenizer); void gtk_css_tokenizer_unref (GtkCssTokenizer *tokenizer); +GBytes * gtk_css_tokenizer_get_bytes (GtkCssTokenizer *tokenizer); const GtkCssLocation * gtk_css_tokenizer_get_location (GtkCssTokenizer *tokenizer) G_GNUC_CONST; gboolean gtk_css_tokenizer_read_token (GtkCssTokenizer *tokenizer, diff --git a/gtk/css/gtkcssvariablevalue.c b/gtk/css/gtkcssvariablevalue.c new file mode 100644 index 0000000000..c829a6a58b --- /dev/null +++ b/gtk/css/gtkcssvariablevalue.c @@ -0,0 +1,141 @@ +/* + * 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 . + * + * Authors: Alice Mikhaylenko + */ + +#include "gtkcssvariablevalueprivate.h" + +GtkCssVariableValue * +gtk_css_variable_value_new (GBytes *bytes, + gsize offset, + gsize end_offset, + gsize length, + GtkCssVariableValueReference *references, + gsize n_references) +{ + GtkCssVariableValue *self = g_new0 (GtkCssVariableValue, 1); + + self->ref_count = 1; + + self->bytes = g_bytes_ref (bytes); + self->offset = offset; + self->end_offset = end_offset; + self->length = length; + + self->references = references; + self->n_references = n_references; + + return self; +} + +GtkCssVariableValue * +gtk_css_variable_value_new_initial (GBytes *bytes, + gsize offset, + gsize end_offset) +{ + GtkCssVariableValue *self = gtk_css_variable_value_new (bytes, offset, end_offset, 1, NULL, 0); + + self->is_invalid = TRUE; + + return self; +} + +GtkCssVariableValue * +gtk_css_variable_value_ref (GtkCssVariableValue *self) +{ + self->ref_count++; + + return self; +} + +void +gtk_css_variable_value_unref (GtkCssVariableValue *self) +{ + gsize i; + + self->ref_count--; + if (self->ref_count > 0) + return; + + g_bytes_unref (self->bytes); + + for (i = 0; i < self->n_references; i++) + { + GtkCssVariableValueReference *ref = &self->references[i]; + + g_free (ref->name); + if (ref->fallback) + gtk_css_variable_value_unref (ref->fallback); + } + + g_free (self->references); + g_free (self); +} + +void +gtk_css_variable_value_print (GtkCssVariableValue *self, + GString *string) +{ + gsize len = self->end_offset - self->offset; + gconstpointer data = g_bytes_get_region (self->bytes, 1, self->offset, len); + + g_assert (data != NULL); + + g_string_append_len (string, (const char *) data, len); +} + +gboolean +gtk_css_variable_value_equal (const GtkCssVariableValue *value1, + const GtkCssVariableValue *value2) +{ + if (value1 == value2) + return TRUE; + + if (value1 == NULL || value2 == NULL) + return FALSE; + + if (value1->bytes != value2->bytes) + return FALSE; + + if (value1->offset != value2->offset) + return FALSE; + + if (value1->end_offset != value2->end_offset) + return FALSE; + + return TRUE; +} + +GtkCssVariableValue * +gtk_css_variable_value_transition (GtkCssVariableValue *start, + GtkCssVariableValue *end, + double progress) +{ + GtkCssVariableValue *ret = progress < 0.5 ? start : end; + + if (ret == NULL) + return NULL; + + return gtk_css_variable_value_ref (ret); +} + +void +gtk_css_variable_value_set_section (GtkCssVariableValue *self, + GtkCssSection *section) +{ + self->section = gtk_css_section_ref (section); +} diff --git a/gtk/css/gtkcssvariablevalueprivate.h b/gtk/css/gtkcssvariablevalueprivate.h new file mode 100644 index 0000000000..cddc83d9df --- /dev/null +++ b/gtk/css/gtkcssvariablevalueprivate.h @@ -0,0 +1,74 @@ +/* + * 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 . + * + * Authors: Alice Mikhaylenko + */ + +#pragma once + +#include "gtkcss.h" +#include "gtkcsstokenizerprivate.h" + +G_BEGIN_DECLS + +typedef struct _GtkCssVariableValueReference GtkCssVariableValueReference; +typedef struct _GtkCssVariableValue GtkCssVariableValue; + +struct _GtkCssVariableValueReference +{ + char *name; + gsize length; + GtkCssVariableValue *fallback; +}; + +struct _GtkCssVariableValue +{ + int ref_count; + + GBytes *bytes; + gsize offset; + gsize end_offset; + gsize length; + + GtkCssVariableValueReference *references; + gsize n_references; + + GtkCssSection *section; + gboolean is_invalid; +}; + +GtkCssVariableValue *gtk_css_variable_value_new (GBytes *bytes, + gsize offset, + gsize end_offset, + gsize length, + GtkCssVariableValueReference *references, + gsize n_references); +GtkCssVariableValue *gtk_css_variable_value_new_initial (GBytes *bytes, + gsize offset, + gsize end_offset); +GtkCssVariableValue *gtk_css_variable_value_ref (GtkCssVariableValue *self); +void gtk_css_variable_value_unref (GtkCssVariableValue *self); +void gtk_css_variable_value_print (GtkCssVariableValue *self, + GString *string); +gboolean gtk_css_variable_value_equal (const GtkCssVariableValue *value1, + const GtkCssVariableValue *value2) G_GNUC_PURE; +GtkCssVariableValue *gtk_css_variable_value_transition (GtkCssVariableValue *start, + GtkCssVariableValue *end, + double progress); +void gtk_css_variable_value_set_section (GtkCssVariableValue *self, + GtkCssSection *section); + +G_END_DECLS diff --git a/gtk/css/meson.build b/gtk/css/meson.build index 9a5f6020b9..8ac659ea7b 100644 --- a/gtk/css/meson.build +++ b/gtk/css/meson.build @@ -6,8 +6,9 @@ gtk_css_public_sources = files([ gtk_css_private_sources = files([ 'gtkcssdataurl.c', 'gtkcssparser.c', - 'gtkcsstokenizer.c', 'gtkcssserializer.c', + 'gtkcsstokenizer.c', + 'gtkcssvariablevalue.c', ]) gtk_css_public_headers = files([ diff --git a/gtk/gtkcsscustompropertypool.c b/gtk/gtkcsscustompropertypool.c new file mode 100644 index 0000000000..2131fd287e --- /dev/null +++ b/gtk/gtkcsscustompropertypool.c @@ -0,0 +1,161 @@ +/* + * 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 . + * + * Authors: Alice Mikhaylenko + */ + +#include "gtkcsscustompropertypoolprivate.h" + +struct _GtkCssCustomPropertyPool +{ + GObject parent_instance; + + GArray *names; + GHashTable *name_mappings; +}; + +struct _GtkCssCustomPropertyName +{ + int ref_count; + char *name; +}; + +typedef struct _GtkCssCustomPropertyName GtkCssCustomPropertyName; + +static GtkCssCustomPropertyPool *instance = NULL; + +G_DEFINE_FINAL_TYPE (GtkCssCustomPropertyPool, gtk_css_custom_property_pool, G_TYPE_OBJECT) + +static void +clear_custom_property_name (GtkCssCustomPropertyName *name) +{ + g_clear_pointer (&name->name, g_free); +} + +static void +gtk_css_custom_property_pool_finalize (GObject *object) +{ + GtkCssCustomPropertyPool *self = (GtkCssCustomPropertyPool *)object; + + g_hash_table_unref (self->name_mappings); + g_array_unref (self->names); + + G_OBJECT_CLASS (gtk_css_custom_property_pool_parent_class)->finalize (object); +} + +static void +gtk_css_custom_property_pool_class_init (GtkCssCustomPropertyPoolClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gtk_css_custom_property_pool_finalize; +} + +static void +gtk_css_custom_property_pool_init (GtkCssCustomPropertyPool *self) +{ + self->name_mappings = g_hash_table_new (g_str_hash, g_str_equal); + self->names = g_array_new (FALSE, FALSE, sizeof (GtkCssCustomPropertyName)); + + g_array_set_clear_func (self->names, (GDestroyNotify) clear_custom_property_name); +} + +GtkCssCustomPropertyPool * +gtk_css_custom_property_pool_get (void) +{ + if (instance == NULL) + instance = g_object_new (GTK_TYPE_CSS_CUSTOM_PROPERTY_POOL, NULL); + + return instance; +} + +int +gtk_css_custom_property_pool_add (GtkCssCustomPropertyPool *self, + const char *str) +{ + GtkCssCustomPropertyName name; + int id; + + id = gtk_css_custom_property_pool_lookup (self, str); + if (id > 0) + return gtk_css_custom_property_pool_ref (self, id); + + name.ref_count = 1; + name.name = g_strdup (str); + + // TODO reuse slots after they're gone + g_array_append_val (self->names, name); + + id = self->names->len; + + g_hash_table_insert (self->name_mappings, (char *) name.name, GINT_TO_POINTER (id)); + + return id; +} + +int +gtk_css_custom_property_pool_lookup (GtkCssCustomPropertyPool *self, + const char *str) +{ + gpointer id; + + id = g_hash_table_lookup (self->name_mappings, str); + + return GPOINTER_TO_INT (id); +} + +int +gtk_css_custom_property_pool_ref (GtkCssCustomPropertyPool *self, + int id) +{ + GtkCssCustomPropertyName *name; + + name = &g_array_index (self->names, GtkCssCustomPropertyName, id - 1); + + name->ref_count++; + + return id; +} + +void +gtk_css_custom_property_pool_unref (GtkCssCustomPropertyPool *self, + int id) +{ + GtkCssCustomPropertyName *name; + + name = &g_array_index (self->names, GtkCssCustomPropertyName, id - 1); + + g_assert (name->ref_count > 0); + + name->ref_count--; + + if (name->ref_count == 0) + { + g_hash_table_remove (self->name_mappings, name->name); + clear_custom_property_name (name); + } +} + +const char * +gtk_css_custom_property_pool_get_name (GtkCssCustomPropertyPool *self, + int id) +{ + GtkCssCustomPropertyName *name; + + name = &g_array_index (self->names, GtkCssCustomPropertyName, id - 1); + + return name->name; +} diff --git a/gtk/gtkcsscustompropertypoolprivate.h b/gtk/gtkcsscustompropertypoolprivate.h new file mode 100644 index 0000000000..8ae1f6556f --- /dev/null +++ b/gtk/gtkcsscustompropertypoolprivate.h @@ -0,0 +1,43 @@ +/* + * 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 . + * + * Authors: Alice Mikhaylenko + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_CSS_CUSTOM_PROPERTY_POOL (gtk_css_custom_property_pool_get_type()) + +G_DECLARE_FINAL_TYPE (GtkCssCustomPropertyPool, gtk_css_custom_property_pool, GTK, CSS_CUSTOM_PROPERTY_POOL, GObject) + +GtkCssCustomPropertyPool *gtk_css_custom_property_pool_get (void); + +int gtk_css_custom_property_pool_add (GtkCssCustomPropertyPool *self, + const char *str); +int gtk_css_custom_property_pool_lookup (GtkCssCustomPropertyPool *self, + const char *str); +const char * gtk_css_custom_property_pool_get_name (GtkCssCustomPropertyPool *self, + int id); +int gtk_css_custom_property_pool_ref (GtkCssCustomPropertyPool *self, + int id); +void gtk_css_custom_property_pool_unref (GtkCssCustomPropertyPool *self, + int id); + +G_END_DECLS diff --git a/gtk/gtkcsslookup.c b/gtk/gtkcsslookup.c index 5d7d4112a9..1bf4ce7739 100644 --- a/gtk/gtkcsslookup.c +++ b/gtk/gtkcsslookup.c @@ -19,6 +19,7 @@ #include "gtkcsslookupprivate.h" +#include "gtkcsscustompropertypoolprivate.h" #include "gtkcssstylepropertyprivate.h" #include "gtkcsstypesprivate.h" #include "gtkprivatetypebuiltins.h" @@ -36,6 +37,9 @@ void _gtk_css_lookup_destroy (GtkCssLookup *lookup) { _gtk_bitmask_free (lookup->set_values); + + if (lookup->custom_values) + g_hash_table_unref (lookup->custom_values); } gboolean @@ -74,3 +78,17 @@ _gtk_css_lookup_set (GtkCssLookup *lookup, lookup->values[id].section = section; lookup->set_values = _gtk_bitmask_set (lookup->set_values, id, TRUE); } + +void +_gtk_css_lookup_set_custom (GtkCssLookup *lookup, + int id, + GtkCssVariableValue *value) +{ + gtk_internal_return_if_fail (lookup != NULL); + + if (!lookup->custom_values) + lookup->custom_values = g_hash_table_new (g_direct_hash, g_direct_equal); + + if (!g_hash_table_contains (lookup->custom_values, GINT_TO_POINTER (id))) + g_hash_table_replace (lookup->custom_values, GINT_TO_POINTER (id), value); +} diff --git a/gtk/gtkcsslookupprivate.h b/gtk/gtkcsslookupprivate.h index 7b0e053ae1..0b8dde87a7 100644 --- a/gtk/gtkcsslookupprivate.h +++ b/gtk/gtkcsslookupprivate.h @@ -23,6 +23,8 @@ #include "gtk/gtkcssstaticstyleprivate.h" #include "gtk/css/gtkcsssection.h" +#include "gtk/css/gtkcsstokenizerprivate.h" +#include "gtk/css/gtkcssvariablevalueprivate.h" G_BEGIN_DECLS @@ -37,6 +39,7 @@ typedef struct { struct _GtkCssLookup { GtkBitmask *set_values; GtkCssLookupValue values[GTK_CSS_PROPERTY_N_PROPERTIES]; + GHashTable *custom_values; }; void _gtk_css_lookup_init (GtkCssLookup *lookup); @@ -47,6 +50,9 @@ void _gtk_css_lookup_set (GtkCssLookup guint id, GtkCssSection *section, GtkCssValue *value); +void _gtk_css_lookup_set_custom (GtkCssLookup *lookup, + int id, + GtkCssVariableValue *value); static inline const GtkBitmask * _gtk_css_lookup_get_set_values (const GtkCssLookup *lookup) diff --git a/gtk/gtkcssprovider.c b/gtk/gtkcssprovider.c index b79c38760a..d018524533 100644 --- a/gtk/gtkcssprovider.c +++ b/gtk/gtkcssprovider.c @@ -22,10 +22,13 @@ #include #include "gtk/css/gtkcsstokenizerprivate.h" #include "gtk/css/gtkcssparserprivate.h" +#include "gtk/css/gtkcssvariablevalueprivate.h" #include "gtkbitmaskprivate.h" #include "gtkcssarrayvalueprivate.h" #include "gtkcsscolorvalueprivate.h" +#include "gtkcsscustompropertypoolprivate.h" #include "gtkcsskeyframesprivate.h" +#include "gtkcssreferencevalueprivate.h" #include "gtkcssselectorprivate.h" #include "gtkcssshorthandpropertyprivate.h" #include "gtksettingsprivate.h" @@ -113,6 +116,7 @@ struct GtkCssRuleset PropertyValue *styles; guint n_styles; guint owns_styles : 1; + GHashTable *custom_properties; }; struct _GtkCssScanner @@ -270,6 +274,8 @@ gtk_css_ruleset_clear (GtkCssRuleset *ruleset) gtk_css_section_unref (ruleset->styles[i].section); } g_free (ruleset->styles); + if (ruleset->custom_properties) + g_hash_table_unref (ruleset->custom_properties); } if (ruleset->selector) _gtk_css_selector_free (ruleset->selector); @@ -285,7 +291,7 @@ gtk_css_ruleset_add (GtkCssRuleset *ruleset, { guint i; - g_return_if_fail (ruleset->owns_styles || ruleset->n_styles == 0); + g_return_if_fail (ruleset->owns_styles || (ruleset->n_styles == 0 && ruleset->custom_properties == NULL)); ruleset->owns_styles = TRUE; @@ -315,6 +321,39 @@ gtk_css_ruleset_add (GtkCssRuleset *ruleset, ruleset->styles[i].section = NULL; } +static void +unref_custom_property_name (gpointer pointer) +{ + GtkCssCustomPropertyPool *pool = gtk_css_custom_property_pool_get (); + + gtk_css_custom_property_pool_unref (pool, GPOINTER_TO_INT (pointer)); +} + +static void +gtk_css_ruleset_add_custom (GtkCssRuleset *ruleset, + const char *name, + GtkCssVariableValue *value) +{ + GtkCssCustomPropertyPool *pool; + int id; + + g_return_if_fail (ruleset->owns_styles || (ruleset->n_styles == 0 && ruleset->custom_properties == NULL)); + + ruleset->owns_styles = TRUE; + + if (ruleset->custom_properties == NULL) + { + ruleset->custom_properties = g_hash_table_new_full (g_direct_hash, g_direct_equal, + unref_custom_property_name, + (GDestroyNotify) gtk_css_variable_value_unref); + } + + pool = gtk_css_custom_property_pool_get (); + id = gtk_css_custom_property_pool_add (pool, name); + + g_hash_table_replace (ruleset->custom_properties, GINT_TO_POINTER (id), value); +} + static void gtk_css_scanner_destroy (GtkCssScanner *scanner) { @@ -490,7 +529,7 @@ gtk_css_style_provider_lookup (GtkStyleProvider *provider, { ruleset = gtk_css_selector_matches_get (&tree_rules, i); - if (ruleset->styles == NULL) + if (ruleset->styles == NULL && ruleset->custom_properties == NULL) continue; for (j = 0; j < ruleset->n_styles; j++) @@ -506,6 +545,18 @@ gtk_css_style_provider_lookup (GtkStyleProvider *provider, ruleset->styles[j].section, ruleset->styles[j].value); } + + if (ruleset->custom_properties) + { + GHashTableIter iter; + gpointer id; + GtkCssVariableValue *value; + + g_hash_table_iter_init (&iter, ruleset->custom_properties); + + while (g_hash_table_iter_next (&iter, &id, (gpointer) &value)) + _gtk_css_lookup_set_custom (lookup, GPOINTER_TO_INT (id), value); + } } } gtk_css_selector_matches_clear (&tree_rules); @@ -572,7 +623,7 @@ css_provider_commit (GtkCssProvider *css_provider, GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (css_provider); guint i; - if (ruleset->styles == NULL) + if (ruleset->styles == NULL && ruleset->custom_properties == NULL) { for (i = 0; i < gtk_css_selectors_get_size (selectors); i++) _gtk_css_selector_free (gtk_css_selectors_get (selectors, i)); @@ -814,6 +865,42 @@ parse_declaration (GtkCssScanner *scanner, if (name == NULL) goto out; + /* This is a custom property */ + if (name[0] == '-' && name[1] == '-') + { + GtkCssVariableValue *value; + GtkCssSection *section; + + if (!gtk_css_parser_try_token (scanner->parser, GTK_CSS_TOKEN_COLON)) + { + gtk_css_parser_error_syntax (scanner->parser, "Expected ':'"); + goto out; + } + + value = gtk_css_parser_parse_value_into_token_stream (scanner->parser); + if (value == NULL) + goto out; + + if (gtk_keep_css_sections) + { + section = gtk_css_section_new (gtk_css_parser_get_file (scanner->parser), + gtk_css_parser_get_block_location (scanner->parser), + gtk_css_parser_get_end_location (scanner->parser)); + } + else + section = NULL; + + if (section != NULL) + { + gtk_css_variable_value_set_section (value, section); + gtk_css_section_unref (section); + } + + gtk_css_ruleset_add_custom (ruleset, name, value); + + goto out; + } + property = _gtk_style_property_lookup (name); if (property) @@ -827,15 +914,60 @@ parse_declaration (GtkCssScanner *scanner, goto out; } - value = _gtk_style_property_parse_value (property, scanner->parser); - - if (value == NULL) - goto out; - - if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF)) + if (gtk_css_parser_has_references (scanner->parser)) { - gtk_css_parser_error_syntax (scanner->parser, "Junk at end of value for %s", property->name); - goto out; + GtkCssVariableValue *var_value; + + var_value = gtk_css_parser_parse_value_into_token_stream (scanner->parser); + if (var_value == NULL) + goto out; + + if (GTK_IS_CSS_SHORTHAND_PROPERTY (property)) + { + GtkCssShorthandProperty *shorthand = GTK_CSS_SHORTHAND_PROPERTY (property); + guint i, n; + GtkCssValue **values; + + n = _gtk_css_shorthand_property_get_n_subproperties (shorthand); + + values = g_new (GtkCssValue *, n); + + for (i = 0; i < n; i++) + { + GtkCssValue *child = + _gtk_css_reference_value_new (property, + var_value, + gtk_css_parser_get_file (scanner->parser)); + _gtk_css_reference_value_set_subproperty (child, i); + + values[i] = _gtk_css_array_value_get_nth (child, i); + } + + value = _gtk_css_array_value_new_from_array (values, n); + g_free (values); + } + else + { + value = _gtk_css_reference_value_new (property, + var_value, + gtk_css_parser_get_file (scanner->parser)); + } + + gtk_css_variable_value_unref (var_value); + } + else + { + value = _gtk_style_property_parse_value (property, scanner->parser); + + if (value == NULL) + goto out; + + if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF)) + { + gtk_css_parser_error_syntax (scanner->parser, "Junk at end of value for %s", property->name); + gtk_css_value_unref (value); + goto out; + } } if (gtk_keep_css_sections) @@ -1473,6 +1605,20 @@ compare_properties (gconstpointer a, gconstpointer b, gpointer style) _gtk_style_property_get_name (GTK_STYLE_PROPERTY (styles[*ub].property))); } +static int +compare_custom_properties (gconstpointer a, gconstpointer b, gpointer user_data) +{ + GtkCssCustomPropertyPool *pool = user_data; + int id1 = GPOINTER_TO_INT (*((gconstpointer *) a)); + int id2 = GPOINTER_TO_INT (*((gconstpointer *) b)); + const char *name1, *name2; + + name1 = gtk_css_custom_property_pool_get_name (pool, id1); + name2 = gtk_css_custom_property_pool_get_name (pool, id2); + + return strcmp (name1, name2); +} + static void gtk_css_ruleset_print (const GtkCssRuleset *ruleset, GString *str) @@ -1503,6 +1649,31 @@ gtk_css_ruleset_print (const GtkCssRuleset *ruleset, g_string_append (str, ";\n"); } + if (ruleset->custom_properties) + { + GtkCssCustomPropertyPool *pool = gtk_css_custom_property_pool_get (); + GPtrArray *keys; + + keys = g_hash_table_get_keys_as_ptr_array (ruleset->custom_properties); + g_ptr_array_sort_with_data (keys, compare_custom_properties, pool); + + for (i = 0; i < keys->len; i++) + { + int id = GPOINTER_TO_INT (g_ptr_array_index (keys, i)); + const char *name = gtk_css_custom_property_pool_get_name (pool, id); + GtkCssVariableValue *value = g_hash_table_lookup (ruleset->custom_properties, + GINT_TO_POINTER (id)); + + g_string_append (str, " "); + g_string_append (str, name); + g_string_append (str, ": "); + gtk_css_variable_value_print (value, str); + g_string_append (str, ";\n"); + } + + g_ptr_array_unref (keys); + } + g_free (sorted); } diff --git a/gtk/gtkcssreferencevalue.c b/gtk/gtkcssreferencevalue.c new file mode 100644 index 0000000000..ba234639f2 --- /dev/null +++ b/gtk/gtkcssreferencevalue.c @@ -0,0 +1,268 @@ +/* + * 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 . + * + * Authors: Alice Mikhaylenko + */ + +#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 MAX_TOKEN_LENGTH 65536 + +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 gboolean +resolve_references_do (GtkCssVariableValue *value, + GtkCssVariableSet *style_variables, + gboolean root, + GPtrArray *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; + + if (value->is_invalid) + goto error; + + if (!root) + { + n_refs += 1; + g_ptr_array_add (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; + + if (!var_value && style_variables) + var_value = gtk_css_variable_set_lookup (style_variables, id, &source); + + if (!var_value || !resolve_references_do (var_value, source, + FALSE, refs, &var_length, &var_refs)) + { + var_value = ref->fallback; + + if (!var_value || !resolve_references_do (var_value, style_variables, + FALSE, refs, &var_length, &var_refs)) + goto error; + } + + length += var_length - ref->length; + n_refs += var_refs; + + if (length > MAX_TOKEN_LENGTH) + goto error; + } + + if (out_length) + *out_length = length; + + if (out_n_refs) + *out_n_refs = n_refs; + + return TRUE; + +error: + /* Remove the references we added as if nothing happened */ + g_ptr_array_remove_range (refs, refs->len - n_refs, n_refs); + + if (out_length) + *out_length = 0; + + if (out_n_refs) + *out_n_refs = 0; + + return FALSE; +} + +static GtkCssVariableValue ** +resolve_references (GtkCssVariableValue *input, + GtkCssStyle *style, + gsize *n_refs) +{ + GPtrArray *refs = g_ptr_array_new (); + + if (!resolve_references_do (input, style->variables, TRUE, refs, NULL, NULL)) + return NULL; + + return (GtkCssVariableValue **) g_ptr_array_steal (refs, n_refs); +} + +static void +parser_error (GtkCssParser *parser, + const GtkCssLocation *start, + const GtkCssLocation *end, + const GError *error, + gpointer user_data) +{ + GtkStyleProvider *provider = user_data; + GtkCssSection *section; + + section = gtk_css_section_new (gtk_css_parser_get_file (parser), + start, + end); + + gtk_style_provider_emit_error (provider, section, (GError *) error); + + gtk_css_section_unref (section); +} + +static GtkCssValue * +gtk_css_value_reference_compute (GtkCssValue *value, + guint property_id, + GtkStyleProvider *provider, + GtkCssStyle *style, + GtkCssStyle *parent_style) +{ + GtkCssValue *result = NULL, *computed; + GtkCssVariableValue **refs; + gsize n_refs = 0; + + refs = resolve_references (value->value, style, &n_refs); + + if (refs != NULL) + { + GtkCssParser *value_parser = + gtk_css_parser_new_for_token_stream (value->value, + value->file, + refs, + n_refs, + parser_error, provider, NULL); + + result = _gtk_style_property_parse_value (value->property, value_parser); + gtk_css_parser_unref (value_parser); + } + + if (result == NULL) + result = _gtk_css_unset_value_new (); + + if (GTK_IS_CSS_SHORTHAND_PROPERTY (value->property)) + { + GtkCssValue *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, + provider, + style, + parent_style); + + 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 gboolean +gtk_css_value_reference_contains_variables (const GtkCssValue *value) +{ + return TRUE; +} + +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, + gtk_css_value_reference_equal, + gtk_css_value_reference_transition, + NULL, + NULL, + gtk_css_value_reference_contains_variables, + 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); + + 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; +} diff --git a/gtk/gtkcssreferencevalueprivate.h b/gtk/gtkcssreferencevalueprivate.h new file mode 100644 index 0000000000..13feca983c --- /dev/null +++ b/gtk/gtkcssreferencevalueprivate.h @@ -0,0 +1,35 @@ +/* + * 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 . + * + * Authors: Alice Mikhaylenko + */ + +#pragma once + +#include +#include "gtkcssvalueprivate.h" +#include "gtkstylepropertyprivate.h" +#include "css/gtkcssvariablevalueprivate.h" + +G_BEGIN_DECLS + +GtkCssValue *_gtk_css_reference_value_new (GtkStyleProperty *property, + GtkCssVariableValue *value, + GFile *file); +void _gtk_css_reference_value_set_subproperty (GtkCssValue *value, + guint property); + +G_END_DECLS diff --git a/gtk/gtkcssstaticstyle.c b/gtk/gtkcssstaticstyle.c index 71a10bc552..098deaaaf2 100644 --- a/gtk/gtkcssstaticstyle.c +++ b/gtk/gtkcssstaticstyle.c @@ -912,6 +912,35 @@ gtk_css_lookup_resolve (GtkCssLookup *lookup, gtk_internal_return_if_fail (GTK_IS_CSS_STATIC_STYLE (style)); gtk_internal_return_if_fail (parent_style == NULL || GTK_IS_CSS_STYLE (parent_style)); + if (lookup->custom_values) + { + GHashTableIter iter; + gpointer id; + GtkCssVariableValue *value; + + style->variables = gtk_css_variable_set_new (); + + g_hash_table_iter_init (&iter, lookup->custom_values); + + while (g_hash_table_iter_next (&iter, &id, (gpointer) &value)) + gtk_css_variable_set_add (style->variables, GPOINTER_TO_INT (id), value); + + gtk_css_variable_set_resolve_cycles (style->variables); + + if (parent_style) + { + GtkCssStyle *parent_sstyle = GTK_CSS_STYLE (parent_style); + gtk_css_variable_set_set_parent (style->variables, + parent_sstyle->variables); + } + } + else if (parent_style) + { + GtkCssStyle *parent_sstyle = GTK_CSS_STYLE (parent_style); + if (parent_sstyle->variables) + style->variables = gtk_css_variable_set_ref (parent_sstyle->variables); + } + if (_gtk_bitmask_is_empty (_gtk_css_lookup_get_set_values (lookup))) { style->background = (GtkCssBackgroundValues *)gtk_css_values_ref (gtk_css_background_initial_values); @@ -1107,3 +1136,15 @@ gtk_css_static_style_get_change (GtkCssStaticStyle *style) return style->change; } + +void +gtk_css_custom_values_compute_changes_and_affects (GtkCssStyle *style1, + GtkCssStyle *style2, + GtkBitmask **changes, + GtkCssAffects *affects) +{ + if (gtk_css_variable_set_equal (style1->variables, style2->variables)) + return; + + *changes = _gtk_bitmask_set (*changes, GTK_CSS_PROPERTY_CUSTOM, TRUE); +} diff --git a/gtk/gtkcssstyle.c b/gtk/gtkcssstyle.c index 0822e80a46..0f9ee23c54 100644 --- a/gtk/gtkcssstyle.c +++ b/gtk/gtkcssstyle.c @@ -24,6 +24,7 @@ #include "gtkcssanimationprivate.h" #include "gtkcssarrayvalueprivate.h" +#include "gtkcsscustompropertypoolprivate.h" #include "gtkcssenumvalueprivate.h" #include "gtkcssinheritvalueprivate.h" #include "gtkcssinitialvalueprivate.h" @@ -73,6 +74,9 @@ gtk_css_style_finalize (GObject *object) gtk_css_values_unref ((GtkCssValues *)style->size); gtk_css_values_unref ((GtkCssValues *)style->other); + if (style->variables) + gtk_css_variable_set_unref (style->variables); + G_OBJECT_CLASS (gtk_css_style_parent_class)->finalize (object); } @@ -368,6 +372,34 @@ gtk_css_style_print (GtkCssStyle *style, retval = TRUE; } + if (style->variables) + { + GtkCssCustomPropertyPool *pool = gtk_css_custom_property_pool_get (); + GArray *ids = gtk_css_variable_set_list_ids (style->variables); + + for (i = 0; i < ids->len; i++) + { + int id = g_array_index (ids, int, i); + const char *name = gtk_css_custom_property_pool_get_name (pool, id); + GtkCssVariableValue *value = gtk_css_variable_set_lookup (style->variables, id, NULL); + + g_string_append_printf (string, "%*s%s: ", indent, "", name); + gtk_css_variable_value_print (value, string); + g_string_append_c (string, ';'); + + if (value->section) + { + g_string_append (string, " /* "); + gtk_css_section_print (value->section, string); + g_string_append (string, " */"); + } + + g_string_append_c (string, '\n'); + } + + retval = TRUE; + } + return retval; } @@ -837,3 +869,13 @@ gtk_css_values_new (GtkCssValuesType type) return values; } + +GtkCssVariableValue * +gtk_css_style_get_custom_property (GtkCssStyle *style, + int id) +{ + if (style->variables) + return gtk_css_variable_set_lookup (style->variables, id, NULL); + + return NULL; +} diff --git a/gtk/gtkcssstylechange.c b/gtk/gtkcssstylechange.c index 3792b0fe51..1405d35d3c 100644 --- a/gtk/gtkcssstylechange.c +++ b/gtk/gtkcssstylechange.c @@ -102,6 +102,12 @@ compute_change (GtkCssStyleChange *change) change->new_style, &change->changes, &change->affects); + + if (change->old_style->variables != change->new_style->variables) + gtk_css_custom_values_compute_changes_and_affects (change->old_style, + change->new_style, + &change->changes, + &change->affects); } void diff --git a/gtk/gtkcssstyleprivate.h b/gtk/gtkcssstyleprivate.h index 82531a6ffe..e3e3364b2d 100644 --- a/gtk/gtkcssstyleprivate.h +++ b/gtk/gtkcssstyleprivate.h @@ -24,6 +24,8 @@ #include "gtk/gtkbitmaskprivate.h" #include "gtk/gtkcssvalueprivate.h" +#include "gtk/gtkcssvariablesetprivate.h" +#include "gtk/css/gtkcssvariablevalueprivate.h" G_BEGIN_DECLS @@ -232,6 +234,10 @@ struct _GtkCssStyle GtkCssTransitionValues *transition; GtkCssSizeValues *size; GtkCssOtherValues *other; + GtkCssVariableSet *variables; + + GtkCssValue *variable_values; + int n_variable_values; }; struct _GtkCssStyleClass @@ -268,6 +274,9 @@ char * gtk_css_style_compute_font_features (GtkCssStyle PangoAttrList * gtk_css_style_get_pango_attributes (GtkCssStyle *style); PangoFontDescription * gtk_css_style_get_pango_font (GtkCssStyle *style); +GtkCssVariableValue * gtk_css_style_get_custom_property (GtkCssStyle *style, + int id); + GtkCssValues *gtk_css_values_new (GtkCssValuesType type); GtkCssValues *gtk_css_values_ref (GtkCssValues *values); void gtk_css_values_unref (GtkCssValues *values); @@ -317,6 +326,10 @@ void gtk_css_other_values_compute_changes_and_affects (GtkCssStyle *style1, GtkCssStyle *style2, GtkBitmask **changes, GtkCssAffects *affects); +void gtk_css_custom_values_compute_changes_and_affects (GtkCssStyle *style1, + GtkCssStyle *style2, + GtkBitmask **changes, + GtkCssAffects *affects); G_END_DECLS diff --git a/gtk/gtkcsstypesprivate.h b/gtk/gtkcsstypesprivate.h index 96eeb6b3c8..7141248188 100644 --- a/gtk/gtkcsstypesprivate.h +++ b/gtk/gtkcsstypesprivate.h @@ -277,7 +277,8 @@ enum { /*< skip >*/ GTK_CSS_PROPERTY_FONT_VARIATION_SETTINGS, GTK_CSS_PROPERTY_LINE_HEIGHT, /* add more */ - GTK_CSS_PROPERTY_N_PROPERTIES + GTK_CSS_PROPERTY_N_PROPERTIES, + GTK_CSS_PROPERTY_CUSTOM, }; typedef enum /*< skip >*/ { diff --git a/gtk/gtkcssvariableset.c b/gtk/gtkcssvariableset.c new file mode 100644 index 0000000000..96a8be3426 --- /dev/null +++ b/gtk/gtkcssvariableset.c @@ -0,0 +1,337 @@ +/* + * 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 . + * + * Authors: Alice Mikhaylenko + */ + +#include "gtkcssvariablesetprivate.h" + +#include "gtkcsscustompropertypoolprivate.h" + +static void +unref_custom_property_name (gpointer pointer) +{ + GtkCssCustomPropertyPool *pool = gtk_css_custom_property_pool_get (); + + gtk_css_custom_property_pool_unref (pool, GPOINTER_TO_INT (pointer)); +} + +static void +maybe_unref_variable (gpointer pointer) +{ + if (pointer != NULL) + gtk_css_variable_value_unref (pointer); +} + +GtkCssVariableSet * +gtk_css_variable_set_new (void) +{ + GtkCssVariableSet *self = g_new0 (GtkCssVariableSet, 1); + + self->ref_count = 1; + self->variables = g_hash_table_new_full (g_direct_hash, + g_direct_equal, + unref_custom_property_name, + maybe_unref_variable); + + return self; +} + +GtkCssVariableSet * +gtk_css_variable_set_ref (GtkCssVariableSet *self) +{ + self->ref_count++; + + return self; +} + +void +gtk_css_variable_set_unref (GtkCssVariableSet *self) +{ + self->ref_count--; + if (self->ref_count > 0) + return; + + g_hash_table_unref (self->variables); + g_clear_pointer (&self->parent, gtk_css_variable_set_unref); + g_free (self); +} + +GtkCssVariableSet * +gtk_css_variable_set_copy (GtkCssVariableSet *self) +{ + GtkCssVariableSet *ret = gtk_css_variable_set_new (); + GHashTableIter iter; + gpointer id; + GtkCssVariableValue *value; + + g_hash_table_iter_init (&iter, self->variables); + + while (g_hash_table_iter_next (&iter, &id, (gpointer) &value)) + gtk_css_variable_set_add (ret, GPOINTER_TO_INT (id), value); + + gtk_css_variable_set_set_parent (ret, self->parent); + + return ret; +} + +void +gtk_css_variable_set_set_parent (GtkCssVariableSet *self, + GtkCssVariableSet *parent) +{ + if (self->parent) + gtk_css_variable_set_unref (self->parent); + + self->parent = parent; + + if (self->parent) + gtk_css_variable_set_ref (self->parent); +} + +void +gtk_css_variable_set_add (GtkCssVariableSet *self, + int id, + GtkCssVariableValue *value) +{ + GtkCssCustomPropertyPool *pool = gtk_css_custom_property_pool_get (); + + g_hash_table_insert (self->variables, + GINT_TO_POINTER (gtk_css_custom_property_pool_ref (pool, id)), + value ? gtk_css_variable_value_ref (value) : NULL); +} + +static gboolean check_variable (GtkCssVariableSet *self, + GHashTable *unvisited_variables, + GArray *stack, + int id, + GtkCssVariableValue *value); + +static gboolean +check_references (GtkCssVariableSet *self, + GHashTable *unvisited_variables, + GArray *stack, + GtkCssVariableValue *value) +{ + GtkCssCustomPropertyPool *pool = gtk_css_custom_property_pool_get (); + int i; + + for (i = 0; i < value->n_references; i++) + { + GtkCssVariableValueReference *ref = &value->references[i]; + int ref_id = gtk_css_custom_property_pool_lookup (pool, ref->name); + + if (g_hash_table_contains (unvisited_variables, GINT_TO_POINTER (ref_id))) + { + GtkCssVariableValue *ref_value; + + ref_value = g_hash_table_lookup (self->variables, GINT_TO_POINTER (ref_id)); + + if (check_variable (self, unvisited_variables, stack, ref_id, ref_value)) + return TRUE; + } + + if (ref->fallback) + { + if (check_references (self, unvisited_variables, stack, ref->fallback)) + return TRUE; + } + } + + return FALSE; +} + +static gboolean +check_variable (GtkCssVariableSet *self, + GHashTable *unvisited_variables, + GArray *stack, + int id, + GtkCssVariableValue *value) +{ + int i; + + g_array_append_val (stack, id); + + for (i = stack->len - 2; i >= 0; i--) + { + /* Found a cycle, stop checking */ + if (id == g_array_index (stack, int, i)) + return TRUE; + } + + if (check_references (self, unvisited_variables, stack, value)) + return TRUE; + + g_array_remove_index_fast (stack, stack->len - 1); + g_hash_table_remove (unvisited_variables, GINT_TO_POINTER (id)); + + return FALSE; +} + +void +gtk_css_variable_set_resolve_cycles (GtkCssVariableSet *self) +{ + GHashTable *variables = g_hash_table_new (g_direct_hash, g_direct_equal); + GHashTable *unvisited_variables = g_hash_table_new (g_direct_hash, g_direct_equal); + GArray *stack; + GHashTableIter iter; + gpointer id; + GtkCssVariableValue *value; + + stack = g_array_new (FALSE, FALSE, sizeof (int)); + + g_hash_table_iter_init (&iter, self->variables); + + while (g_hash_table_iter_next (&iter, &id, (gpointer) &value)) + { + /* Have 2 copies of self->variables: "variables" can be iterated safely as + * we remove values, and "unvisited_variables" as a visited node marker + * for DFS */ + g_hash_table_insert (variables, id, value); + g_hash_table_add (unvisited_variables, id); + } + + g_hash_table_iter_init (&iter, variables); + + while (g_hash_table_iter_next (&iter, &id, (gpointer) &value)) + { + if (!g_hash_table_contains (unvisited_variables, id)) + continue; + + if (!g_hash_table_contains (self->variables, id)) + continue; + + if (check_variable (self, unvisited_variables, stack, GPOINTER_TO_INT (id), value)) + { + int i; + + /* Found a cycle, remove the offending variables */ + for (i = stack->len - 2; i >= 0; i--) + { + int id_to_remove = g_array_index (stack, int, i); + + g_hash_table_remove (self->variables, GINT_TO_POINTER (id_to_remove)); + + if (g_array_index (stack, int, i) == g_array_index (stack, int, stack->len - 1)) + break; + } + + g_array_remove_range (stack, 0, stack->len); + } + } + + g_array_unref (stack); + g_hash_table_unref (variables); + g_hash_table_unref (unvisited_variables); +} + +GtkCssVariableValue * +gtk_css_variable_set_lookup (GtkCssVariableSet *self, + int id, + GtkCssVariableSet **source) +{ + GtkCssVariableValue *ret = g_hash_table_lookup (self->variables, GINT_TO_POINTER (id)); + + if (ret) + { + if (source) + *source = self; + return ret; + } + + if (self->parent) + return gtk_css_variable_set_lookup (self->parent, id, source); + + if (source) + *source = NULL; + + return NULL; +} + +static int +compare_ids (gconstpointer a, gconstpointer b, gpointer user_data) +{ + GtkCssCustomPropertyPool *pool = user_data; + int id1 = GPOINTER_TO_INT (*((gconstpointer *) a)); + int id2 = GPOINTER_TO_INT (*((gconstpointer *) b)); + const char *name1, *name2; + + name1 = gtk_css_custom_property_pool_get_name (pool, id1); + name2 = gtk_css_custom_property_pool_get_name (pool, id2); + + return strcmp (name1, name2); +} + +GArray * +gtk_css_variable_set_list_ids (GtkCssVariableSet *self) +{ + GtkCssCustomPropertyPool *pool = gtk_css_custom_property_pool_get (); + GArray *ret = g_array_new (FALSE, FALSE, sizeof (int)); + GHashTableIter iter; + gpointer id; + + g_hash_table_iter_init (&iter, self->variables); + + while (g_hash_table_iter_next (&iter, &id, NULL)) + g_array_append_val (ret, id); + + g_array_sort_with_data (ret, compare_ids, pool); + + return ret; +} + +gboolean +gtk_css_variable_set_equal (GtkCssVariableSet *set1, + GtkCssVariableSet *set2) +{ + GHashTableIter iter; + gpointer id; + GtkCssVariableValue *value1; + + if (set1 == set2) + return TRUE; + + if (set1 == NULL || set2 == NULL) + return FALSE; + + if (g_hash_table_size (set1->variables) != g_hash_table_size (set2->variables)) + return FALSE; + + if (set1->parent != set2->parent) + return FALSE; + + g_hash_table_iter_init (&iter, set2->variables); + + while (g_hash_table_iter_next (&iter, &id, NULL)) + { + if (!g_hash_table_contains (set1->variables, id)) + return FALSE; + } + + g_hash_table_iter_init (&iter, set1->variables); + + while (g_hash_table_iter_next (&iter, &id, (gpointer) &value1)) + { + GtkCssVariableValue *value2 = g_hash_table_lookup (set2->variables, id); + + if (value2 == NULL) + return FALSE; + + if (!gtk_css_variable_value_equal (value1, value2)) + return FALSE; + } + + return TRUE; +} diff --git a/gtk/gtkcssvariablesetprivate.h b/gtk/gtkcssvariablesetprivate.h new file mode 100644 index 0000000000..39bd1d4bde --- /dev/null +++ b/gtk/gtkcssvariablesetprivate.h @@ -0,0 +1,60 @@ +/* + * 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 . + * + * Authors: Alice Mikhaylenko + */ + +#pragma once + +#include "gtk/css/gtkcssvariablevalueprivate.h" + +G_BEGIN_DECLS + +typedef struct _GtkCssVariableSet GtkCssVariableSet; + +struct _GtkCssVariableSet +{ + int ref_count; + + GHashTable *variables; + GtkCssVariableSet *parent; +}; + +GtkCssVariableSet * gtk_css_variable_set_new (void); + +GtkCssVariableSet * gtk_css_variable_set_ref (GtkCssVariableSet *self); +void gtk_css_variable_set_unref (GtkCssVariableSet *self); + +GtkCssVariableSet * gtk_css_variable_set_copy (GtkCssVariableSet *self); + +void gtk_css_variable_set_set_parent (GtkCssVariableSet *self, + GtkCssVariableSet *parent); + +void gtk_css_variable_set_add (GtkCssVariableSet *self, + int id, + GtkCssVariableValue *value); +void gtk_css_variable_set_resolve_cycles (GtkCssVariableSet *self); + +GtkCssVariableValue *gtk_css_variable_set_lookup (GtkCssVariableSet *self, + int id, + GtkCssVariableSet **source); + +GArray * gtk_css_variable_set_list_ids (GtkCssVariableSet *self); + +gboolean gtk_css_variable_set_equal (GtkCssVariableSet *set1, + GtkCssVariableSet *set2); + +G_END_DECLS diff --git a/gtk/meson.build b/gtk/meson.build index 436a167ff0..db3c553d37 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -53,6 +53,7 @@ gtk_private_sources = files([ 'gtkcsscalcvalue.c', 'gtkcsscolorvalue.c', 'gtkcsscornervalue.c', + 'gtkcsscustompropertypool.c', 'gtkcssdimensionvalue.c', 'gtkcssdynamic.c', 'gtkcsseasevalue.c', @@ -84,6 +85,7 @@ gtk_private_sources = files([ 'gtkcssnumbervalue.c', 'gtkcsspalettevalue.c', 'gtkcsspositionvalue.c', + 'gtkcssreferencevalue.c', 'gtkcssrepeatvalue.c', 'gtkcssselector.c', 'gtkcssshadowvalue.c', @@ -101,6 +103,7 @@ gtk_private_sources = files([ 'gtkcsstypes.c', 'gtkcssunsetvalue.c', 'gtkcssvalue.c', + 'gtkcssvariableset.c', 'gtkcsswidgetnode.c', 'gtkdrop.c', 'gtkfilechooserentry.c',