mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-29 21:34:50 +00:00
Add a clear-focus()
function on all elements that have a focus()
function
This is the counter-part, which removes focus from the element if it's currently focused. The window - if focused - may still be focused towards the windowing system.
This commit is contained in:
parent
e10e97c944
commit
31767eb6ab
21 changed files with 268 additions and 23 deletions
|
@ -25,6 +25,7 @@ All notable changes to this project are documented in this file.
|
||||||
- Fixed compiler panic with state property change involving a state in a parent component. (#5038)
|
- Fixed compiler panic with state property change involving a state in a parent component. (#5038)
|
||||||
- Fixed interpreter overwriting property named `index`. (#4961)
|
- Fixed interpreter overwriting property named `index`. (#4961)
|
||||||
- Fixed compiler panic when a callback aliases itself. (#4938)
|
- Fixed compiler panic when a callback aliases itself. (#4938)
|
||||||
|
- Added `clear-focus()` function to focusable elements, to allow for programmatic focus clearing.
|
||||||
|
|
||||||
## Widgets
|
## Widgets
|
||||||
|
|
||||||
|
|
|
@ -93,10 +93,10 @@ public:
|
||||||
items, &inner);
|
items, &inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_focus_item(const ItemTreeRc &component_rc, uint32_t item_index)
|
void set_focus_item(const ItemTreeRc &component_rc, uint32_t item_index, bool set_focus)
|
||||||
{
|
{
|
||||||
cbindgen_private::ItemRc item_rc { component_rc, item_index };
|
cbindgen_private::ItemRc item_rc { component_rc, item_index };
|
||||||
cbindgen_private::slint_windowrc_set_focus_item(&inner, &item_rc);
|
cbindgen_private::slint_windowrc_set_focus_item(&inner, &item_rc, set_focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Component>
|
template<typename Component>
|
||||||
|
|
|
@ -216,6 +216,7 @@ or it will be mapped to a private unicode character. The mapping of these non-pr
|
||||||
|
|
||||||
- **`focus()`** Call this function to transfer keyboard focus to this `FocusScope`,
|
- **`focus()`** Call this function to transfer keyboard focus to this `FocusScope`,
|
||||||
to receive future [`KeyEvent`](structs.md#keyevent)s.
|
to receive future [`KeyEvent`](structs.md#keyevent)s.
|
||||||
|
- **`clear-focus()`** Call this function to remove keyboard focus from this `FocusScope` if it currently has the focus.
|
||||||
|
|
||||||
### Callbacks
|
### Callbacks
|
||||||
|
|
||||||
|
@ -680,6 +681,7 @@ When not part of a layout, its width or height defaults to 100% of the parent el
|
||||||
### Functions
|
### Functions
|
||||||
|
|
||||||
- **`focus()`** Call this function to focus the text input and make it receive future keyboard events.
|
- **`focus()`** Call this function to focus the text input and make it receive future keyboard events.
|
||||||
|
- **`clear-focus()`** Call this function to remove keyboard focus from this `TextInput` if it currently has the focus.
|
||||||
- **`set-selection-offsets(int, int)`** Selects the text between two UTF-8 offsets.
|
- **`set-selection-offsets(int, int)`** Selects the text between two UTF-8 offsets.
|
||||||
- **`select-all()`** Selects all text.
|
- **`select-all()`** Selects all text.
|
||||||
- **`clear-selection()`** Clears the selection.
|
- **`clear-selection()`** Clears the selection.
|
||||||
|
|
|
@ -19,6 +19,7 @@ a widget able to handle several lines of text.
|
||||||
### Functions
|
### Functions
|
||||||
|
|
||||||
- **`focus()`** Call this function to focus the LineEdit and make it receive future keyboard events.
|
- **`focus()`** Call this function to focus the LineEdit and make it receive future keyboard events.
|
||||||
|
- **`clear-focus()`** Call this function to remove keyboard focus from this `LineEdit` if it currently has the focus.
|
||||||
- **`set-selection-offsets(int, int)`** Selects the text between two UTF-8 offsets.
|
- **`set-selection-offsets(int, int)`** Selects the text between two UTF-8 offsets.
|
||||||
- **`select-all()`** Selects all text.
|
- **`select-all()`** Selects all text.
|
||||||
- **`clear-selection()`** Clears the selection.
|
- **`clear-selection()`** Clears the selection.
|
||||||
|
|
|
@ -19,6 +19,7 @@ shortcut will be implemented in a future version: <https://github.com/slint-ui/s
|
||||||
### Functions
|
### Functions
|
||||||
|
|
||||||
- **`focus()`** Call this function to focus the TextEdit and make it receive future keyboard events.
|
- **`focus()`** Call this function to focus the TextEdit and make it receive future keyboard events.
|
||||||
|
- **`clear-focus()`** Call this function to remove keyboard focus from this `TextEdit` if it currently has the focus.
|
||||||
- **`set-selection-offsets(int, int)`** Selects the text between two UTF-8 offsets.
|
- **`set-selection-offsets(int, int)`** Selects the text between two UTF-8 offsets.
|
||||||
- **`select-all()`** Selects all text.
|
- **`select-all()`** Selects all text.
|
||||||
- **`clear-selection()`** Clears the selection.
|
- **`clear-selection()`** Clears the selection.
|
||||||
|
|
|
@ -209,7 +209,7 @@ impl Item for NativeSlider {
|
||||||
click_count: _,
|
click_count: _,
|
||||||
} => {
|
} => {
|
||||||
if !self.has_focus() {
|
if !self.has_focus() {
|
||||||
WindowInner::from_pub(window_adapter.window()).set_focus_item(self_rc);
|
WindowInner::from_pub(window_adapter.window()).set_focus_item(self_rc, true);
|
||||||
}
|
}
|
||||||
data.pressed_x = if vertical { pos.y as f32 } else { pos.x as f32 };
|
data.pressed_x = if vertical { pos.y as f32 } else { pos.x as f32 };
|
||||||
data.pressed = 1;
|
data.pressed = 1;
|
||||||
|
|
|
@ -222,7 +222,7 @@ impl Item for NativeSpinBox {
|
||||||
|
|
||||||
if let MouseEvent::Pressed { .. } = event {
|
if let MouseEvent::Pressed { .. } = event {
|
||||||
if !self.has_focus() {
|
if !self.has_focus() {
|
||||||
WindowInner::from_pub(window_adapter.window()).set_focus_item(self_rc);
|
WindowInner::from_pub(window_adapter.window()).set_focus_item(self_rc, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputEventResult::EventAccepted
|
InputEventResult::EventAccepted
|
||||||
|
|
|
@ -440,7 +440,7 @@ impl Item for NativeTab {
|
||||||
if matches!(event, MouseEvent::Released { button, .. } if !click_on_press && button == PointerEventButton::Left)
|
if matches!(event, MouseEvent::Released { button, .. } if !click_on_press && button == PointerEventButton::Left)
|
||||||
|| matches!(event, MouseEvent::Pressed { button, .. } if click_on_press && button == PointerEventButton::Left)
|
|| matches!(event, MouseEvent::Pressed { button, .. } if click_on_press && button == PointerEventButton::Left)
|
||||||
{
|
{
|
||||||
WindowInner::from_pub(window_adapter.window()).set_focus_item(self_rc);
|
WindowInner::from_pub(window_adapter.window()).set_focus_item(self_rc, true);
|
||||||
self.current.set(self.tab_index());
|
self.current.set(self.tab_index());
|
||||||
InputEventResult::EventAccepted
|
InputEventResult::EventAccepted
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -126,7 +126,7 @@ impl AccessKitAdapter {
|
||||||
Action::Default => AccessibilityAction::Default,
|
Action::Default => AccessibilityAction::Default,
|
||||||
Action::Focus => {
|
Action::Focus => {
|
||||||
if let Some(item) = self.item_rc_for_node_id(request.target) {
|
if let Some(item) = self.item_rc_for_node_id(request.target) {
|
||||||
WindowInner::from_pub(window_adapter.window()).set_focus_item(&item);
|
WindowInner::from_pub(window_adapter.window()).set_focus_item(&item, true);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ pub enum BuiltinFunction {
|
||||||
Log,
|
Log,
|
||||||
Pow,
|
Pow,
|
||||||
SetFocusItem,
|
SetFocusItem,
|
||||||
|
ClearFocusItem,
|
||||||
ShowPopupWindow,
|
ShowPopupWindow,
|
||||||
ClosePopupWindow,
|
ClosePopupWindow,
|
||||||
SetSelectionOffsets,
|
SetSelectionOffsets,
|
||||||
|
@ -133,6 +134,10 @@ impl BuiltinFunction {
|
||||||
return_type: Box::new(Type::Void),
|
return_type: Box::new(Type::Void),
|
||||||
args: vec![Type::ElementReference],
|
args: vec![Type::ElementReference],
|
||||||
},
|
},
|
||||||
|
BuiltinFunction::ClearFocusItem => Type::Function {
|
||||||
|
return_type: Box::new(Type::Void),
|
||||||
|
args: vec![Type::ElementReference],
|
||||||
|
},
|
||||||
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => {
|
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => {
|
||||||
Type::Function {
|
Type::Function {
|
||||||
return_type: Box::new(Type::Void),
|
return_type: Box::new(Type::Void),
|
||||||
|
@ -292,7 +297,7 @@ impl BuiltinFunction {
|
||||||
| BuiltinFunction::Log
|
| BuiltinFunction::Log
|
||||||
| BuiltinFunction::Pow
|
| BuiltinFunction::Pow
|
||||||
| BuiltinFunction::ATan => true,
|
| BuiltinFunction::ATan => true,
|
||||||
BuiltinFunction::SetFocusItem => false,
|
BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
|
||||||
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
|
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
|
||||||
BuiltinFunction::SetSelectionOffsets => false,
|
BuiltinFunction::SetSelectionOffsets => false,
|
||||||
BuiltinFunction::ItemMemberFunction(..) => false,
|
BuiltinFunction::ItemMemberFunction(..) => false,
|
||||||
|
@ -349,7 +354,7 @@ impl BuiltinFunction {
|
||||||
| BuiltinFunction::Log
|
| BuiltinFunction::Log
|
||||||
| BuiltinFunction::Pow
|
| BuiltinFunction::Pow
|
||||||
| BuiltinFunction::ATan => true,
|
| BuiltinFunction::ATan => true,
|
||||||
BuiltinFunction::SetFocusItem => false,
|
BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
|
||||||
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
|
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => false,
|
||||||
BuiltinFunction::SetSelectionOffsets => false,
|
BuiltinFunction::SetSelectionOffsets => false,
|
||||||
BuiltinFunction::ItemMemberFunction(..) => false,
|
BuiltinFunction::ItemMemberFunction(..) => false,
|
||||||
|
|
|
@ -3100,11 +3100,20 @@ fn compile_builtin_function_call(
|
||||||
if let [llr::Expression::PropertyReference(pr)] = arguments {
|
if let [llr::Expression::PropertyReference(pr)] = arguments {
|
||||||
let window = access_window_field(ctx);
|
let window = access_window_field(ctx);
|
||||||
let focus_item = access_item_rc(pr, ctx);
|
let focus_item = access_item_rc(pr, ctx);
|
||||||
format!("{}.set_focus_item({});", window, focus_item)
|
format!("{}.set_focus_item({}, true);", window, focus_item)
|
||||||
} else {
|
} else {
|
||||||
panic!("internal error: invalid args to SetFocusItem {:?}", arguments)
|
panic!("internal error: invalid args to SetFocusItem {:?}", arguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BuiltinFunction::ClearFocusItem => {
|
||||||
|
if let [llr::Expression::PropertyReference(pr)] = arguments {
|
||||||
|
let window = access_window_field(ctx);
|
||||||
|
let focus_item = access_item_rc(pr, ctx);
|
||||||
|
format!("{}.set_focus_item({}, false);", window, focus_item)
|
||||||
|
} else {
|
||||||
|
panic!("internal error: invalid args to ClearFocusItem {:?}", arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
/* std::from_chars is unfortunately not yet implemented in gcc
|
/* std::from_chars is unfortunately not yet implemented in gcc
|
||||||
BuiltinFunction::StringIsFloat => {
|
BuiltinFunction::StringIsFloat => {
|
||||||
"[](const auto &a){ double v; auto r = std::from_chars(std::begin(a), std::end(a), v); return r.ptr == std::end(a); }"
|
"[](const auto &a){ double v; auto r = std::from_chars(std::begin(a), std::end(a), v); return r.ptr == std::end(a); }"
|
||||||
|
|
|
@ -2396,12 +2396,23 @@ fn compile_builtin_function_call(
|
||||||
let window_tokens = access_window_adapter_field(ctx);
|
let window_tokens = access_window_adapter_field(ctx);
|
||||||
let focus_item = access_item_rc(pr, ctx);
|
let focus_item = access_item_rc(pr, ctx);
|
||||||
quote!(
|
quote!(
|
||||||
sp::WindowInner::from_pub(#window_tokens.window()).set_focus_item(#focus_item)
|
sp::WindowInner::from_pub(#window_tokens.window()).set_focus_item(#focus_item, true)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
panic!("internal error: invalid args to SetFocusItem {:?}", arguments)
|
panic!("internal error: invalid args to SetFocusItem {:?}", arguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BuiltinFunction::ClearFocusItem => {
|
||||||
|
if let [Expression::PropertyReference(pr)] = arguments {
|
||||||
|
let window_tokens = access_window_adapter_field(ctx);
|
||||||
|
let focus_item = access_item_rc(pr, ctx);
|
||||||
|
quote!(
|
||||||
|
sp::WindowInner::from_pub(#window_tokens.window()).set_focus_item(#focus_item, false)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
panic!("internal error: invalid args to ClearFocusItem {:?}", arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
BuiltinFunction::ShowPopupWindow => {
|
BuiltinFunction::ShowPopupWindow => {
|
||||||
if let [Expression::NumberLiteral(popup_index), x, y, close_on_click, Expression::PropertyReference(parent_ref)] =
|
if let [Expression::NumberLiteral(popup_index), x, y, close_on_click, Expression::PropertyReference(parent_ref)] =
|
||||||
arguments
|
arguments
|
||||||
|
|
|
@ -81,7 +81,7 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize {
|
||||||
BuiltinFunction::ATan => 10,
|
BuiltinFunction::ATan => 10,
|
||||||
BuiltinFunction::Log => 10,
|
BuiltinFunction::Log => 10,
|
||||||
BuiltinFunction::Pow => 10,
|
BuiltinFunction::Pow => 10,
|
||||||
BuiltinFunction::SetFocusItem => isize::MAX,
|
BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => isize::MAX,
|
||||||
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => isize::MAX,
|
BuiltinFunction::ShowPopupWindow | BuiltinFunction::ClosePopupWindow => isize::MAX,
|
||||||
BuiltinFunction::SetSelectionOffsets => isize::MAX,
|
BuiltinFunction::SetSelectionOffsets => isize::MAX,
|
||||||
BuiltinFunction::ItemMemberFunction(..) => isize::MAX,
|
BuiltinFunction::ItemMemberFunction(..) => isize::MAX,
|
||||||
|
|
|
@ -35,7 +35,7 @@ pub fn replace_forward_focus_bindings_with_focus_functions(
|
||||||
// Phase 2: Filter out focus-forward bindings that aren't callable
|
// Phase 2: Filter out focus-forward bindings that aren't callable
|
||||||
local_forwards.remove_uncallable_forwards();
|
local_forwards.remove_uncallable_forwards();
|
||||||
|
|
||||||
// Phase 3: For `focus-forward` in the root element, create a `focus()` function that's callable from the outside
|
// Phase 3: For `focus-forward` in the root element, create `focus()` and `clear-focus()` functions that are callable from the outside
|
||||||
if let Some((root_focus_forward, focus_forward_location)) =
|
if let Some((root_focus_forward, focus_forward_location)) =
|
||||||
local_forwards.focus_forward_for_element(&component.root_element)
|
local_forwards.focus_forward_for_element(&component.root_element)
|
||||||
{
|
{
|
||||||
|
@ -59,6 +59,27 @@ pub fn replace_forward_focus_bindings_with_focus_functions(
|
||||||
.bindings
|
.bindings
|
||||||
.insert("focus".into(), RefCell::new(set_focus_code.into()));
|
.insert("focus".into(), RefCell::new(set_focus_code.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(clear_focus_code) =
|
||||||
|
call_clear_focus_function(&root_focus_forward, Some(&focus_forward_location))
|
||||||
|
{
|
||||||
|
component.root_element.borrow_mut().property_declarations.insert(
|
||||||
|
"clear-focus".into(),
|
||||||
|
PropertyDeclaration {
|
||||||
|
property_type: Type::Function {
|
||||||
|
return_type: Type::Void.into(),
|
||||||
|
args: vec![],
|
||||||
|
},
|
||||||
|
visibility: PropertyVisibility::Public,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
component
|
||||||
|
.root_element
|
||||||
|
.borrow_mut()
|
||||||
|
.bindings
|
||||||
|
.insert("clear-focus".into(), RefCell::new(clear_focus_code.into()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 4: All calls to `.focus()` may need to be changed with `focus-forward` resolved or changed from the built-in
|
// Phase 4: All calls to `.focus()` may need to be changed with `focus-forward` resolved or changed from the built-in
|
||||||
|
@ -249,3 +270,47 @@ fn call_focus_function(
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn call_clear_focus_function(
|
||||||
|
element: &ElementRc,
|
||||||
|
source_location: Option<&SourceLocation>,
|
||||||
|
) -> Option<Expression> {
|
||||||
|
let declares_focus_function = {
|
||||||
|
let mut element = element.clone();
|
||||||
|
loop {
|
||||||
|
if element.borrow().property_declarations.contains_key("clear-focus") {
|
||||||
|
break true;
|
||||||
|
}
|
||||||
|
let base = element.borrow().base_type.clone();
|
||||||
|
match base {
|
||||||
|
ElementType::Component(compo) => element = compo.root_element.clone(),
|
||||||
|
_ => break false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let builtin_focus_function =
|
||||||
|
element.borrow().builtin_type().map_or(false, |ty| ty.accepts_focus);
|
||||||
|
|
||||||
|
if declares_focus_function {
|
||||||
|
Some(Expression::FunctionCall {
|
||||||
|
function: Box::new(Expression::FunctionReference(
|
||||||
|
NamedReference::new(element, "clear-focus"),
|
||||||
|
None,
|
||||||
|
)),
|
||||||
|
arguments: vec![],
|
||||||
|
source_location: source_location.cloned(),
|
||||||
|
})
|
||||||
|
} else if builtin_focus_function {
|
||||||
|
let source_location = source_location.cloned();
|
||||||
|
Some(Expression::FunctionCall {
|
||||||
|
function: Box::new(Expression::BuiltinFunctionReference(
|
||||||
|
BuiltinFunction::ClearFocusItem,
|
||||||
|
source_location.clone(),
|
||||||
|
)),
|
||||||
|
arguments: vec![Expression::ElementReference(Rc::downgrade(element))],
|
||||||
|
source_location,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -92,7 +92,7 @@ fn load_component(component: &Rc<i_slint_compiler::object_tree::Component>) -> C
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
// Synthesize focus() as styles written in .slint will have it but the qt style exposes NativeXX directly.
|
// Synthesize focus() and `clear-focus()` as styles written in .slint will have it but the qt style exposes NativeXX directly.
|
||||||
if b.accepts_focus {
|
if b.accepts_focus {
|
||||||
result.properties.insert(
|
result.properties.insert(
|
||||||
"focus".into(),
|
"focus".into(),
|
||||||
|
@ -102,6 +102,14 @@ fn load_component(component: &Rc<i_slint_compiler::object_tree::Component>) -> C
|
||||||
pure: false,
|
pure: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
result.properties.insert(
|
||||||
|
"clear-focus".into(),
|
||||||
|
PropertyInfo {
|
||||||
|
ty: Type::Function { return_type: Type::Void.into(), args: vec![] },
|
||||||
|
vis: PropertyVisibility::Public,
|
||||||
|
pure: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,6 +140,7 @@ pub fn reserved_properties() -> impl Iterator<Item = (&'static str, Type, Proper
|
||||||
("absolute-position", logical_point_type(), PropertyVisibility::Output),
|
("absolute-position", logical_point_type(), PropertyVisibility::Output),
|
||||||
("forward-focus", Type::ElementReference, PropertyVisibility::Constexpr),
|
("forward-focus", Type::ElementReference, PropertyVisibility::Constexpr),
|
||||||
("focus", BuiltinFunction::SetFocusItem.ty(), PropertyVisibility::Public),
|
("focus", BuiltinFunction::SetFocusItem.ty(), PropertyVisibility::Public),
|
||||||
|
("clear-focus", BuiltinFunction::ClearFocusItem.ty(), PropertyVisibility::Public),
|
||||||
(
|
(
|
||||||
"dialog-button-role",
|
"dialog-button-role",
|
||||||
Type::Enumeration(BUILTIN_ENUMS.with(|e| e.DialogButtonRole.clone())),
|
Type::Enumeration(BUILTIN_ENUMS.with(|e| e.DialogButtonRole.clone())),
|
||||||
|
@ -206,6 +207,7 @@ pub fn reserved_property(name: &str) -> PropertyLookupResult {
|
||||||
pub fn reserved_member_function(name: &str) -> Option<BuiltinFunction> {
|
pub fn reserved_member_function(name: &str) -> Option<BuiltinFunction> {
|
||||||
for (m, e) in [
|
for (m, e) in [
|
||||||
("focus", BuiltinFunction::SetFocusItem), // match for callable "focus" property
|
("focus", BuiltinFunction::SetFocusItem), // match for callable "focus" property
|
||||||
|
("clear-focus", BuiltinFunction::ClearFocusItem), // match for callable "clear-focus" property
|
||||||
] {
|
] {
|
||||||
if m == name {
|
if m == name {
|
||||||
return Some(e);
|
return Some(e);
|
||||||
|
|
|
@ -775,7 +775,7 @@ impl Item for FocusScope {
|
||||||
self_rc: &ItemRc,
|
self_rc: &ItemRc,
|
||||||
) -> InputEventResult {
|
) -> InputEventResult {
|
||||||
if self.enabled() && matches!(event, MouseEvent::Pressed { .. }) && !self.has_focus() {
|
if self.enabled() && matches!(event, MouseEvent::Pressed { .. }) && !self.has_focus() {
|
||||||
WindowInner::from_pub(window_adapter.window()).set_focus_item(self_rc);
|
WindowInner::from_pub(window_adapter.window()).set_focus_item(self_rc, true);
|
||||||
InputEventResult::EventAccepted
|
InputEventResult::EventAccepted
|
||||||
} else {
|
} else {
|
||||||
InputEventResult::EventIgnored
|
InputEventResult::EventIgnored
|
||||||
|
|
|
@ -1476,7 +1476,7 @@ impl TextInput {
|
||||||
self_rc: &ItemRc,
|
self_rc: &ItemRc,
|
||||||
) {
|
) {
|
||||||
if !self.has_focus() {
|
if !self.has_focus() {
|
||||||
WindowInner::from_pub(window_adapter.window()).set_focus_item(self_rc);
|
WindowInner::from_pub(window_adapter.window()).set_focus_item(self_rc, true);
|
||||||
} else if !self.read_only() {
|
} else if !self.read_only() {
|
||||||
if let Some(w) = window_adapter.internal(crate::InternalToken) {
|
if let Some(w) = window_adapter.internal(crate::InternalToken) {
|
||||||
w.input_method_request(InputMethodRequest::Enable(
|
w.input_method_request(InputMethodRequest::Enable(
|
||||||
|
|
|
@ -691,13 +691,24 @@ impl WindowInner {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the focus to the item pointed to by item_ptr. This will remove the focus from any
|
/// Sets the focus to the item pointed to by item_ptr. This will remove the focus from any
|
||||||
/// currently focused item.
|
/// currently focused item. If set_focus is false, the focus is cleared.
|
||||||
pub fn set_focus_item(&self, focus_item: &ItemRc) {
|
pub fn set_focus_item(&self, new_focus_item: &ItemRc, set_focus: bool) {
|
||||||
if self.prevent_focus_change.get() {
|
if self.prevent_focus_change.get() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if !set_focus {
|
||||||
|
let current_focus_item = self.focus_item.borrow().clone();
|
||||||
|
if let Some(current_focus_item_rc) = current_focus_item.upgrade() {
|
||||||
|
if current_focus_item_rc != *new_focus_item {
|
||||||
|
// can't clear focus unless called with currently focused item.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let old = self.take_focus_item();
|
let old = self.take_focus_item();
|
||||||
let new = self.move_focus(focus_item.clone(), next_focus_item);
|
let new =
|
||||||
|
if set_focus { self.move_focus(new_focus_item.clone(), next_focus_item) } else { None };
|
||||||
let window_adapter = self.window_adapter();
|
let window_adapter = self.window_adapter();
|
||||||
if let Some(window_adapter) = window_adapter.internal(crate::InternalToken) {
|
if let Some(window_adapter) = window_adapter.internal(crate::InternalToken) {
|
||||||
window_adapter.handle_focus_change(old, new);
|
window_adapter.handle_focus_change(old, new);
|
||||||
|
@ -1250,9 +1261,10 @@ pub mod ffi {
|
||||||
pub unsafe extern "C" fn slint_windowrc_set_focus_item(
|
pub unsafe extern "C" fn slint_windowrc_set_focus_item(
|
||||||
handle: *const WindowAdapterRcOpaque,
|
handle: *const WindowAdapterRcOpaque,
|
||||||
focus_item: &ItemRc,
|
focus_item: &ItemRc,
|
||||||
|
set_focus: bool,
|
||||||
) {
|
) {
|
||||||
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
|
let window_adapter = &*(handle as *const Rc<dyn WindowAdapter>);
|
||||||
WindowInner::from_pub(window_adapter.window()).set_focus_item(focus_item)
|
WindowInner::from_pub(window_adapter.window()).set_focus_item(focus_item, set_focus)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Associates the window with the given component.
|
/// Associates the window with the given component.
|
||||||
|
|
|
@ -527,16 +527,56 @@ fn call_builtin_function(
|
||||||
enclosing_component.self_weak().get().unwrap().upgrade().unwrap();
|
enclosing_component.self_weak().get().unwrap().upgrade().unwrap();
|
||||||
|
|
||||||
component.access_window(|window| {
|
component.access_window(|window| {
|
||||||
window.set_focus_item(&corelib::items::ItemRc::new(
|
window.set_focus_item(
|
||||||
vtable::VRc::into_dyn(focus_item_comp),
|
&corelib::items::ItemRc::new(
|
||||||
item_info.item_index(),
|
vtable::VRc::into_dyn(focus_item_comp),
|
||||||
))
|
item_info.item_index(),
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
Value::Void
|
Value::Void
|
||||||
} else {
|
} else {
|
||||||
panic!("internal error: argument to SetFocusItem must be an element")
|
panic!("internal error: argument to SetFocusItem must be an element")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BuiltinFunction::ClearFocusItem => {
|
||||||
|
if arguments.len() != 1 {
|
||||||
|
panic!("internal error: incorrect argument count to SetFocusItem")
|
||||||
|
}
|
||||||
|
let component = match local_context.component_instance {
|
||||||
|
ComponentInstance::InstanceRef(c) => c,
|
||||||
|
ComponentInstance::GlobalComponent(_) => {
|
||||||
|
panic!("Cannot access the focus item from a global component")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Expression::ElementReference(focus_item) = &arguments[0] {
|
||||||
|
generativity::make_guard!(guard);
|
||||||
|
|
||||||
|
let focus_item = focus_item.upgrade().unwrap();
|
||||||
|
let enclosing_component =
|
||||||
|
enclosing_component_for_element(&focus_item, component, guard);
|
||||||
|
let description = enclosing_component.description;
|
||||||
|
|
||||||
|
let item_info = &description.items[focus_item.borrow().id.as_str()];
|
||||||
|
|
||||||
|
let focus_item_comp =
|
||||||
|
enclosing_component.self_weak().get().unwrap().upgrade().unwrap();
|
||||||
|
|
||||||
|
component.access_window(|window| {
|
||||||
|
window.set_focus_item(
|
||||||
|
&corelib::items::ItemRc::new(
|
||||||
|
vtable::VRc::into_dyn(focus_item_comp),
|
||||||
|
item_info.item_index(),
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
Value::Void
|
||||||
|
} else {
|
||||||
|
panic!("internal error: argument to ClearFocusItem must be an element")
|
||||||
|
}
|
||||||
|
}
|
||||||
BuiltinFunction::ShowPopupWindow => {
|
BuiltinFunction::ShowPopupWindow => {
|
||||||
if arguments.len() != 1 {
|
if arguments.len() != 1 {
|
||||||
panic!("internal error: incorrect argument count to ShowPopupWindow")
|
panic!("internal error: incorrect argument count to ShowPopupWindow")
|
||||||
|
|
88
tests/cases/focus/clear_focus.slint
Normal file
88
tests/cases/focus/clear_focus.slint
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial
|
||||||
|
|
||||||
|
export component TestCase inherits Window {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
|
||||||
|
le1 := TextInput {
|
||||||
|
x: 0px;
|
||||||
|
y: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
le2 := TextInput {
|
||||||
|
y: 100px;
|
||||||
|
x: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
out property le1-has-focus <=> le1.has-focus;
|
||||||
|
out property le2-has-focus <=> le2.has-focus;
|
||||||
|
out property <bool> te-focused: TextInputInterface.text-input-focused;
|
||||||
|
|
||||||
|
callback clear-le1-focus();
|
||||||
|
clear-le1-focus => {
|
||||||
|
le1.clear-focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
callback clear-le2-focus();
|
||||||
|
clear-le2-focus => {
|
||||||
|
le2.clear-focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
callback focus-le1();
|
||||||
|
focus-le1 => {
|
||||||
|
le1.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
callback focus-le2();
|
||||||
|
focus-le2 => {
|
||||||
|
le2.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let instance = TestCase::new().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(instance.get_le1_has_focus(), false);
|
||||||
|
assert_eq!(instance.get_le2_has_focus(), false);
|
||||||
|
//assert_eq!(instance.get_te_focused(), false);
|
||||||
|
|
||||||
|
// Focus first line edit
|
||||||
|
eprintln!("send event");
|
||||||
|
slint_testing::send_mouse_click(&instance, 50., 50.);
|
||||||
|
|
||||||
|
assert_eq!(instance.get_le1_has_focus(), true);
|
||||||
|
assert_eq!(instance.get_le2_has_focus(), false);
|
||||||
|
assert_eq!(instance.get_te_focused(), true);
|
||||||
|
|
||||||
|
// Focus second line edit programmatically
|
||||||
|
eprintln!("set programmatically");
|
||||||
|
instance.invoke_focus_le2();
|
||||||
|
|
||||||
|
assert_eq!(instance.get_le1_has_focus(), false);
|
||||||
|
assert_eq!(instance.get_le2_has_focus(), true);
|
||||||
|
assert_eq!(instance.get_te_focused(), true);
|
||||||
|
|
||||||
|
// Clear focus (should fail because item is not focused)
|
||||||
|
instance.invoke_clear_le1_focus();
|
||||||
|
|
||||||
|
assert_eq!(instance.get_le1_has_focus(), false);
|
||||||
|
assert_eq!(instance.get_le2_has_focus(), true);
|
||||||
|
assert_eq!(instance.get_te_focused(), true);
|
||||||
|
|
||||||
|
// Clear focus on currently focused item
|
||||||
|
instance.invoke_clear_le2_focus();
|
||||||
|
|
||||||
|
assert_eq!(instance.get_le1_has_focus(), false);
|
||||||
|
assert_eq!(instance.get_le2_has_focus(), false);
|
||||||
|
assert_eq!(instance.get_te_focused(), false);
|
||||||
|
```
|
||||||
|
|
||||||
|
*/
|
Loading…
Add table
Add a link
Reference in a new issue