Added TextInputInterface.text-input-focused

This commit is contained in:
Olivier Goffart 2023-04-12 13:36:58 +02:00 committed by Olivier Goffart
parent b565eb8820
commit a57c7eb6bc
17 changed files with 265 additions and 1 deletions

View file

@ -12,6 +12,7 @@ All notable changes to this project are documented in this file.
### Slint Language ### Slint Language
- Fixed parent `FocusScope` objects stealing the focus from inner `FocusScope`s when clicked. - Fixed parent `FocusScope` objects stealing the focus from inner `FocusScope`s when clicked.
- Added `TextInputInterface.text-input-focused` to detect when a virtual keyboard should open
### C++ ### C++

View file

@ -318,6 +318,8 @@ fn gen_corelib(
"slint_windowrc_is_visible", "slint_windowrc_is_visible",
"slint_windowrc_get_scale_factor", "slint_windowrc_get_scale_factor",
"slint_windowrc_set_scale_factor", "slint_windowrc_set_scale_factor",
"slint_windowrc_get_text_input_focused",
"slint_windowrc_set_text_input_focused",
"slint_windowrc_set_focus_item", "slint_windowrc_set_focus_item",
"slint_windowrc_set_component", "slint_windowrc_set_component",
"slint_windowrc_show_popup", "slint_windowrc_show_popup",

View file

@ -118,6 +118,12 @@ public:
bool dark_color_scheme() const { return slint_windowrc_dark_color_scheme(&inner); } bool dark_color_scheme() const { return slint_windowrc_dark_color_scheme(&inner); }
bool text_input_focused() const { return slint_windowrc_get_text_input_focused(&inner); }
void set_text_input_focused(bool value) const
{
slint_windowrc_set_text_input_focused(&inner, value);
}
template<typename Component, typename ItemArray> template<typename Component, typename ItemArray>
void unregister_component(Component *c, ItemArray items) const void unregister_component(Component *c, ItemArray items) const
{ {

View file

@ -94,6 +94,7 @@ to get started.
src/builtins/elements.md src/builtins/elements.md
src/builtins/enums.md src/builtins/enums.md
src/builtins/functions.md src/builtins/functions.md
src/builtins/globals.md
src/builtins/namespaces.md src/builtins/namespaces.md
src/builtins/structs.md src/builtins/structs.md
src/builtins/widgets.md src/builtins/widgets.md

View file

@ -0,0 +1,30 @@
# Builtin Global Singletons
## `TextInputInterface`
The `TextInputInterface.text-input-focused` property can be used to find out if a `TextInput` element has the focus.
If you're implementing your own virtual keyboard, this property is an indicator whether the virtual keyboard should be shown or hidden.
### Properties
- **`text-input-focused`** (_bool_): True if an `TextInput` element has the focus; false otherwise.
### Example
```slint
import { LineEdit } from "std-widgets.slint";
component VKB {
Rectangle { background: yellow; }
}
export component Example inherits Window {
width: 200px;
height: 100px;
VerticalLayout {
LineEdit {}
FocusScope {}
if TextInputInterface.text-input-focused: VKB {}
}
}
```

View file

@ -415,6 +415,10 @@ export struct StateInfo {
//-is_internal //-is_internal
} }
export global TextInputInterface {
in property <bool> text-input-focused;
}
export component NativeButton { export component NativeButton {
in property <length> x; in property <length> x;
in property <length> y; in property <length> y;

View file

@ -49,6 +49,8 @@ pub enum BuiltinFunction {
ArrayLength, ArrayLength,
Rgb, Rgb,
DarkColorScheme, DarkColorScheme,
TextInputFocused,
SetTextInputFocused,
ImplicitLayoutInfo(Orientation), ImplicitLayoutInfo(Orientation),
RegisterCustomFontByPath, RegisterCustomFontByPath,
RegisterCustomFontByMemory, RegisterCustomFontByMemory,
@ -160,6 +162,12 @@ impl BuiltinFunction {
BuiltinFunction::DarkColorScheme => { BuiltinFunction::DarkColorScheme => {
Type::Function { return_type: Box::new(Type::Bool), args: vec![] } Type::Function { return_type: Box::new(Type::Bool), args: vec![] }
} }
BuiltinFunction::TextInputFocused => {
Type::Function { return_type: Box::new(Type::Bool), args: vec![] }
}
BuiltinFunction::SetTextInputFocused => {
Type::Function { return_type: Box::new(Type::Void), args: vec![Type::Bool] }
}
BuiltinFunction::RegisterCustomFontByPath => { BuiltinFunction::RegisterCustomFontByPath => {
Type::Function { return_type: Box::new(Type::Void), args: vec![Type::String] } Type::Function { return_type: Box::new(Type::Void), args: vec![Type::String] }
} }
@ -209,6 +217,8 @@ impl BuiltinFunction {
BuiltinFunction::ImageSize => false, BuiltinFunction::ImageSize => false,
BuiltinFunction::ArrayLength => true, BuiltinFunction::ArrayLength => true,
BuiltinFunction::Rgb => true, BuiltinFunction::Rgb => true,
BuiltinFunction::SetTextInputFocused => false,
BuiltinFunction::TextInputFocused => false,
BuiltinFunction::ImplicitLayoutInfo(_) => false, BuiltinFunction::ImplicitLayoutInfo(_) => false,
BuiltinFunction::RegisterCustomFontByPath BuiltinFunction::RegisterCustomFontByPath
| BuiltinFunction::RegisterCustomFontByMemory | BuiltinFunction::RegisterCustomFontByMemory
@ -247,6 +257,8 @@ impl BuiltinFunction {
BuiltinFunction::ArrayLength => true, BuiltinFunction::ArrayLength => true,
BuiltinFunction::Rgb => true, BuiltinFunction::Rgb => true,
BuiltinFunction::ImplicitLayoutInfo(_) => true, BuiltinFunction::ImplicitLayoutInfo(_) => true,
BuiltinFunction::SetTextInputFocused => false,
BuiltinFunction::TextInputFocused => true,
BuiltinFunction::RegisterCustomFontByPath BuiltinFunction::RegisterCustomFontByPath
| BuiltinFunction::RegisterCustomFontByMemory | BuiltinFunction::RegisterCustomFontByMemory
| BuiltinFunction::RegisterBitmapFont => false, | BuiltinFunction::RegisterBitmapFont => false,

View file

@ -2728,6 +2728,12 @@ fn compile_builtin_function_call(
BuiltinFunction::DarkColorScheme => { BuiltinFunction::DarkColorScheme => {
format!("{}.dark_color_scheme()", access_window_field(ctx)) format!("{}.dark_color_scheme()", access_window_field(ctx))
} }
BuiltinFunction::SetTextInputFocused => {
format!("{}.set_text_input_focused({})", access_window_field(ctx), a.next().unwrap())
}
BuiltinFunction::TextInputFocused => {
format!("{}.text_input_focused()", access_window_field(ctx))
}
BuiltinFunction::ShowPopupWindow => { BuiltinFunction::ShowPopupWindow => {
if let [llr::Expression::NumberLiteral(popup_index), x, y, llr::Expression::PropertyReference(parent_ref)] = if let [llr::Expression::NumberLiteral(popup_index), x, y, llr::Expression::PropertyReference(parent_ref)] =
arguments arguments

View file

@ -2373,6 +2373,14 @@ fn compile_builtin_function_call(
let window_adapter_tokens = access_window_adapter_field(ctx); let window_adapter_tokens = access_window_adapter_field(ctx);
quote!(#window_adapter_tokens.dark_color_scheme()) quote!(#window_adapter_tokens.dark_color_scheme())
} }
BuiltinFunction::TextInputFocused => {
let window_adapter_tokens = access_window_adapter_field(ctx);
quote!(slint::private_unstable_api::re_exports::WindowInner::from_pub(#window_adapter_tokens.window()).text_input_focused())
}
BuiltinFunction::SetTextInputFocused => {
let window_adapter_tokens = access_window_adapter_field(ctx);
quote!(slint::private_unstable_api::re_exports::WindowInner::from_pub(#window_adapter_tokens.window()).set_text_input_focused(#(#a)*))
}
} }
} }

View file

@ -100,6 +100,8 @@ fn builtin_function_cost(function: BuiltinFunction) -> isize {
BuiltinFunction::RegisterCustomFontByMemory => isize::MAX, BuiltinFunction::RegisterCustomFontByMemory => isize::MAX,
BuiltinFunction::RegisterBitmapFont => isize::MAX, BuiltinFunction::RegisterBitmapFont => isize::MAX,
BuiltinFunction::DarkColorScheme => isize::MAX, BuiltinFunction::DarkColorScheme => isize::MAX,
BuiltinFunction::SetTextInputFocused => PROPERTY_ACCESS_COST,
BuiltinFunction::TextInputFocused => PROPERTY_ACCESS_COST,
} }
} }

View file

@ -32,6 +32,7 @@ mod lower_property_to_element;
mod lower_shadows; mod lower_shadows;
mod lower_states; mod lower_states;
mod lower_tabwidget; mod lower_tabwidget;
mod lower_text_input_interface;
mod materialize_fake_properties; mod materialize_fake_properties;
mod move_declarations; mod move_declarations;
mod optimize_useless_rectangles; mod optimize_useless_rectangles;
@ -90,6 +91,7 @@ pub async fn run_passes(
diag, diag,
); );
lower_states::lower_states(component, &doc.local_registry, diag); lower_states::lower_states(component, &doc.local_registry, diag);
lower_text_input_interface::lower_text_input_interface(component);
} }
inlining::inline(doc, inlining::InlineSelection::InlineOnlyRequiredComponents); inlining::inline(doc, inlining::InlineSelection::InlineOnlyRequiredComponents);

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
//! This pass follows the forward-focus property on the root element to determine the initial focus item //! This pass follows the forward-focus property on the root element to determine the initial focus item
// as well as handle the forward for `focus()` calls in code. //! as well as handle the forward for `focus()` calls in code.
use std::rc::Rc; use std::rc::Rc;

View file

@ -0,0 +1,51 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
//! Passe lower the access to the global TextInputInterface.text-input-focused to getter or setter.
use crate::expression_tree::{BuiltinFunction, Expression};
use crate::namedreference::NamedReference;
use crate::object_tree::{visit_all_expressions, Component};
use std::rc::Rc;
pub fn lower_text_input_interface(component: &Rc<Component>) {
visit_all_expressions(component, |e, _| {
e.visit_recursive_mut(&mut |e| match e {
Expression::PropertyReference(nr) if is_input_text_focused_prop(nr) => {
*e = Expression::FunctionCall {
function: Expression::BuiltinFunctionReference(
BuiltinFunction::TextInputFocused,
None,
)
.into(),
arguments: vec![],
source_location: None,
};
}
Expression::SelfAssignment{ lhs, rhs, .. } => {
if matches!(&**lhs, Expression::PropertyReference(nr) if is_input_text_focused_prop(nr) ) {
let rhs = std::mem::take(&mut **rhs);
*e = Expression::FunctionCall {
function: Expression::BuiltinFunctionReference(
BuiltinFunction::SetTextInputFocused,
None,
)
.into(),
arguments: vec![rhs],
source_location: None,
};
}
}
_ => {}
})
})
}
fn is_input_text_focused_prop(nr: &NamedReference) -> bool {
if !nr.element().borrow().builtin_type().map_or(false, |bt| bt.name == "TextInputInterface") {
return false;
}
assert_eq!(nr.name(), "text-input-focused");
true
}

View file

@ -560,11 +560,13 @@ impl Item for TextInput {
FocusEvent::FocusIn | FocusEvent::WindowReceivedFocus => { FocusEvent::FocusIn | FocusEvent::WindowReceivedFocus => {
self.has_focus.set(true); self.has_focus.set(true);
self.show_cursor(window_adapter); self.show_cursor(window_adapter);
WindowInner::from_pub(window_adapter.window()).set_text_input_focused(true);
window_adapter.enable_input_method(self.input_type()); window_adapter.enable_input_method(self.input_type());
} }
FocusEvent::FocusOut | FocusEvent::WindowLostFocus => { FocusEvent::FocusOut | FocusEvent::WindowLostFocus => {
self.has_focus.set(false); self.has_focus.set(false);
self.hide_cursor(); self.hide_cursor();
WindowInner::from_pub(window_adapter.window()).set_text_input_focused(false);
window_adapter.disable_input_method(); window_adapter.disable_input_method();
} }
} }

View file

@ -221,6 +221,8 @@ struct WindowPinnedFields {
scale_factor: Property<f32>, scale_factor: Property<f32>,
#[pin] #[pin]
active: Property<bool>, active: Property<bool>,
#[pin]
text_input_focused: Property<bool>,
} }
/// Inner datastructure for the [`crate::api::Window`] /// Inner datastructure for the [`crate::api::Window`]
@ -282,6 +284,10 @@ impl WindowInner {
window_properties_tracker: window_properties_tracker, window_properties_tracker: window_properties_tracker,
scale_factor: Property::new_named(1., "i_slint_core::Window::scale_factor"), scale_factor: Property::new_named(1., "i_slint_core::Window::scale_factor"),
active: Property::new_named(false, "i_slint_core::Window::active"), active: Property::new_named(false, "i_slint_core::Window::active"),
text_input_focused: Property::new_named(
false,
"i_slint_core::Window::text_input_focused",
),
}), }),
focus_item: Default::default(), focus_item: Default::default(),
cursor_blinker: Default::default(), cursor_blinker: Default::default(),
@ -745,6 +751,16 @@ impl WindowInner {
self.pinned_fields.scale_factor.set(factor) self.pinned_fields.scale_factor.set(factor)
} }
/// Returns the scale factor set on the window, as provided by the windowing system.
pub fn text_input_focused(&self) -> bool {
self.pinned_fields.as_ref().project_ref().text_input_focused.get()
}
/// Sets the scale factor for the window. This is set by the backend or for testing.
pub fn set_text_input_focused(&self, value: bool) {
self.pinned_fields.text_input_focused.set(value)
}
/// Returns the window item that is the first item in the component. /// Returns the window item that is the first item in the component.
pub fn window_item(&self) -> Option<VRcMapped<ComponentVTable, crate::items::WindowItem>> { pub fn window_item(&self) -> Option<VRcMapped<ComponentVTable, crate::items::WindowItem>> {
self.try_component().and_then(|component_rc| { self.try_component().and_then(|component_rc| {
@ -893,6 +909,29 @@ pub mod ffi {
WindowInner::from_pub(window_adapter.window()).set_scale_factor(value) WindowInner::from_pub(window_adapter.window()).set_scale_factor(value)
} }
/// Returns the text-input-focused property value.
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_get_text_input_focused(
handle: *const WindowAdapterRcOpaque,
) -> bool {
assert_eq!(
core::mem::size_of::<Rc<dyn WindowAdapter>>(),
core::mem::size_of::<WindowAdapterRcOpaque>()
);
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
WindowInner::from_pub(window_adapter.window()).text_input_focused()
}
/// Set the text-input-focused property.
#[no_mangle]
pub unsafe extern "C" fn slint_windowrc_set_text_input_focused(
handle: *const WindowAdapterRcOpaque,
value: bool,
) {
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
WindowInner::from_pub(window_adapter.window()).set_text_input_focused(value)
}
/// Sets the focus item. /// Sets the focus item.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn slint_windowrc_set_focus_item( pub unsafe extern "C" fn slint_windowrc_set_focus_item(

View file

@ -678,6 +678,25 @@ fn call_builtin_function(
panic!("Cannot get the window from a global component") panic!("Cannot get the window from a global component")
} }
}, },
BuiltinFunction::TextInputFocused => match local_context.component_instance {
ComponentInstance::InstanceRef(component) => {
Value::Bool(window_ref(component).unwrap().text_input_focused() as _)
}
ComponentInstance::GlobalComponent(_) => {
panic!("Cannot get the window from a global component")
}
},
BuiltinFunction::SetTextInputFocused => match local_context.component_instance {
ComponentInstance::InstanceRef(component) => {
window_ref(component).unwrap().set_text_input_focused(
eval_expression(&arguments[0], local_context).try_into().unwrap(),
);
Value::Void
}
ComponentInstance::GlobalComponent(_) => {
panic!("Cannot get the window from a global component")
}
},
BuiltinFunction::ImplicitLayoutInfo(orient) => { BuiltinFunction::ImplicitLayoutInfo(orient) => {
let component = match local_context.component_instance { let component = match local_context.component_instance {
ComponentInstance::InstanceRef(c) => c, ComponentInstance::InstanceRef(c) => c,

View file

@ -0,0 +1,79 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
import { LineEdit } from "std-widgets.slint";
TestCase := Rectangle {
public function focus_the_line_edit() {
le.focus();
}
public function focus_the_focus_scope() {
fs.focus();
}
public function set_manual(v:bool) {
TextInputInterface.text-input-focused = v;
}
le := LineEdit { }
fs := FocusScope { }
out property<bool> focused: TextInputInterface.text-input-focused;
}
/*
```rust
let instance = TestCase::new().unwrap();
assert!(!instance.get_focused());
instance.invoke_focus_the_line_edit();
assert!(instance.get_focused());
instance.invoke_focus_the_focus_scope();
assert!(!instance.get_focused());
instance.invoke_focus_the_line_edit();
assert!(instance.get_focused());
instance.invoke_set_manual(false);
assert!(!instance.get_focused());
instance.invoke_focus_the_focus_scope();
assert!(!instance.get_focused());
instance.invoke_focus_the_line_edit();
assert!(instance.get_focused());
```
```cpp
auto handle = TestCase::create();
const TestCase &instance = *handle;
assert(!instance.get_focused());
instance.invoke_focus_the_line_edit();
assert(instance.get_focused());
instance.invoke_focus_the_focus_scope();
assert(!instance.get_focused());
instance.invoke_focus_the_line_edit();
assert(instance.get_focused());
instance.invoke_set_manual(false);
assert(!instance.get_focused());
instance.invoke_focus_the_focus_scope();
assert(!instance.get_focused());
instance.invoke_focus_the_line_edit();
assert(instance.get_focused());
```
```js
var instance = new slint.TestCase();
assert(!instance.focused);
instance.focus_the_line_edit();
assert(instance.focused);
instance.focus_the_focus_scope();
assert(!instance.focused);
instance.focus_the_line_edit();
assert(instance.focused);
instance.set_manual(false);
assert(!instance.focused);
instance.focus_the_focus_scope();
assert(!instance.focused);
instance.focus_the_line_edit();
assert(instance.focused);
```
*/