From 1689ec9100b0e9283795291555fe2f214d7159fc Mon Sep 17 00:00:00 2001 From: Dan Klishch Date: Sun, 5 Nov 2023 18:44:15 -0500 Subject: [PATCH] LibGUI: Introduce property deserializers for GML property values --- Userland/Libraries/LibGUI/CMakeLists.txt | 1 + Userland/Libraries/LibGUI/Object.h | 38 ++++++ .../Libraries/LibGUI/PropertyDeserializer.cpp | 117 ++++++++++++++++++ .../Libraries/LibGUI/PropertyDeserializer.h | 30 +++++ 4 files changed, 186 insertions(+) create mode 100644 Userland/Libraries/LibGUI/PropertyDeserializer.cpp create mode 100644 Userland/Libraries/LibGUI/PropertyDeserializer.h diff --git a/Userland/Libraries/LibGUI/CMakeLists.txt b/Userland/Libraries/LibGUI/CMakeLists.txt index e61d088fc3..0b33fb1d11 100644 --- a/Userland/Libraries/LibGUI/CMakeLists.txt +++ b/Userland/Libraries/LibGUI/CMakeLists.txt @@ -94,6 +94,7 @@ set(SOURCES ProcessChooser.cpp Progressbar.cpp Property.cpp + PropertyDeserializer.cpp RadioButton.cpp RangeSlider.cpp RegularEditingEngine.cpp diff --git a/Userland/Libraries/LibGUI/Object.h b/Userland/Libraries/LibGUI/Object.h index 9d3dd3b817..777b5b65e8 100644 --- a/Userland/Libraries/LibGUI/Object.h +++ b/Userland/Libraries/LibGUI/Object.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace GUI { @@ -59,6 +60,43 @@ protected: void register_property(ByteString const& name, Function getter, Function setter = nullptr); + template + void register_property(StringView name, Getter&& getter, Deserializer&& deserializer, Setter&& setter) + { + Function getter_fn = nullptr; + Function setter_fn = nullptr; + + if constexpr (!SameAs) { + static_assert(ConvertibleTo, JsonValue>); + getter_fn = [captured_getter = forward(getter)]() -> JsonValue { + return captured_getter(); + }; + } + + static_assert(SameAs == SameAs); + if constexpr (!SameAs) { + using DeserializerReturnValue = RemoveReference>; + static_assert(SpecializationOf); + using DeserializedValue = RemoveReference; + // FIXME: Should we allow setter to fail? + static_assert(SameAs, void>); + + setter_fn = [captured_deserializer = forward(deserializer), captured_setter = forward(setter)](JsonValue const& value) -> bool { + DeserializerReturnValue deserializer_return_value = captured_deserializer(value); + if (deserializer_return_value.is_error()) { + // FIXME: Propagate error up to a place where we have enough context to print/show meaningful message. + dbgln("Got error while deserializing GML property: {}", deserializer_return_value.error()); + return false; + } + DeserializedValue deserialized_value = deserializer_return_value.release_value(); + captured_setter(move(deserialized_value)); + return true; + }; + } + + register_property(name, move(getter_fn), move(setter_fn)); + } + private: HashMap> m_properties; }; diff --git a/Userland/Libraries/LibGUI/PropertyDeserializer.cpp b/Userland/Libraries/LibGUI/PropertyDeserializer.cpp new file mode 100644 index 0000000000..e1a86e5bdd --- /dev/null +++ b/Userland/Libraries/LibGUI/PropertyDeserializer.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "PropertyDeserializer.h" +#include +#include +#include + +namespace GUI { + +template<> +ErrorOr PropertyDeserializer::operator()(JsonValue const& value) const +{ + if (value.is_bool()) + return value.as_bool(); + return Error::from_string_literal("Boolean is expected"); +} + +template<> +ErrorOr PropertyDeserializer::operator()(JsonValue const& value) const +{ + if (value.is_string()) { + // FIXME: Port JsonValue to the new String class. + return String::from_deprecated_string(value.as_string()); + } + return Error::from_string_literal("UTF-8 string is expected"); +} + +template<> +ErrorOr PropertyDeserializer::operator()(JsonValue const& value) const +{ + if (value.is_string()) + return value.as_string(); + return Error::from_string_literal("String is expected"); +} + +template<> +ErrorOr PropertyDeserializer::operator()(JsonValue const& value) const +{ + if (!value.is_object() && !(value.is_array() && value.as_array().size() == 4)) + return Error::from_string_literal("An array with 4 integers or an object is expected"); + + Gfx::IntRect rect; + + Optional x; + Optional y; + Optional width; + Optional height; + + if (value.is_object()) { + auto const& object = value.as_object(); + + if (object.size() != 4 || !object.has("x"sv) || !object.has("y"sv) || !object.has("width"sv) || !object.has("height"sv)) + return Error::from_string_literal("Object with keys \"x\", \"y\", \"width\", and \"height\" is expected"); + + x = object.get_i32("x"sv); + y = object.get_i32("y"sv); + width = object.get_i32("width"sv); + height = object.get_i32("height"sv); + } else { + auto const& array = value.as_array(); + + auto get_i32 = [](JsonValue const& value) -> Optional { + if (value.is_integer()) + return value.to_i32(); + return {}; + }; + + x = get_i32(array[0]); + y = get_i32(array[1]); + width = get_i32(array[2]); + height = get_i32(array[3]); + } + + if (!x.has_value()) + return Error::from_string_literal("X coordinate must be an integer"); + if (!y.has_value()) + return Error::from_string_literal("Y coordinate must be an integer"); + if (!width.has_value()) + return Error::from_string_literal("Width must be an integer"); + if (!height.has_value()) + return Error::from_string_literal("Height must be an integer"); + + rect.set_x(x.value()); + rect.set_y(y.value()); + rect.set_width(width.value()); + rect.set_height(height.value()); + + return rect; +} + +template<> +ErrorOr PropertyDeserializer::operator()(JsonValue const& value) const +{ + if (!value.is_array() || value.as_array().size() != 2) + return Error::from_string_literal("Expected array with 2 integers"); + + auto const& array = value.as_array(); + + auto const& width = array[0]; + if (!width.is_integer()) + return Error::from_string_literal("Width must be an integer"); + auto const& height = array[1]; + if (!height.is_integer()) + return Error::from_string_literal("Height must be an integer"); + + Gfx::IntSize size; + size.set_width(width.to_i32()); + size.set_height(height.to_i32()); + + return size; +} + +}; diff --git a/Userland/Libraries/LibGUI/PropertyDeserializer.h b/Userland/Libraries/LibGUI/PropertyDeserializer.h new file mode 100644 index 0000000000..00f6d56726 --- /dev/null +++ b/Userland/Libraries/LibGUI/PropertyDeserializer.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace GUI { + +template +struct PropertyDeserializer { + ErrorOr operator()(JsonValue const& value) const; +}; + +template +requires(!IsSame) +struct PropertyDeserializer { + ErrorOr operator()(JsonValue const& value) const + { + if (!value.is_integer()) + return Error::from_string_literal("Value is either not an integer or out of range for requested type"); + return value.to_number(); + } +}; + +}