diff --git a/api/cpp/include/slint.h b/api/cpp/include/slint.h index e5dacba0d06..21571f2fbb9 100644 --- a/api/cpp/include/slint.h +++ b/api/cpp/include/slint.h @@ -356,6 +356,11 @@ inline bool open_url(const SharedString &url, const WindowAdapterRc &window_adap return cbindgen_private::slint_open_url(&url, &window_adapter.handle()); } +inline void bring_all_to_front() +{ + cbindgen_private::slint_bring_all_to_front(); +} + inline SharedString translate_from_bundle(std::span strs, cbindgen_private::Slice arguments) { diff --git a/api/cpp/lib.rs b/api/cpp/lib.rs index 964a2f0b2bb..3f99dfd59db 100644 --- a/api/cpp/lib.rs +++ b/api/cpp/lib.rs @@ -275,6 +275,11 @@ pub unsafe extern "C" fn slint_open_url( i_slint_core::open_url(url, window_adapter.window()).is_ok() } +#[unsafe(no_mangle)] +pub extern "C" fn slint_bring_all_to_front() { + i_slint_core::bring_all_to_front() +} + #[unsafe(no_mangle)] pub extern "C" fn slint_string_to_styled_text(text: &SharedString, out: &mut StyledText) { *out = i_slint_core::styled_text::string_to_styled_text(text.to_string()); diff --git a/api/rs/slint/private_unstable_api.rs b/api/rs/slint/private_unstable_api.rs index 5c39dec1747..64f3152699e 100644 --- a/api/rs/slint/private_unstable_api.rs +++ b/api/rs/slint/private_unstable_api.rs @@ -174,6 +174,7 @@ pub mod re_exports { }; pub use i_slint_core::animations::{EasingCurve, animation_tick, current_tick}; pub use i_slint_core::api::LogicalPosition; + pub use i_slint_core::bring_all_to_front; pub use i_slint_core::callbacks::Callback; pub use i_slint_core::context::SlintContext; pub use i_slint_core::data_transfer::DataTransfer; diff --git a/internal/compiler/builtins.slint b/internal/compiler/builtins.slint index f7937db283b..434741307e8 100644 --- a/internal/compiler/builtins.slint +++ b/internal/compiler/builtins.slint @@ -2740,6 +2740,13 @@ export global Platform { function open-url(url: string) -> bool { return false; } + /// Brings all application windows to the front of the screen. + /// + /// On macOS this invokes `[NSApp arrangeInFront:]`, which raises every application window + /// to the top of the window stack. On other platforms this function is a no-op. + /// + /// This corresponds to the standard macOS **Window › Bring All to Front** menu item. + function bring-all-to-front() { } /// The decimal separator currently used for localized float to string and vice versa conversions /// For more information about localization and how to change the decimal separator see . out property decimal-separator; diff --git a/internal/compiler/expression_tree.rs b/internal/compiler/expression_tree.rs index 5e01fcf29e3..c415d5c6b04 100644 --- a/internal/compiler/expression_tree.rs +++ b/internal/compiler/expression_tree.rs @@ -123,6 +123,7 @@ pub enum BuiltinFunction { StopTimer, RestartTimer, OpenUrl, + BringAllToFront, ParseMarkdown, StringToStyledText, DecimalSeparator, @@ -305,6 +306,7 @@ declare_builtin_function_types!( ParseMarkdown: (Type::String, Type::Array(Type::StyledText.into())) -> Type::StyledText, StringToStyledText: (Type::String) -> Type::StyledText OpenUrl: (Type::String) -> Type::Bool, + BringAllToFront: () -> Type::Void, ); impl Default for BuiltinFunctionTypes { @@ -415,6 +417,7 @@ impl BuiltinFunction { BuiltinFunction::ParseMarkdown => false, BuiltinFunction::StringToStyledText => true, BuiltinFunction::OpenUrl => false, + BuiltinFunction::BringAllToFront => false, } } @@ -501,6 +504,7 @@ impl BuiltinFunction { BuiltinFunction::ParseMarkdown => true, BuiltinFunction::StringToStyledText => true, BuiltinFunction::OpenUrl => false, + BuiltinFunction::BringAllToFront => false, } } } diff --git a/internal/compiler/generator/cpp.rs b/internal/compiler/generator/cpp.rs index 8ef1ab35532..6a555d0e580 100644 --- a/internal/compiler/generator/cpp.rs +++ b/internal/compiler/generator/cpp.rs @@ -4932,6 +4932,9 @@ fn compile_builtin_function_call( let window = access_window_field(ctx); format!("slint::private_api::open_url({url}, {window})") } + BuiltinFunction::BringAllToFront => { + "slint::private_api::bring_all_to_front()".to_owned() + } BuiltinFunction::ParseMarkdown => { let format_string = a.next().unwrap(); let args = a.next().unwrap(); diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index ef701359fc0..5e38b299388 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -4164,6 +4164,9 @@ fn compile_builtin_function_call( let window_adapter_tokens = access_window_adapter_field(ctx); quote!(sp::open_url(&#url, #window_adapter_tokens.window()).is_ok()) } + BuiltinFunction::BringAllToFront => { + quote!(sp::bring_all_to_front()) + } BuiltinFunction::ParseMarkdown => { let format_string = a.next().unwrap(); let args = a.next().unwrap(); diff --git a/internal/compiler/llr/optim_passes/inline_expressions.rs b/internal/compiler/llr/optim_passes/inline_expressions.rs index 5b8959d1da5..49447d045d0 100644 --- a/internal/compiler/llr/optim_passes/inline_expressions.rs +++ b/internal/compiler/llr/optim_passes/inline_expressions.rs @@ -168,6 +168,7 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize { BuiltinFunction::ParseMarkdown => isize::MAX, BuiltinFunction::StringToStyledText => ALLOC_COST, BuiltinFunction::OpenUrl => isize::MAX, + BuiltinFunction::BringAllToFront => isize::MAX, } } diff --git a/internal/compiler/passes/lower_platform.rs b/internal/compiler/passes/lower_platform.rs index 7daaf4b7c1d..a0b89dc233d 100644 --- a/internal/compiler/passes/lower_platform.rs +++ b/internal/compiler/passes/lower_platform.rs @@ -40,6 +40,12 @@ pub fn lower_platform(component: &Rc, type_loader: &mut crate::typelo { *function = BuiltinFunction::OpenUrl.into(); } + Expression::FunctionCall { function, .. } + if matches!(&*function, Callable::Function(nr) + if is_platform(nr) && nr.name() == "bring-all-to-front") => + { + *function = BuiltinFunction::BringAllToFront.into(); + } _ => {} }) }) diff --git a/internal/core/lib.rs b/internal/core/lib.rs index 401206892fa..fdb772ffe28 100644 --- a/internal/core/lib.rs +++ b/internal/core/lib.rs @@ -175,3 +175,14 @@ pub fn is_apple_platform() -> bool { pub fn open_url(url: &str, window: &crate::api::Window) -> Result<(), crate::api::PlatformError> { crate::window::WindowInner::from_pub(window).context().platform().open_url(url) } + +#[cfg(target_os = "macos")] +pub fn bring_all_to_front() { + use objc2::MainThreadMarker; + use objc2_app_kit::NSApplication; + let Some(mtm) = MainThreadMarker::new() else { return }; + NSApplication::sharedApplication(mtm).arrangeInFront(None); +} + +#[cfg(not(target_os = "macos"))] +pub fn bring_all_to_front() {} diff --git a/internal/interpreter/eval.rs b/internal/interpreter/eval.rs index 88895f2697c..9332d0a244a 100644 --- a/internal/interpreter/eval.rs +++ b/internal/interpreter/eval.rs @@ -1724,6 +1724,10 @@ fn call_builtin_function( let window_adapter = local_context.component_instance.window_adapter(); Value::Bool(corelib::open_url(&url, window_adapter.window()).is_ok()) } + BuiltinFunction::BringAllToFront => { + corelib::bring_all_to_front(); + Value::Void + } BuiltinFunction::ParseMarkdown => { let format_string: SharedString = eval_expression(&arguments[0], local_context).try_into().unwrap(); diff --git a/tests/cases/globals/bring_all_to_front.slint b/tests/cases/globals/bring_all_to_front.slint new file mode 100644 index 00000000000..adf82104cf0 --- /dev/null +++ b/tests/cases/globals/bring_all_to_front.slint @@ -0,0 +1,31 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +// Test that Platform.bring-all-to-front() compiles and runs (it is a no-op on non-macOS). + +export component TestCase inherits Window { + public function do-bring-all-to-front() { + Platform.bring-all-to-front(); + } + out property test: true; +} + +/* +```rust +let instance = TestCase::new().unwrap(); +instance.invoke_do_bring_all_to_front(); +assert!(instance.get_test()); +``` + +```cpp +auto instance = TestCase::create(); +instance->invoke_do_bring_all_to_front(); +assert(instance->get_test()); +``` + +```js +let instance = new slint.TestCase({}); +instance.do_bring_all_to_front(); +assert(instance.test); +``` +*/