-
Notifications
You must be signed in to change notification settings - Fork 91
Desktop. Fix input methods on JBR, disable input methods when we lose focus #881
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,6 +54,11 @@ import org.jetbrains.skiko.* | |
* Provides a base implementation for integrating a Compose scene with AWT/Swing. | ||
* It allows setting Compose content by [setContent], this content should be drawn on [component]. | ||
* | ||
* This bridge contain 2 components that should be added to the view hirarachy: | ||
* [component] the main visible Swing component, on which Compose will be shown | ||
* [invisibleComponent] service component used to bypass Swing issues: | ||
* - for forcing refocus on input methods change | ||
* | ||
* Inheritors should call [attachComposeToComponent], so events that came to [component] will be transferred to [ComposeScene] | ||
*/ | ||
internal abstract class ComposeBridge( | ||
|
@@ -66,7 +71,10 @@ internal abstract class ComposeBridge( | |
mainOwnerProvider = { scene.mainOwner } | ||
) | ||
|
||
private val _invisibleComponent = InvisibleComponent() | ||
|
||
abstract val component: JComponent | ||
val invisibleComponent: Component get() = _invisibleComponent | ||
|
||
abstract val renderApi: GraphicsApi | ||
|
||
|
@@ -86,16 +94,30 @@ internal abstract class ComposeBridge( | |
|
||
private var window: Window? = null | ||
|
||
private fun refocus() { | ||
if (component.isFocusOwner) { | ||
_invisibleComponent.requestFocusTemporary() | ||
component.requestFocus() | ||
} | ||
} | ||
|
||
private val platformComponent: PlatformComponent = object : PlatformComponent { | ||
override fun enableInput(inputMethodRequests: InputMethodRequests) { | ||
currentInputMethodRequests = inputMethodRequests | ||
component.enableInputMethods(true) | ||
val focusGainedEvent = FocusEvent(focusComponentDelegate, FocusEvent.FOCUS_GAINED) | ||
component.inputContext.dispatchEvent(focusGainedEvent) | ||
// Without resetting the focus, Swing won't update the status (doesn't show/hide popup) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to cover such a case with a test? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There 2 useful tests that could be written:
Other kind of tests aren't useful:
|
||
// enableInputMethods is design to used per-Swing component level at init stage, | ||
// not dynamically | ||
refocus() | ||
} | ||
|
||
override fun disableInput() { | ||
currentInputMethodRequests = null | ||
component.enableInputMethods(false) | ||
// Without resetting the focus, Swing won't update the status (doesn't show/hide popup) | ||
// enableInputMethods is design to used per-Swing component level at init stage, | ||
// not dynamically | ||
refocus() | ||
} | ||
|
||
override val locationOnScreen: Point | ||
|
@@ -181,6 +203,7 @@ internal abstract class ComposeBridge( | |
|
||
@OptIn(ExperimentalComposeUiApi::class) | ||
protected fun attachComposeToComponent() { | ||
component.enableInputMethods(false) | ||
component.addInputMethodListener(object : InputMethodListener { | ||
override fun caretPositionChanged(event: InputMethodEvent?) { | ||
if (isDisposed) return | ||
|
@@ -393,6 +416,12 @@ internal abstract class ComposeBridge( | |
override val touchSlop: Float get() = with(platformComponent.density) { 18.dp.toPx() } | ||
} | ||
} | ||
|
||
private class InvisibleComponent : Component() { | ||
fun requestFocusTemporary(): Boolean { | ||
return super.requestFocus(true) | ||
} | ||
} | ||
} | ||
|
||
private fun ComposeScene.onMouseEvent( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -113,6 +113,7 @@ class ComposePanel @ExperimentalComposeUiApi constructor( | |
if (bridge != null) { | ||
bridge!!.dispose() | ||
super.remove(bridge!!.component) | ||
super.remove(bridge!!.invisibleComponent) | ||
bridge = null | ||
} | ||
} | ||
|
@@ -187,6 +188,7 @@ class ComposePanel @ExperimentalComposeUiApi constructor( | |
if (bridge == null) { | ||
bridge = createComposeBridge() | ||
initContent() | ||
super.add(bridge!!.invisibleComponent, Integer.valueOf(1)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we add it to another layer then? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will it make a difference? |
||
super.add(bridge!!.component, Integer.valueOf(1)) | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class ComposeBridge is internal, so no need to add it for field