mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 18:58:36 +00:00
Introduce a rem
unit in the type system
This allows specifying font sizes relative to the Window's default-font-size, similar to CSS rem.
This commit is contained in:
parent
75ccd897ed
commit
d8a1f2cf01
22 changed files with 232 additions and 58 deletions
|
@ -238,6 +238,7 @@ fn to_debug_string(
|
|||
Type::Duration
|
||||
| Type::PhysicalLength
|
||||
| Type::LogicalLength
|
||||
| Type::Rem
|
||||
| Type::Angle
|
||||
| Type::Percent
|
||||
| Type::UnitProduct(_) => Expression::BinaryExpression {
|
||||
|
|
|
@ -19,6 +19,7 @@ pub use crate::passes::resolving;
|
|||
/// A function built into the run-time
|
||||
pub enum BuiltinFunction {
|
||||
GetWindowScaleFactor,
|
||||
GetWindowDefaultFontSize,
|
||||
AnimationTick,
|
||||
Debug,
|
||||
Mod,
|
||||
|
@ -81,6 +82,9 @@ impl BuiltinFunction {
|
|||
return_type: Box::new(Type::UnitProduct(vec![(Unit::Phx, 1), (Unit::Px, -1)])),
|
||||
args: vec![],
|
||||
},
|
||||
BuiltinFunction::GetWindowDefaultFontSize => {
|
||||
Type::Function { return_type: Box::new(Type::LogicalLength), args: vec![] }
|
||||
}
|
||||
BuiltinFunction::AnimationTick => {
|
||||
Type::Function { return_type: Type::Duration.into(), args: vec![] }
|
||||
}
|
||||
|
@ -171,6 +175,7 @@ impl BuiltinFunction {
|
|||
fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
BuiltinFunction::GetWindowScaleFactor => false,
|
||||
BuiltinFunction::GetWindowDefaultFontSize => false,
|
||||
BuiltinFunction::AnimationTick => false,
|
||||
BuiltinFunction::DarkColorScheme => false,
|
||||
// Even if it is not pure, we optimize it away anyway
|
||||
|
@ -291,6 +296,8 @@ declare_units! {
|
|||
In = "in" -> LogicalLength * 96,
|
||||
/// Points
|
||||
Pt = "pt" -> LogicalLength * 96./72.,
|
||||
/// Logical pixels multiplied with the window's default-font-size
|
||||
Rem = "rem" -> Rem,
|
||||
|
||||
// durations
|
||||
|
||||
|
@ -977,25 +984,47 @@ impl Expression {
|
|||
}
|
||||
(left, right) => match (left.as_unit_product(), right.as_unit_product()) {
|
||||
(Some(left), Some(right)) => {
|
||||
if let Some(power) =
|
||||
if let Some(conversion_powers) =
|
||||
crate::langtype::unit_product_length_conversion(&left, &right)
|
||||
{
|
||||
let op = if power < 0 { '*' } else { '/' };
|
||||
let mut result = self;
|
||||
for _ in 0..power.abs() {
|
||||
result = Expression::BinaryExpression {
|
||||
lhs: Box::new(result),
|
||||
rhs: Box::new(Expression::FunctionCall {
|
||||
function: Box::new(Expression::BuiltinFunctionReference(
|
||||
BuiltinFunction::GetWindowScaleFactor,
|
||||
Some(node.to_source_location()),
|
||||
)),
|
||||
arguments: vec![],
|
||||
source_location: Some(node.to_source_location()),
|
||||
}),
|
||||
op,
|
||||
let apply_power = |mut result, power: i8, builtin_fn| {
|
||||
let op = if power < 0 { '*' } else { '/' };
|
||||
for _ in 0..power.abs() {
|
||||
result = Expression::BinaryExpression {
|
||||
lhs: Box::new(result),
|
||||
rhs: Box::new(Expression::FunctionCall {
|
||||
function: Box::new(
|
||||
Expression::BuiltinFunctionReference(
|
||||
builtin_fn,
|
||||
Some(node.to_source_location()),
|
||||
),
|
||||
),
|
||||
arguments: vec![],
|
||||
source_location: Some(node.to_source_location()),
|
||||
}),
|
||||
op,
|
||||
}
|
||||
}
|
||||
result
|
||||
};
|
||||
|
||||
let mut result = self;
|
||||
|
||||
if conversion_powers.rem_to_px_power != 0 {
|
||||
result = apply_power(
|
||||
result,
|
||||
conversion_powers.rem_to_px_power,
|
||||
BuiltinFunction::GetWindowDefaultFontSize,
|
||||
)
|
||||
}
|
||||
if conversion_powers.px_to_phx_power != 0 {
|
||||
result = apply_power(
|
||||
result,
|
||||
conversion_powers.px_to_phx_power,
|
||||
BuiltinFunction::GetWindowScaleFactor,
|
||||
)
|
||||
}
|
||||
|
||||
result
|
||||
} else {
|
||||
self
|
||||
|
@ -1087,6 +1116,7 @@ impl Expression {
|
|||
Type::Angle => Expression::NumberLiteral(0., Unit::Deg),
|
||||
Type::PhysicalLength => Expression::NumberLiteral(0., Unit::Phx),
|
||||
Type::LogicalLength => Expression::NumberLiteral(0., Unit::Px),
|
||||
Type::Rem => Expression::NumberLiteral(0., Unit::Rem),
|
||||
Type::Percent => Expression::NumberLiteral(100., Unit::Percent),
|
||||
Type::Image => Expression::ImageReference {
|
||||
resource_ref: ImageReference::None,
|
||||
|
|
|
@ -314,6 +314,7 @@ impl CppType for Type {
|
|||
Type::Angle => Some("float".to_owned()),
|
||||
Type::PhysicalLength => Some("float".to_owned()),
|
||||
Type::LogicalLength => Some("float".to_owned()),
|
||||
Type::Rem => Some("float".to_owned()),
|
||||
Type::Percent => Some("float".to_owned()),
|
||||
Type::Bool => Some("bool".to_owned()),
|
||||
Type::Struct { name: Some(name), node: Some(_), .. } => Some(ident(name)),
|
||||
|
@ -2395,6 +2396,10 @@ fn compile_builtin_function_call(
|
|||
let window = access_window_field(ctx);
|
||||
format!("{}.scale_factor()", window)
|
||||
}
|
||||
BuiltinFunction::GetWindowDefaultFontSize => {
|
||||
let window_item_name = ident(&ctx.public_component.item_tree.root.items[0].name);
|
||||
format!("{}->{}.default_font_size.get()", ctx.generator_state, window_item_name)
|
||||
}
|
||||
BuiltinFunction::AnimationTick => "slint::cbindgen_private::slint_animation_tick()".into(),
|
||||
BuiltinFunction::Debug => {
|
||||
format!("std::cout << {} << std::endl;", a.join("<<"))
|
||||
|
|
|
@ -76,6 +76,7 @@ fn rust_primitive_type(ty: &Type) -> Option<proc_macro2::TokenStream> {
|
|||
Type::Angle => Some(quote!(f32)),
|
||||
Type::PhysicalLength => Some(quote!(slint::private_unstable_api::re_exports::Coord)),
|
||||
Type::LogicalLength => Some(quote!(slint::private_unstable_api::re_exports::Coord)),
|
||||
Type::Rem => Some(quote!(f32)),
|
||||
Type::Percent => Some(quote!(f32)),
|
||||
Type::Bool => Some(quote!(bool)),
|
||||
Type::Image => Some(quote!(slint::private_unstable_api::re_exports::Image)),
|
||||
|
@ -1863,6 +1864,7 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream
|
|||
| Type::LogicalLength
|
||||
| Type::Angle
|
||||
| Type::Percent
|
||||
| Type::Rem
|
||||
) =>
|
||||
{
|
||||
(Some(quote!(as f64)), Some(quote!(as f64)))
|
||||
|
@ -2152,6 +2154,13 @@ fn compile_builtin_function_call(
|
|||
let window_adapter_tokens = access_window_adapter_field(ctx);
|
||||
quote!(slint::private_unstable_api::re_exports::WindowInner::from_pub(#window_adapter_tokens.window()).scale_factor())
|
||||
}
|
||||
BuiltinFunction::GetWindowDefaultFontSize => {
|
||||
let window_item_name = ident(&ctx.public_component.item_tree.root.items[0].name);
|
||||
let root_access = &ctx.generator_state;
|
||||
let root_component_id = inner_component_id(&ctx.public_component.item_tree.root);
|
||||
let item_field = access_component_field_offset(&root_component_id, &window_item_name);
|
||||
quote!((#item_field + slint::private_unstable_api::re_exports::WindowItem::FIELD_OFFSETS.default_font_size).apply_pin(#root_access.as_pin_ref()).get().get())
|
||||
}
|
||||
BuiltinFunction::AnimationTick => {
|
||||
quote!(slint::private_unstable_api::re_exports::animation_tick())
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ pub enum Type {
|
|||
Duration,
|
||||
PhysicalLength,
|
||||
LogicalLength,
|
||||
Rem,
|
||||
Angle,
|
||||
Percent,
|
||||
Image,
|
||||
|
@ -93,6 +94,7 @@ impl core::cmp::PartialEq for Type {
|
|||
Type::Angle => matches!(other, Type::Angle),
|
||||
Type::PhysicalLength => matches!(other, Type::PhysicalLength),
|
||||
Type::LogicalLength => matches!(other, Type::LogicalLength),
|
||||
Type::Rem => matches!(other, Type::Rem),
|
||||
Type::Percent => matches!(other, Type::Percent),
|
||||
Type::Image => matches!(other, Type::Image),
|
||||
Type::Bool => matches!(other, Type::Bool),
|
||||
|
@ -153,6 +155,7 @@ impl Display for Type {
|
|||
Type::Angle => write!(f, "angle"),
|
||||
Type::PhysicalLength => write!(f, "physical-length"),
|
||||
Type::LogicalLength => write!(f, "length"),
|
||||
Type::Rem => write!(f, "relative-font-size"),
|
||||
Type::Percent => write!(f, "percent"),
|
||||
Type::Color => write!(f, "color"),
|
||||
Type::Image => write!(f, "image"),
|
||||
|
@ -207,6 +210,7 @@ impl Type {
|
|||
| Self::Angle
|
||||
| Self::PhysicalLength
|
||||
| Self::LogicalLength
|
||||
| Self::Rem
|
||||
| Self::Percent
|
||||
| Self::Image
|
||||
| Self::Bool
|
||||
|
@ -266,6 +270,10 @@ impl Type {
|
|||
| (Type::Int32, Type::Model)
|
||||
| (Type::PhysicalLength, Type::LogicalLength)
|
||||
| (Type::LogicalLength, Type::PhysicalLength)
|
||||
| (Type::Rem, Type::LogicalLength)
|
||||
| (Type::Rem, Type::PhysicalLength)
|
||||
| (Type::LogicalLength, Type::Rem)
|
||||
| (Type::PhysicalLength, Type::Rem)
|
||||
| (Type::Percent, Type::Float32)
|
||||
| (Type::Brush, Type::Color)
|
||||
| (Type::Color, Type::Brush) => true,
|
||||
|
@ -291,6 +299,7 @@ impl Type {
|
|||
Type::Duration => Some(Unit::Ms),
|
||||
Type::PhysicalLength => Some(Unit::Phx),
|
||||
Type::LogicalLength => Some(Unit::Px),
|
||||
Type::Rem => Some(Unit::Rem),
|
||||
// Unit::Percent is special that it does not combine with other units like
|
||||
Type::Percent => None,
|
||||
Type::Angle => Some(Unit::Deg),
|
||||
|
@ -764,62 +773,86 @@ impl EnumerationValue {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct LengthConversionPowers {
|
||||
pub rem_to_px_power: i8,
|
||||
pub px_to_phx_power: i8,
|
||||
}
|
||||
|
||||
/// If the `Type::UnitProduct(a)` can be converted to `Type::UnitProduct(a)` by multiplying
|
||||
/// by the scale factor, return that scale factor, otherwise, return None
|
||||
pub fn unit_product_length_conversion(a: &[(Unit, i8)], b: &[(Unit, i8)]) -> Option<i8> {
|
||||
let mut it1 = a.iter();
|
||||
let mut it2 = b.iter();
|
||||
let (mut v1, mut v2) = (it1.next(), it2.next());
|
||||
let mut ppx = 0;
|
||||
let mut lpx = 0;
|
||||
loop {
|
||||
match (v1, v2) {
|
||||
(None, None) => return (ppx == -lpx && ppx != 0).then(|| ppx),
|
||||
(Some(a), Some(b)) if a == b => (),
|
||||
(Some((Unit::Phx, a)), Some((Unit::Phx, b))) => ppx += a - b,
|
||||
(Some((Unit::Px, a)), Some((Unit::Px, b))) => lpx += a - b,
|
||||
(Some((Unit::Phx, a)), _) => {
|
||||
ppx += *a;
|
||||
v1 = it1.next();
|
||||
continue;
|
||||
}
|
||||
(_, Some((Unit::Phx, b))) => {
|
||||
ppx += -b;
|
||||
v2 = it2.next();
|
||||
continue;
|
||||
}
|
||||
(Some((Unit::Px, a)), _) => {
|
||||
lpx += *a;
|
||||
v1 = it1.next();
|
||||
continue;
|
||||
}
|
||||
(_, Some((Unit::Px, b))) => {
|
||||
lpx += -b;
|
||||
v2 = it2.next();
|
||||
continue;
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
v1 = it1.next();
|
||||
v2 = it2.next();
|
||||
pub fn unit_product_length_conversion(
|
||||
a: &[(Unit, i8)],
|
||||
b: &[(Unit, i8)],
|
||||
) -> Option<LengthConversionPowers> {
|
||||
let mut units = [0i8; 16];
|
||||
for (u, count) in a {
|
||||
units[*u as usize] += count;
|
||||
}
|
||||
for (u, count) in b {
|
||||
units[*u as usize] -= count;
|
||||
}
|
||||
|
||||
if units[Unit::Px as usize] + units[Unit::Phx as usize] + units[Unit::Rem as usize] != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
if units[Unit::Rem as usize] != 0
|
||||
&& units[Unit::Phx as usize] == -units[Unit::Rem as usize]
|
||||
&& units[Unit::Px as usize] == 0
|
||||
{
|
||||
units[Unit::Px as usize] = -units[Unit::Rem as usize];
|
||||
units[Unit::Phx as usize] = -units[Unit::Rem as usize];
|
||||
}
|
||||
|
||||
let result = LengthConversionPowers {
|
||||
rem_to_px_power: if units[Unit::Rem as usize] != 0 { units[Unit::Px as usize] } else { 0 },
|
||||
px_to_phx_power: if units[Unit::Px as usize] != 0 { units[Unit::Phx as usize] } else { 0 },
|
||||
};
|
||||
|
||||
units[Unit::Px as usize] = 0;
|
||||
units[Unit::Phx as usize] = 0;
|
||||
units[Unit::Rem as usize] = 0;
|
||||
units.into_iter().all(|x| x == 0).then(|| result)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_product_length_conversion_test() {
|
||||
use Option::None;
|
||||
use Unit::*;
|
||||
assert_eq!(unit_product_length_conversion(&[(Px, 1)], &[(Phx, 1)]), Some(-1));
|
||||
assert_eq!(unit_product_length_conversion(&[(Phx, -2)], &[(Px, -2)]), Some(-2));
|
||||
assert_eq!(unit_product_length_conversion(&[(Px, 1), (Phx, -2)], &[(Phx, -1)]), Some(-1));
|
||||
assert_eq!(
|
||||
unit_product_length_conversion(&[(Px, 1)], &[(Phx, 1)]),
|
||||
Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -1 })
|
||||
);
|
||||
assert_eq!(
|
||||
unit_product_length_conversion(&[(Phx, -2)], &[(Px, -2)]),
|
||||
Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -2 })
|
||||
);
|
||||
assert_eq!(
|
||||
unit_product_length_conversion(&[(Px, 1), (Phx, -2)], &[(Phx, -1)]),
|
||||
Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -1 })
|
||||
);
|
||||
assert_eq!(
|
||||
unit_product_length_conversion(
|
||||
&[(Deg, 3), (Phx, 2), (Ms, -1)],
|
||||
&[(Phx, 4), (Deg, 3), (Ms, -1), (Px, -2)]
|
||||
),
|
||||
Some(-2)
|
||||
Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -2 })
|
||||
);
|
||||
assert_eq!(unit_product_length_conversion(&[(Px, 1)], &[(Phx, -1)]), None);
|
||||
assert_eq!(unit_product_length_conversion(&[(Deg, 1), (Phx, -2)], &[(Px, -2)]), None);
|
||||
assert_eq!(unit_product_length_conversion(&[(Px, 1)], &[(Phx, -1)]), None);
|
||||
|
||||
assert_eq!(
|
||||
unit_product_length_conversion(&[(Rem, 1)], &[(Px, 1)]),
|
||||
Some(LengthConversionPowers { rem_to_px_power: -1, px_to_phx_power: 0 })
|
||||
);
|
||||
assert_eq!(
|
||||
unit_product_length_conversion(&[(Rem, 1)], &[(Phx, 1)]),
|
||||
Some(LengthConversionPowers { rem_to_px_power: -1, px_to_phx_power: -1 })
|
||||
);
|
||||
assert_eq!(
|
||||
unit_product_length_conversion(&[(Rem, 2)], &[(Phx, 2)]),
|
||||
Some(LengthConversionPowers { rem_to_px_power: -2, px_to_phx_power: -2 })
|
||||
);
|
||||
}
|
||||
|
|
|
@ -195,6 +195,7 @@ impl Expression {
|
|||
| Type::Angle
|
||||
| Type::PhysicalLength
|
||||
| Type::LogicalLength
|
||||
| Type::Rem
|
||||
| Type::UnitProduct(_) => Expression::NumberLiteral(0.),
|
||||
Type::Percent => Expression::NumberLiteral(1.),
|
||||
Type::String => Expression::StringLiteral(String::new()),
|
||||
|
|
|
@ -68,6 +68,7 @@ fn callback_cost(_callback: &crate::llr::PropertyReference, _ctx: &EvaluationCon
|
|||
fn builtin_function_cost(function: BuiltinFunction) -> isize {
|
||||
match function {
|
||||
BuiltinFunction::GetWindowScaleFactor => PROPERTY_ACCESS_COST,
|
||||
BuiltinFunction::GetWindowDefaultFontSize => PROPERTY_ACCESS_COST,
|
||||
BuiltinFunction::AnimationTick => PROPERTY_ACCESS_COST,
|
||||
BuiltinFunction::Debug => isize::MAX,
|
||||
BuiltinFunction::Mod => 10,
|
||||
|
|
|
@ -30,6 +30,11 @@ fn check_expression(component: &Rc<Component>, e: &Expression, diag: &mut BuildD
|
|||
diag.push_error("Cannot convert between logical and physical length in a global component, because the scale factor is not known".into(), loc);
|
||||
}
|
||||
}
|
||||
Expression::BuiltinFunctionReference(BuiltinFunction::GetWindowDefaultFontSize, loc) => {
|
||||
if component.is_global() {
|
||||
diag.push_error("Cannot convert between rem and logical length in a global component, because the default font size is not known".into(), loc);
|
||||
}
|
||||
}
|
||||
_ => e.visit(|e| check_expression(component, e, diag)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,11 @@ global Glob := {
|
|||
// ^error{Cannot convert between logical and physical length in a global component, because the scale factor is not known}
|
||||
property <float> ratio: allowed / 1phx;
|
||||
// ^error{Cannot convert between logical and physical length in a global component, because the scale factor is not known}
|
||||
property <float> should_work: 45px / 8px;
|
||||
property <length> converted_rem: 2rem;
|
||||
// ^error{Cannot convert between rem and logical length in a global component, because the default font size is not known}
|
||||
property <float> should_work: 45px / 8px + (4rem / 2rem);
|
||||
property <length> allowed: 45px * 5;
|
||||
property <relative-font-size> rem_allowed: 42rem;
|
||||
}
|
||||
|
||||
X := Rectangle {
|
||||
|
|
|
@ -227,6 +227,7 @@ impl TypeRegister {
|
|||
register.insert_type(Type::Easing);
|
||||
register.insert_type(Type::Angle);
|
||||
register.insert_type(Type::Brush);
|
||||
register.insert_type(Type::Rem);
|
||||
|
||||
BUILTIN_ENUMS.with(|e| e.fill_register(&mut register));
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue