diff --git a/crates/cxx-qt-lib/include/gui/qguiapplication.h b/crates/cxx-qt-lib/include/gui/qguiapplication.h index 7eb58e731..81ac81a86 100644 --- a/crates/cxx-qt-lib/include/gui/qguiapplication.h +++ b/crates/cxx-qt-lib/include/gui/qguiapplication.h @@ -31,5 +31,14 @@ qguiapplicationSetDesktopFileName(const QString& name); QString qguiapplicationDesktopFileName(); +Qt::KeyboardModifiers +qguiapplicationKeyboardModifiers(); + +Qt::MouseButtons +qguiapplicationMouseButtons(); + +Qt::KeyboardModifiers +qguiapplicationQueryKeyboardModifiers(); + } } diff --git a/crates/cxx-qt-lib/src/core/mod.rs b/crates/cxx-qt-lib/src/core/mod.rs index 644020739..51e9acb5d 100644 --- a/crates/cxx-qt-lib/src/core/mod.rs +++ b/crates/cxx-qt-lib/src/core/mod.rs @@ -17,6 +17,9 @@ mod qdatetime; #[cfg(not(target_os = "emscripten"))] pub use qdatetime::QDateTime; +mod qflags; +pub use qflags::{QFlag, QFlagRepr, QFlags}; + mod qhash; pub use qhash::{QHash, QHashPair, QHashPair_QString_QVariant, QHashPair_i32_QByteArray}; @@ -76,8 +79,8 @@ pub use qstringlist::QStringList; mod qt; pub use qt::{ AspectRatioMode, BGMode, CaseSensitivity, ClipOperation, ConnectionType, DateFormat, FillRule, - LayoutDirection, PenCapStyle, PenJoinStyle, PenStyle, SizeMode, SplitBehaviorFlags, TimeSpec, - TransformationMode, + KeyboardModifier, KeyboardModifiers, LayoutDirection, MouseButton, MouseButtons, PenCapStyle, + PenJoinStyle, PenStyle, SizeMode, SplitBehaviorFlags, TimeSpec, TransformationMode, }; mod qtime; diff --git a/crates/cxx-qt-lib/src/core/qflags/mod.rs b/crates/cxx-qt-lib/src/core/qflags/mod.rs new file mode 100644 index 000000000..d7d22be6b --- /dev/null +++ b/crates/cxx-qt-lib/src/core/qflags/mod.rs @@ -0,0 +1,340 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Joshua Booth +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use cxx::ExternType; +use std::fmt::Debug; +use std::hash::Hash; +use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}; + +mod qflag; +pub use qflag::QFlag; +use qflag::QFlagExt; + +mod repr; +pub use repr::QFlagRepr; + +mod util; + +/// The `QFlags` class is a template class, where T is an enum type. +/// QFlags are used throughout Qt for storing combinations of enum values. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct QFlags { + repr: ::Int, +} + +impl Copy for QFlags {} + +impl Clone for QFlags { + fn clone(&self) -> Self { + *self + } +} + +impl From for QFlags { + /// Returns the value stored in the QFlags object as an integer. + fn from(value: T) -> Self { + Self { + repr: value.to_int(), + } + } +} + +impl Default for QFlags { + /// Constructs an empty QFlags object. + fn default() -> Self { + Self::new() + } +} + +impl QFlags { + /// Constructs an empty QFlags object. + pub const fn new() -> Self { + Self::from_int(T::Repr::ZERO) + } + + /// Constructs a QFlags object representing the integer value *i*. + pub const fn from_int(i: ::Int) -> Self { + Self { repr: i } + } + + /// Returns the value stored in the QFlags object as an integer. + pub const fn to_int(self) -> ::Int { + self.repr + } + + /// Returns `true` if no flag is set (i.e., if the value stored by the QFlags object is 0); + /// otherwise returns `false`. + pub fn is_empty(self) -> bool { + self.repr == T::Repr::ZERO + } + + /// Sets the flag *flag* if *on* is `true` or unsets it if *on* is `false`. + /// Returns a mutable reference to this object. + pub fn set_flag(&mut self, flag: T, on: bool) -> &mut Self { + if on { + self.repr |= flag.to_int(); + } else { + self.repr &= !flag.to_int(); + } + self + } + + /// Returns `true` if the flag `flag` is set, otherwise false. + /// + /// Note: if *flag* contains multiple bits set to 1 (for instance, if it's an enumerator equal + /// to the bitwise-OR of other enumerators) then this function will return `true` if and only if + /// all the bits are set in this flags object. On the other hand, if *flag* contains no bits set + /// to 1 (that is, its value as a integer is 0), then this function will return `true` if and + /// only if this flags object also has no bits set to 1. + pub fn test_flag(self, flag: T) -> bool { + self.test_flags(Self::from(flag)) + } + + /// Returns `true` if this *flags* object matches the given flags. + /// + /// If *flags* has any flags set, this flags object matches precisely if all flags set in + /// *flags* are also set in this flags object. Otherwise, when *flags* has no flags set, this + /// flags object only matches if it also has no flags set. + pub fn test_flags(self, flags: Self) -> bool { + if flags.is_empty() { + self.is_empty() + } else { + self.repr & flags.repr == flags.repr + } + } + + /// Returns `true` if any flag set in *flag* is also set in this flags object, otherwise + /// `false`. If *flag* has no flags set, the return will always be `false`. + pub fn test_any_flag(self, flag: T) -> bool { + self.test_any_flags(Self::from(flag)) + } + + /// Returns `true` if any flag set in *flags* is also set in this flags object, otherwise + /// `false`. If *flags* has no flags set, the return will always be `false`. + pub fn test_any_flags(self, flags: Self) -> bool { + (self.repr & flags.repr) != T::Repr::ZERO + } +} + +impl Not for QFlags { + type Output = Self; + + /// Returns a QFlags object that contains the bitwise negation of this object. + fn not(self) -> Self::Output { + Self { repr: !self.repr } + } +} + +impl BitAnd for QFlags { + type Output = Self; + + /// Returns a QFlags object containing the result of the bitwise AND operation on this object + /// and `mask`. + fn bitand(self, mask: Self) -> Self::Output { + Self { + repr: self.repr & mask.repr, + } + } +} +impl BitAnd for QFlags { + type Output = Self; + + /// Returns a QFlags object containing the result of the bitwise AND operation on this object + /// and `mask`. + fn bitand(self, mask: T) -> Self::Output { + Self { + repr: self.repr & mask.to_int(), + } + } +} +impl BitAndAssign for QFlags { + /// Performs a bitwise AND operation with mask and stores the result in this QFlags object. + fn bitand_assign(&mut self, mask: Self) { + self.repr &= mask.repr; + } +} +impl BitAndAssign for QFlags { + /// Performs a bitwise AND operation with mask and stores the result in this QFlags object. + fn bitand_assign(&mut self, mask: T) { + self.repr &= mask.to_int(); + } +} + +impl BitXor for QFlags { + type Output = Self; + + /// Returns a QFlags object containing the result of the bitwise XOR operation on this object + /// and `other`. + fn bitxor(self, other: Self) -> Self::Output { + Self { + repr: self.repr ^ other.repr, + } + } +} +impl BitXor for QFlags { + type Output = Self; + + /// Returns a QFlags object containing the result of the bitwise XOR operation on this object + /// and `other`. + fn bitxor(self, other: T) -> Self::Output { + Self { + repr: self.repr ^ other.to_int(), + } + } +} +impl BitXorAssign for QFlags { + /// Performs a bitwise XOR operation with `other` and stores the result in this QFlags object. + fn bitxor_assign(&mut self, other: Self) { + self.repr ^= other.repr; + } +} +impl BitXorAssign for QFlags { + /// Performs a bitwise XOR operation with `other` and stores the result in this QFlags object. + fn bitxor_assign(&mut self, other: T) { + self.repr ^= other.to_int(); + } +} + +impl BitOr for QFlags { + type Output = Self; + + /// Returns a QFlags object containing the result of the bitwise OR operation on this object and + /// `other`. + fn bitor(self, other: Self) -> Self::Output { + Self { + repr: self.repr | other.repr, + } + } +} +impl BitOr for QFlags { + type Output = Self; + + /// Returns a QFlags object containing the result of the bitwise OR operation on this object and + /// `other`. + fn bitor(self, other: T) -> Self::Output { + Self { + repr: self.repr | other.to_int(), + } + } +} +impl BitOrAssign for QFlags { + /// Performs a bitwise OR operation with `other` and stores the result in this QFlags object. + fn bitor_assign(&mut self, other: Self) { + self.repr |= other.repr; + } +} +impl BitOrAssign for QFlags { + /// Performs a bitwise OR operation with `other` and stores the result in this QFlags object. + fn bitor_assign(&mut self, mask: T) { + self.repr |= mask.to_int(); + } +} + +impl FromIterator for QFlags { + fn from_iter>(iter: I) -> Self { + let repr = iter + .into_iter() + .fold(T::Repr::ZERO, |repr, item| repr | item.to_int()); + Self { repr } + } +} + +// Safety: +// +// Established by the `QFlag` contract. +unsafe impl ExternType for QFlags { + type Id = T::TypeId; + + type Kind = cxx::kind::Trivial; +} + +#[cfg(test)] +mod test { + use crate::{KeyboardModifier, KeyboardModifiers}; + + use super::*; + + const ALL_KEYBOARD_MODIFIERS: &[KeyboardModifier] = &[ + KeyboardModifier::AltModifier, + KeyboardModifier::ControlModifier, + KeyboardModifier::GroupSwitchModifier, + KeyboardModifier::KeypadModifier, + KeyboardModifier::MetaModifier, + KeyboardModifier::ShiftModifier, + ]; + + #[test] + fn qflags_set_flag() { + let mut flags = KeyboardModifiers::new(); + flags + .set_flag(KeyboardModifier::AltModifier, true) + .set_flag(KeyboardModifier::ControlModifier, true) + .set_flag(KeyboardModifier::ShiftModifier, true) + .set_flag(KeyboardModifier::AltModifier, false); + let contained = ALL_KEYBOARD_MODIFIERS + .iter() + .copied() + .filter(|&key| flags.test_flag(key)) + .collect::>(); + assert_eq!( + contained, + vec![ + KeyboardModifier::ControlModifier, + KeyboardModifier::ShiftModifier + ] + ); + } + + #[test] + fn qflags_test_flags() { + let flags = KeyboardModifier::ControlModifier + | KeyboardModifier::ShiftModifier + | KeyboardModifier::KeypadModifier; + let mut other = KeyboardModifier::AltModifier + | KeyboardModifier::ControlModifier + | KeyboardModifier::KeypadModifier; + assert!(!flags.test_flags(other)); + other.set_flag(KeyboardModifier::AltModifier, false); + assert!(flags.test_flags(other)); + } + + #[test] + fn qflags_test_any_flags() { + let flags = KeyboardModifier::ControlModifier + | KeyboardModifier::ShiftModifier + | KeyboardModifier::KeypadModifier; + let mut other = KeyboardModifier::AltModifier | KeyboardModifier::ControlModifier; + assert!(flags.test_any_flags(other)); + other.set_flag(KeyboardModifier::ControlModifier, false); + assert!(!flags.test_any_flags(other)); + } + + #[test] + fn qflags_test_no_flags() { + let mut flags = KeyboardModifiers::from(KeyboardModifier::AltModifier); + assert!(!flags.test_flag(KeyboardModifier::NoModifier)); + flags.set_flag(KeyboardModifier::AltModifier, false); + assert!(flags.test_flag(KeyboardModifier::NoModifier)); + } + + #[test] + fn qflags_from_iter() { + let flags = [ + KeyboardModifier::AltModifier, + KeyboardModifier::MetaModifier, + KeyboardModifier::ShiftModifier, + ] + .iter() + .copied() + .collect::>(); + assert_eq!( + flags.to_int(), + KeyboardModifier::AltModifier.repr + | KeyboardModifier::MetaModifier.repr + | KeyboardModifier::ShiftModifier.repr + ); + } +} diff --git a/crates/cxx-qt-lib/src/core/qflags/qflag.rs b/crates/cxx-qt-lib/src/core/qflags/qflag.rs new file mode 100644 index 000000000..939ec772b --- /dev/null +++ b/crates/cxx-qt-lib/src/core/qflags/qflag.rs @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Joshua Booth +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use super::QFlagRepr; + +use cxx::ExternType; + +/// # Safety +/// +/// By writing the unsafe `QFlag` impl, the programmer asserts that the C++ namespace and type name +/// given in the type id refers to a `QFlags` C++Qt type where T is equivalent to the Rust type +/// that is the `Repr` type of the impl. +/// +/// Furthermore, the programmer asserts that `Repr` is the backing integer representation of the +/// enum type that is the `Self` type of the impl. +pub unsafe trait QFlag: Sized { + /// A type-level representation of the C++ namespace and type name of this type's `QFlags`. + /// + /// This will always be defined using [cxx::type_id!]. + type TypeId; + /// The backing integer representation of `Self`. + /// For example, if the enum is defined with `#[repr(i32)]`, then `Repr` should be `i32`. + type Repr: QFlagRepr + ExternType; + + /// Converts the enum type that is the `Self` type of this impl to its backing representation. + /// + /// This will always be defined in the following form: + /// + /// ```ignore + /// fn to_repr(self) -> Self::Repr { + /// self.repr + /// } + /// ``` + fn to_repr(self) -> Self::Repr; +} + +/// Internal utility trait for converting `T` in a `QFlag` to the corresponding integer type. +pub(super) trait QFlagExt: QFlag { + fn to_int(self) -> ::Int; +} + +impl QFlagExt for T { + #[inline(always)] + fn to_int(self) -> ::Int { + self.to_repr().into() + } +} diff --git a/crates/cxx-qt-lib/src/core/qflags/repr.rs b/crates/cxx-qt-lib/src/core/qflags/repr.rs new file mode 100644 index 000000000..ce4de7f9a --- /dev/null +++ b/crates/cxx-qt-lib/src/core/qflags/repr.rs @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Joshua Booth +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::fmt::Debug; +use std::hash::Hash; +use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}; + +use cxx::ExternType; + +mod private { + pub trait Sealed {} +} + +/// An integer type that can be used as the backing representation of a `T` in `QFlags`. +pub trait QFlagRepr: Sized + private::Sealed { + /// The underlying integer representation for a `QFlags`. + /// + /// Qt chooses the integer representation as follows: + /// + /// - If `T` is signed, use a signed integer. Otherwise, use an unsigned integer. + /// - If `T` is 32 bits or less, use a 32-bit integer. + /// - If `T` is 64 bits and the Qt version is at least 6.9, use a 64-bit integer. + type Int: From + + Copy + + Debug + + Default + + Eq + + Ord + + Hash + + BitAnd + + BitAndAssign + + BitOr + + BitOrAssign + + BitXor + + BitXorAssign + + Not + + ExternType; + + const ZERO: Self::Int; +} + +macro_rules! impl_repr { + ($t:ty, $i:ty) => { + impl private::Sealed for $t {} + + impl QFlagRepr for $t { + type Int = $i; + + const ZERO: Self::Int = 0; + } + }; +} + +impl_repr!(i8, i32); +impl_repr!(i16, i32); +impl_repr!(i32, i32); +impl_repr!(u8, u32); +impl_repr!(u16, u32); +impl_repr!(u32, u32); + +#[cfg(cxxqt_qt_version_at_least_6_9)] +impl_repr!(i64, i64); +#[cfg(cxxqt_qt_version_at_least_6_9)] +impl_repr!(u64, u64); diff --git a/crates/cxx-qt-lib/src/core/qflags/util.rs b/crates/cxx-qt-lib/src/core/qflags/util.rs new file mode 100644 index 000000000..baae219ba --- /dev/null +++ b/crates/cxx-qt-lib/src/core/qflags/util.rs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Joshua Booth +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#[macro_export] +macro_rules! unsafe_impl_qflag { + ( $typeName:ty, $typeId:literal, $repr:ident ) => { + unsafe impl $crate::QFlag for $typeName { + type TypeId = ::cxx::type_id!($typeId); + type Repr = $repr; + + fn to_repr(self) -> Self::Repr { + self.repr + } + } + + impl ::std::ops::BitOr for $typeName { + type Output = $crate::QFlags<$typeName>; + + fn bitor(self, other: Self) -> Self::Output { + $crate::QFlags::from(self) | other + } + } + + impl ::std::ops::BitOr<$crate::QFlags<$typeName>> for $typeName { + type Output = $crate::QFlags<$typeName>; + + fn bitor(self, other: $crate::QFlags<$typeName>) -> Self::Output { + other | self + } + } + }; +} diff --git a/crates/cxx-qt-lib/src/core/qt.rs b/crates/cxx-qt-lib/src/core/qt.rs index 3f8398b8e..41b4fe72f 100644 --- a/crates/cxx-qt-lib/src/core/qt.rs +++ b/crates/cxx-qt-lib/src/core/qt.rs @@ -3,6 +3,8 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use crate::unsafe_impl_qflag; + #[cxx::bridge(namespace = "Qt")] mod ffi { /// This enum type defines what happens to the aspect ratio when scaling an rectangle. @@ -145,6 +147,72 @@ mod ffi { RelativeSize, } + #[derive(Debug)] + #[repr(u32)] + enum KeyboardModifier { + /// No modifier key is pressed. + NoModifier = 0x00000000, + /// A Shift key on the keyboard is pressed. + ShiftModifier = 0x02000000, + /// A Ctrl key on the keyboard is pressed. + ControlModifier = 0x04000000, + /// An Alt key on the keyboard is pressed. + AltModifier = 0x08000000, + /// A Meta key on the keyboard is pressed. + MetaModifier = 0x10000000, + /// A keypad button is pressed. + KeypadModifier = 0x20000000, + /// X11 only (unless activated on Windows by a command line argument). + /// A Mode_switch key on the keyboard is pressed. + GroupSwitchModifier = 0x40000000, + } + + #[derive(Debug)] + #[repr(u32)] + enum MouseButton { + /// The button state does not refer to any button. + NoButton = 0x00000000, + /// This value corresponds to a mask of all possible mouse buttons. Use to set the + /// 'acceptedButtons' property of a MouseArea to accept ALL mouse buttons. + AllButtons = 0x07ffffff, + /// The left button is pressed, or an event refers to the left button. (The left button may + /// be the right button on left-handed mice.) + LeftButton = 0x00000001, + /// The right button. + RightButton = 0x00000002, + /// The middle button. + MiddleButton = 0x00000004, + /// The 'Back' button. (Typically present on the 'thumb' side of a mouse with extra buttons. + /// This is NOT the tilt wheel.) + BackButton = 0x00000008, + /// The 'Forward' button. (Typically present beside the 'Back' button, and also pressed by + /// the thumb.) + ForwardButton = 0x00000010, + /// The 'Task' button. + TaskButton = 0x00000020, + ExtraButton4 = 0x00000040, + ExtraButton5 = 0x00000080, + ExtraButton6 = 0x00000100, + ExtraButton7 = 0x00000200, + ExtraButton8 = 0x00000400, + ExtraButton9 = 0x00000800, + ExtraButton10 = 0x00001000, + ExtraButton11 = 0x00002000, + ExtraButton12 = 0x00004000, + ExtraButton13 = 0x00008000, + ExtraButton14 = 0x00010000, + ExtraButton15 = 0x00020000, + ExtraButton16 = 0x00040000, + ExtraButton17 = 0x00080000, + ExtraButton18 = 0x00100000, + ExtraButton19 = 0x00200000, + ExtraButton20 = 0x00400000, + ExtraButton21 = 0x00800000, + ExtraButton22 = 0x01000000, + ExtraButton23 = 0x02000000, + ExtraButton24 = 0x04000000, + } + unsafe extern "C++" { include!("cxx-qt-lib/qt.h"); type AspectRatioMode; @@ -161,14 +229,22 @@ mod ffi { type BGMode; type ClipOperation; type SizeMode; + type MouseButton; + type KeyboardModifier; } } pub use ffi::{ - AspectRatioMode, BGMode, CaseSensitivity, ClipOperation, DateFormat, FillRule, LayoutDirection, - PenCapStyle, PenJoinStyle, PenStyle, SizeMode, SplitBehaviorFlags, TimeSpec, - TransformationMode, + AspectRatioMode, BGMode, CaseSensitivity, ClipOperation, DateFormat, FillRule, + KeyboardModifier, LayoutDirection, MouseButton, PenCapStyle, PenJoinStyle, PenStyle, SizeMode, + SplitBehaviorFlags, TimeSpec, TransformationMode, }; // Reexport ConnectionType from cxx-qt pub use cxx_qt::ConnectionType; + +pub type MouseButtons = crate::QFlags; +pub type KeyboardModifiers = crate::QFlags; + +unsafe_impl_qflag!(MouseButton, "Qt::MouseButtons", u32); +unsafe_impl_qflag!(KeyboardModifier, "Qt::KeyboardModifiers", u32); diff --git a/crates/cxx-qt-lib/src/gui/qguiapplication.cpp b/crates/cxx-qt-lib/src/gui/qguiapplication.cpp index 1dc45679f..961bc9da9 100644 --- a/crates/cxx-qt-lib/src/gui/qguiapplication.cpp +++ b/crates/cxx-qt-lib/src/gui/qguiapplication.cpp @@ -53,5 +53,23 @@ qguiapplicationDesktopFileName() return QGuiApplication::desktopFileName(); } +Qt::KeyboardModifiers +qguiapplicationKeyboardModifiers() +{ + return QGuiApplication::keyboardModifiers(); +} + +Qt::MouseButtons +qguiapplicationMouseButtons() +{ + return QGuiApplication::mouseButtons(); +} + +Qt::KeyboardModifiers +qguiapplicationQueryKeyboardModifiers() +{ + return QGuiApplication::queryKeyboardModifiers(); +} + } } diff --git a/crates/cxx-qt-lib/src/gui/qguiapplication.rs b/crates/cxx-qt-lib/src/gui/qguiapplication.rs index 971d263ea..70863e6b3 100644 --- a/crates/cxx-qt-lib/src/gui/qguiapplication.rs +++ b/crates/cxx-qt-lib/src/gui/qguiapplication.rs @@ -4,7 +4,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::{QByteArray, QFont, QString, QStringList, QVector}; +use crate::{KeyboardModifiers, MouseButtons, QByteArray, QFont, QString, QStringList, QVector}; use core::pin::Pin; #[cxx_qt::bridge] @@ -25,6 +25,12 @@ mod ffi { type QCoreApplication = crate::QCoreApplication; } + #[namespace = "Qt"] + unsafe extern "C++" { + type KeyboardModifiers = crate::KeyboardModifiers; + type MouseButtons = crate::MouseButtons; + } + unsafe extern "C++Qt" { include!("cxx-qt-lib/qguiapplication.h"); #[qobject] @@ -96,6 +102,15 @@ mod ffi { #[doc(hidden)] #[rust_name = "qguiapplication_desktop_file_name"] fn qguiapplicationDesktopFileName() -> QString; + #[doc(hidden)] + #[rust_name = "qguiapplication_keyboard_modifiers"] + fn qguiapplicationKeyboardModifiers() -> KeyboardModifiers; + #[doc(hidden)] + #[rust_name = "qguiapplication_mouse_buttons"] + fn qguiapplicationMouseButtons() -> MouseButtons; + #[doc(hidden)] + #[rust_name = "qguiapplication_query_keyboard_modifiers"] + fn qguiapplicationQueryKeyboardModifiers() -> KeyboardModifiers; } // QGuiApplication is not a trivial to CXX and is not relocatable in Qt @@ -221,4 +236,39 @@ impl QGuiApplication { pub fn desktop_file_name() -> QString { ffi::qguiapplication_desktop_file_name() } + + /// Returns the current state of the modifier keys on the keyboard. The current state is updated + /// synchronously as the event queue is emptied of events that will spontaneously change the + /// keyboard state (QEvent::KeyPress and QEvent::KeyRelease events). + /// + /// It should be noted this may not reflect the actual keys held on the input device at the time + /// of calling but rather the modifiers as last reported in an event. + /// If no keys are being held Qt::NoModifier is returned. + pub fn keyboard_modifiers(&self) -> KeyboardModifiers { + ffi::qguiapplication_keyboard_modifiers() + } + + /// Returns the current state of the buttons on the mouse. The current state is updated + /// synchronously as the event queue is emptied of events that will spontaneously change the + /// mouse state (QEvent::MouseButtonPress and QEvent::MouseButtonRelease events). + /// + /// It should be noted this may not reflect the actual buttons held on the input device at the + /// time of calling but rather the mouse buttons as last reported in one of the above events. + /// If no mouse buttons are being held Qt::NoButton is returned. + pub fn mouse_buttons(&self) -> MouseButtons { + ffi::qguiapplication_mouse_buttons() + } + + /// Queries and returns the state of the modifier keys on the keyboard. Unlike + /// keyboardModifiers, this method returns the actual keys held on the input device at the time + /// of calling the method. + /// + /// It does not rely on the keypress events having been received by this process, which makes it + /// possible to check the modifiers while moving a window, for instance. Note that in most + /// cases, you should use keyboardModifiers(), which is faster and more accurate since it + /// contains the state of the modifiers as they were when the currently processed event was + /// received. + pub fn query_keyboard_modifiers(&self) -> KeyboardModifiers { + ffi::qguiapplication_query_keyboard_modifiers() + } } diff --git a/tests/qt_types_standalone/CMakeLists.txt b/tests/qt_types_standalone/CMakeLists.txt index 379b6903e..089f45093 100644 --- a/tests/qt_types_standalone/CMakeLists.txt +++ b/tests/qt_types_standalone/CMakeLists.txt @@ -64,6 +64,7 @@ add_executable(${APP_NAME} cpp/qcoreapplication.h cpp/qdate.h cpp/qdatetime.h + cpp/qflags.h cpp/qguiapplication.h cpp/qhash.h cpp/qline.h diff --git a/tests/qt_types_standalone/cpp/main.cpp b/tests/qt_types_standalone/cpp/main.cpp index 76cf83407..692e98760 100644 --- a/tests/qt_types_standalone/cpp/main.cpp +++ b/tests/qt_types_standalone/cpp/main.cpp @@ -16,6 +16,7 @@ #include "qcoreapplication.h" #include "qdate.h" #include "qdatetime.h" +#include "qflags.h" #include "qguiapplication.h" #include "qhash.h" #include "qline.h" @@ -71,6 +72,7 @@ main(int argc, char* argv[]) runTest(QScopedPointer(new QCoreApplicationTest)); runTest(QScopedPointer(new QDateTest)); runTest(QScopedPointer(new QDateTimeTest)); + runTest(QScopedPointer(new QFlagsTest)); runTest(QScopedPointer(new QGuiApplicationTest)); runTest(QScopedPointer(new QHashTest)); runTest(QScopedPointer(new QLineTest)); diff --git a/tests/qt_types_standalone/cpp/qflags.h b/tests/qt_types_standalone/cpp/qflags.h new file mode 100644 index 000000000..d080a79a0 --- /dev/null +++ b/tests/qt_types_standalone/cpp/qflags.h @@ -0,0 +1,51 @@ +// clang-format off +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +#pragma once + +#include +#include + +#include "qt_types_standalone/src/qflags.cxx.h" + +class QFlagsTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void construct() + { + const auto f = construct_qflags(); + QCOMPARE(f, Qt::MouseButtons(Qt::ForwardButton) | Qt::LeftButton); + } + + void read() + { + const auto f = Qt::MouseButtons(Qt::ForwardButton) | Qt::LeftButton; + QVERIFY(read_qflags(f)); + } + + void clone() + { + const auto f = Qt::MouseButtons(Qt::ForwardButton) | Qt::LeftButton; + const auto c = clone_qflags(f); + QCOMPARE(c, f); + } + + void emptyQFlags() + { + const auto f = Qt::MouseButtons(); + QVERIFY(test_is_empty(f)); + } + + void addFlags() + { + const auto f = Qt::MouseButtons(Qt::ForwardButton) | Qt::LeftButton; + const auto m2 = Qt::MouseButtons(Qt::LeftButton) | Qt::RightButton; + const auto m3 = add_flags(f, m2); + QCOMPARE(m3, f | Qt::RightButton); + } +}; diff --git a/tests/qt_types_standalone/rust/build.rs b/tests/qt_types_standalone/rust/build.rs index 3c752a8f2..51f6df496 100644 --- a/tests/qt_types_standalone/rust/build.rs +++ b/tests/qt_types_standalone/rust/build.rs @@ -15,6 +15,7 @@ fn main() { .file("src/qcoreapplication.rs") .file("src/qdate.rs") .file("src/qdatetime.rs") + .file("src/qflags.rs") .file("src/qguiapplication.rs") .file("src/qhash.rs") .file("src/qline.rs") diff --git a/tests/qt_types_standalone/rust/src/lib.rs b/tests/qt_types_standalone/rust/src/lib.rs index edee936a9..40b8715fe 100644 --- a/tests/qt_types_standalone/rust/src/lib.rs +++ b/tests/qt_types_standalone/rust/src/lib.rs @@ -12,6 +12,7 @@ mod qcolor; mod qcoreapplication; mod qdate; mod qdatetime; +mod qflags; mod qguiapplication; mod qhash; mod qline; diff --git a/tests/qt_types_standalone/rust/src/qflags.rs b/tests/qt_types_standalone/rust/src/qflags.rs new file mode 100644 index 000000000..0a7a9683d --- /dev/null +++ b/tests/qt_types_standalone/rust/src/qflags.rs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use cxx_qt_lib::{MouseButton, MouseButtons}; + +#[cxx::bridge] +mod qmargins_cxx { + unsafe extern "C++" { + include!(); + #[namespace = "Qt"] + type MouseButtons = cxx_qt_lib::MouseButtons; + } + + extern "Rust" { + fn construct_qflags() -> MouseButtons; + fn read_qflags(f: &MouseButtons) -> bool; + fn clone_qflags(f: &MouseButtons) -> MouseButtons; + fn test_is_empty(f: &MouseButtons) -> bool; + fn add_flags(f1: MouseButtons, f2: MouseButtons) -> MouseButtons; + } +} + +fn construct_qflags() -> MouseButtons { + MouseButton::ForwardButton | MouseButton::LeftButton +} + +fn read_qflags(f: &MouseButtons) -> bool { + f.to_int() == MouseButton::ForwardButton.repr | MouseButton::LeftButton.repr +} + +fn clone_qflags(f: &MouseButtons) -> MouseButtons { + #[allow(clippy::clone_on_copy)] + f.clone() +} + +fn test_is_empty(f: &MouseButtons) -> bool { + f.is_empty() +} + +fn add_flags(f1: MouseButtons, f2: MouseButtons) -> MouseButtons { + f1 | f2 +}