diff --git a/internal/compiler/builtin_macros.rs b/internal/compiler/builtin_macros.rs index ccaf1d01649..92e285f658a 100644 --- a/internal/compiler/builtin_macros.rs +++ b/internal/compiler/builtin_macros.rs @@ -6,7 +6,7 @@ use crate::diagnostics::{BuildDiagnostics, Spanned}; use crate::expression_tree::{ - BuiltinFunction, BuiltinMacroFunction, EasingCurve, Expression, Unit, + BuiltinFunction, BuiltinMacroFunction, EasingCurve, Expression, MinMaxOp, Unit, }; use crate::langtype::{EnumerationValue, Type}; use crate::parser::NodeOrToken; @@ -22,8 +22,8 @@ pub fn lower_macro( diag: &mut BuildDiagnostics, ) -> Expression { match mac { - BuiltinMacroFunction::Min => min_max_macro(n, '<', sub_expr.collect(), diag), - BuiltinMacroFunction::Max => min_max_macro(n, '>', sub_expr.collect(), diag), + BuiltinMacroFunction::Min => min_max_macro(n, MinMaxOp::Min, sub_expr.collect(), diag), + BuiltinMacroFunction::Max => min_max_macro(n, MinMaxOp::Max, sub_expr.collect(), diag), BuiltinMacroFunction::Mod => mod_macro(n, sub_expr.collect(), diag), BuiltinMacroFunction::Debug => debug_macro(n, sub_expr.collect(), diag), BuiltinMacroFunction::CubicBezier => { @@ -67,7 +67,7 @@ pub fn lower_macro( fn min_max_macro( node: Option, - op: char, + op: MinMaxOp, args: Vec<(Expression, Option)>, diag: &mut BuildDiagnostics, ) -> Expression { @@ -345,24 +345,7 @@ fn to_debug_string( /// Generate an expression which is like `min(lhs, rhs)` if op is '<' or `max(lhs, rhs)` if op is '>'. /// counter is an unique id. /// The rhs and lhs of the expression must have the same numerical type -pub fn min_max_expression(lhs: Expression, rhs: Expression, op: char) -> Expression { +pub fn min_max_expression(lhs: Expression, rhs: Expression, op: MinMaxOp) -> Expression { let ty = lhs.ty(); - let id = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let n1 = format!("minmax_lhs{}", id); - let n2 = format!("minmax_rhs{}", id); - let a1 = Box::new(Expression::ReadLocalVariable { name: n1.clone(), ty: ty.clone() }); - let a2 = Box::new(Expression::ReadLocalVariable { name: n2.clone(), ty }); - Expression::CodeBlock(vec![ - Expression::StoreLocalVariable { name: n1, value: Box::new(lhs) }, - Expression::StoreLocalVariable { name: n2, value: Box::new(rhs) }, - Expression::Condition { - condition: Box::new(Expression::BinaryExpression { - lhs: a1.clone(), - rhs: a2.clone(), - op, - }), - true_expr: a1, - false_expr: a2, - }, - ]) + Expression::MinMax { ty, op, lhs: Box::new(lhs), rhs: Box::new(rhs) } } diff --git a/internal/compiler/expression_tree.rs b/internal/compiler/expression_tree.rs index 9fc01f02e73..684f4e2f153 100644 --- a/internal/compiler/expression_tree.rs +++ b/internal/compiler/expression_tree.rs @@ -429,6 +429,12 @@ impl Default for Unit { } } +#[derive(Debug, Clone, Copy)] +pub enum MinMaxOp { + Min, + Max, +} + /// The Expression is hold by properties, so it should not hold any strong references to node from the object_tree #[derive(Debug, Clone, Default)] pub enum Expression { @@ -612,6 +618,13 @@ pub enum Expression { /// The orientation is the orientation of the cache, not the orientation of the layout ComputeLayoutInfo(crate::layout::Layout, crate::layout::Orientation), SolveLayout(crate::layout::Layout, crate::layout::Orientation), + + MinMax { + ty: Type, + op: MinMaxOp, + lhs: Box, + rhs: Box, + }, } impl Expression { @@ -738,6 +751,7 @@ impl Expression { Expression::LayoutCacheAccess { .. } => Type::LogicalLength, Expression::ComputeLayoutInfo(..) => crate::layout::layout_info_type(), Expression::SolveLayout(..) => Type::LayoutCache, + Expression::MinMax { ty, .. } => ty.clone(), } } @@ -836,6 +850,10 @@ impl Expression { } Expression::ComputeLayoutInfo(..) => {} Expression::SolveLayout(..) => {} + Expression::MinMax { lhs, rhs, .. } => { + visitor(lhs); + visitor(rhs); + } } } @@ -936,6 +954,10 @@ impl Expression { } Expression::ComputeLayoutInfo(..) => {} Expression::SolveLayout(..) => {} + Expression::MinMax { lhs, rhs, .. } => { + visitor(lhs); + visitor(rhs); + } } } @@ -1010,6 +1032,7 @@ impl Expression { Expression::LayoutCacheAccess { .. } => false, Expression::ComputeLayoutInfo(..) => false, Expression::SolveLayout(..) => false, + Expression::MinMax { lhs, rhs, .. } => lhs.is_constant() && rhs.is_constant(), } } @@ -1623,5 +1646,15 @@ pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std } Expression::ComputeLayoutInfo(..) => write!(f, "layout_info(..)"), Expression::SolveLayout(..) => write!(f, "solve_layout(..)"), + Expression::MinMax { ty: _, op, lhs, rhs } => { + match op { + MinMaxOp::Min => write!(f, "min(")?, + MinMaxOp::Max => write!(f, "max(")?, + } + pretty_print(f, lhs)?; + write!(f, ", ")?; + pretty_print(f, rhs)?; + write!(f, ")") + } } } diff --git a/internal/compiler/generator/cpp.rs b/internal/compiler/generator/cpp.rs index 75c487f99e1..89876f77c59 100644 --- a/internal/compiler/generator/cpp.rs +++ b/internal/compiler/generator/cpp.rs @@ -309,7 +309,7 @@ mod cpp_ast { } } -use crate::expression_tree::{BuiltinFunction, EasingCurve}; +use crate::expression_tree::{BuiltinFunction, EasingCurve, MinMaxOp}; use crate::langtype::{ElementType, Enumeration, EnumerationValue, NativeClass, Type}; use crate::layout::Orientation; use crate::llr::{ @@ -2776,6 +2776,21 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String ) } + Expression::MinMax { ty, op, lhs, rhs } => { + let ident = match op { + MinMaxOp::Min => "min", + MinMaxOp::Max => "max", + }; + let lhs_code = return_compile_expression(lhs, ctx, Some(&ty)); + let rhs_code = return_compile_expression(rhs, ctx, Some(&ty)); + format!( + r#"[&]() -> {} {{ using namespace std; return {}({}, {}); }}()"#, + ty.cpp_type().unwrap_or_else(|| "void".to_string()), + ident, + lhs_code, + rhs_code + ) + } } } diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index 1e59c970659..9edb64f7c9b 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -12,7 +12,7 @@ Some convention used in the generated code: this is usually a local variable to the init code that shouldn't rbe relied upon by the binding code. */ -use crate::expression_tree::{BuiltinFunction, EasingCurve, OperatorClass}; +use crate::expression_tree::{BuiltinFunction, EasingCurve, MinMaxOp, OperatorClass}; use crate::langtype::{ElementType, Enumeration, EnumerationValue, Type}; use crate::layout::Orientation; use crate::llr::{ @@ -2189,6 +2189,23 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream let #cells_variable = sp::Slice::from_slice(&#cells_variable); } } + Expression::MinMax { ty, op, lhs, rhs } => { + let t = rust_primitive_type(ty); + let wrap = |expr| match &t { + Some(t) => quote!((#expr as #t)), + None => expr, + }; + let lhs = wrap(compile_expression(lhs, ctx)); + let rhs = wrap(compile_expression(rhs, ctx)); + match op { + MinMaxOp::Min => { + quote!(#lhs.min(#rhs)) + } + MinMaxOp::Max => { + quote!(#lhs.max(#rhs)) + } + } + } } } diff --git a/internal/compiler/llr/expression.rs b/internal/compiler/llr/expression.rs index 0e0413d8458..1200dbc1159 100644 --- a/internal/compiler/llr/expression.rs +++ b/internal/compiler/llr/expression.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial use super::PropertyReference; -use crate::expression_tree::{BuiltinFunction, OperatorClass}; +use crate::expression_tree::{BuiltinFunction, MinMaxOp, OperatorClass}; use crate::langtype::Type; use crate::layout::Orientation; use itertools::Either; @@ -180,6 +180,13 @@ pub enum Expression { /// This is an Expression::Array unsorted_cells: Box, }, + + MinMax { + ty: Type, + op: MinMaxOp, + lhs: Box, + rhs: Box, + }, } impl Expression { @@ -292,6 +299,7 @@ impl Expression { Self::ComputeDialogLayoutCells { .. } => { Type::Array(super::lower_expression::grid_layout_cell_data_ty().into()) } + Self::MinMax { ty, .. } => ty.clone(), } } } @@ -374,6 +382,10 @@ macro_rules! visit_impl { $visitor(roles); $visitor(unsorted_cells); } + Expression::MinMax { ty: _, op: _, lhs, rhs } => { + $visitor(lhs); + $visitor(rhs); + } } }; } diff --git a/internal/compiler/llr/lower_expression.rs b/internal/compiler/llr/lower_expression.rs index 1da865d9cc7..725a2f8316c 100644 --- a/internal/compiler/llr/lower_expression.rs +++ b/internal/compiler/llr/lower_expression.rs @@ -197,6 +197,12 @@ pub fn lower_expression( } tree_Expression::ComputeLayoutInfo(l, o) => compute_layout_info(l, *o, ctx), tree_Expression::SolveLayout(l, o) => solve_layout(l, *o, ctx), + tree_Expression::MinMax { ty, op, lhs, rhs } => llr_Expression::MinMax { + ty: ty.clone(), + op: *op, + lhs: Box::new(lower_expression(lhs, ctx)), + rhs: Box::new(lower_expression(rhs, ctx)), + }, } } diff --git a/internal/compiler/llr/optim_passes/inline_expressions.rs b/internal/compiler/llr/optim_passes/inline_expressions.rs index 125468719be..262351185b1 100644 --- a/internal/compiler/llr/optim_passes/inline_expressions.rs +++ b/internal/compiler/llr/optim_passes/inline_expressions.rs @@ -54,6 +54,7 @@ fn expression_cost(exp: &Expression, ctx: &EvaluationContext) -> isize { Expression::LayoutCacheAccess { .. } => PROPERTY_ACCESS_COST, Expression::BoxLayoutFunction { .. } => return isize::MAX, Expression::ComputeDialogLayoutCells { .. } => return isize::MAX, + Expression::MinMax { .. } => 10, }; exp.visit(|e| cost = cost.saturating_add(expression_cost(e, ctx))); diff --git a/internal/compiler/llr/pretty_print.rs b/internal/compiler/llr/pretty_print.rs index 202c1b4116a..e4ba365aa17 100644 --- a/internal/compiler/llr/pretty_print.rs +++ b/internal/compiler/llr/pretty_print.rs @@ -5,6 +5,8 @@ use std::fmt::{Display, Result, Write}; use itertools::Itertools; +use crate::expression_tree::MinMaxOp; + use super::{ EvaluationContext, Expression, ParentCtx, PropertyReference, PublicComponent, SubComponent, }; @@ -250,6 +252,10 @@ impl<'a, T> Display for DisplayExpression<'a, T> { Expression::ComputeDialogLayoutCells { .. } => { write!(f, "ComputeDialogLayoutCells(TODO)",) } + Expression::MinMax { ty: _, op, lhs, rhs } => match op { + MinMaxOp::Min => write!(f, "min({}, {})", e(lhs), e(rhs)), + MinMaxOp::Max => write!(f, "max({}, {})", e(lhs), e(rhs)), + }, } } } diff --git a/internal/compiler/passes/default_geometry.rs b/internal/compiler/passes/default_geometry.rs index d26117c7fb6..558e92c5f25 100644 --- a/internal/compiler/passes/default_geometry.rs +++ b/internal/compiler/passes/default_geometry.rs @@ -14,7 +14,7 @@ use std::rc::Rc; use crate::diagnostics::{BuildDiagnostics, Spanned}; use crate::expression_tree::{ - BindingExpression, BuiltinFunction, Expression, NamedReference, Unit, + BindingExpression, BuiltinFunction, Expression, MinMaxOp, NamedReference, Unit, }; use crate::langtype::{BuiltinElement, DefaultSizeBinding, PropertyLookupResult, Type}; use crate::layout::{implicit_layout_info_call, LayoutConstraints, Orientation}; @@ -297,7 +297,7 @@ fn make_default_implicit(elem: &ElementRc, property: &str) { &format!("preferred-{}", property), )), Expression::PropertyReference(NamedReference::new(elem, &format!("min-{}", property))), - '>', + MinMaxOp::Max, ); elem.borrow_mut().set_binding_if_not_set(property.into(), || e); } diff --git a/internal/compiler/passes/flickable.rs b/internal/compiler/passes/flickable.rs index 269e21cc47f..4755d896302 100644 --- a/internal/compiler/passes/flickable.rs +++ b/internal/compiler/passes/flickable.rs @@ -13,7 +13,7 @@ use std::cell::RefCell; use std::rc::Rc; -use crate::expression_tree::{BindingExpression, Expression, NamedReference}; +use crate::expression_tree::{BindingExpression, Expression, MinMaxOp, NamedReference}; use crate::langtype::{ElementType, NativeClass}; use crate::object_tree::{Component, Element, ElementRc}; use crate::typeregister::TypeRegister; @@ -85,7 +85,7 @@ fn create_viewport_element(flickable: &ElementRc, native_empty: &Rc } fn fixup_geometry(flickable_elem: &ElementRc) { - let forward_minmax_of = |prop: &str, op: char| { + let forward_minmax_of = |prop: &str, op: MinMaxOp| { set_binding_if_not_explicit(flickable_elem, prop, || { flickable_elem .borrow() @@ -100,12 +100,12 @@ fn fixup_geometry(flickable_elem: &ElementRc) { }; if !flickable_elem.borrow().bindings.contains_key("height") { - forward_minmax_of("max-height", '<'); - forward_minmax_of("preferred-height", '<'); + forward_minmax_of("max-height", MinMaxOp::Min); + forward_minmax_of("preferred-height", MinMaxOp::Min); } if !flickable_elem.borrow().bindings.contains_key("width") { - forward_minmax_of("max-width", '<'); - forward_minmax_of("preferred-width", '<'); + forward_minmax_of("max-width", MinMaxOp::Min); + forward_minmax_of("preferred-width", MinMaxOp::Min); } set_binding_if_not_explicit(flickable_elem, "viewport-width", || { Some( @@ -119,7 +119,7 @@ fn fixup_geometry(flickable_elem: &ElementRc) { .map(|x| Expression::PropertyReference(NamedReference::new(x, "min-width"))) .fold( Expression::PropertyReference(NamedReference::new(flickable_elem, "width")), - |lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, '>'), + |lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, MinMaxOp::Max), ), ) }); @@ -135,7 +135,7 @@ fn fixup_geometry(flickable_elem: &ElementRc) { .map(|x| Expression::PropertyReference(NamedReference::new(x, "min-height"))) .fold( Expression::PropertyReference(NamedReference::new(flickable_elem, "height")), - |lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, '>'), + |lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, MinMaxOp::Max), ), ) }); diff --git a/internal/compiler/passes/lower_tabwidget.rs b/internal/compiler/passes/lower_tabwidget.rs index 72047dcf437..6bd23b5bafa 100644 --- a/internal/compiler/passes/lower_tabwidget.rs +++ b/internal/compiler/passes/lower_tabwidget.rs @@ -9,7 +9,7 @@ //! be further inlined as it may expends to native widget that needs inlining use crate::diagnostics::BuildDiagnostics; -use crate::expression_tree::{BindingExpression, Expression, NamedReference, Unit}; +use crate::expression_tree::{BindingExpression, Expression, MinMaxOp, NamedReference, Unit}; use crate::langtype::{ElementType, Type}; use crate::object_tree::*; use std::cell::RefCell; @@ -167,14 +167,14 @@ fn process_tabwidget( if let Some(expr) = children .iter() .map(|x| Expression::PropertyReference(NamedReference::new(x, "min-width"))) - .reduce(|lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, '>')) + .reduce(|lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, MinMaxOp::Max)) { elem.borrow_mut().bindings.insert("content-min-width".into(), RefCell::new(expr.into())); }; if let Some(expr) = children .iter() .map(|x| Expression::PropertyReference(NamedReference::new(x, "min-height"))) - .reduce(|lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, '>')) + .reduce(|lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, MinMaxOp::Max)) { elem.borrow_mut().bindings.insert("content-min-height".into(), RefCell::new(expr.into())); }; diff --git a/internal/interpreter/eval.rs b/internal/interpreter/eval.rs index 883e7d8d6d6..00d74f016f5 100644 --- a/internal/interpreter/eval.rs +++ b/internal/interpreter/eval.rs @@ -11,7 +11,8 @@ use corelib::model::{Model, ModelRc}; use corelib::rtti::AnimatedBindingKind; use corelib::{Brush, Color, PathData, SharedString, SharedVector}; use i_slint_compiler::expression_tree::{ - BuiltinFunction, EasingCurve, Expression, Path as ExprPath, PathElement as ExprPathElement, + BuiltinFunction, EasingCurve, Expression, MinMaxOp, Path as ExprPath, + PathElement as ExprPathElement, }; use i_slint_compiler::langtype::Type; use i_slint_compiler::object_tree::ElementRc; @@ -386,6 +387,18 @@ pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalCon } Expression::ComputeLayoutInfo(lay, o) => crate::eval_layout::compute_layout_info(lay, *o, local_context), Expression::SolveLayout(lay, o) => crate::eval_layout::solve_layout(lay, *o, local_context), + Expression::MinMax { ty: _, op, lhs, rhs } => { + let Value::Number(lhs) = eval_expression(lhs, local_context) else { + return local_context.return_value.clone().expect("minmax lhs expression did not evaluate to number"); + }; + let Value::Number(rhs) = eval_expression(rhs, local_context) else { + return local_context.return_value.clone().expect("minmax rhs expression did not evaluate to number"); + }; + match op { + MinMaxOp::Min => Value::Number(lhs.min(rhs)), + MinMaxOp::Max => Value::Number(lhs.max(rhs)), + } + } } }