Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add serde support for cxx-qt-lib using proxy types and deriving #1154

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
15c5917
cxx-qt-lib: deserialize QString from &str rather than String
jnbooth Jan 12, 2025
ffe93e3
cxx-qt-lib: (de)serialize QByteArray as &[u8]
jnbooth Jan 12, 2025
831332d
cxx-qt-lib: (de)serialize QUrl as &str
jnbooth Jan 12, 2025
a70d176
cxx-qt-lib: (de)serialize QDate, QDateTime, and QTime as ISO 8601
jnbooth Jan 12, 2025
2c5153a
cxx-qt-lib: (de)serialize QColor as hex code
jnbooth Jan 12, 2025
ad439e4
cxx-qt-lib: derive Deserialize and Serialize where possible
jnbooth Jan 12, 2025
525e761
cxx-qt-lib: deref QPolygon(F) as QList<QPoint(F)>
jnbooth Jan 12, 2025
b50c61c
cxx-qt-lib: add QSet::reserve
jnbooth Jan 12, 2025
87c5f8d
cxx-qt-lib: (de)serialize lists
jnbooth Jan 12, 2025
fc31d9b
add serde notes to CHANGELOG.md
jnbooth Jan 12, 2025
1b6d208
cxx-qt-lib: fix documentation typo
jnbooth Jan 12, 2025
6f59bea
cxx-qt-lib: use QVector rather than QList for QPolygon(F)
jnbooth Jan 12, 2025
cf2976f
cxx-qt-lib: format qpolygon/f files
jnbooth Jan 12, 2025
205ad64
cxx-qt-lib: fix FFI name
jnbooth Jan 12, 2025
7b37fe1
cxx-qt-lib: fix QColor serialization alpha check
jnbooth Jan 12, 2025
6509fdd
cxx-qt-lib: replace explicit Deref with **self
jnbooth Jan 12, 2025
8c881b7
cxx-qt-lib: move serde_impl from src/core to src
jnbooth Jan 12, 2025
a061037
implement std::fmt::Debug for lists
jnbooth Jan 12, 2025
ea17206
cxx-qt-lib: deserialize QByteArray and QString from bytes
jnbooth Jan 12, 2025
41ff1ae
cxx-qt-lib: better deserialization for QUrl and QColor via QString
jnbooth Jan 12, 2025
9d1afba
cxx-qt-lib: serde unit tests
jnbooth Jan 12, 2025
fd34a21
cxx-qt-lib: use debug_set instead of debug_list for QSet
jnbooth Jan 12, 2025
cae4be5
Merge branch 'main' into serde-containers
jnbooth Jan 13, 2025
1e9a400
cxx-qt-lib: don't implement serde for QDateTime on emscripten
jnbooth Jan 14, 2025
c5ae04e
Merge remote-tracking branch 'origin/main' into serde-containers
jnbooth Jan 14, 2025
d15945d
cxx-qt-lib: (de)serialization for QUuid
jnbooth Jan 14, 2025
636e4e7
cxx-qt-lib: expand CHANGELOG.md
jnbooth Jan 17, 2025
0169819
cxx-qt-lib: set copyright header in serde_impl to 2025
jnbooth Jan 17, 2025
f160d57
cxx-qt-lib: (de)serialize QFont as QString
jnbooth Jan 17, 2025
fd3cc14
cxx-qt-lib: rename to_format to format
jnbooth Jan 17, 2025
cb0f343
cxx-qt-lib: rename format to format_enum
jnbooth Jan 17, 2025
7f79cde
cxx-qt-lib: fix QFont serialization equality matching on Qt5
jnbooth Jan 18, 2025
cb42efa
cxx-qt-lib: use serde(from =, into =)
jnbooth Jan 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ 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`
- 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`

### Fixed

Expand Down Expand Up @@ -121,7 +125,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

Expand Down
3 changes: 3 additions & 0 deletions crates/cxx-qt-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ serde = { version = "1", features=["derive"], optional = true }
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"]
default = []
Expand Down
13 changes: 13 additions & 0 deletions crates/cxx-qt-lib/include/core/qset.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,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
}

}
}
}
Expand Down
4 changes: 1 addition & 3 deletions crates/cxx-qt-lib/include/core/quuid.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include "rust/cxx.h"

using QUuidStringFormat = QUuid::StringFormat;
using QUuidVariant = QUuid::Variant;
using QUuidVersion = QUuid::Version;

Expand All @@ -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);

Expand Down
13 changes: 13 additions & 0 deletions crates/cxx-qt-lib/include/gui/qpolygon.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
#pragma once

#include <QtCore/QPoint>
#include <QtCore/QVector>
#include <QtGui/QPolygon>

#include "rust/cxx.h"
Expand All @@ -19,3 +21,14 @@ struct IsRelocatable<QPolygon> : ::std::true_type
{};

} // namespace rust

namespace rust {
namespace cxxqtlib1 {

const QVector<QPoint>&
qpolygonAsQVectorQPointRef(const QPolygon& shape);
QVector<QPoint>&
qpolygonAsQVectorQPointRef(QPolygon& shape);

}
}
13 changes: 13 additions & 0 deletions crates/cxx-qt-lib/include/gui/qpolygonf.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
#pragma once

#include <QtCore/QPointF>
#include <QtCore/QVector>
#include <QtGui/QPolygonF>

#include "rust/cxx.h"
Expand All @@ -19,3 +21,14 @@ struct IsRelocatable<QPolygonF> : ::std::true_type
{};

} // namespace rust

namespace rust {
namespace cxxqtlib1 {

const QVector<QPointF>&
qpolygonfAsQVectorQPointFRef(const QPolygonF& shape);
QVector<QPointF>&
qpolygonfAsQVectorQPointFRef(QPolygonF& shape);

}
}
55 changes: 55 additions & 0 deletions crates/cxx-qt-lib/src/core/qbytearray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,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.
Expand All @@ -333,6 +381,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() {
Expand Down
13 changes: 10 additions & 3 deletions crates/cxx-qt-lib/src/core/qdate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ impl fmt::Display for QDate {

impl fmt::Debug for QDate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self}")
write!(f, "{}", ffi::qdate_to_qstring(self))
}
}

Expand Down Expand Up @@ -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() {
Expand All @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down
17 changes: 17 additions & 0 deletions crates/cxx-qt-lib/src/core/qdatetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ mod ffi {
/// Returns the date part of the datetime.
fn date(self: &QDateTime) -> QDate;

/// Returns the datetime as a string. The format parameter determines the format of the string.
#[rust_name = "format_enum"]
fn toString(self: &QDateTime, format: DateFormat) -> QString;

/// Returns if this datetime falls in Daylight-Saving Time.
#[rust_name = "is_daylight_time"]
fn isDaylightTime(self: &QDateTime) -> bool;
Expand Down Expand Up @@ -168,8 +172,10 @@ mod ffi {
#[doc(hidden)]
#[rust_name = "qdatetime_to_secs_since_epoch"]
fn qdatetimeToSecsSinceEpoch(datetime: &QDateTime) -> i64;
#[doc(hidden)]
#[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;
}
Expand Down Expand Up @@ -545,6 +551,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)]
Expand Down
4 changes: 4 additions & 0 deletions crates/cxx-qt-lib/src/core/qline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions crates/cxx-qt-lib/src/core/qlinef.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
16 changes: 16 additions & 0 deletions crates/cxx-qt-lib/src/core/qlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ where

impl<T> Eq for QList<T> where T: QListElement + Eq {}

impl<T> std::fmt::Debug for QList<T>
where
T: QListElement + std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}

impl<T> QList<T>
where
T: QListElement,
Expand Down Expand Up @@ -390,4 +399,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);
}
}
4 changes: 4 additions & 0 deletions crates/cxx-qt-lib/src/core/qmargins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions crates/cxx-qt-lib/src/core/qmarginsf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions crates/cxx-qt-lib/src/core/qpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions crates/cxx-qt-lib/src/core/qpointf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions crates/cxx-qt-lib/src/core/qrect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions crates/cxx-qt-lib/src/core/qrectf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading
Loading