mirror of
https://github.com/slint-ui/slint.git
synced 2025-11-01 20:31:27 +00:00
Added TextInputInterface.text-input-focused
This commit is contained in:
parent
b565eb8820
commit
a57c7eb6bc
17 changed files with 265 additions and 1 deletions
|
|
@ -12,6 +12,7 @@ All notable changes to this project are documented in this file.
|
|||
### Slint Language
|
||||
|
||||
- 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++
|
||||
|
||||
|
|
|
|||
|
|
@ -318,6 +318,8 @@ fn gen_corelib(
|
|||
"slint_windowrc_is_visible",
|
||||
"slint_windowrc_get_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_component",
|
||||
"slint_windowrc_show_popup",
|
||||
|
|
|
|||
|
|
@ -118,6 +118,12 @@ public:
|
|||
|
||||
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>
|
||||
void unregister_component(Component *c, ItemArray items) const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ to get started.
|
|||
src/builtins/elements.md
|
||||
src/builtins/enums.md
|
||||
src/builtins/functions.md
|
||||
src/builtins/globals.md
|
||||
src/builtins/namespaces.md
|
||||
src/builtins/structs.md
|
||||
src/builtins/widgets.md
|
||||
|
|
|
|||
30
docs/language/src/builtins/globals.md
Normal file
30
docs/language/src/builtins/globals.md
Normal 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 {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -415,6 +415,10 @@ export struct StateInfo {
|
|||
//-is_internal
|
||||
}
|
||||
|
||||
export global TextInputInterface {
|
||||
in property <bool> text-input-focused;
|
||||
}
|
||||
|
||||
export component NativeButton {
|
||||
in property <length> x;
|
||||
in property <length> y;
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ pub enum BuiltinFunction {
|
|||
ArrayLength,
|
||||
Rgb,
|
||||
DarkColorScheme,
|
||||
TextInputFocused,
|
||||
SetTextInputFocused,
|
||||
ImplicitLayoutInfo(Orientation),
|
||||
RegisterCustomFontByPath,
|
||||
RegisterCustomFontByMemory,
|
||||
|
|
@ -160,6 +162,12 @@ impl BuiltinFunction {
|
|||
BuiltinFunction::DarkColorScheme => {
|
||||
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 => {
|
||||
Type::Function { return_type: Box::new(Type::Void), args: vec![Type::String] }
|
||||
}
|
||||
|
|
@ -209,6 +217,8 @@ impl BuiltinFunction {
|
|||
BuiltinFunction::ImageSize => false,
|
||||
BuiltinFunction::ArrayLength => true,
|
||||
BuiltinFunction::Rgb => true,
|
||||
BuiltinFunction::SetTextInputFocused => false,
|
||||
BuiltinFunction::TextInputFocused => false,
|
||||
BuiltinFunction::ImplicitLayoutInfo(_) => false,
|
||||
BuiltinFunction::RegisterCustomFontByPath
|
||||
| BuiltinFunction::RegisterCustomFontByMemory
|
||||
|
|
@ -247,6 +257,8 @@ impl BuiltinFunction {
|
|||
BuiltinFunction::ArrayLength => true,
|
||||
BuiltinFunction::Rgb => true,
|
||||
BuiltinFunction::ImplicitLayoutInfo(_) => true,
|
||||
BuiltinFunction::SetTextInputFocused => false,
|
||||
BuiltinFunction::TextInputFocused => true,
|
||||
BuiltinFunction::RegisterCustomFontByPath
|
||||
| BuiltinFunction::RegisterCustomFontByMemory
|
||||
| BuiltinFunction::RegisterBitmapFont => false,
|
||||
|
|
|
|||
|
|
@ -2728,6 +2728,12 @@ fn compile_builtin_function_call(
|
|||
BuiltinFunction::DarkColorScheme => {
|
||||
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 => {
|
||||
if let [llr::Expression::NumberLiteral(popup_index), x, y, llr::Expression::PropertyReference(parent_ref)] =
|
||||
arguments
|
||||
|
|
|
|||
|
|
@ -2373,6 +2373,14 @@ fn compile_builtin_function_call(
|
|||
let window_adapter_tokens = access_window_adapter_field(ctx);
|
||||
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)*))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@ fn builtin_function_cost(function: BuiltinFunction) -> isize {
|
|||
BuiltinFunction::RegisterCustomFontByMemory => isize::MAX,
|
||||
BuiltinFunction::RegisterBitmapFont => isize::MAX,
|
||||
BuiltinFunction::DarkColorScheme => isize::MAX,
|
||||
BuiltinFunction::SetTextInputFocused => PROPERTY_ACCESS_COST,
|
||||
BuiltinFunction::TextInputFocused => PROPERTY_ACCESS_COST,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ mod lower_property_to_element;
|
|||
mod lower_shadows;
|
||||
mod lower_states;
|
||||
mod lower_tabwidget;
|
||||
mod lower_text_input_interface;
|
||||
mod materialize_fake_properties;
|
||||
mod move_declarations;
|
||||
mod optimize_useless_rectangles;
|
||||
|
|
@ -90,6 +91,7 @@ pub async fn run_passes(
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// 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
|
||||
// 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;
|
||||
|
||||
|
|
|
|||
51
internal/compiler/passes/lower_text_input_interface.rs
Normal file
51
internal/compiler/passes/lower_text_input_interface.rs
Normal 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
|
||||
}
|
||||
|
|
@ -560,11 +560,13 @@ impl Item for TextInput {
|
|||
FocusEvent::FocusIn | FocusEvent::WindowReceivedFocus => {
|
||||
self.has_focus.set(true);
|
||||
self.show_cursor(window_adapter);
|
||||
WindowInner::from_pub(window_adapter.window()).set_text_input_focused(true);
|
||||
window_adapter.enable_input_method(self.input_type());
|
||||
}
|
||||
FocusEvent::FocusOut | FocusEvent::WindowLostFocus => {
|
||||
self.has_focus.set(false);
|
||||
self.hide_cursor();
|
||||
WindowInner::from_pub(window_adapter.window()).set_text_input_focused(false);
|
||||
window_adapter.disable_input_method();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,6 +221,8 @@ struct WindowPinnedFields {
|
|||
scale_factor: Property<f32>,
|
||||
#[pin]
|
||||
active: Property<bool>,
|
||||
#[pin]
|
||||
text_input_focused: Property<bool>,
|
||||
}
|
||||
|
||||
/// Inner datastructure for the [`crate::api::Window`]
|
||||
|
|
@ -282,6 +284,10 @@ impl WindowInner {
|
|||
window_properties_tracker: window_properties_tracker,
|
||||
scale_factor: Property::new_named(1., "i_slint_core::Window::scale_factor"),
|
||||
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(),
|
||||
cursor_blinker: Default::default(),
|
||||
|
|
@ -745,6 +751,16 @@ impl WindowInner {
|
|||
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.
|
||||
pub fn window_item(&self) -> Option<VRcMapped<ComponentVTable, crate::items::WindowItem>> {
|
||||
self.try_component().and_then(|component_rc| {
|
||||
|
|
@ -893,6 +909,29 @@ pub mod ffi {
|
|||
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.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn slint_windowrc_set_focus_item(
|
||||
|
|
|
|||
|
|
@ -678,6 +678,25 @@ fn call_builtin_function(
|
|||
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) => {
|
||||
let component = match local_context.component_instance {
|
||||
ComponentInstance::InstanceRef(c) => c,
|
||||
|
|
|
|||
79
tests/cases/focus/text-input-focused.slint
Normal file
79
tests/cases/focus/text-input-focused.slint
Normal 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);
|
||||
```
|
||||
*/
|
||||
Loading…
Add table
Add a link
Reference in a new issue