diff --git a/CHANGELOG.md b/CHANGELOG.md index a87b13c08..596c81131 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `Deref` and `DerefMut` implementations to `QList` for `QStringList`, `QPolygon`, and `QPolygonF`. +- `QDate::format`, `QDateTime::format`, and `QTime::format` to create a `QString` with a specific `DateFormat`. - `QDateTime::from_string` to parse `QDateTime` from a `QString`. +- `QSet::reserve` to reserve capacity up-front. - Support for further types: `QUuid` - New example: Basic greeter app - Support for further types: `qreal`, `qint64`, `qintptr`, `qsizetype`, `quint64`, `quintptr` @@ -30,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add pure virtual function specified through the `#[cxx_pure]` attribute - Add wrappers for up and down casting, for all types which inherit from QObject, available for &T, &mut T and Pin<&mut T> - Support for `QMessageLogContext` and sending log messages to the Qt message handler. +- Serde support for further types: `QByteArray`, `QColor`, `QDate`, `QDateTime`, `QFont`, `QLine`, `QLineF`, `QList`, `QMargins`, `QMarginsF`, `QPoint`, `QPointF`, `QPolygon`, `QPolygonF`, `QRect`, `QRectF`, `QSet`, `QSize`, `QSizeF`, `QStringList`, `QVector`, `QVector2D`, `QVector3D`, `QVector4D`, `QTime`, `QUrl`, `QUuid` ### Removed @@ -144,7 +148,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Do not use -bundle otherwise CMake builds are missing qt-static-initalizers (note this is broken in rustc 1.69) - Do not import `Pin` in hidden module as invokables are outside now, resolving IDE integration -- Rust always links against a non-debug Windows runtime with *-msvc targets, so we need to link to MultiThreadedDLL +- Rust always links against a non-debug Windows runtime with \*-msvc targets, so we need to link to MultiThreadedDLL ### Removed diff --git a/Cargo.lock b/Cargo.lock index f2761bb9f..5545b4a24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -422,6 +422,7 @@ dependencies = [ "qt-build-utils", "rgb", "serde", + "serde_json", "time", "url", "uuid", diff --git a/crates/cxx-qt-lib/Cargo.toml b/crates/cxx-qt-lib/Cargo.toml index 65b606618..3797f503a 100644 --- a/crates/cxx-qt-lib/Cargo.toml +++ b/crates/cxx-qt-lib/Cargo.toml @@ -42,6 +42,9 @@ image-v0-25 = { version = "0.25", optional = true, package = "image", default-fe cxx-qt-build.workspace = true qt-build-utils.workspace = true +[dev-dependencies] +serde_json = "1.0.135" + [features] full = ["qt_full", "serde", "url", "uuid", "time", "rgb", "http", "chrono", "bytes", "image-v0-24", "image-v0-25"] default = [] diff --git a/crates/cxx-qt-lib/include/core/qset/qset_private.h b/crates/cxx-qt-lib/include/core/qset/qset_private.h index 3c7c3232c..651c6a38f 100644 --- a/crates/cxx-qt-lib/include/core/qset/qset_private.h +++ b/crates/cxx-qt-lib/include/core/qset/qset_private.h @@ -58,6 +58,19 @@ qsetLen(const QSet<T>& s) noexcept return static_cast<::rust::isize>(s.size()); } +template<typename T> +void +qsetReserve(QSet<T>& s, ::rust::isize size) noexcept +{ + Q_ASSERT(size >= 0); + // Qt 5 has an int Qt 6 has a qsizetype +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + s.reserve(static_cast<qsizetype>(size)); +#else + s.reserve(static_cast<int>(size)); +#endif +} + } } } \ No newline at end of file diff --git a/crates/cxx-qt-lib/include/core/quuid.h b/crates/cxx-qt-lib/include/core/quuid.h index c4ff8b724..7a39223fd 100644 --- a/crates/cxx-qt-lib/include/core/quuid.h +++ b/crates/cxx-qt-lib/include/core/quuid.h @@ -10,6 +10,7 @@ #include "rust/cxx.h" +using QUuidStringFormat = QUuid::StringFormat; using QUuidVariant = QUuid::Variant; using QUuidVersion = QUuid::Version; @@ -24,9 +25,6 @@ quuidCreateUuid(); QUuid quuidCreateUuidV5(const QUuid& ns, ::rust::Slice<const ::std::uint8_t> slice); -QString -quuidToString(const QUuid& uuid); - QUuid quuidFromString(const QString& string); diff --git a/crates/cxx-qt-lib/src/core/qbytearray.rs b/crates/cxx-qt-lib/src/core/qbytearray.rs index 6be2cd9b5..e8cab0c95 100644 --- a/crates/cxx-qt-lib/src/core/qbytearray.rs +++ b/crates/cxx-qt-lib/src/core/qbytearray.rs @@ -321,6 +321,54 @@ impl QByteArray { } } +#[cfg(feature = "serde")] +impl serde::Serialize for QByteArray { + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + serializer.serialize_bytes(self.as_slice()) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for QByteArray { + fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + use serde::de::{Error as DeError, SeqAccess, Visitor}; + + struct BytesVisitor; + + impl<'de> Visitor<'de> for BytesVisitor { + type Value = QByteArray; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("an array of bytes") + } + + fn visit_bytes<E: DeError>(self, v: &[u8]) -> Result<Self::Value, E> { + Ok(Self::Value::from(v)) + } + + fn visit_str<E: DeError>(self, v: &str) -> Result<Self::Value, E> { + Ok(Self::Value::from(v)) + } + + fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> { + let mut values = Self::Value::default(); + if let Some(size_hint) = seq.size_hint() { + if size_hint != 0 && size_hint <= isize::MAX as usize { + values.reserve(size_hint as isize); + } + } + while let Some(value) = seq.next_element()? { + values.append(value); + } + Ok(values) + } + } + + let visitor = BytesVisitor; + deserializer.deserialize_byte_buf(visitor) + } +} + // Safety: // // Static checks on the C++ side to ensure the size is the same. @@ -334,6 +382,13 @@ mod tests { #[cfg(feature = "bytes")] use super::*; + #[cfg(feature = "serde")] + #[test] + fn qbytearray_serde() { + let qbytearray = QByteArray::from("KDAB"); + assert_eq!(crate::serde_impl::roundtrip(&qbytearray), qbytearray) + } + #[cfg(feature = "bytes")] #[test] fn test_bytes() { diff --git a/crates/cxx-qt-lib/src/core/qdate.rs b/crates/cxx-qt-lib/src/core/qdate.rs index e1460dc62..beff45b61 100644 --- a/crates/cxx-qt-lib/src/core/qdate.rs +++ b/crates/cxx-qt-lib/src/core/qdate.rs @@ -176,7 +176,7 @@ impl QDate { Self { jd } } - /// Returns the QTime represented by the string, using the format given, or None if the string cannot be parsed. + /// Returns the QDate represented by the string, using the format given, or None if the string cannot be parsed. pub fn from_string(string: &ffi::QString, format: &ffi::QString) -> Option<Self> { let date = ffi::qdate_from_string(string, format); if date.is_valid() { @@ -186,7 +186,7 @@ impl QDate { } } - /// Returns the time represented in the string as a QTime using the format given, or None if this is not possible. + /// Returns the time represented in the string as a QDate using the format given, or None if this is not possible. pub fn from_string_enum(string: &ffi::QString, format: ffi::DateFormat) -> Option<Self> { let date = ffi::qdate_from_string_enum(string, format); if date.is_valid() { @@ -290,6 +290,13 @@ mod test { assert_eq!(QDate::from(naive), qdate); } + #[cfg(feature = "serde")] + #[test] + fn qdate_serde() { + let qdate = QDate::new(2023, 1, 1); + assert_eq!(crate::serde_impl::roundtrip(&qdate), qdate); + } + #[cfg(feature = "chrono")] #[test] fn qdate_to_chrono_naive() { diff --git a/crates/cxx-qt-lib/src/core/qdatetime.rs b/crates/cxx-qt-lib/src/core/qdatetime.rs index 231f388d9..1b2193f9d 100644 --- a/crates/cxx-qt-lib/src/core/qdatetime.rs +++ b/crates/cxx-qt-lib/src/core/qdatetime.rs @@ -195,6 +195,7 @@ mod ffi { fn qdatetimeTimeZone(datetime: &QDateTime) -> UniquePtr<QTimeZone>; #[rust_name = "qdatetime_settimezone"] fn qdatetimeSetTimeZone(datetime: &mut QDateTime, time_zone: &QTimeZone); + #[doc(hidden)] #[rust_name = "qdatetime_from_string"] fn qdatetimeFromQString(string: &QString, format: DateFormat) -> QDateTime; } @@ -514,6 +515,17 @@ mod test { assert_eq!(qdatetime_b.cmp(&qdatetime_a), Ordering::Greater); assert_eq!(qdatetime_a.cmp(&qdatetime_a), Ordering::Equal); } + + #[cfg(feature = "serde")] + #[test] + fn qdatetime_serde() { + let qdatetime = QDateTime::from_date_and_time_time_zone( + &QDate::new(2023, 1, 1), + &QTime::new(1, 1, 1, 1), + &ffi::QTimeZone::utc(), + ); + assert_eq!(crate::serde_impl::roundtrip(&qdatetime), qdatetime); + } } #[cfg(test)] diff --git a/crates/cxx-qt-lib/src/core/qline.rs b/crates/cxx-qt-lib/src/core/qline.rs index 0bf67c7f7..aef459160 100644 --- a/crates/cxx-qt-lib/src/core/qline.rs +++ b/crates/cxx-qt-lib/src/core/qline.rs @@ -98,8 +98,12 @@ mod ffi { } } +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + /// The QLine class provides a two-dimensional vector using integer precision #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct QLine { pt1: QPoint, diff --git a/crates/cxx-qt-lib/src/core/qlinef.rs b/crates/cxx-qt-lib/src/core/qlinef.rs index 8c80837d1..83d4e2acd 100644 --- a/crates/cxx-qt-lib/src/core/qlinef.rs +++ b/crates/cxx-qt-lib/src/core/qlinef.rs @@ -131,8 +131,12 @@ mod ffi { } } +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + /// The QLineF class provides a two-dimensional vector using floating point precision. #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct QLineF { pt1: QPointF, diff --git a/crates/cxx-qt-lib/src/core/qlist/mod.rs b/crates/cxx-qt-lib/src/core/qlist/mod.rs index de82d4dff..abe6b3073 100644 --- a/crates/cxx-qt-lib/src/core/qlist/mod.rs +++ b/crates/cxx-qt-lib/src/core/qlist/mod.rs @@ -400,4 +400,11 @@ mod test { let qlist = QList::<u8>::from(array); assert_eq!(Vec::from(&qlist), array); } + + #[cfg(feature = "serde")] + #[test] + fn qlist_serde() { + let qlist = QList::<u8>::from([0, 1, 2]); + assert_eq!(crate::serde_impl::roundtrip(&qlist), qlist); + } } diff --git a/crates/cxx-qt-lib/src/core/qmargins.rs b/crates/cxx-qt-lib/src/core/qmargins.rs index 5da187b8c..430682118 100644 --- a/crates/cxx-qt-lib/src/core/qmargins.rs +++ b/crates/cxx-qt-lib/src/core/qmargins.rs @@ -96,8 +96,12 @@ mod ffi { } } +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + /// The QMargins class defines the four margins of a rectangle. #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct QMargins { left: i32, diff --git a/crates/cxx-qt-lib/src/core/qmarginsf.rs b/crates/cxx-qt-lib/src/core/qmarginsf.rs index 8fcbcf800..93e5397fd 100644 --- a/crates/cxx-qt-lib/src/core/qmarginsf.rs +++ b/crates/cxx-qt-lib/src/core/qmarginsf.rs @@ -92,8 +92,12 @@ mod ffi { } } +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + /// The QMarginsF class defines the four margins of a rectangle. #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct QMarginsF { left: f64, diff --git a/crates/cxx-qt-lib/src/core/qpoint.rs b/crates/cxx-qt-lib/src/core/qpoint.rs index 5c4dbc342..c7aa3f8a4 100644 --- a/crates/cxx-qt-lib/src/core/qpoint.rs +++ b/crates/cxx-qt-lib/src/core/qpoint.rs @@ -93,8 +93,12 @@ mod ffi { } } +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + /// The QPoint struct defines a point in the plane using integer precision. #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct QPoint { x: i32, diff --git a/crates/cxx-qt-lib/src/core/qpointf.rs b/crates/cxx-qt-lib/src/core/qpointf.rs index 3a12b91c2..1b53fd1d1 100644 --- a/crates/cxx-qt-lib/src/core/qpointf.rs +++ b/crates/cxx-qt-lib/src/core/qpointf.rs @@ -86,9 +86,12 @@ mod ffi { fn operatorDiv(a: f64, b: &QPointF) -> QPointF; } } +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; /// The QPointF struct defines a point in the plane using floating point precision. #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct QPointF { x: f64, diff --git a/crates/cxx-qt-lib/src/core/qrect.rs b/crates/cxx-qt-lib/src/core/qrect.rs index c25abe19e..3a1f80e16 100644 --- a/crates/cxx-qt-lib/src/core/qrect.rs +++ b/crates/cxx-qt-lib/src/core/qrect.rs @@ -268,8 +268,12 @@ mod ffi { } } +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + /// The QRect struct defines a rectangle in the plane using integer precision. #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct QRect { // Note that Qt stores QRect as two points rather than a point and size (which QRectF is) diff --git a/crates/cxx-qt-lib/src/core/qrectf.rs b/crates/cxx-qt-lib/src/core/qrectf.rs index 3ba51b236..f4a51b6af 100644 --- a/crates/cxx-qt-lib/src/core/qrectf.rs +++ b/crates/cxx-qt-lib/src/core/qrectf.rs @@ -269,9 +269,12 @@ mod ffi { fn operatorMinus(a: &QRectF, b: &QMarginsF) -> QRectF; } } +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; /// The QRectF struct defines a rectangle in the plane using floating point precision. #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct QRectF { xp: f64, diff --git a/crates/cxx-qt-lib/src/core/qset/generate.sh b/crates/cxx-qt-lib/src/core/qset/generate.sh index 953177b2a..18fad9759 100755 --- a/crates/cxx-qt-lib/src/core/qset/generate.sh +++ b/crates/cxx-qt-lib/src/core/qset/generate.sh @@ -72,6 +72,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_$1, _: &$1); #[rust_name = "len_$1"] fn qsetLen(_: &QSet_$1) -> isize; + #[rust_name = "reserve_$1"] + fn qsetReserve(_: &mut QSet_$1, size: isize); } } @@ -98,6 +100,10 @@ pub(crate) fn insert(s: &mut ffi::QSet_$1, value: &$1) { pub(crate) fn len(s: &ffi::QSet_$1) -> isize { ffi::len_$1(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_$1, size: isize) { + ffi::reserve_$1(s, size); +} EOF rustfmt "$SCRIPTPATH/qset_$1.rs" } @@ -148,6 +154,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_$1, _: &$1); #[rust_name = "len_$1"] fn qsetLen(_: &QSet_$1) -> isize; + #[rust_name = "reserve_$1"] + fn qsetReserve(_: &mut QSet_$1, size: isize); } } @@ -174,6 +182,10 @@ pub(crate) fn insert(s: &mut ffi::QSet_$1, value: &ffi::$1) { pub(crate) fn len(s: &ffi::QSet_$1) -> isize { ffi::len_$1(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_$1, size: isize) { + ffi::reserve_$1(s, size); +} EOF rustfmt "$SCRIPTPATH/qset_$2.rs" } diff --git a/crates/cxx-qt-lib/src/core/qset/mod.rs b/crates/cxx-qt-lib/src/core/qset/mod.rs index 82888a404..c5f8b9a6f 100644 --- a/crates/cxx-qt-lib/src/core/qset/mod.rs +++ b/crates/cxx-qt-lib/src/core/qset/mod.rs @@ -121,6 +121,12 @@ where pub fn remove(&mut self, value: &T) -> bool { T::remove(self, value) } + + /// Reserve the specified capacity to prevent repeated allocations + /// when the maximum size is known. + pub fn reserve(&mut self, size: isize) { + T::reserve(self, size); + } } impl<T> QSet<T> @@ -201,6 +207,7 @@ pub trait QSetElement: Sized { fn insert_clone(set: &mut QSet<Self>, value: &Self); fn len(set: &QSet<Self>) -> isize; fn remove(set: &mut QSet<Self>, value: &Self) -> bool; + fn reserve(set: &mut QSet<Self>, size: isize); } macro_rules! impl_qset_element { @@ -249,6 +256,10 @@ macro_rules! impl_qset_element { fn remove(set: &mut QSet<Self>, value: &Self) -> bool { set.cxx_remove(value) } + + fn reserve(set: &mut QSet<Self>, size: isize) { + $module::reserve(set, size); + } } }; } @@ -277,3 +288,18 @@ impl_qset_element!(u8, qset_u8, "QSet_u8"); impl_qset_element!(u16, qset_u16, "QSet_u16"); impl_qset_element!(u32, qset_u32, "QSet_u32"); impl_qset_element!(u64, qset_u64, "QSet_u64"); + +#[cfg(test)] +mod test { + use super::*; + + #[cfg(feature = "serde")] + #[test] + fn qset_serde() { + let mut set = QSet::default(); + set.insert(0); + set.insert(1); + set.insert(2); + assert_eq!(crate::serde_impl::roundtrip(&set), set) + } +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_bool.rs b/crates/cxx-qt-lib/src/core/qset/qset_bool.rs index f5353efcc..be0ca0d31 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_bool.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_bool.rs @@ -40,6 +40,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_bool, _: &bool); #[rust_name = "len_bool"] fn qsetLen(_: &QSet_bool) -> isize; + #[rust_name = "reserve_bool"] + fn qsetReserve(_: &mut QSet_bool, size: isize); } } @@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_bool, value: &bool) { pub(crate) fn len(s: &ffi::QSet_bool) -> isize { ffi::len_bool(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_bool, size: isize) { + ffi::reserve_bool(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_f32.rs b/crates/cxx-qt-lib/src/core/qset/qset_f32.rs index 0c9e32ab0..fe7458c30 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_f32.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_f32.rs @@ -40,6 +40,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_f32, _: &f32); #[rust_name = "len_f32"] fn qsetLen(_: &QSet_f32) -> isize; + #[rust_name = "reserve_f32"] + fn qsetReserve(_: &mut QSet_f32, size: isize); } } @@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_f32, value: &f32) { pub(crate) fn len(s: &ffi::QSet_f32) -> isize { ffi::len_f32(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_f32, size: isize) { + ffi::reserve_f32(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_f64.rs b/crates/cxx-qt-lib/src/core/qset/qset_f64.rs index 9fe50615c..d670a8de3 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_f64.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_f64.rs @@ -40,6 +40,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_f64, _: &f64); #[rust_name = "len_f64"] fn qsetLen(_: &QSet_f64) -> isize; + #[rust_name = "reserve_f64"] + fn qsetReserve(_: &mut QSet_f64, size: isize); } } @@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_f64, value: &f64) { pub(crate) fn len(s: &ffi::QSet_f64) -> isize { ffi::len_f64(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_f64, size: isize) { + ffi::reserve_f64(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_i16.rs b/crates/cxx-qt-lib/src/core/qset/qset_i16.rs index f0981c15c..9b9d1edc0 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_i16.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_i16.rs @@ -40,6 +40,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_i16, _: &i16); #[rust_name = "len_i16"] fn qsetLen(_: &QSet_i16) -> isize; + #[rust_name = "reserve_i16"] + fn qsetReserve(_: &mut QSet_i16, size: isize); } } @@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_i16, value: &i16) { pub(crate) fn len(s: &ffi::QSet_i16) -> isize { ffi::len_i16(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_i16, size: isize) { + ffi::reserve_i16(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_i32.rs b/crates/cxx-qt-lib/src/core/qset/qset_i32.rs index dd703fa28..b1cbc0a0b 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_i32.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_i32.rs @@ -40,6 +40,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_i32, _: &i32); #[rust_name = "len_i32"] fn qsetLen(_: &QSet_i32) -> isize; + #[rust_name = "reserve_i32"] + fn qsetReserve(_: &mut QSet_i32, size: isize); } } @@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_i32, value: &i32) { pub(crate) fn len(s: &ffi::QSet_i32) -> isize { ffi::len_i32(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_i32, size: isize) { + ffi::reserve_i32(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_i64.rs b/crates/cxx-qt-lib/src/core/qset/qset_i64.rs index edd74528b..267859b00 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_i64.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_i64.rs @@ -40,6 +40,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_i64, _: &i64); #[rust_name = "len_i64"] fn qsetLen(_: &QSet_i64) -> isize; + #[rust_name = "reserve_i64"] + fn qsetReserve(_: &mut QSet_i64, size: isize); } } @@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_i64, value: &i64) { pub(crate) fn len(s: &ffi::QSet_i64) -> isize { ffi::len_i64(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_i64, size: isize) { + ffi::reserve_i64(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_i8.rs b/crates/cxx-qt-lib/src/core/qset/qset_i8.rs index 8ff1e68b1..b1ef22887 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_i8.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_i8.rs @@ -40,6 +40,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_i8, _: &i8); #[rust_name = "len_i8"] fn qsetLen(_: &QSet_i8) -> isize; + #[rust_name = "reserve_i8"] + fn qsetReserve(_: &mut QSet_i8, size: isize); } } @@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_i8, value: &i8) { pub(crate) fn len(s: &ffi::QSet_i8) -> isize { ffi::len_i8(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_i8, size: isize) { + ffi::reserve_i8(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_qbytearray.rs b/crates/cxx-qt-lib/src/core/qset/qset_qbytearray.rs index 3ffd1f82b..67c19b58a 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_qbytearray.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_qbytearray.rs @@ -42,6 +42,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_QByteArray, _: &QByteArray); #[rust_name = "len_QByteArray"] fn qsetLen(_: &QSet_QByteArray) -> isize; + #[rust_name = "reserve_QByteArray"] + fn qsetReserve(_: &mut QSet_QByteArray, size: isize); } } @@ -68,3 +70,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QByteArray, value: &ffi::QByteArray) { pub(crate) fn len(s: &ffi::QSet_QByteArray) -> isize { ffi::len_QByteArray(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_QByteArray, size: isize) { + ffi::reserve_QByteArray(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_qdate.rs b/crates/cxx-qt-lib/src/core/qset/qset_qdate.rs index 889b77691..e4ee199ae 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_qdate.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_qdate.rs @@ -42,6 +42,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_QDate, _: &QDate); #[rust_name = "len_QDate"] fn qsetLen(_: &QSet_QDate) -> isize; + #[rust_name = "reserve_QDate"] + fn qsetReserve(_: &mut QSet_QDate, size: isize); } } @@ -68,3 +70,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QDate, value: &ffi::QDate) { pub(crate) fn len(s: &ffi::QSet_QDate) -> isize { ffi::len_QDate(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_QDate, size: isize) { + ffi::reserve_QDate(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_qdatetime.rs b/crates/cxx-qt-lib/src/core/qset/qset_qdatetime.rs index 37bd4adb9..b88bb8b2e 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_qdatetime.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_qdatetime.rs @@ -42,6 +42,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_QDateTime, _: &QDateTime); #[rust_name = "len_QDateTime"] fn qsetLen(_: &QSet_QDateTime) -> isize; + #[rust_name = "reserve_QDateTime"] + fn qsetReserve(_: &mut QSet_QDateTime, size: isize); } } @@ -68,3 +70,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QDateTime, value: &ffi::QDateTime) { pub(crate) fn len(s: &ffi::QSet_QDateTime) -> isize { ffi::len_QDateTime(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_QDateTime, size: isize) { + ffi::reserve_QDateTime(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_qpersistentmodelindex.rs b/crates/cxx-qt-lib/src/core/qset/qset_qpersistentmodelindex.rs index 72d8c8bcd..34c6cc2ea 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_qpersistentmodelindex.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_qpersistentmodelindex.rs @@ -45,6 +45,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_QPersistentModelIndex, _: &QPersistentModelIndex); #[rust_name = "len_QPersistentModelIndex"] fn qsetLen(_: &QSet_QPersistentModelIndex) -> isize; + #[rust_name = "reserve_QPersistentModelIndex"] + fn qsetReserve(_: &mut QSet_QPersistentModelIndex, size: isize); } } @@ -74,3 +76,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QPersistentModelIndex, value: &ffi::QPers pub(crate) fn len(s: &ffi::QSet_QPersistentModelIndex) -> isize { ffi::len_QPersistentModelIndex(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_QPersistentModelIndex, size: isize) { + ffi::reserve_QPersistentModelIndex(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_qstring.rs b/crates/cxx-qt-lib/src/core/qset/qset_qstring.rs index d6960813b..7c81e3d16 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_qstring.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_qstring.rs @@ -42,6 +42,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_QString, _: &QString); #[rust_name = "len_QString"] fn qsetLen(_: &QSet_QString) -> isize; + #[rust_name = "reserve_QString"] + fn qsetReserve(_: &mut QSet_QString, size: isize); } } @@ -68,3 +70,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QString, value: &ffi::QString) { pub(crate) fn len(s: &ffi::QSet_QString) -> isize { ffi::len_QString(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_QString, size: isize) { + ffi::reserve_QString(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_qtime.rs b/crates/cxx-qt-lib/src/core/qset/qset_qtime.rs index 2784888c4..36e3982a0 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_qtime.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_qtime.rs @@ -42,6 +42,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_QTime, _: &QTime); #[rust_name = "len_QTime"] fn qsetLen(_: &QSet_QTime) -> isize; + #[rust_name = "reserve_QTime"] + fn qsetReserve(_: &mut QSet_QTime, size: isize); } } @@ -68,3 +70,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QTime, value: &ffi::QTime) { pub(crate) fn len(s: &ffi::QSet_QTime) -> isize { ffi::len_QTime(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_QTime, size: isize) { + ffi::reserve_QTime(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_qurl.rs b/crates/cxx-qt-lib/src/core/qset/qset_qurl.rs index c3e0b6708..a40793fd2 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_qurl.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_qurl.rs @@ -42,6 +42,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_QUrl, _: &QUrl); #[rust_name = "len_QUrl"] fn qsetLen(_: &QSet_QUrl) -> isize; + #[rust_name = "reserve_QUrl"] + fn qsetReserve(_: &mut QSet_QUrl, size: isize); } } @@ -68,3 +70,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QUrl, value: &ffi::QUrl) { pub(crate) fn len(s: &ffi::QSet_QUrl) -> isize { ffi::len_QUrl(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_QUrl, size: isize) { + ffi::reserve_QUrl(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_quuid.rs b/crates/cxx-qt-lib/src/core/qset/qset_quuid.rs index 8229df3db..937dd4806 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_quuid.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_quuid.rs @@ -42,6 +42,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_QUuid, _: &QUuid); #[rust_name = "len_QUuid"] fn qsetLen(_: &QSet_QUuid) -> isize; + #[rust_name = "reserve_QUuid"] + fn qsetReserve(_: &mut QSet_QUuid, size: isize); } } @@ -68,3 +70,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_QUuid, value: &ffi::QUuid) { pub(crate) fn len(s: &ffi::QSet_QUuid) -> isize { ffi::len_QUuid(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_QUuid, size: isize) { + ffi::reserve_QUuid(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_u16.rs b/crates/cxx-qt-lib/src/core/qset/qset_u16.rs index f0cc404fe..954d927ad 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_u16.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_u16.rs @@ -40,6 +40,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_u16, _: &u16); #[rust_name = "len_u16"] fn qsetLen(_: &QSet_u16) -> isize; + #[rust_name = "reserve_u16"] + fn qsetReserve(_: &mut QSet_u16, size: isize); } } @@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_u16, value: &u16) { pub(crate) fn len(s: &ffi::QSet_u16) -> isize { ffi::len_u16(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_u16, size: isize) { + ffi::reserve_u16(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_u32.rs b/crates/cxx-qt-lib/src/core/qset/qset_u32.rs index accb74b25..bebacae24 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_u32.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_u32.rs @@ -40,6 +40,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_u32, _: &u32); #[rust_name = "len_u32"] fn qsetLen(_: &QSet_u32) -> isize; + #[rust_name = "reserve_u32"] + fn qsetReserve(_: &mut QSet_u32, size: isize); } } @@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_u32, value: &u32) { pub(crate) fn len(s: &ffi::QSet_u32) -> isize { ffi::len_u32(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_u32, size: isize) { + ffi::reserve_u32(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_u64.rs b/crates/cxx-qt-lib/src/core/qset/qset_u64.rs index 0eb9821d3..00cebaaca 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_u64.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_u64.rs @@ -40,6 +40,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_u64, _: &u64); #[rust_name = "len_u64"] fn qsetLen(_: &QSet_u64) -> isize; + #[rust_name = "reserve_u64"] + fn qsetReserve(_: &mut QSet_u64, size: isize); } } @@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_u64, value: &u64) { pub(crate) fn len(s: &ffi::QSet_u64) -> isize { ffi::len_u64(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_u64, size: isize) { + ffi::reserve_u64(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qset/qset_u8.rs b/crates/cxx-qt-lib/src/core/qset/qset_u8.rs index 8392079c6..898b071df 100644 --- a/crates/cxx-qt-lib/src/core/qset/qset_u8.rs +++ b/crates/cxx-qt-lib/src/core/qset/qset_u8.rs @@ -40,6 +40,8 @@ pub mod ffi { fn qsetInsert(_: &mut QSet_u8, _: &u8); #[rust_name = "len_u8"] fn qsetLen(_: &QSet_u8) -> isize; + #[rust_name = "reserve_u8"] + fn qsetReserve(_: &mut QSet_u8, size: isize); } } @@ -66,3 +68,7 @@ pub(crate) fn insert(s: &mut ffi::QSet_u8, value: &u8) { pub(crate) fn len(s: &ffi::QSet_u8) -> isize { ffi::len_u8(s) } + +pub(crate) fn reserve(s: &mut ffi::QSet_u8, size: isize) { + ffi::reserve_u8(s, size); +} diff --git a/crates/cxx-qt-lib/src/core/qsize.rs b/crates/cxx-qt-lib/src/core/qsize.rs index 41266e132..c51a0078a 100644 --- a/crates/cxx-qt-lib/src/core/qsize.rs +++ b/crates/cxx-qt-lib/src/core/qsize.rs @@ -113,9 +113,12 @@ mod ffi { fn operatorDiv(a: f64, b: &QSize) -> QSize; } } +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; /// The QSize struct defines the size of a two-dimensional object using integer point precision. #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct QSize { width: i32, diff --git a/crates/cxx-qt-lib/src/core/qsizef.rs b/crates/cxx-qt-lib/src/core/qsizef.rs index 9a3ec13b3..02dd8556e 100644 --- a/crates/cxx-qt-lib/src/core/qsizef.rs +++ b/crates/cxx-qt-lib/src/core/qsizef.rs @@ -116,9 +116,12 @@ mod ffi { fn operatorDiv(a: f64, b: &QSizeF) -> QSizeF; } } +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; /// The QSizeF class defines the size of a two-dimensional object using floating point precision. #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct QSizeF { width: f64, diff --git a/crates/cxx-qt-lib/src/core/qstring.rs b/crates/cxx-qt-lib/src/core/qstring.rs index b27b95867..9bcff0100 100644 --- a/crates/cxx-qt-lib/src/core/qstring.rs +++ b/crates/cxx-qt-lib/src/core/qstring.rs @@ -191,15 +191,10 @@ mod ffi { } } -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - /// The QString class provides a Unicode character string. /// /// Note that QString is a UTF-16 whereas Rust strings are a UTF-8 #[repr(C)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(from = "String", into = "String"))] pub struct QString { /// The layout has changed between Qt 5 and Qt 6 /// @@ -425,10 +420,55 @@ unsafe impl ExternType for QString { type Kind = cxx::kind::Trivial; } +#[cfg(feature = "serde")] +impl serde::Serialize for QString { + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + serializer.serialize_str(&String::from(self)) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for QString { + fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + use serde::de::{Error as DeError, Unexpected, Visitor}; + + struct StringVisitor; + + impl<'de> Visitor<'de> for StringVisitor { + type Value = QString; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string") + } + + fn visit_str<E: DeError>(self, v: &str) -> Result<Self::Value, E> { + Ok(Self::Value::from(v)) + } + + fn visit_bytes<E: DeError>(self, v: &[u8]) -> Result<Self::Value, E> { + match std::str::from_utf8(v) { + Ok(s) => Ok(Self::Value::from(s)), + Err(_) => Err(E::invalid_value(Unexpected::Bytes(v), &self)), + } + } + } + + let visitor = StringVisitor; + deserializer.deserialize_string(visitor) + } +} + #[cfg(test)] mod test { use super::*; + #[cfg(feature = "serde")] + #[test] + fn qstring_serde() { + let qstring = QString::from("KDAB"); + assert_eq!(crate::serde_impl::roundtrip(&qstring), qstring); + } + #[test] fn test_ordering() { let qstring_a = QString::from("a"); diff --git a/crates/cxx-qt-lib/src/core/qstringlist.rs b/crates/cxx-qt-lib/src/core/qstringlist.rs index 8294b766d..1f07b9593 100644 --- a/crates/cxx-qt-lib/src/core/qstringlist.rs +++ b/crates/cxx-qt-lib/src/core/qstringlist.rs @@ -244,4 +244,13 @@ mod test { Some("element".to_owned()) ); } + + #[cfg(feature = "serde")] + #[test] + fn qstringlist_serde() { + let mut qstringlist = QStringList::default(); + qstringlist.append(QString::from("element 1")); + qstringlist.append(QString::from("element 2")); + assert_eq!(crate::serde_impl::roundtrip(&qstringlist), qstringlist) + } } diff --git a/crates/cxx-qt-lib/src/core/qtime.rs b/crates/cxx-qt-lib/src/core/qtime.rs index bb95975d5..b3047c75c 100644 --- a/crates/cxx-qt-lib/src/core/qtime.rs +++ b/crates/cxx-qt-lib/src/core/qtime.rs @@ -140,11 +140,31 @@ impl QTime { ffi::qtime_from_string(string, format) } + /// Returns the QTime represented by the string, using the format given, or None if the string cannot be parsed. + pub fn from_string_opt(string: &ffi::QString, format: &ffi::QString) -> Option<Self> { + let time = ffi::qtime_from_string(string, format); + if time.is_valid() { + Some(time) + } else { + None + } + } + /// Returns the time represented in the string as a QTime using the format given, or an invalid time if this is not possible. pub fn from_string_enum(string: &ffi::QString, format: ffi::DateFormat) -> Self { ffi::qtime_from_string_enum(string, format) } + /// Returns the time represented in the string as a QTime using the format given, or None if this is not possible. + pub fn from_string_enum_opt(string: &ffi::QString, format: ffi::DateFormat) -> Option<Self> { + let time = ffi::qtime_from_string_enum(string, format); + if time.is_valid() { + Some(time) + } else { + None + } + } + /// Returns the number of milliseconds from this time to t. /// If t is earlier than this time, the number of milliseconds returned is negative. pub fn msecs_to(&self, t: Self) -> i32 { @@ -266,6 +286,18 @@ unsafe impl ExternType for QTime { type Kind = cxx::kind::Trivial; } +#[cfg(test)] +mod test { + use super::*; + + #[cfg(feature = "serde")] + #[test] + fn qtime_serde() { + let qtime = QTime::new(1, 2, 3, 4); + assert_eq!(crate::serde_impl::roundtrip(&qtime), qtime); + } +} + #[cfg(test)] #[cfg(feature = "chrono")] mod test_chrono { diff --git a/crates/cxx-qt-lib/src/core/qurl.rs b/crates/cxx-qt-lib/src/core/qurl.rs index a009bfcae..b5501a4d9 100644 --- a/crates/cxx-qt-lib/src/core/qurl.rs +++ b/crates/cxx-qt-lib/src/core/qurl.rs @@ -501,6 +501,21 @@ impl TryFrom<&QUrl> for url::Url { } } +#[cfg(feature = "serde")] +impl serde::Serialize for QUrl { + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + ffi::qurl_to_qstring(self).serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for QUrl { + fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + let string = ffi::QString::deserialize(deserializer)?; + Ok(Self::from(&string)) + } +} + // Safety: // // Static checks on the C++ side to ensure the size is the same. @@ -511,9 +526,15 @@ unsafe impl ExternType for QUrl { #[cfg(test)] mod tests { - #[cfg(any(feature = "http", feature = "url"))] use super::*; + #[cfg(feature = "serde")] + #[test] + fn qurl_serde() { + let qurl = QUrl::from("https://github.com/kdab/cxx-qt"); + assert_eq!(crate::serde_impl::roundtrip(&qurl), qurl); + } + #[cfg(feature = "http")] #[test] fn test_http() { diff --git a/crates/cxx-qt-lib/src/core/quuid.cpp b/crates/cxx-qt-lib/src/core/quuid.cpp index e96d5ed17..7aa5f7399 100644 --- a/crates/cxx-qt-lib/src/core/quuid.cpp +++ b/crates/cxx-qt-lib/src/core/quuid.cpp @@ -52,12 +52,6 @@ quuidCreateUuidV5(const QUuid& ns, ::rust::Slice<const ::std::uint8_t> slice) return QUuid::createUuidV5(ns, byteView(slice)); } -QString -quuidToString(const QUuid& uuid) -{ - return uuid.toString(); -} - QUuid quuidFromString(const QString& string) { diff --git a/crates/cxx-qt-lib/src/core/quuid.rs b/crates/cxx-qt-lib/src/core/quuid.rs index 2b789935e..8116579ee 100644 --- a/crates/cxx-qt-lib/src/core/quuid.rs +++ b/crates/cxx-qt-lib/src/core/quuid.rs @@ -10,6 +10,21 @@ use uuid::Uuid; #[cxx::bridge] mod ffi { + #[repr(i32)] + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + enum QUuidStringFormat { + /// Five hex fields, separated by dashes and surrounded by braces. + /// Example: {00000000-0000-0000-0000-000000000000}. + WithBraces = 0, + /// Only the five dash-separated fields, without the braces. + /// Example: 00000000-0000-0000-0000-000000000000. + WithoutBraces = 1, + /// Only the hex digits, without braces or dashes. + /// Example: 00000000000000000000000000000000. + /// Note that QUuid cannot parse this back again as input. + Id128 = 3, + } + #[repr(i32)] #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] enum QUuidVariant { @@ -55,9 +70,15 @@ mod ffi { unsafe extern "C++" { include!("cxx-qt-lib/quuid.h"); type QUuid = super::QUuid; + type QUuidStringFormat; type QUuidVariant; type QUuidVersion; + /// Returns the string representation of this QUuid, with the formatting controlled by the + /// `mode` parameter. + #[rust_name = "format"] + fn toString(self: &QUuid, mode: QUuidStringFormat) -> QString; + /// Returns the value in the variant field of the UUID. If the return value is /// `QUuidVariant::DCE`, call `version()` to see which layout it uses. The null UUID is /// considered to be of an unknown variant. @@ -85,9 +106,6 @@ mod ffi { #[rust_name = "quuid_create_uuid_v5"] fn quuidCreateUuidV5(ns: &QUuid, data: &[u8]) -> QUuid; #[doc(hidden)] - #[rust_name = "quuid_to_string"] - fn quuidToString(uuid: &QUuid) -> QString; - #[doc(hidden)] #[rust_name = "quuid_from_string"] fn quuidFromString(string: &QString) -> QUuid; #[doc(hidden)] @@ -99,7 +117,7 @@ mod ffi { } } -pub use ffi::{QUuidVariant, QUuidVersion}; +pub use ffi::{QUuidStringFormat, QUuidVariant, QUuidVersion}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C)] @@ -120,7 +138,7 @@ impl Default for QUuid { impl fmt::Display for QUuid { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", ffi::quuid_to_string(self)) + write!(f, "{}", self.format(QUuidStringFormat::WithBraces)) } } @@ -250,7 +268,7 @@ unsafe impl ExternType for QUuid { impl From<QUuid> for QString { fn from(value: QUuid) -> Self { - ffi::quuid_to_string(&value) + value.format(QUuidStringFormat::WithBraces) } } @@ -333,6 +351,29 @@ impl From<QUuid> for Uuid { } } +#[cfg(feature = "serde")] +impl serde::Serialize for QUuid { + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + if serializer.is_human_readable() { + self.format(QUuidStringFormat::WithoutBraces) + .serialize(serializer) + } else { + self.to_bytes().serialize(serializer) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for QUuid { + fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + if deserializer.is_human_readable() { + QString::deserialize(deserializer).map(|s| Self::from(&s)) + } else { + <[u8; 16]>::deserialize(deserializer).map(Self::from_bytes) + } + } +} + #[cfg(test)] mod test { use super::*; @@ -457,4 +498,12 @@ mod test { let roundtrip = QUuid::from_u128(uuid.to_u128()); assert_eq!(uuid, roundtrip) } + + #[cfg(feature = "serde")] + #[test] + fn quuid_serde() { + let uuid = QUuid::create_uuid(); + let roundtrip = crate::serde_impl::roundtrip(&uuid); + assert_eq!(roundtrip, uuid) + } } diff --git a/crates/cxx-qt-lib/src/core/qvector/mod.rs b/crates/cxx-qt-lib/src/core/qvector/mod.rs index aef9a6c1a..f1d94752f 100644 --- a/crates/cxx-qt-lib/src/core/qvector/mod.rs +++ b/crates/cxx-qt-lib/src/core/qvector/mod.rs @@ -400,4 +400,11 @@ mod test { let qvec = QVector::<u8>::from(array); assert_eq!(Vec::from(&qvec), array); } + + #[cfg(feature = "serde")] + #[test] + fn qvec_serde() { + let qvec = QVector::<u8>::from([0, 1, 2]); + assert_eq!(crate::serde_impl::roundtrip(&qvec), qvec); + } } diff --git a/crates/cxx-qt-lib/src/gui/qcolor.rs b/crates/cxx-qt-lib/src/gui/qcolor.rs index 415868dad..561f65c89 100644 --- a/crates/cxx-qt-lib/src/gui/qcolor.rs +++ b/crates/cxx-qt-lib/src/gui/qcolor.rs @@ -619,6 +619,29 @@ impl From<&QColor> for rgb::RGBA8 { } } +#[cfg(feature = "serde")] +impl serde::Serialize for QColor { + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + let format = if self.alpha() == 255 { + ffi::QColorNameFormat::HexRgb + } else { + ffi::QColorNameFormat::HexArgb + }; + self.name(format).serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for QColor { + fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + let string = ffi::QString::deserialize(deserializer)?; + Self::try_from(&string).map_err(|_| { + use serde::de::{Error as _, Unexpected}; + D::Error::invalid_value(Unexpected::Str(&String::from(&string)), &"hex color code") + }) + } +} + // Safety: // // Static checks on the C++ side to ensure the size is the same. @@ -629,9 +652,15 @@ unsafe impl ExternType for QColor { #[cfg(test)] mod tests { - #[cfg(feature = "rgb")] use super::*; + #[cfg(feature = "serde")] + #[test] + fn serde_qcolor() { + let qcolor = QColor::from_rgba(10, 20, 30, 40); + assert_eq!(crate::serde_impl::roundtrip(&qcolor), qcolor); + } + #[cfg(feature = "rgb")] #[test] fn test_rgb() { diff --git a/crates/cxx-qt-lib/src/gui/qfont.rs b/crates/cxx-qt-lib/src/gui/qfont.rs index e9287974a..8ea5becf9 100644 --- a/crates/cxx-qt-lib/src/gui/qfont.rs +++ b/crates/cxx-qt-lib/src/gui/qfont.rs @@ -159,6 +159,10 @@ mod ffi { #[rust_name = "default_family"] fn defaultFamily(self: &QFont) -> QString; + /// Returns a description of the font. The description is a comma-separated list of the attributes. + #[rust_name = "description"] + fn toString(self: &QFont) -> QString; + /// Returns true if a window system font exactly matching /// the settings of this font is available. #[rust_name = "exact_match"] @@ -177,8 +181,8 @@ mod ffi { #[rust_name = "fixed_pitch"] fn fixedPitch(self: &QFont) -> bool; - /// Sets this font to match the description descrip. The description is a comma-separated - /// list of the font attributes, as returned by toString(). + /// Sets this font to match the description *descrip*. The description is a comma-separated + /// list of the font attributes, as returned by [`QFont::description`]. #[rust_name = "from_string"] fn fromString(self: &mut QFont, descrip: &QString) -> bool; @@ -423,6 +427,30 @@ impl QFont { } } +#[cfg(feature = "serde")] +impl serde::Serialize for QFont { + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + self.description().serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for QFont { + fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + use serde::de::{Error as _, Unexpected}; + + let qstring = ffi::QString::deserialize(deserializer)?; + let mut font = QFont::default(); + if !font.from_string(&qstring) { + return Err(D::Error::invalid_value( + Unexpected::Str(&String::from(&qstring)), + &"QFont description", + )); + } + Ok(font) + } +} + // Safety: // // Static checks on the C++ side to ensure the size is the same. @@ -430,3 +458,29 @@ unsafe impl ExternType for QFont { type Id = type_id!("QFont"); type Kind = cxx::kind::Trivial; } + +#[cfg(test)] +mod test { + use super::*; + + #[cfg(feature = "serde")] + #[test] + fn qfont_serde() { + let mut qfont = QFont::default(); + qfont.set_family(&ffi::QString::from("Arial")); + qfont.set_bold(true); + qfont.set_pixel_size(10); + qfont.set_style_hint(QFontStyleHint::Courier, QFontStyleStrategy::PreferAntialias); + qfont.set_strikeout(true); + qfont.set_capitalization(QFontCapitalization::AllLowercase); + qfont.set_letter_spacing(QFontSpacingType::AbsoluteSpacing, 10.0); + qfont.set_word_spacing(1.5); + qfont.set_stretch(2); + qfont.set_style(QFontStyle::StyleItalic); + + assert_eq!( + crate::serde_impl::roundtrip(&qfont).description(), + qfont.description() + ); + } +} diff --git a/crates/cxx-qt-lib/src/gui/qpolygon.rs b/crates/cxx-qt-lib/src/gui/qpolygon.rs index 314372639..b506d361e 100644 --- a/crates/cxx-qt-lib/src/gui/qpolygon.rs +++ b/crates/cxx-qt-lib/src/gui/qpolygon.rs @@ -195,3 +195,17 @@ unsafe impl ExternType for QPolygon { type Id = type_id!("QPolygon"); type Kind = cxx::kind::Trivial; } + +#[cfg(test)] +mod test { + use super::*; + + #[cfg(feature = "serde")] + #[test] + fn qpolygon_serde() { + let mut polygon = QPolygon::default(); + polygon.append(QPoint::new(1, 2)); + polygon.append(QPoint::new(3, 4)); + assert_eq!(crate::serde_impl::roundtrip(&polygon), polygon); + } +} diff --git a/crates/cxx-qt-lib/src/gui/qpolygonf.rs b/crates/cxx-qt-lib/src/gui/qpolygonf.rs index 7b843df4d..a4b94494e 100644 --- a/crates/cxx-qt-lib/src/gui/qpolygonf.rs +++ b/crates/cxx-qt-lib/src/gui/qpolygonf.rs @@ -177,3 +177,17 @@ unsafe impl ExternType for QPolygonF { type Id = type_id!("QPolygonF"); type Kind = cxx::kind::Trivial; } + +#[cfg(test)] +mod test { + use super::*; + + #[cfg(feature = "serde")] + #[test] + fn qpolygonf_serde() { + let mut polygonf = QPolygonF::default(); + polygonf.append(QPointF::new(1.0, 2.0)); + polygonf.append(QPointF::new(3.0, 4.0)); + assert_eq!(crate::serde_impl::roundtrip(&polygonf), polygonf); + } +} diff --git a/crates/cxx-qt-lib/src/gui/qvector2d.rs b/crates/cxx-qt-lib/src/gui/qvector2d.rs index 7349319e6..6c269c0b4 100644 --- a/crates/cxx-qt-lib/src/gui/qvector2d.rs +++ b/crates/cxx-qt-lib/src/gui/qvector2d.rs @@ -127,7 +127,11 @@ mod ffi { } /// The QVector2D class represents a vector or vertex in 2D space. +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct QVector2D { v: [f32; 2], diff --git a/crates/cxx-qt-lib/src/gui/qvector3d.rs b/crates/cxx-qt-lib/src/gui/qvector3d.rs index c8b76046b..4cd8f85c4 100644 --- a/crates/cxx-qt-lib/src/gui/qvector3d.rs +++ b/crates/cxx-qt-lib/src/gui/qvector3d.rs @@ -142,8 +142,12 @@ mod ffi { } } +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + /// The QVector3D class represents a vector or vertex in 3D space. #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct QVector3D { v: [f32; 3], diff --git a/crates/cxx-qt-lib/src/gui/qvector4d.rs b/crates/cxx-qt-lib/src/gui/qvector4d.rs index 944f74b70..980399c18 100644 --- a/crates/cxx-qt-lib/src/gui/qvector4d.rs +++ b/crates/cxx-qt-lib/src/gui/qvector4d.rs @@ -133,8 +133,12 @@ mod ffi { } } +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + /// The QVector4D class represents a vector or vertex in 4D space. #[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[repr(C)] pub struct QVector4D { v: [f32; 4], diff --git a/crates/cxx-qt-lib/src/lib.rs b/crates/cxx-qt-lib/src/lib.rs index 21e9d1f9a..006890a2c 100644 --- a/crates/cxx-qt-lib/src/lib.rs +++ b/crates/cxx-qt-lib/src/lib.rs @@ -9,6 +9,9 @@ compile_error!("cxxqt_qt_version_major must be either \"5\" or \"6\""); mod core; +#[cfg(feature = "serde")] +mod serde_impl; + pub use crate::core::*; #[cfg(feature = "qt_gui")] diff --git a/crates/cxx-qt-lib/src/serde_impl.rs b/crates/cxx-qt-lib/src/serde_impl.rs new file mode 100644 index 000000000..aa85a6a3f --- /dev/null +++ b/crates/cxx-qt-lib/src/serde_impl.rs @@ -0,0 +1,190 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> +// SPDX-FileContributor: Joshua Booth <joshua.n.booth@gmail.com> +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +use crate::{ + DateFormat, QDate, QList, QListElement, QSet, QSetElement, QString, QStringList, QTime, + QVector, QVectorElement, +}; +use cxx::ExternType; +use serde::de::{Error as _, SeqAccess, Unexpected, Visitor}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt::{self, Formatter}; +use std::marker::PhantomData; +use std::num::NonZeroIsize; + +/// Serializes and deserializes a time-like value using an ISO-8601 string as the intermediary. +macro_rules! datetime_impl { + ($t:ty, $construct:expr, $f:expr, $expected:literal) => { + impl Serialize for $t { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + self.format_enum($f).serialize(serializer) + } + } + + impl<'de> Deserialize<'de> for $t { + fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + let string = QString::deserialize(deserializer)?; + $construct(&string, $f).ok_or_else(|| { + D::Error::invalid_value(Unexpected::Str(&String::from(&string)), &$expected) + }) + } + } + }; +} + +datetime_impl!( + QDate, + QDate::from_string_enum, + DateFormat::ISODate, + "ISO-8601 date" +); +datetime_impl!( + QTime, + QTime::from_string_enum_opt, + DateFormat::ISODateWithMs, + "ISO-8601 time" +); +#[cfg(not(target_os = "emscripten"))] +datetime_impl!( + crate::QDateTime, + crate::QDateTime::from_string, + DateFormat::ISODateWithMs, + "ISO-8601 datetime" +); + +/// Serde deserializers provide an `Option<usize>` size hint, but Qt containers use signed types +/// for size. This helper function converts between the two. +/// It also returns `None` if the size hint is 0, because there's no need to reserve capacity of 0. +const fn get_size_hint(size_hint: Option<usize>) -> Option<NonZeroIsize> { + match size_hint { + Some(n) if n <= isize::MAX as usize => NonZeroIsize::new(n as isize), + _ => None, + } +} + +/// Serializes and deserializes a list-like container by iterating over values. +macro_rules! seq_impl { + ($t:ident, $element:ident, $insert:expr) => { + impl<T> Serialize for $t<T> + where + T: $element + Serialize, + { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + serializer.collect_seq(self.iter()) + } + } + + impl<'de, T> Deserialize<'de> for $t<T> + where + T: $element + Deserialize<'de> + ExternType<Kind = cxx::kind::Trivial>, + { + fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + struct SeqVisitor<T: $element> { + marker: PhantomData<$t<T>>, + } + + impl<'de, T> Visitor<'de> for SeqVisitor<T> + where + T: $element + Deserialize<'de> + ExternType<Kind = cxx::kind::Trivial>, + { + type Value = $t<T>; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("a sequence") + } + + #[inline] + fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> + where + A: SeqAccess<'de>, + { + let mut values = Self::Value::default(); + if let Some(size_hint) = get_size_hint(seq.size_hint()) { + values.reserve(size_hint.get()); + } + while let Some(value) = seq.next_element()? { + $insert(&mut values, value); + } + Ok(values) + } + } + + let visitor = SeqVisitor { + marker: PhantomData, + }; + deserializer.deserialize_seq(visitor) + } + } + }; +} + +seq_impl!(QList, QListElement, QList::append); +seq_impl!(QSet, QSetElement, QSet::insert); +seq_impl!(QVector, QVectorElement, QVector::append); + +/// Like seq_impl, but for Qt classes that dereference to a container. +macro_rules! deref_impl { + ($t:ty) => { + impl Serialize for $t { + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + (**self).serialize(serializer) + } + } + + impl<'de> Deserialize<'de> for $t { + fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + struct SeqVisitor; + + impl<'de> Visitor<'de> for SeqVisitor { + type Value = $t; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("a sequence") + } + + #[inline] + fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> + where + A: SeqAccess<'de>, + { + let mut list = Self::Value::default(); + let values = &mut *list; + if let Some(size_hint) = get_size_hint(seq.size_hint()) { + values.reserve(size_hint.get()); + } + while let Some(value) = seq.next_element()? { + values.append(value); + } + Ok(list) + } + } + + let visitor = SeqVisitor; + deserializer.deserialize_seq(visitor) + } + } + }; +} + +deref_impl!(QStringList); + +#[cfg(feature = "qt_gui")] +deref_impl!(crate::QPolygon); +#[cfg(feature = "qt_gui")] +deref_impl!(crate::QPolygonF); + +#[cfg(test)] +pub fn roundtrip<T>(value: &T) -> T +where + T: Serialize + serde::de::DeserializeOwned, +{ + let serialized = serde_json::to_value(value).expect("error serializing value"); + match serde_json::from_value(serialized) { + Ok(deserialized) => deserialized, + Err(e) => panic!( + "error deserializing {}: {e}", + serde_json::to_value(value).unwrap() + ), + } +}