diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml
new file mode 100644
index 0000000..d13420a
--- /dev/null
+++ b/.github/workflows/build-branch.yml
@@ -0,0 +1,23 @@
+name: Build branch
+on:
+ push:
+ branches-ignore:
+ - main
+ pull_request:
+ branches-ignore:
+ - main
+jobs:
+ build_main:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v4.1.7
+ - name: Setup Java
+ uses: actions/setup-java@v4.2.2
+ with:
+ distribution: 'temurin'
+ java-version: 22
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v4
+ - name: Build
+ run: ./gradlew build
diff --git a/demo/mvp-presenter/src/test/kotlin/BrowserPresenterTest.kt b/demo/mvp-presenter/src/test/kotlin/BrowserPresenterTest.kt
index 5ba2fe0..69ffb43 100644
--- a/demo/mvp-presenter/src/test/kotlin/BrowserPresenterTest.kt
+++ b/demo/mvp-presenter/src/test/kotlin/BrowserPresenterTest.kt
@@ -30,7 +30,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.test.advanceUntilIdle
import kotlin.test.Test
-@OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class)
+@OptIn(ExperimentalCoroutinesApi::class)
class BrowserPresenterTest {
@Test
diff --git a/demo/simple-kotlin-dsl/src/main/kotlin/FocusDemo.kt b/demo/simple-kotlin-dsl/src/main/kotlin/FocusDemo.kt
new file mode 100644
index 0000000..9055f9e
--- /dev/null
+++ b/demo/simple-kotlin-dsl/src/main/kotlin/FocusDemo.kt
@@ -0,0 +1,52 @@
+/*
+ * This file is part of xemantic-kotlin-swing-dsl - Kotlin goodies for Java Swing.
+ *
+ * Copyright (C) 2024 Kazimierz Pogoda
+ *
+ * xemantic-kotlin-swing-dsl is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * xemantic-kotlin-swing-dsl is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with xemantic-kotlin-swing-dsl. If not,
+ * see .
+ */
+
+package com.xemantic.kotlin.swing.demo
+
+import com.xemantic.kotlin.swing.*
+import java.awt.Dimension
+
+fun main() = MainWindow("Component Focus") {
+ val helpBox = Label {
+ preferredSize = Dimension(200, 0)
+ }
+ BorderPanel {
+ center { helpBox }
+ west {
+ BoxPanel(BoxLayoutAxis.Y) {
+ +Button("action") {
+ focusGains.listen {
+ helpBox.text = "Action help"
+ }
+ }
+ +CheckBox("choice") {
+ focusGains.listen {
+ helpBox.text = "Choice help"
+ }
+ }
+ +Label("label") {
+ focusGains.listen {
+ helpBox.text = "Label help"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/xemantic-kotlin-swing-dsl-core/src/main/kotlin/Events.kt b/xemantic-kotlin-swing-dsl-core/src/main/kotlin/Events.kt
index b247f7d..b8410db 100644
--- a/xemantic-kotlin-swing-dsl-core/src/main/kotlin/Events.kt
+++ b/xemantic-kotlin-swing-dsl-core/src/main/kotlin/Events.kt
@@ -68,6 +68,38 @@ val Component.mouseMoves: Flow get() =
val Component.mouseClicks: Flow get() =
mouseEvents.filter { it.id == MouseEvent.MOUSE_CLICKED }
+val Component.mouseDrags: Flow get() =
+ mouseEvents.filter { it.id == MouseEvent.MOUSE_DRAGGED }
+
+val Component.mousePresses: Flow get() =
+ mouseEvents.filter { it.id == MouseEvent.MOUSE_PRESSED }
+
+val Component.mouseReleases: Flow get() =
+ mouseEvents.filter { it.id == MouseEvent.MOUSE_RELEASED }
+
+val Component.mouseEnters: Flow get() =
+ mouseEvents.filter { it.id == MouseEvent.MOUSE_ENTERED }
+
+val Component.mouseExits: Flow get() =
+ mouseEvents.filter { it.id == MouseEvent.MOUSE_EXITED }
+
+val Component.focusEvents: Flow get() = callbackFlow {
+ val listener = object : FocusListener {
+ override fun focusGained(e: FocusEvent) { trySend(e) }
+ override fun focusLost(e: FocusEvent) { trySend(e) }
+ }
+ addFocusListener(listener)
+ awaitClose {
+ removeFocusListener(listener)
+ }
+}
+
+val Component.focusGains: Flow get() =
+ focusEvents.filter { it.id == FocusEvent.FOCUS_GAINED }
+
+val Component.focusLosses: Flow get() =
+ focusEvents.filter { it.id == FocusEvent.FOCUS_LOST }
+
val JTextField.actionEvents: Flow get() = callbackFlow {
val listener = ActionListener { e -> trySend(e) }
addActionListener(listener)