Started implementing VarValue: PartialEq.

This commit is contained in:
Samuel Guerra 2023-06-19 01:15:37 -03:00
parent 4dcdee7d2f
commit a5bef70ebd
44 changed files with 289 additions and 98 deletions

View File

@ -607,7 +607,7 @@ impl_from_and_into_var! {
///
/// [`corner_radius`]: fn@corner_radius
/// [`corner_radius_fit`]: fn@corner_radius_fit
#[derive(Clone, Copy, Default, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum CornerRadiusFit {
/// Corner radius is computed for each usage.
None,

View File

@ -27,7 +27,7 @@ use crate::{
/// ```
///
/// The example above creates a filter that lowers the opacity to `50%` and blurs by `3px`.
#[derive(Clone, Default)]
#[derive(Clone, Default, PartialEq)]
pub struct Filter {
filters: Vec<FilterData>,
needs_layout: bool,
@ -207,7 +207,7 @@ impl Layout2d for Filter {
/// A computed [`Filter`], ready for Webrender.
pub type RenderFilter = Vec<FilterOp>;
#[derive(Clone)]
#[derive(Clone, PartialEq)]
enum FilterData {
Op(FilterOp),
Blur(Length),

View File

@ -109,7 +109,7 @@ impl<S: Config, F: Config> AnyConfig for FallbackConfig<S, F> {
// based on `Var::bind_bidi` code.
let binding_tag = BindMapBidiTag::new_unique();
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
struct ResetTag;
// fallback->res binding can re-enable on reset.

View File

@ -1449,12 +1449,12 @@ impl LAYOUT {
}
/// Current screen PPI.
pub fn screen_ppi(&self) -> f32 {
pub fn screen_ppi(&self) -> Ppi {
LAYOUT_CTX.get().metrics.screen_ppi()
}
/// Calls `f` with `screen_ppi` in the context.
pub fn with_screen_ppi<R>(&self, screen_ppi: f32, f: impl FnOnce() -> R) -> R {
pub fn with_screen_ppi<R>(&self, screen_ppi: Ppi, f: impl FnOnce() -> R) -> R {
self.with_context(self.metrics().with_screen_ppi(screen_ppi), f)
}
@ -2730,7 +2730,7 @@ pub struct LayoutMetricsSnapshot {
/// The [`screen_ppi`].
///
/// [`screen_ppi`]: LayoutMetrics::screen_ppi
pub screen_ppi: f32,
pub screen_ppi: Ppi,
/// The [`direction`].
///
@ -2751,7 +2751,7 @@ impl LayoutMetricsSnapshot {
&& (!mask.contains(LayoutMask::ROOT_FONT_SIZE) || self.root_font_size == other.root_font_size)
&& (!mask.contains(LayoutMask::SCALE_FACTOR) || self.scale_factor == other.scale_factor)
&& (!mask.contains(LayoutMask::VIEWPORT) || self.viewport == other.viewport)
&& (!mask.contains(LayoutMask::SCREEN_PPI) || about_eq(self.screen_ppi, other.screen_ppi, 0.0001))
&& (!mask.contains(LayoutMask::SCREEN_PPI) || self.screen_ppi == other.screen_ppi)
&& (!mask.contains(LayoutMask::DIRECTION) || self.direction == other.direction)
&& (!mask.contains(LayoutMask::LEFTOVER) || self.leftover == other.leftover)
}
@ -2764,7 +2764,7 @@ impl PartialEq for LayoutMetricsSnapshot {
&& self.root_font_size == other.root_font_size
&& self.scale_factor == other.scale_factor
&& self.viewport == other.viewport
&& about_eq(self.screen_ppi, other.screen_ppi, 0.0001)
&& self.screen_ppi == other.screen_ppi
}
}
impl std::hash::Hash for LayoutMetricsSnapshot {
@ -2775,7 +2775,7 @@ impl std::hash::Hash for LayoutMetricsSnapshot {
self.root_font_size.hash(state);
self.scale_factor.hash(state);
self.viewport.hash(state);
about_eq_hash(self.screen_ppi, 0.0001, state);
self.screen_ppi.hash(state);
}
}
@ -2800,7 +2800,7 @@ impl LayoutMetrics {
root_font_size: font_size,
scale_factor,
viewport,
screen_ppi: 96.0,
screen_ppi: Ppi::default(),
direction: LayoutDirection::default(),
leftover: euclid::size2(None, None),
},
@ -2880,7 +2880,7 @@ impl LayoutMetrics {
///
/// [`MONITORS`]: crate::window::MONITORS
/// [`scale_factor`]: LayoutMetrics::scale_factor
pub fn screen_ppi(&self) -> f32 {
pub fn screen_ppi(&self) -> Ppi {
self.s.screen_ppi
}
@ -2933,7 +2933,7 @@ impl LayoutMetrics {
/// Sets the [`screen_ppi`].
///
/// [`screen_ppi`]: Self::screen_ppi
pub fn with_screen_ppi(mut self, screen_ppi: f32) -> Self {
pub fn with_screen_ppi(mut self, screen_ppi: Ppi) -> Self {
self.s.screen_ppi = screen_ppi;
self
}

View File

@ -576,6 +576,12 @@ impl<K: Eq + Hash + Send> FromParallelIterator<K> for IdSet<K> {
Self(FromParallelIterator::from_par_iter(par_iter))
}
}
impl<K: Eq + Hash> PartialEq for IdSet<K> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<K: Eq + Hash> Eq for IdSet<K> {}
/// Entry in [`IdMap`].
pub type IdEntry<'a, K, V> = hashbrown::hash_map::Entry<'a, K, V, BuildIdHasher>;

View File

@ -493,7 +493,7 @@ impl<const N: usize> crate::var::IntoVar<Shortcuts> for [Shortcut; N] {
}
/// Multiple shortcuts.
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Shortcuts(pub Vec<Shortcut>);
impl Shortcuts {
/// New default (empty).

View File

@ -590,7 +590,7 @@ impl fmt::Debug for GradientStop {
/// Stops in a gradient.
///
/// Use [`stops!`] to create a new instance, you can convert from arrays for simpler gradients.
#[derive(Clone, serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct GradientStops {
/// First color stop.
pub start: ColorStop,

View File

@ -187,7 +187,7 @@ impl Img {
/// [`ppi`]: Self::ppi
/// [`screen_ppi`]: LayoutMetrics::screen_ppi
pub fn layout_size(&self, ctx: &LayoutMetrics) -> PxSize {
self.calc_size(ctx, ImagePpi::splat(ctx.screen_ppi()), false)
self.calc_size(ctx, ImagePpi::splat(ctx.screen_ppi().0), false)
}
/// Calculate a layout size for the image.
@ -210,8 +210,8 @@ impl Img {
let mut size = self.size();
let fct = ctx.scale_factor().0;
size.width *= (s_ppi / dpi.x) * fct;
size.height *= (s_ppi / dpi.y) * fct;
size.width *= (s_ppi.0 / dpi.x) * fct;
size.height *= (s_ppi.0 / dpi.y) * fct;
size
}
@ -954,6 +954,19 @@ impl<U: 'static> ops::BitOrAssign for ImageSourceFilter<U> {
*self = mem::replace(self, Self::BlockAll).or(rhs);
}
}
impl<U> PartialEq for ImageSourceFilter<U> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
// can only fail by returning `false` in some cases where the value pointer is actually equal.
// see: https://github.com/rust-lang/rust/issues/103763
//
// we are fine with this, worst case is just an extra var update
#[allow(clippy::vtable_address_comparisons)]
(Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
/// Represents a [`ImageSource::Read`] path request filter.
///
@ -1030,7 +1043,7 @@ impl<F: Fn(&task::http::Uri) -> bool + Send + Sync + 'static> From<F> for UriFil
}
/// Limits for image loading and decoding.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct ImageLimits {
/// Maximum encoded file size allowed.
///

View File

@ -435,7 +435,7 @@ pub trait L10nSource: Send + 'static {
fn available_langs_status(&mut self) -> BoxedVar<LangResourceStatus>;
/// Gets a read-only variable that provides the fluent resource for the `lang` and `file` if available.
fn lang_resource(&mut self, lang: Lang, file: Txt) -> BoxedVar<Option<Arc<fluent::FluentResource>>>;
fn lang_resource(&mut self, lang: Lang, file: Txt) -> BoxedVar<Option<ArcEq<fluent::FluentResource>>>;
/// Gets a read-only variable that is the status of the [`lang_resource`] value.
///
/// [`lang_resource`]: Self::lang_resource

View File

@ -23,7 +23,7 @@ pub(super) struct L10nService {
sys_lang: ArcVar<Langs>,
app_lang: ArcCowVar<Langs, ArcVar<Langs>>,
perm_res: Vec<BoxedVar<Option<Arc<fluent::FluentResource>>>>,
perm_res: Vec<BoxedVar<Option<ArcEq<fluent::FluentResource>>>>,
bundles: HashMap<(Langs, Txt), BoxedWeakVar<ArcFluentBundle>>,
}
impl L10nService {
@ -205,7 +205,7 @@ impl L10nService {
res.map(move |r| {
let mut bundle = ConcurrentFluentBundle::new_concurrent(vec![lang.clone()]);
if let Some(r) = r {
bundle.add_resource_overriding(r.clone());
bundle.add_resource_overriding(r.0.clone());
}
ArcFluentBundle(Arc::new(bundle))
})
@ -222,7 +222,7 @@ impl L10nService {
res.build(move |res| {
let mut bundle = ConcurrentFluentBundle::new_concurrent(langs.clone());
for r in res.iter().flatten() {
bundle.add_resource_overriding(r.clone());
bundle.add_resource_overriding(r.0.clone());
}
ArcFluentBundle(Arc::new(bundle))
})
@ -272,6 +272,11 @@ impl fmt::Debug for ArcFluentBundle {
write!(f, "ArcFluentBundle")
}
}
impl PartialEq for ArcFluentBundle {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl ops::Deref for ArcFluentBundle {
type Target = ConcurrentFluentBundle;
@ -329,7 +334,7 @@ fn format_fallback(file: &str, id: &str, attribute: &str, fallback: &Txt, args:
Txt::from_str(txt.as_ref())
}
fn fluent_args_var(args: Vec<(Txt, BoxedVar<L10nArgument>)>) -> impl Var<Arc<Mutex<fluent::FluentArgs<'static>>>> {
fn fluent_args_var(args: Vec<(Txt, BoxedVar<L10nArgument>)>) -> impl Var<ArcEq<Mutex<fluent::FluentArgs<'static>>>> {
let mut fluent_args = MergeVarBuilder::new();
let mut names = Vec::with_capacity(args.len());
for (name, arg) in args {
@ -344,7 +349,7 @@ fn fluent_args_var(args: Vec<(Txt, BoxedVar<L10nArgument>)>) -> impl Var<Arc<Mut
}
// Mutex because ValueType is not Sync
Arc::new(Mutex::new(args))
ArcEq::new(Mutex::new(args))
})
}

View File

@ -140,7 +140,7 @@ impl L10nSource for L10nDir {
self.dir_watch_status.clone()
}
fn lang_resource(&mut self, lang: Lang, file: Txt) -> BoxedVar<Option<Arc<fluent::FluentResource>>> {
fn lang_resource(&mut self, lang: Lang, file: Txt) -> BoxedVar<Option<ArcEq<fluent::FluentResource>>> {
match self.res.entry((lang, file)) {
std::collections::hash_map::Entry::Occupied(mut e) => {
if let Some(out) = e.get().res.upgrade() {
@ -173,7 +173,7 @@ impl L10nSource for L10nDir {
}
}
struct L10nFile {
res: BoxedWeakVar<Option<Arc<fluent::FluentResource>>>,
res: BoxedWeakVar<Option<ArcEq<fluent::FluentResource>>>,
status: ArcVar<LangResourceStatus>,
}
impl L10nFile {
@ -184,7 +184,7 @@ impl L10nFile {
}
}
}
fn load_file(status: ArcVar<LangResourceStatus>, dir: &Path, lang: &Lang, file: &Txt) -> BoxedVar<Option<Arc<fluent::FluentResource>>> {
fn load_file(status: ArcVar<LangResourceStatus>, dir: &Path, lang: &Lang, file: &Txt) -> BoxedVar<Option<ArcEq<fluent::FluentResource>>> {
status.set_ne(LangResourceStatus::Loading);
let path = if file.is_empty() {
@ -204,7 +204,7 @@ fn load_file(status: ArcVar<LangResourceStatus>, dir: &Path, lang: &Lang, file:
Ok(flt) => {
// ok
// Loaded set by `r` to avoid race condition in waiter.
return Some(Some(Arc::new(flt)));
return Some(Some(ArcEq::new(flt)));
}
Err(e) => {
let e = FluentParserErrors(e.1);
@ -302,7 +302,7 @@ impl L10nSource for SwapL10nSource {
self.available_langs_status.read_only().boxed()
}
fn lang_resource(&mut self, lang: Lang, file: Txt) -> BoxedVar<Option<Arc<fluent::FluentResource>>> {
fn lang_resource(&mut self, lang: Lang, file: Txt) -> BoxedVar<Option<ArcEq<fluent::FluentResource>>> {
match self.res.entry((lang, file)) {
std::collections::hash_map::Entry::Occupied(mut e) => {
if let Some(res) = e.get().res.upgrade() {
@ -366,7 +366,7 @@ impl L10nSource for SwapL10nSource {
}
}
struct SwapFile {
res: BoxedWeakVar<Option<Arc<fluent::FluentResource>>>,
res: BoxedWeakVar<Option<ArcEq<fluent::FluentResource>>>,
status: ArcVar<LangResourceStatus>,
actual_weak_res: VarHandle,
res_strong_actual: VarHandle,
@ -395,7 +395,7 @@ impl L10nSource for NilL10nSource {
LocalVar(LangResourceStatus::NotAvailable).boxed()
}
fn lang_resource(&mut self, _: Lang, _: Txt) -> BoxedVar<Option<Arc<fluent::FluentResource>>> {
fn lang_resource(&mut self, _: Lang, _: Txt) -> BoxedVar<Option<ArcEq<fluent::FluentResource>>> {
LocalVar(None).boxed()
}

View File

@ -44,7 +44,7 @@ impl LangResources {
#[derive(Clone)]
#[must_use = "resource can unload if dropped"]
pub struct LangResource {
pub(super) res: BoxedVar<Option<Arc<fluent::FluentResource>>>,
pub(super) res: BoxedVar<Option<ArcEq<fluent::FluentResource>>>,
pub(super) status: BoxedVar<LangResourceStatus>,
}
@ -57,7 +57,7 @@ impl fmt::Debug for LangResource {
}
impl LangResource {
/// Read-only variable with the resource.
pub fn resource(&self) -> &BoxedVar<Option<Arc<fluent::FluentResource>>> {
pub fn resource(&self) -> &BoxedVar<Option<ArcEq<fluent::FluentResource>>> {
&self.res
}
@ -184,7 +184,7 @@ impl L10nMessageBuilder {
/// Represents an argument value for a localization message.
///
/// See [`L10nMessageBuilder::arg`] for more details.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub enum L10nArgument {
/// String.
Txt(Txt),
@ -549,6 +549,20 @@ impl<V> IntoIterator for LangMap<V> {
self.inner.into_iter()
}
}
impl<V: PartialEq> PartialEq for LangMap<V> {
fn eq(&self, other: &Self) -> bool {
if self.len() != other.len() {
return false;
}
for (k, v) in &self.inner {
if other.get_exact(k) != Some(v) {
return false;
}
}
true
}
}
impl<V: Eq> Eq for LangMap<V> {}
/// Errors found parsing a fluent resource file.
#[derive(Clone, Debug)]

View File

@ -341,6 +341,19 @@ impl fmt::Debug for TextTransformFn {
}
}
}
impl PartialEq for TextTransformFn {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
// can only fail by returning `false` in some cases where the value pointer is actually equal.
// see: https://github.com/rust-lang/rust/issues/103763
//
// we are fine with this, worst case is just an extra var update
#[allow(clippy::vtable_address_comparisons)]
(Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
/// Text white space transform.
#[derive(Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]

View File

@ -26,7 +26,7 @@ pub const FEATURE_DISABLED: u32 = 0;
type FontFeaturesMap = FxHashMap<FontFeatureName, u32>;
/// Font features configuration.
#[derive(Default, Clone)]
#[derive(Default, Clone, PartialEq)]
pub struct FontFeatures(FontFeaturesMap);
impl FontFeatures {
/// New default.
@ -1571,7 +1571,7 @@ pub type FontVariationName = &'static [u8; 4];
/// A small map of font variations.
///
/// Use [`font_variations!`] to manually initialize.
#[derive(Default, Clone)]
#[derive(Default, Clone, PartialEq)]
pub struct FontVariations(Vec<(FontVariationName, f32)>);
impl FontVariations {
/// New empty.

View File

@ -27,7 +27,7 @@ use crate::{
l10n::LangMap,
task,
units::*,
var::{response_done_var, var, AnyVar, ArcVar, ResponseVar, Var},
var::{response_done_var, var, AnyVar, ArcVar, ResponseVar, Var, ArcEq},
};
event! {
@ -198,9 +198,9 @@ impl FONTS {
///
/// The returned response is already set if the font is [`CustomFont::from_bytes`] or [`CustomFont::from_other`] and the other
/// is already loaded, otherwise the returned response will update once when the font finishes loading.
pub fn register(&self, custom_font: CustomFont) -> ResponseVar<Result<(), Arc<FontLoadingError>>> {
pub fn register(&self, custom_font: CustomFont) -> ResponseVar<Result<(), ArcEq<FontLoadingError>>> {
task::poll_respond(async move {
FontFaceLoader::register(custom_font).await.map_err(Arc::new)?;
FontFaceLoader::register(custom_font).await.map_err(ArcEq::new)?;
GENERIC_FONTS_SV.write().notify(FontChange::CustomFonts);
Ok(())
})

View File

@ -783,7 +783,7 @@ pub type TimerVar = ReadOnlyArcVar<Timer>;
///
/// This type uses interior mutability to communicate with the timer, the values provided by the methods
/// can be changed anytime by the [`TimerVar`] owners without the variable updating.
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub struct Timer(TimerHandle);
impl fmt::Debug for Timer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

View File

@ -667,7 +667,7 @@ impl FactorUnits for i32 {
}
/// Scale factor applied to ***x*** and ***y*** dimensions.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Factor2d {
/// Scale factor applied in the ***x*** dimension.
pub x: Factor,
@ -949,7 +949,7 @@ impl ops::DivAssign<Factor2d> for DipRect {
}
/// Scale factor applied to margins.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FactorSideOffsets {
/// Factor of top offset.
pub top: Factor,

View File

@ -3,7 +3,7 @@ use std::fmt;
use crate::impl_from_and_into_var;
/// Pixels-per-inch resolution.
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct Ppi(pub f32);
impl Ppi {
@ -23,6 +23,16 @@ impl Default for Ppi {
Ppi(96.0)
}
}
impl PartialEq for Ppi {
fn eq(&self, other: &Self) -> bool {
super::about_eq(self.0, other.0, 0.0001)
}
}
impl std::hash::Hash for Ppi {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
super::about_eq_hash(self.0, 0.0001, state)
}
}
/// Pixels-per-meter resolution.
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]

View File

@ -17,12 +17,12 @@ use super::{AngleRadian, AngleUnits, Factor, Layout1d, Length, PxTransform};
/// ```
///
///
#[derive(Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Transform {
parts: Vec<TransformPart>,
needs_layout: bool,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
enum TransformPart {
Computed(PxTransform),
Translate(Length, Length),

View File

@ -121,8 +121,16 @@ pub mod types {
///
/// This trait is used like a type alias for traits and is
/// already implemented for all types it applies to.
pub trait VarValue: fmt::Debug + Clone + Any + Send + Sync {}
impl<T: fmt::Debug + Clone + Any + Send + Sync> VarValue for T {}
///
/// # Implementing
///
/// Types need to be `Debug + Clone + PartialEq + Send + Sync + Any` to auto-implement this trait,
/// if you want to place an external type in a variable and it does not implement all the traits
/// you may need to declare a *newtype* wrapper, if the external type is `Debug + Send + Sync + Any` at
/// least you can use the [`ArcEq<T>`] wrapper to quickly implement `Clone + PartialEq`, this is particularly
/// useful for error types in [`ResponseVar<Result<_, E>>`].
pub trait VarValue: fmt::Debug + Clone + PartialEq + Any + Send + Sync {}
impl<T: fmt::Debug + Clone + Any + PartialEq + Send + Sync> VarValue for T {}
/// Trait implemented for all [`VarValue`] types.
pub trait AnyVarValue: fmt::Debug + Any + Send + Sync {
@ -415,6 +423,41 @@ impl IntoIterator for VarHandles {
}
}
/// Arc value that implements equality by pointer comparison.
///
/// This type allows types external types that are only `Debug + Send + Sync` to become
/// a full [`VarValue`] to be allowed as a variable value.
pub struct ArcEq<T: fmt::Debug + Send + Sync>(pub Arc<T>);
impl<T: fmt::Debug + Send + Sync> ops::Deref for ArcEq<T> {
type Target = Arc<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: fmt::Debug + Send + Sync> ArcEq<T> {
/// Constructs a new `ArcEq<T>`.
pub fn new(value: T) -> Self {
Self(Arc::new(value))
}
}
impl<T: fmt::Debug + Send + Sync> PartialEq for ArcEq<T> {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl<T: fmt::Debug + Send + Sync> Eq for ArcEq<T> {}
impl<T: fmt::Debug + Send + Sync> Clone for ArcEq<T> {
fn clone(&self) -> Self {
Self(Arc::clone(&self.0))
}
}
impl<T: fmt::Debug + Send + Sync> fmt::Debug for ArcEq<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&*self.0, f)
}
}
/// Methods of [`Var<T>`] that don't depend on the value type.
///
/// This trait is [sealed] and cannot be implemented for types outside of `zero_ui_core`.
@ -570,7 +613,7 @@ pub trait AnyVar: Any + Send + Sync + crate::private::Sealed {
/// Represents an [`AnyVar`] *pointer* that can be used for comparison.
///
/// If two of these values are equal, both variables point to the same *rc* or *context* at the moment of comparison.
/// If two of these values are equal, both variables point to the same *arc* or *context* at the moment of comparison.
pub struct VarPtr<'a> {
_lt: std::marker::PhantomData<&'a ()>,
eq: VarPtrData,

View File

@ -23,7 +23,7 @@ pub type ResponderVar<T> = ArcVar<Response<T>>;
pub type ResponseVar<T> = types::ReadOnlyVar<Response<T>, ArcVar<Response<T>>>;
/// Raw value in a [`ResponseVar`] or [`ResponseSender`].
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq)]
pub enum Response<T: VarValue> {
/// Responder has not set the response yet.
Waiting,

View File

@ -818,6 +818,11 @@ mod flat_map {
pub bar: bool,
pub var: ArcVar<usize>,
}
impl PartialEq for Foo {
fn eq(&self, other: &Self) -> bool {
self.bar == other.bar && self.var.var_ptr() == other.var.var_ptr()
}
}
impl fmt::Debug for Foo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Foo").field("bar", &self.bar).finish_non_exhaustive()

View File

@ -1109,7 +1109,7 @@ pub fn new_dyn_other<'a, T: Any + Send>(
/// Error value used in a reference to an [`UiNode`] property input is made in `when` expression.
///
/// Only variables and values can be referenced in `when` expression.
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub struct UiNodeInWhenExprError;
impl fmt::Debug for UiNodeInWhenExprError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -1129,7 +1129,7 @@ impl std::error::Error for UiNodeInWhenExprError {}
/// Error value used in a reference to an [`UiNodeList`] property input is made in `when` expression.
///
/// Only variables and values can be referenced in `when` expression.
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub struct UiNodeListInWhenExprError;
impl fmt::Debug for UiNodeListInWhenExprError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -1149,7 +1149,7 @@ impl std::error::Error for UiNodeListInWhenExprError {}
/// Error value used in a reference to an [`UiNodeList`] property input is made in `when` expression.
///
/// Only variables and values can be referenced in `when` expression.
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub struct WidgetHandlerInWhenExprError;
impl fmt::Debug for WidgetHandlerInWhenExprError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

View File

@ -195,7 +195,7 @@ impl HeadlessAppWindowExt for HeadlessApp {
{
let response = WINDOWS.open(new_window);
self.run_task(async move {
let window_id = response.wait_rsp().await.window_id;
let window_id = response.wait_rsp().await;
if !WINDOWS.is_loaded(window_id) {
let rcv = FRAME_IMAGE_READY_EVENT.receiver();
while let Ok(args) = rcv.recv_async().await {

View File

@ -1619,7 +1619,7 @@ impl ContentCtrl {
}
/// Run an `action` in the context of a monitor screen that is parent of this content.
pub fn outer_layout<R>(&mut self, scale_factor: Factor, screen_ppi: f32, screen_size: PxSize, action: impl FnOnce() -> R) -> R {
pub fn outer_layout<R>(&mut self, scale_factor: Factor, screen_ppi: Ppi, screen_size: PxSize, action: impl FnOnce() -> R) -> R {
let metrics = LayoutMetrics::new(scale_factor, screen_size, Length::pt_to_px(11.0, scale_factor)).with_screen_ppi(screen_ppi);
LAYOUT.with_context(metrics, action)
}
@ -1630,7 +1630,7 @@ impl ContentCtrl {
&mut self,
layout_widgets: Arc<LayoutUpdates>,
scale_factor: Factor,
screen_ppi: f32,
screen_ppi: Ppi,
min_size: PxSize,
max_size: PxSize,
size: PxSize,

View File

@ -93,9 +93,6 @@ app_local! {
/// [`WindowManager`]: crate::window::WindowManager
pub struct MONITORS;
impl MONITORS {
/// Initial PPI of monitors, `96.0`.
pub const DEFAULT_PPI: f32 = 96.0;
/// Get monitor info.
///
/// Returns `None` if the monitor was not found or the app is running in headless mode without renderer.
@ -169,7 +166,7 @@ impl MonitorsService {
/// "Monitor" configuration used by windows in [headless mode].
///
/// [headless mode]: crate::window::WindowMode::is_headless
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq)]
pub struct HeadlessMonitor {
/// The scale factor used for the headless layout and rendering.
///
@ -191,11 +188,11 @@ pub struct HeadlessMonitor {
/// Pixel-per-inches used for the headless layout and rendering.
///
/// [`MONITORS::DEFAULT_PPI`] by default.
pub ppi: f32,
pub ppi: Ppi,
}
impl fmt::Debug for HeadlessMonitor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() || about_eq(self.ppi, MONITORS::DEFAULT_PPI, 0.001) {
if f.alternate() || self.ppi != Ppi::default() {
f.debug_struct("HeadlessMonitor")
.field("scale_factor", &self.scale_factor)
.field("screen_size", &self.size)
@ -212,7 +209,7 @@ impl HeadlessMonitor {
HeadlessMonitor {
scale_factor: None,
size,
ppi: MONITORS::DEFAULT_PPI,
ppi: Ppi::default(),
}
}
@ -221,7 +218,7 @@ impl HeadlessMonitor {
HeadlessMonitor {
scale_factor: Some(scale_factor),
size,
ppi: MONITORS::DEFAULT_PPI,
ppi: Ppi::default(),
}
}
@ -258,7 +255,7 @@ pub struct MonitorInfo {
size: ArcVar<PxSize>,
video_modes: ArcVar<Vec<VideoMode>>,
scale_factor: ArcVar<Factor>,
ppi: ArcVar<f32>,
ppi: ArcVar<Ppi>,
}
impl fmt::Debug for MonitorInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -276,7 +273,7 @@ impl MonitorInfo {
size: var(info.size),
scale_factor: var(info.scale_factor.fct()),
video_modes: var(info.video_modes),
ppi: var(MONITORS::DEFAULT_PPI),
ppi: var(Ppi::default()),
}
}
@ -332,7 +329,7 @@ impl MonitorInfo {
self.scale_factor.read_only()
}
/// PPI config var.
pub fn ppi(&self) -> ArcVar<f32> {
pub fn ppi(&self) -> ArcVar<Ppi> {
self.ppi.clone()
}
@ -366,7 +363,7 @@ impl MonitorInfo {
size: var(defaults.size.to_px(fct.0)),
video_modes: var(vec![]),
scale_factor: var(fct),
ppi: var(MONITORS::DEFAULT_PPI),
ppi: var(Ppi::default()),
}
}
}

View File

@ -83,7 +83,7 @@ impl WindowsService {
id: WindowId,
new_window: UiTask<WindowRoot>,
force_headless: Option<WindowMode>,
) -> ResponseVar<WindowOpenArgs> {
) -> ResponseVar<WindowId> {
let (responder, response) = response_var();
let request = OpenWindowRequest {
id,
@ -265,7 +265,7 @@ impl WINDOWS {
///
/// The `new_window` future runs in a [`UiTask`] inside the new [`WINDOW`] context.
///
/// Returns a response variable that will update once when the window is opened, note that while the `window_id` is
/// Returns a response variable that will update once when the window is opened, note that while the [`WINDOW`] is
/// available in the `new_window` argument already, the window is only available in this service after
/// the returned variable updates. Also note that the window might not be fully [loaded] yet.
///
@ -273,7 +273,7 @@ impl WINDOWS {
/// can use the context [`WINDOW`] to set variables that will be read on init with the new value.
///
/// [loaded]: Self::is_loaded
pub fn open(&self, new_window: impl Future<Output = WindowRoot> + Send + 'static) -> ResponseVar<WindowOpenArgs> {
pub fn open(&self, new_window: impl Future<Output = WindowRoot> + Send + 'static) -> ResponseVar<WindowId> {
WINDOWS_SV
.write()
.open_impl(WindowId::new_unique(), UiTask::new(None, new_window), None)
@ -288,7 +288,7 @@ impl WINDOWS {
&self,
window_id: impl Into<WindowId>,
new_window: impl Future<Output = WindowRoot> + Send + 'static,
) -> ResponseVar<WindowOpenArgs> {
) -> ResponseVar<WindowId> {
let window_id = window_id.into();
self.assert_id_unused(window_id);
WINDOWS_SV.write().open_impl(window_id, UiTask::new(None, new_window), None)
@ -306,7 +306,7 @@ impl WINDOWS {
&self,
new_window: impl Future<Output = WindowRoot> + Send + 'static,
with_renderer: bool,
) -> ResponseVar<WindowOpenArgs> {
) -> ResponseVar<WindowId> {
WINDOWS_SV.write().open_impl(
WindowId::new_unique(),
UiTask::new(None, new_window),
@ -328,7 +328,7 @@ impl WINDOWS {
window_id: impl Into<WindowId>,
new_window: impl Future<Output = WindowRoot> + Send + 'static,
with_renderer: bool,
) -> ResponseVar<WindowOpenArgs> {
) -> ResponseVar<WindowId> {
let window_id = window_id.into();
self.assert_id_unused(window_id);
WINDOWS_SV.write().open_impl(
@ -554,7 +554,7 @@ impl WINDOWS {
&self,
window_id: impl Into<WindowId>,
open: impl Future<Output = WindowRoot> + Send + 'static,
) -> Option<ResponseVar<WindowOpenArgs>> {
) -> Option<ResponseVar<WindowId>> {
let window_id = window_id.into();
if self.focus(window_id).is_ok() {
None
@ -864,14 +864,13 @@ impl WINDOWS {
let window_id = task.ctx.id();
let (window, info, responder) = task.finish(wns.open_loading.remove(&window_id).unwrap());
let args = WindowOpenArgs::now(window_id);
if wns.windows.insert(window_id, window).is_some() {
// id conflict resolved on request.
unreachable!();
}
wns.windows_info.insert(info.id, info);
responder.respond(args.clone());
responder.respond(window_id);
// WINDOW_OPEN_EVENT.notify happens after init, so that handlers
// on the window itself can subscribe to the event.
} else {
@ -1148,7 +1147,7 @@ struct OpenWindowRequest {
id: WindowId,
new: Mutex<UiTask<WindowRoot>>, // never locked, makes `OpenWindowRequest: Sync`
force_headless: Option<WindowMode>,
responder: ResponderVar<WindowOpenArgs>,
responder: ResponderVar<WindowId>,
}
struct CloseWindowRequest {
responder: ResponderVar<CloseWindowResult>,
@ -1159,7 +1158,7 @@ struct AppWindowTask {
ctx: WindowCtx,
mode: WindowMode,
task: Mutex<UiTask<WindowRoot>>, // never locked, used to make `AppWindowTask: Sync`
responder: ResponderVar<WindowOpenArgs>,
responder: ResponderVar<WindowId>,
}
impl AppWindowTask {
fn new(
@ -1167,7 +1166,7 @@ impl AppWindowTask {
mode: WindowMode,
color_scheme: ColorScheme,
new: UiTask<WindowRoot>,
responder: ResponderVar<WindowOpenArgs>,
responder: ResponderVar<WindowId>,
) -> Self {
let primary_scale_factor = MONITORS
.primary_monitor()
@ -1198,7 +1197,7 @@ impl AppWindowTask {
self.task.get_mut().is_ready()
}
fn finish(self, loading: WindowLoading) -> (AppWindow, AppWindowInfo, ResponderVar<WindowOpenArgs>) {
fn finish(self, loading: WindowLoading) -> (AppWindow, AppWindowInfo, ResponderVar<WindowId>) {
let window = self.task.into_inner().into_result().unwrap_or_else(|_| panic!());
let mut ctx = self.ctx;
let mode = self.mode;

View File

@ -296,7 +296,7 @@ impl_from_and_into_var! {
///
/// The startup position affects the window once, at the moment the window
/// is open just after the first [`UiNode::render`] call.
#[derive(Clone, Copy, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum StartPosition {
/// Resolves the `position` property.
Default,

View File

@ -603,5 +603,11 @@ impl WindowVars {
self.0.renderer_debug.clone()
}
}
impl PartialEq for WindowVars {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for WindowVars {}
pub(super) static WINDOW_VARS_ID: StaticStateId<WindowVars> = StaticStateId::new_unique();

View File

@ -91,6 +91,22 @@ pub struct IpcBytes {
#[cfg(not(feature = "ipc"))]
bytes: std::sync::Arc<Vec<u8>>,
}
/// Pointer equal.
impl PartialEq for IpcBytes {
#[cfg(not(feature = "ipc"))]
fn eq(&self, other: &Self) -> bool {
std::sync::Arc::ptr_eq(&self.bytes, &other.bytes)
}
#[cfg(feature = "ipc")]
fn eq(&self, other: &Self) -> bool {
match (&self.bytes, &other.bytes) {
(None, None) => true,
(Some(a), Some(b)) => a.as_ptr() == b.as_ptr(),
_ => false,
}
}
}
impl IpcBytes {
/// Copy the `bytes` to a new shared memory allocation.
pub fn from_slice(bytes: &[u8]) -> Self {

View File

@ -730,7 +730,7 @@ impl WindowState {
}
/// [`Event::FrameRendered`] payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EventFrameRendered {
/// Window that was rendered.
pub window: WindowId,
@ -2080,7 +2080,7 @@ fn ppi_key(ppi: Option<ImagePpi>) -> Option<(u16, u16)> {
/// Represents a successfully decoded image.
///
/// See [`Event::ImageLoaded`].
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct ImageLoadedData {
/// Image ID.
pub id: ImageId,

View File

@ -8,7 +8,7 @@ use crate::widgets::scroll::ScrollMode;
/// See [`lazy`] property for more details.
///
/// [`lazy`]: fn@lazy
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub enum LazyMode {
/// Node always loaded.
Disabled,

View File

@ -311,7 +311,7 @@ pub fn img_loading_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<ImgLoadi
/// Arguments for [`img_loading_fn`].
///
/// [`img_loading_fn`]: fn@img_loading_fn
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct ImgLoadingArgs {}
/// Arguments for [`on_load`].
@ -324,7 +324,7 @@ pub struct ImgLoadArgs {}
///
/// [`on_error`]: fn@on_error
/// [`img_error_fn`]: fn@img_error_fn
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct ImgErrorArgs {
/// Error message.
pub error: Txt,

View File

@ -232,8 +232,8 @@ pub fn image_presenter() -> impl UiNode {
let mut scale = IMAGE_SCALE_VAR.get();
if IMAGE_SCALE_PPI_VAR.get() {
let sppi = metrics.screen_ppi();
let ippi = CONTEXT_IMAGE_VAR.with(Img::ppi).unwrap_or(ImagePpi::splat(sppi));
scale *= Factor2d::new(sppi / ippi.x, sppi / ippi.y);
let ippi = CONTEXT_IMAGE_VAR.with(Img::ppi).unwrap_or(ImagePpi::splat(sppi.0));
scale *= Factor2d::new(sppi.0 / ippi.x, sppi.0 / ippi.y);
}
if IMAGE_SCALE_FACTOR_VAR.get() {
scale *= metrics.scale_factor();
@ -261,8 +261,8 @@ pub fn image_presenter() -> impl UiNode {
let mut scale = IMAGE_SCALE_VAR.get();
if IMAGE_SCALE_PPI_VAR.get() {
let sppi = metrics.screen_ppi();
let ippi = CONTEXT_IMAGE_VAR.with(Img::ppi).unwrap_or(ImagePpi::splat(sppi));
scale *= Factor2d::new(sppi / ippi.x, sppi / ippi.y);
let ippi = CONTEXT_IMAGE_VAR.with(Img::ppi).unwrap_or(ImagePpi::splat(sppi.0));
scale *= Factor2d::new(sppi.0 / ippi.x, sppi.0 / ippi.y);
}
if IMAGE_SCALE_FACTOR_VAR.get() {
scale *= metrics.scale_factor();

View File

@ -26,7 +26,7 @@ use std::{fmt, mem, ops};
/// [`stack::children_align`]: fn@crate::widgets::layouts::stack::children_align
/// [`stack::spacing`]: fn@crate::widgets::layouts::stack::spacing
/// [`Stack!`]: struct@crate::widgets::layouts::Stack
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct StackDirection {
/// Point on the previous item where the next item is placed.
pub place: Point,

View File

@ -98,6 +98,19 @@ impl fmt::Debug for ImageResolver {
}
}
}
impl PartialEq for ImageResolver {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
// can only fail by returning `false` in some cases where the value pointer is actually equal.
// see: https://github.com/rust-lang/rust/issues/103763
//
// we are fine with this, worst case is just an extra var update
#[allow(clippy::vtable_address_comparisons)]
(Self::Resolve(l0), Self::Resolve(r0)) => Arc::ptr_eq(l0, r0),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
/// Markdown link resolver.
///
@ -156,6 +169,19 @@ impl fmt::Debug for LinkResolver {
}
}
}
impl PartialEq for LinkResolver {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
// can only fail by returning `false` in some cases where the value pointer is actually equal.
// see: https://github.com/rust-lang/rust/issues/103763
//
// we are fine with this, worst case is just an extra var update
#[allow(clippy::vtable_address_comparisons)]
(Self::Resolve(l0), Self::Resolve(r0)) => Arc::ptr_eq(l0, r0),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
event! {
/// Event raised by markdown links when clicked.

View File

@ -169,7 +169,7 @@ command! {
}
/// Parameters for the scroll and page commands.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct ScrollRequest {
/// If the [alt factor] should be applied to the base scroll unit when scrolling.
///
@ -231,7 +231,7 @@ impl_from_and_into_var! {
}
/// Parameters for the [`SCROLL_TO_CMD`].
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct ScrollToRequest {
/// Widget that will be scrolled into view.
pub widget_id: WidgetId,
@ -281,7 +281,7 @@ impl_from_and_into_var! {
}
/// Defines how much the [`SCROLL_TO_CMD`] will scroll to showcase the target widget.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub enum ScrollToMode {
/// Scroll will change only just enough so that the widget inner rect is fully visible with the optional
/// extra margin offsets.

View File

@ -227,7 +227,7 @@ pub fn auto_hide_extra(child: impl UiNode, extra: impl IntoVar<SideOffsets>) ->
}
/// Arguments for scrollbar widget functions.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct ScrollBarArgs {
/// Scrollbar orientation.
pub orientation: scrollbar::Orientation,

View File

@ -403,6 +403,16 @@ impl fmt::Debug for SmoothScrolling {
.finish_non_exhaustive()
}
}
impl PartialEq for SmoothScrolling {
// can only fail by returning `false` in some cases where the value pointer is actually equal.
// see: https://github.com/rust-lang/rust/issues/103763
//
// we are fine with this, worst case is just an extra var update
#[allow(clippy::vtable_address_comparisons)]
fn eq(&self, other: &Self) -> bool {
self.duration == other.duration && Arc::ptr_eq(&self.easing, &other.easing)
}
}
impl Default for SmoothScrolling {
fn default() -> Self {
Self::new(150.ms(), easing::linear)

View File

@ -287,6 +287,20 @@ impl Default for StyleFn {
Self::nil()
}
}
impl PartialEq for StyleFn {
// can only fail by returning `false` in some cases where the value pointer is actually equal.
// see: https://github.com/rust-lang/rust/issues/103763
//
// we are fine with this, worst case is just an extra var update
#[allow(clippy::vtable_address_comparisons)]
fn eq(&self, other: &Self) -> bool {
match (&self.0, &other.0) {
(None, None) => true,
(Some(a), Some(b)) => Arc::ptr_eq(a, b),
_ => false,
}
}
}
impl StyleFn {
/// Default function, produces an empty style.
pub fn nil() -> Self {

View File

@ -683,6 +683,11 @@ impl fmt::Debug for Selector {
write!(f, "Selector(_)")
}
}
impl PartialEq for Selector {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
/// Error for [`Selector`] operations.
#[derive(Debug, Clone)]

View File

@ -43,6 +43,15 @@ impl<D> fmt::Debug for WidgetFn<D> {
write!(f, "WidgetFn<{}>", pretty_type_name::<D>())
}
}
impl<D> PartialEq for WidgetFn<D> {
fn eq(&self, other: &Self) -> bool {
match (&self.0, &other.0) {
(None, None) => true,
(Some(a), Some(b)) => Arc::ptr_eq(a, b),
_ => false,
}
}
}
impl<D> WidgetFn<D> {
/// New from a closure that generates a node from data.
pub fn new<U: UiNode>(func: impl Fn(D) -> U + Send + Sync + 'static) -> Self {

View File

@ -8,7 +8,7 @@ use std::time::Duration;
/// Defines if a widget load affects the parent window load.
///
/// Widgets that support this behavior have a `block_window_load` property.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BlockWindowLoad {
/// Widget requests a [`WindowLoadingHandle`] and retains it until the widget is loaded.
///

View File

@ -140,7 +140,7 @@ pub fn clear_color(child: impl UiNode, color: impl IntoVar<Rgba>) -> impl UiNode
/// See the [`save_state`] property for more details.
///
/// [`save_state`]: fn@save_state
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum SaveState {
/// Save & restore state.
Enabled {