Utility methods for Val (#6134)
# Objective Adds a better interface for performing mathematical operations with UI unit `Val`. Fixes #6080. ## Solution - Added `try_add` and `try_sub` methods to Val. - Removed the `Add` and `AddAssign` impls for `Val` that introduced unintuitive and bug-prone behaviour. - As a consequence of the prior, ~~changed the `Add` and `Sub` impls for the `Size` struct to take a `(Val, Val)` instead of `Vec2`~~ deleted the `Add` and `Sub` impls for the `Size` struct - Added a `From<(Val, Val)>` impl for the `Size` struct - Added `evaluate(size: f32)` method that converts from `Val::Percent` to `Val::Px`. - Added `try_add_with_size` and `try_sub_with_size` methods to `Val`, which evaluate `Val::Percent` values into `Val::Px` values before adding. --- ## Migration Guide Instead of using the + and - operators, perform calculations on `Val`s using the new `try_add` and `try_sub` methods. Multiplication and division remained unchanged. Also, when adding or subtracting from `Size`, ~~use a `Val` tuple instead of `Vec2`~~ perform the addition on `width` and `height` separately. Co-authored-by: Dawid Piotrowski <41804418+Pietrek14@users.noreply.github.com>
This commit is contained in:
parent
7a41efa227
commit
9066d51420
@ -34,3 +34,4 @@ taffy = "0.1.0"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
smallvec = { version = "1.6", features = ["union", "const_generics"] }
|
smallvec = { version = "1.6", features = ["union", "const_generics"] }
|
||||||
bytemuck = { version = "1.5", features = ["derive"] }
|
bytemuck = { version = "1.5", features = ["derive"] }
|
||||||
|
thiserror = "1.0.37"
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
use crate::Val;
|
use crate::Val;
|
||||||
use bevy_math::Vec2;
|
|
||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
|
use std::ops::{Div, DivAssign, Mul, MulAssign};
|
||||||
|
|
||||||
/// A type which is commonly used to define positions, margins, paddings and borders.
|
/// A type which is commonly used to define positions, margins, paddings and borders.
|
||||||
///
|
///
|
||||||
@ -356,42 +355,15 @@ impl Size {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add<Vec2> for Size {
|
impl From<(Val, Val)> for Size {
|
||||||
type Output = Size;
|
fn from(vals: (Val, Val)) -> Self {
|
||||||
|
|
||||||
fn add(self, rhs: Vec2) -> Self::Output {
|
|
||||||
Self {
|
Self {
|
||||||
width: self.width + rhs.x,
|
width: vals.0,
|
||||||
height: self.height + rhs.y,
|
height: vals.1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddAssign<Vec2> for Size {
|
|
||||||
fn add_assign(&mut self, rhs: Vec2) {
|
|
||||||
self.width += rhs.x;
|
|
||||||
self.height += rhs.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sub<Vec2> for Size {
|
|
||||||
type Output = Size;
|
|
||||||
|
|
||||||
fn sub(self, rhs: Vec2) -> Self::Output {
|
|
||||||
Self {
|
|
||||||
width: self.width - rhs.x,
|
|
||||||
height: self.height - rhs.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SubAssign<Vec2> for Size {
|
|
||||||
fn sub_assign(&mut self, rhs: Vec2) {
|
|
||||||
self.width -= rhs.x;
|
|
||||||
self.height -= rhs.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mul<f32> for Size {
|
impl Mul<f32> for Size {
|
||||||
type Output = Size;
|
type Output = Size;
|
||||||
|
|
||||||
@ -433,27 +405,16 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_size_add() {
|
fn test_size_from() {
|
||||||
|
let size: Size = (Val::Px(20.), Val::Px(30.)).into();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Size::new(Val::Px(10.), Val::Px(10.)) + Vec2::new(10., 10.),
|
size,
|
||||||
Size::new(Val::Px(20.), Val::Px(20.))
|
Size {
|
||||||
|
width: Val::Px(20.),
|
||||||
|
height: Val::Px(30.),
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut size = Size::new(Val::Px(10.), Val::Px(10.));
|
|
||||||
size += Vec2::new(10., 10.);
|
|
||||||
assert_eq!(size, Size::new(Val::Px(20.), Val::Px(20.)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_size_sub() {
|
|
||||||
assert_eq!(
|
|
||||||
Size::new(Val::Px(20.), Val::Px(20.)) - Vec2::new(10., 10.),
|
|
||||||
Size::new(Val::Px(10.), Val::Px(10.))
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut size = Size::new(Val::Px(20.), Val::Px(20.));
|
|
||||||
size -= Vec2::new(10., 10.);
|
|
||||||
assert_eq!(size, Size::new(Val::Px(10.), Val::Px(10.)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -9,7 +9,8 @@ use bevy_render::{
|
|||||||
texture::{Image, DEFAULT_IMAGE_HANDLE},
|
texture::{Image, DEFAULT_IMAGE_HANDLE},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
|
use std::ops::{Div, DivAssign, Mul, MulAssign};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
/// Describes the size of a UI node
|
/// Describes the size of a UI node
|
||||||
#[derive(Component, Debug, Clone, Default, Reflect)]
|
#[derive(Component, Debug, Clone, Default, Reflect)]
|
||||||
@ -43,50 +44,6 @@ pub enum Val {
|
|||||||
Percent(f32),
|
Percent(f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add<f32> for Val {
|
|
||||||
type Output = Val;
|
|
||||||
|
|
||||||
fn add(self, rhs: f32) -> Self::Output {
|
|
||||||
match self {
|
|
||||||
Val::Undefined => Val::Undefined,
|
|
||||||
Val::Auto => Val::Auto,
|
|
||||||
Val::Px(value) => Val::Px(value + rhs),
|
|
||||||
Val::Percent(value) => Val::Percent(value + rhs),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AddAssign<f32> for Val {
|
|
||||||
fn add_assign(&mut self, rhs: f32) {
|
|
||||||
match self {
|
|
||||||
Val::Undefined | Val::Auto => {}
|
|
||||||
Val::Px(value) | Val::Percent(value) => *value += rhs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sub<f32> for Val {
|
|
||||||
type Output = Val;
|
|
||||||
|
|
||||||
fn sub(self, rhs: f32) -> Self::Output {
|
|
||||||
match self {
|
|
||||||
Val::Undefined => Val::Undefined,
|
|
||||||
Val::Auto => Val::Auto,
|
|
||||||
Val::Px(value) => Val::Px(value - rhs),
|
|
||||||
Val::Percent(value) => Val::Percent(value - rhs),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SubAssign<f32> for Val {
|
|
||||||
fn sub_assign(&mut self, rhs: f32) {
|
|
||||||
match self {
|
|
||||||
Val::Undefined | Val::Auto => {}
|
|
||||||
Val::Px(value) | Val::Percent(value) => *value -= rhs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mul<f32> for Val {
|
impl Mul<f32> for Val {
|
||||||
type Output = Val;
|
type Output = Val;
|
||||||
|
|
||||||
@ -131,6 +88,105 @@ impl DivAssign<f32> for Val {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone, Copy, Error)]
|
||||||
|
pub enum ValArithmeticError {
|
||||||
|
#[error("the variants of the Vals don't match")]
|
||||||
|
NonIdenticalVariants,
|
||||||
|
#[error("the given variant of Val is not evaluateable (non-numeric)")]
|
||||||
|
NonEvaluateable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Val {
|
||||||
|
/// Tries to add the values of two [`Val`]s.
|
||||||
|
/// Returns [`ValArithmeticError::NonIdenticalVariants`] if two [`Val`]s are of different variants.
|
||||||
|
/// When adding non-numeric [`Val`]s, it returns the value unchanged.
|
||||||
|
pub fn try_add(&self, rhs: Val) -> Result<Val, ValArithmeticError> {
|
||||||
|
match (self, rhs) {
|
||||||
|
(Val::Undefined, Val::Undefined) | (Val::Auto, Val::Auto) => Ok(*self),
|
||||||
|
(Val::Px(value), Val::Px(rhs_value)) => Ok(Val::Px(value + rhs_value)),
|
||||||
|
(Val::Percent(value), Val::Percent(rhs_value)) => Ok(Val::Percent(value + rhs_value)),
|
||||||
|
_ => Err(ValArithmeticError::NonIdenticalVariants),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds `rhs` to `self` and assigns the result to `self` (see [`Val::try_add`])
|
||||||
|
pub fn try_add_assign(&mut self, rhs: Val) -> Result<(), ValArithmeticError> {
|
||||||
|
*self = self.try_add(rhs)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to subtract the values of two [`Val`]s.
|
||||||
|
/// Returns [`ValArithmeticError::NonIdenticalVariants`] if two [`Val`]s are of different variants.
|
||||||
|
/// When adding non-numeric [`Val`]s, it returns the value unchanged.
|
||||||
|
pub fn try_sub(&self, rhs: Val) -> Result<Val, ValArithmeticError> {
|
||||||
|
match (self, rhs) {
|
||||||
|
(Val::Undefined, Val::Undefined) | (Val::Auto, Val::Auto) => Ok(*self),
|
||||||
|
(Val::Px(value), Val::Px(rhs_value)) => Ok(Val::Px(value - rhs_value)),
|
||||||
|
(Val::Percent(value), Val::Percent(rhs_value)) => Ok(Val::Percent(value - rhs_value)),
|
||||||
|
_ => Err(ValArithmeticError::NonIdenticalVariants),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subtracts `rhs` from `self` and assigns the result to `self` (see [`Val::try_sub`])
|
||||||
|
pub fn try_sub_assign(&mut self, rhs: Val) -> Result<(), ValArithmeticError> {
|
||||||
|
*self = self.try_sub(rhs)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A convenience function for simple evaluation of [`Val::Percent`] variant into a concrete [`Val::Px`] value.
|
||||||
|
/// Returns a [`ValArithmeticError::NonEvaluateable`] if the [`Val`] is impossible to evaluate into [`Val::Px`].
|
||||||
|
/// Otherwise it returns an [`f32`] containing the evaluated value in pixels.
|
||||||
|
///
|
||||||
|
/// **Note:** If a [`Val::Px`] is evaluated, it's innver value returned unchanged.
|
||||||
|
pub fn evaluate(&self, size: f32) -> Result<f32, ValArithmeticError> {
|
||||||
|
match self {
|
||||||
|
Val::Percent(value) => Ok(size * value / 100.0),
|
||||||
|
Val::Px(value) => Ok(*value),
|
||||||
|
_ => Err(ValArithmeticError::NonEvaluateable),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar to [`Val::try_add`], but performs [`Val::evaluate`] on both values before adding.
|
||||||
|
/// Returns an [`f32`] value in pixels.
|
||||||
|
pub fn try_add_with_size(&self, rhs: Val, size: f32) -> Result<f32, ValArithmeticError> {
|
||||||
|
let lhs = self.evaluate(size)?;
|
||||||
|
let rhs = rhs.evaluate(size)?;
|
||||||
|
|
||||||
|
Ok(lhs + rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar to [`Val::try_add_assign`], but performs [`Val::evaluate`] on both values before adding.
|
||||||
|
/// The value gets converted to [`Val::Px`].
|
||||||
|
pub fn try_add_assign_with_size(
|
||||||
|
&mut self,
|
||||||
|
rhs: Val,
|
||||||
|
size: f32,
|
||||||
|
) -> Result<(), ValArithmeticError> {
|
||||||
|
*self = Val::Px(self.evaluate(size)? + rhs.evaluate(size)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar to [`Val::try_sub`], but performs [`Val::evaluate`] on both values before subtracting.
|
||||||
|
/// Returns an [`f32`] value in pixels.
|
||||||
|
pub fn try_sub_with_size(&self, rhs: Val, size: f32) -> Result<f32, ValArithmeticError> {
|
||||||
|
let lhs = self.evaluate(size)?;
|
||||||
|
let rhs = rhs.evaluate(size)?;
|
||||||
|
|
||||||
|
Ok(lhs - rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar to [`Val::try_sub_assign`], but performs [`Val::evaluate`] on both values before adding.
|
||||||
|
/// The value gets converted to [`Val::Px`].
|
||||||
|
pub fn try_sub_assign_with_size(
|
||||||
|
&mut self,
|
||||||
|
rhs: Val,
|
||||||
|
size: f32,
|
||||||
|
) -> Result<(), ValArithmeticError> {
|
||||||
|
*self = Val::Px(self.try_add_with_size(rhs, size)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Describes the style of a UI node
|
/// Describes the style of a UI node
|
||||||
///
|
///
|
||||||
/// It uses the [Flexbox](https://cssreference.io/flexbox/) system.
|
/// It uses the [Flexbox](https://cssreference.io/flexbox/) system.
|
||||||
@ -422,3 +478,168 @@ pub struct CalculatedClip {
|
|||||||
/// The rect of the clip
|
/// The rect of the clip
|
||||||
pub clip: Rect,
|
pub clip: Rect,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::ValArithmeticError;
|
||||||
|
|
||||||
|
use super::Val;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn val_try_add() {
|
||||||
|
let undefined_sum = Val::Undefined.try_add(Val::Undefined).unwrap();
|
||||||
|
let auto_sum = Val::Auto.try_add(Val::Auto).unwrap();
|
||||||
|
let px_sum = Val::Px(20.).try_add(Val::Px(22.)).unwrap();
|
||||||
|
let percent_sum = Val::Percent(50.).try_add(Val::Percent(50.)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(undefined_sum, Val::Undefined);
|
||||||
|
assert_eq!(auto_sum, Val::Auto);
|
||||||
|
assert_eq!(px_sum, Val::Px(42.));
|
||||||
|
assert_eq!(percent_sum, Val::Percent(100.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn val_try_add_to_self() {
|
||||||
|
let mut val = Val::Px(5.);
|
||||||
|
|
||||||
|
val.try_add_assign(Val::Px(3.)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(val, Val::Px(8.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn val_try_sub() {
|
||||||
|
let undefined_sum = Val::Undefined.try_sub(Val::Undefined).unwrap();
|
||||||
|
let auto_sum = Val::Auto.try_sub(Val::Auto).unwrap();
|
||||||
|
let px_sum = Val::Px(72.).try_sub(Val::Px(30.)).unwrap();
|
||||||
|
let percent_sum = Val::Percent(100.).try_sub(Val::Percent(50.)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(undefined_sum, Val::Undefined);
|
||||||
|
assert_eq!(auto_sum, Val::Auto);
|
||||||
|
assert_eq!(px_sum, Val::Px(42.));
|
||||||
|
assert_eq!(percent_sum, Val::Percent(50.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn different_variant_val_try_add() {
|
||||||
|
let different_variant_sum_1 = Val::Undefined.try_add(Val::Auto);
|
||||||
|
let different_variant_sum_2 = Val::Px(50.).try_add(Val::Percent(50.));
|
||||||
|
let different_variant_sum_3 = Val::Percent(50.).try_add(Val::Undefined);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
different_variant_sum_1,
|
||||||
|
Err(ValArithmeticError::NonIdenticalVariants)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
different_variant_sum_2,
|
||||||
|
Err(ValArithmeticError::NonIdenticalVariants)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
different_variant_sum_3,
|
||||||
|
Err(ValArithmeticError::NonIdenticalVariants)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn different_variant_val_try_sub() {
|
||||||
|
let different_variant_diff_1 = Val::Undefined.try_sub(Val::Auto);
|
||||||
|
let different_variant_diff_2 = Val::Px(50.).try_sub(Val::Percent(50.));
|
||||||
|
let different_variant_diff_3 = Val::Percent(50.).try_sub(Val::Undefined);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
different_variant_diff_1,
|
||||||
|
Err(ValArithmeticError::NonIdenticalVariants)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
different_variant_diff_2,
|
||||||
|
Err(ValArithmeticError::NonIdenticalVariants)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
different_variant_diff_3,
|
||||||
|
Err(ValArithmeticError::NonIdenticalVariants)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn val_evaluate() {
|
||||||
|
let size = 250.;
|
||||||
|
let result = Val::Percent(80.).evaluate(size).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, size * 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn val_evaluate_px() {
|
||||||
|
let size = 250.;
|
||||||
|
let result = Val::Px(10.).evaluate(size).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, 10.);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn val_invalid_evaluation() {
|
||||||
|
let size = 250.;
|
||||||
|
let evaluate_undefined = Val::Undefined.evaluate(size);
|
||||||
|
let evaluate_auto = Val::Auto.evaluate(size);
|
||||||
|
|
||||||
|
assert_eq!(evaluate_undefined, Err(ValArithmeticError::NonEvaluateable));
|
||||||
|
assert_eq!(evaluate_auto, Err(ValArithmeticError::NonEvaluateable));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn val_try_add_with_size() {
|
||||||
|
let size = 250.;
|
||||||
|
|
||||||
|
let px_sum = Val::Px(21.).try_add_with_size(Val::Px(21.), size).unwrap();
|
||||||
|
let percent_sum = Val::Percent(20.)
|
||||||
|
.try_add_with_size(Val::Percent(30.), size)
|
||||||
|
.unwrap();
|
||||||
|
let mixed_sum = Val::Px(20.)
|
||||||
|
.try_add_with_size(Val::Percent(30.), size)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(px_sum, 42.);
|
||||||
|
assert_eq!(percent_sum, 0.5 * size);
|
||||||
|
assert_eq!(mixed_sum, 20. + 0.3 * size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn val_try_sub_with_size() {
|
||||||
|
let size = 250.;
|
||||||
|
|
||||||
|
let px_sum = Val::Px(60.).try_sub_with_size(Val::Px(18.), size).unwrap();
|
||||||
|
let percent_sum = Val::Percent(80.)
|
||||||
|
.try_sub_with_size(Val::Percent(30.), size)
|
||||||
|
.unwrap();
|
||||||
|
let mixed_sum = Val::Percent(50.)
|
||||||
|
.try_sub_with_size(Val::Px(30.), size)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(px_sum, 42.);
|
||||||
|
assert_eq!(percent_sum, 0.5 * size);
|
||||||
|
assert_eq!(mixed_sum, 0.5 * size - 30.);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn val_try_add_non_numeric_with_size() {
|
||||||
|
let size = 250.;
|
||||||
|
|
||||||
|
let undefined_sum = Val::Undefined.try_add_with_size(Val::Undefined, size);
|
||||||
|
let percent_sum = Val::Auto.try_add_with_size(Val::Auto, size);
|
||||||
|
|
||||||
|
assert_eq!(undefined_sum, Err(ValArithmeticError::NonEvaluateable));
|
||||||
|
assert_eq!(percent_sum, Err(ValArithmeticError::NonEvaluateable));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn val_arithmetic_error_messages() {
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", ValArithmeticError::NonIdenticalVariants),
|
||||||
|
"the variants of the Vals don't match"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
format!("{}", ValArithmeticError::NonEvaluateable),
|
||||||
|
"the given variant of Val is not evaluateable (non-numeric)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user