Skip to content

Commit 2afe439

Browse files
Rsedaikinakurasov
authored and
akurasov
committed
Updated tutorial of [Swing Integration]. (JetBrains#1844)
* Updated tutorial of Swing interop. Co-authored-by: akurasov <[email protected]>
1 parent 10ddb9a commit 2afe439

File tree

3 files changed

+284
-4
lines changed

3 files changed

+284
-4
lines changed

Diff for: tutorials/Swing_Integration/README.md

+284-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,42 @@
1-
# Integration Compose for Desktop into Swing-based application
1+
# Integration of Compose Multiplatform and Swing
22

33
## What is covered
44

5-
In this tutorial, we will show you how to use ComposePanel and SwingPanel in your application.
5+
In this tutorial, we'll show you how to make the `Swing/Compose` interop work in your application, what its limitations are, what you can achieve with it, in which cases you may use it and when you shouldn't do that.
6+
7+
The main goals of the interoperability between Compose Multiplatform and Swing are
8+
- make it easier and smoother to migrate Swing applications to to Compose
9+
- allow to enhance Compose application with Swing components that don't have 'Compose' analogues
10+
11+
In many cases it is more efficient to implement a missing Component in Compose (and contribute it to community) rather than using a Swing component in a Compose Application.
12+
13+
## Swing interop use cases and limitations
14+
15+
Before combining Compose Multiplatform and Swing, it's important to keep in mind that these two technologies have different approaches to the content rendering. Compose Multiplatform uses one heavyweight Swing component to render all its content and has logical rendering layers, while Swing operates on both heavyweight and lightweight components (`Component/JComponent`). For Swing logic Compose Multiplatform is just one more heavyweight component and it interacts with it the same way as with all other Swing components.
16+
17+
The first use-case is addition of a Compose part into a Swing application. It could be done use `ComposePanel` Swing component to render the "Compose" part of the application. From Swing perspective it will be just another Swing component, that should be treated accordingly. Important point, that all Compose components will be rendered inside the `ComposePanel`, including popups, tooltips, context menus, etc. They will be positioned and resized inside the `ComposePanel`. So probably it would be better to replace them with a Swing based implementation.
18+
19+
Below you can find several cases where the use of `ComposePanel` is justified:
20+
- you want to embed animated objects or a whole panel of animated objects into your application (selection of emoticons, toolbar with animated reaction to events, etc.)
21+
- you want to implement an interactive rendering area in your application, which is easier and more convenient to implement using Compose (for example, any type of graphics or infographics)
22+
- you want to embed a complex rendering area (perhaps even animated) into your application - this is easier and more convenient to do using Compose
23+
- you want to replace complex parts of the user interface of your Swing-based application - Compose has a convenient component layout system, and Compose offers a wide range of built-in components and options for quickly creating your own components
24+
25+
If your case is somewhat similar to one of the above, then you should try to implement it using `ComposePanel`.
26+
27+
The second use case is situation when you want to use some component, that exists in Swing and there is no analogue in Compose. And creating it from scratch is too expensive. In this case, you can use `SwingPanel`. A `SwingPanel` is a wrapper that controls the size, position and rendering of a Swing component that is placed on top of a Compose Multiplatform component, meaning the component inside a `SwingPanel` will always be on top of the Compose in depth. Anything that is misplaced and rests on the `SwingPanel` will be clipped by the Swing component placed there, so try to think about these situations, and if there is such a risk, then it is better to either redesign the UI accordingly, or stop using the `SwingPanel` and still try to implement the missing component, thereby contributing to the development of technology and making life easier for other developers.
28+
29+
Below you can find several cases where the use of `SwingPanel` is justified:
30+
- there are no popups, tooltips, context menus, etc. in your application. or they are not used inside your `SwingPanel`
31+
- in your application, the `SwingPanel` will always be in the same position. This will reduce the risk of glitches and artifacts when changing the position of the Swing component (this condition is not mandatory and you need to test each such case separately)
32+
33+
If your case is somewhat similar to one of the above, then you should try to implement it using `SwingPanel`.
34+
35+
Since Compose Multiplatform and Swing can be combined in both directions, it is quite possible to place a `SwingPanel` into a `ComposePanel`, which in turn could be placed into another `SwingPanel`. In this case, you should be careful to minimize rendering glitches. At the end of this tutorial, you can find an example covering this case.
636

737
## Using ComposePanel
838

9-
ComposePanel lets you create a UI using Compose for Desktop in a Swing-based UI. To achieve this you need to create an instance of ComposePanel, add it to your Swing layout, and describe the composition inside `setContent`. You may also need to clear the CFD application events via `AppManager.setEvents`.
39+
`ComposePanel` lets you create a UI using Compose Multiplatform in a Swing-based UI. To achieve this you need to create an instance of `ComposePanel`, add it to your Swing layout, and describe the composition inside `setContent`.
1040

1141
```kotlin
1242
import androidx.compose.foundation.layout.Box
@@ -128,7 +158,7 @@ fun Counter(text: String, counter: MutableState<Int>) {
128158

129159
<img alt="IntegrationWithSwing" src="screenshot.png" height="781" />
130160

131-
## Adding a Swing component to CFD composition using SwingPanel
161+
## Adding a Swing component to Compose Multiplatform composition using SwingPanel
132162

133163
SwingPanel lets you create a UI using Swing in a Compose-based UI. To achieve this you need to create Swing `JComponent` in the `factory` parameter of `SwingPanel`.
134164

@@ -222,3 +252,253 @@ fun actionButton(
222252
```
223253

224254
<img alt="IntegrationWithSwing" src="swing_panel.gif" height="523" />
255+
256+
## Updating Swing component when Сompose state changes
257+
258+
Example below shows how to update a Swing component in a `SwingPanel` when the composable state changes. To do this, you need to provide an `update: (T) -> Unit` callback that is called when the composable state changes or after the layout is inflated.
259+
260+
```kotlin
261+
import androidx.compose.foundation.layout.Column
262+
import androidx.compose.foundation.layout.fillMaxSize
263+
import androidx.compose.foundation.layout.fillMaxWidth
264+
import androidx.compose.foundation.layout.height
265+
import androidx.compose.foundation.layout.Spacer
266+
import androidx.compose.foundation.layout.padding
267+
import androidx.compose.foundation.layout.Row
268+
import androidx.compose.foundation.layout.width
269+
import androidx.compose.material.Button
270+
import androidx.compose.material.Text
271+
import androidx.compose.runtime.Composable
272+
import androidx.compose.runtime.mutableStateOf
273+
import androidx.compose.runtime.remember
274+
import androidx.compose.ui.awt.SwingPanel
275+
import androidx.compose.ui.Alignment
276+
import androidx.compose.ui.Modifier
277+
import androidx.compose.ui.unit.dp
278+
import androidx.compose.ui.window.application
279+
import androidx.compose.ui.window.Window
280+
import androidx.compose.ui.window.rememberWindowState
281+
import java.awt.BorderLayout
282+
import javax.swing.JPanel
283+
import javax.swing.JLabel
284+
285+
val swingLabel = JLabel()
286+
287+
fun main() = application {
288+
Window(
289+
onCloseRequest = ::exitApplication,
290+
state = rememberWindowState(width = 400.dp, height = 200.dp),
291+
) {
292+
val clicks = remember { mutableStateOf(0) }
293+
Column(
294+
modifier = Modifier.fillMaxSize().padding(20.dp),
295+
horizontalAlignment = Alignment.CenterHorizontally
296+
) {
297+
SwingPanel(
298+
modifier = Modifier.fillMaxWidth().height(40.dp),
299+
factory = {
300+
JPanel().apply {
301+
add(swingLabel, BorderLayout.CENTER)
302+
}
303+
},
304+
update = {
305+
swingLabel.setText("SwingLabel Clicks: ${clicks.value}")
306+
}
307+
)
308+
Spacer(modifier = Modifier.height(40.dp))
309+
Row (
310+
modifier = Modifier.height(40.dp),
311+
verticalAlignment = Alignment.CenterVertically
312+
) {
313+
Button(onClick = { clicks.value++ }) {
314+
Text(text = "Increment")
315+
}
316+
Spacer(modifier = Modifier.width(20.dp))
317+
Button(onClick = { clicks.value-- }) {
318+
Text(text = "Decrement")
319+
}
320+
}
321+
}
322+
}
323+
}
324+
```
325+
326+
<img alt="IntegrationWithSwing" src="swing_panel_update.gif" height="254" />
327+
328+
## Layouting with SwingPanel and ComposePanel
329+
330+
Example below shows how Compose for Desktop and Swing can be combined in both directions i.e. adding a `SwingPanel` to a `ComposePanel` which is in turn added to another `SwingPanel`.
331+
332+
```kotlin
333+
import androidx.compose.foundation.*
334+
import androidx.compose.foundation.layout.*
335+
import androidx.compose.foundation.shape.RoundedCornerShape
336+
import androidx.compose.material.*
337+
import androidx.compose.material.icons.Icons
338+
import androidx.compose.material.icons.filled.*
339+
import androidx.compose.runtime.*
340+
import androidx.compose.ui.awt.*
341+
import androidx.compose.ui.*
342+
import androidx.compose.ui.draw.*
343+
import androidx.compose.ui.graphics.Color
344+
import androidx.compose.ui.graphics.vector.ImageVector
345+
import androidx.compose.ui.window.*
346+
import androidx.compose.ui.unit.*
347+
import java.awt.BorderLayout
348+
import java.awt.Canvas
349+
import java.awt.Dimension
350+
import java.awt.Insets
351+
import java.awt.event.*
352+
import javax.swing.*
353+
import javax.swing.border.EmptyBorder
354+
355+
val Gray = java.awt.Color(64, 64, 64)
356+
val DarkGray = java.awt.Color(32, 32, 32)
357+
val LightGray = java.awt.Color(210, 210, 210)
358+
359+
data class Item(
360+
val text: String,
361+
val icon: ImageVector,
362+
val color: Color,
363+
val state: MutableState<Boolean> = mutableStateOf(false)
364+
)
365+
val panelItemsList = listOf(
366+
Item(text = "Person", icon = Icons.Filled.Person, color = Color(10, 232, 162)),
367+
Item(text = "Favorite", icon = Icons.Filled.Favorite, color = Color(150, 232, 150)),
368+
Item(text = "Search", icon = Icons.Filled.Search, color = Color(232, 10, 162)),
369+
Item(text = "Settings", icon = Icons.Filled.Settings, color = Color(232, 162, 10)),
370+
Item(text = "Close", icon = Icons.Filled.Close, color = Color(232, 100, 100))
371+
)
372+
val itemSize = 50.dp
373+
374+
fun java.awt.Color.toCompose(): Color {
375+
return Color(red, green, blue)
376+
}
377+
378+
fun main() = application {
379+
Window(
380+
onCloseRequest = ::exitApplication,
381+
state = rememberWindowState(width = 500.dp, height = 500.dp),
382+
) {
383+
Column(
384+
modifier = Modifier.fillMaxSize().background(color = Gray.toCompose()).padding(20.dp),
385+
horizontalAlignment = Alignment.CenterHorizontally
386+
) {
387+
Text(text = "Compose Area", color = LightGray.toCompose())
388+
Spacer(modifier = Modifier.height(40.dp))
389+
SwingPanel(
390+
background = DarkGray.toCompose(),
391+
modifier = Modifier.fillMaxSize(),
392+
factory = {
393+
ComposePanel().apply {
394+
setContent {
395+
Box {
396+
SwingPanel(
397+
modifier = Modifier.fillMaxSize(),
398+
factory = { SwingComponent() }
399+
)
400+
Box (
401+
modifier = Modifier.align(Alignment.TopStart)
402+
.padding(start = 20.dp, top = 80.dp)
403+
.background(color = DarkGray.toCompose())
404+
) {
405+
SwingPanel(
406+
modifier = Modifier.size(itemSize * panelItemsList.size, itemSize),
407+
factory = {
408+
ComposePanel().apply {
409+
setContent {
410+
ComposeOverlay()
411+
}
412+
}
413+
}
414+
)
415+
}
416+
}
417+
}
418+
}
419+
}
420+
)
421+
}
422+
}
423+
}
424+
425+
fun SwingComponent() : JPanel {
426+
return JPanel().apply {
427+
background = DarkGray
428+
border = EmptyBorder(20, 20, 20, 20)
429+
layout = BorderLayout()
430+
add(
431+
JLabel("TextArea Swing Component").apply {
432+
foreground = LightGray
433+
verticalAlignment = SwingConstants.NORTH
434+
horizontalAlignment = SwingConstants.CENTER
435+
preferredSize = Dimension(40, 160)
436+
},
437+
BorderLayout.NORTH
438+
)
439+
add(
440+
JTextArea().apply {
441+
background = LightGray
442+
lineWrap = true
443+
wrapStyleWord = true
444+
margin = Insets(10, 10, 10, 10)
445+
text = "The five boxing wizards jump quickly. " +
446+
"Crazy Fredrick bought many very exquisite opal jewels. " +
447+
"Pack my box with five dozen liquor jugs.\n" +
448+
"Cozy sphinx waves quart jug of bad milk. " +
449+
"The jay, pig, fox, zebra and my wolves quack!"
450+
},
451+
BorderLayout.CENTER
452+
)
453+
}
454+
}
455+
456+
@Composable
457+
fun ComposeOverlay() {
458+
Box(
459+
modifier = Modifier.fillMaxSize().
460+
background(color = DarkGray.toCompose()),
461+
contentAlignment = Alignment.Center
462+
) {
463+
Row(
464+
modifier = Modifier.background(
465+
shape = RoundedCornerShape(4.dp),
466+
color = Color.DarkGray.copy(alpha = 0.5f)
467+
)
468+
) {
469+
for (item in panelItemsList) {
470+
SelectableItem(
471+
text = item.text,
472+
icon = item.icon,
473+
color = item.color,
474+
selected = item.state
475+
)
476+
}
477+
}
478+
}
479+
}
480+
481+
@Composable
482+
fun SelectableItem(
483+
text: String,
484+
icon: ImageVector,
485+
color: Color,
486+
selected: MutableState<Boolean>
487+
) {
488+
Box(
489+
modifier = Modifier.size(itemSize)
490+
.clickable { selected.value = !selected.value },
491+
contentAlignment = Alignment.Center
492+
) {
493+
Column(
494+
modifier = Modifier.alpha(if (selected.value) 1.0f else 0.5f),
495+
horizontalAlignment = Alignment.CenterHorizontally
496+
) {
497+
Icon(modifier = Modifier.size(32.dp), imageVector = icon, contentDescription = null, tint = color)
498+
Text(text = text, color = Color.White, fontSize = 10.sp)
499+
}
500+
}
501+
}
502+
```
503+
504+
<img alt="IntegrationWithSwing" src="swing_compose_layouting.gif" height="600" />
5.17 MB
Loading

Diff for: tutorials/Swing_Integration/swing_panel_update.gif

150 KB
Loading

0 commit comments

Comments
 (0)