{"id":386901,"date":"2023-09-15T15:46:27","date_gmt":"2023-09-15T14:46:27","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=kotlin&#038;p=386901"},"modified":"2023-09-15T16:00:46","modified_gmt":"2023-09-15T15:00:46","slug":"publication-de-compose-multiplatform-1-5-0","status":"publish","type":"kotlin","link":"https:\/\/blog.jetbrains.com\/fr\/kotlin\/2023\/09\/publication-de-compose-multiplatform-1-5-0\/","title":{"rendered":"Publication de Compose Multiplatform 1.5.0"},"content":{"rendered":"<p>Nous avons le plaisir d&#8217;annoncer la disponibilit\u00e9 de Compose Multiplatform 1.5.0. Cette mise \u00e0 jour utilise le framework d&#8217;interface utilisateur d\u00e9claratif Jetpack Compose pour Kotlin et \u00e9tend sa prise en charge \u00e0 de nouvelles plateformes, au-del\u00e0 d\u2019Android : bureau, iOS et web. La version bureau est stable, la versions iOS est en phase Alpha et la prise en charge pour le web est encore exp\u00e9rimentale. Pour une pr\u00e9sentation d\u00e9taill\u00e9e, veuillez consulter <a href=\"https:\/\/www.jetbrains.com\/fr-fr\/lp\/compose-multiplatform\/\" target=\"_blank\" rel=\"noopener\">le site Compose Multiplatform<\/a>.<\/p>\n<p>Voici les principales am\u00e9liorations qu&#8217;apporte cette mise \u00e0 jour :<\/p>\n<ol>\n<li>Les API Dialog, Popup et WindowInsets sont maintenant en code commun.<\/li>\n<li>Nous avons am\u00e9lior\u00e9 le d\u00e9filement, la gestion des ressources et les champs de texte sous iOS.<\/li>\n<li>Le framework de test de l\u2019interface utilisateur a \u00e9t\u00e9 stabilis\u00e9 sur la version bureau.<\/li>\n<\/ol>\n<p>Compose Multiplatform 1.5.0 est bas\u00e9 sur <a href=\"https:\/\/android-developers.googleblog.com\/2023\/08\/whats-new-in-jetpack-compose-august-23-release.html\" target=\"_blank\" rel=\"noreferrer noopener\">Jetpack Compose 1.5<\/a>, qui est principalement d\u00e9di\u00e9 \u00e0 l&#8217;am\u00e9lioration des performances. Elle s&#8217;appuie \u00e9galement sur <a href=\"https:\/\/material.io\/blog\/material-3-compose-1-1\" target=\"_blank\" rel=\"noreferrer noopener\">la version 1.1 de Material Design 3<\/a>, qui fournit de nouveaux composants tels que les s\u00e9lecteurs de date et d&#8217;heure.<\/p>\n<p align=\"center\"><a class=\"ek-link jb-download-button\" title=\"Essayer Compose Multiplatform 1.5.0\" href=\"https:\/\/github.com\/JetBrains\/compose-multiplatform\/releases\/tag\/v1.5.0\" target=\"_blank\" rel=\"noopener\">Essayer Compose Multiplatform 1.5.0<\/a><\/p>\n<h2 class=\"wp-block-heading\">Compose Multiplatform prend en charge les bo\u00eetes de dialogue, popups et WindowInsets<\/h2>\n<p>Les bo\u00eetes de dialogue et les popups sont maintenant disponibles dans Compose Multiplatform. Les bo\u00eetes de dialogue sont utilis\u00e9es pour les \u00e9v\u00e9nements modaux, qui requi\u00e8rent une action de la part de l&#8217;utilisateur, par exemple faire un choix ou saisir des donn\u00e9es. Les fen\u00eatres contextuelles sont quant \u00e0 elles utilis\u00e9es pour les actions non modales, comme la fourniture de fonctionnalit\u00e9s optionnelles.<\/p>\n<p>Dans cette version, les types de base <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/ui\/window\/package-summary#Dialog(kotlin.Function0,androidx.compose.ui.window.DialogProperties,kotlin.Function0)\" target=\"_blank\" rel=\"noreferrer noopener\">Dialog<\/a> et <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/ui\/window\/package-summary#Popup(androidx.compose.ui.window.PopupPositionProvider,kotlin.Function0,androidx.compose.ui.window.PopupProperties,kotlin.Function0)\" target=\"_blank\" rel=\"noreferrer noopener\">Popup<\/a>, ainsi que <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/material\/package-summary#DropdownMenu(kotlin.Boolean,kotlin.Function0,androidx.compose.ui.Modifier,androidx.compose.ui.unit.DpOffset,androidx.compose.foundation.ScrollState,androidx.compose.ui.window.PopupProperties,kotlin.Function1)\" target=\"_blank\" rel=\"noreferrer noopener\">DropdownMenu<\/a> and <a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/material\/package-summary#AlertDialog(kotlin.Function0,kotlin.Function0,androidx.compose.ui.Modifier,kotlin.Function0,kotlin.Function0,androidx.compose.ui.graphics.Shape,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.window.DialogProperties)\" target=\"_blank\" rel=\"noreferrer noopener\">AlertDialog<\/a>, sont accessibles \u00e0 partir du code commun, ce qui \u00e9vite de devoir fournir des fonctionnalit\u00e9s sp\u00e9cifiques aux plateformes.<\/p>\n<p>Par exemple, l\u2019\u00e9l\u00e9ment Composable ci-dessous est enti\u00e8rement \u00e9crit en code commun\u00a0:<\/p>\n<pre class=\"kotlin-code\" style=\"padding: 36px 0;\" data-highlight-only=\"true\">@Composable\nfun CommonDialog() {\n   var isDialogOpen by remember { mutableStateOf(false) }\n   Button(onClick = { isDialogOpen = true }) {\n       Text(\"Open\")\n   }\n   if (isDialogOpen) {\n       AlertDialog(\n           onDismissRequest = { },\n           confirmButton = {\n               Button(onClick = { isDialogOpen = false }) {\n                   Text(\"OK\")\n               }\n           },\n           title = { Text(\"Alert Dialog\") },\n           text = { Text(\"Lore ipsum\") },\n       )\n   }\n}\n<\/pre>\n<p>Voici comment il appara\u00eet sur bureau, Android et iOS\u00a0:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/DialogBoxesDesktop-resized-1.png\" alt=\"D\u00e9mo des bo\u00eetes de dialogue sur Bureau\" data-gif-src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/DialogBoxesDesktop-resized-1.gif\" \/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/DialogBoxesAndroidiPhone-resized-1.png\" alt=\"D\u00e9mo des bo\u00eetes de dialogue sur Android et iOS\" data-gif-src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/DialogBoxesAndroidiPhone-resized-1.gif\" \/><\/p>\n<p>Une troisi\u00e8me fonctionnalit\u00e9 disponible dans cette version\u00a0est l&#8217;<a href=\"https:\/\/developer.android.com\/jetpack\/compose\/layouts\/insets\" target=\"_blank\" rel=\"noreferrer noopener\">API WindowInsets API<\/a>, qui d\u00e9crit le niveau d&#8217;ajustement requis pour \u00e9viter que votre contenu chevauche l&#8217;interface utilisateur du syst\u00e8me. Avec Compose Multiplatform 1.5.0, cette fonctionnalit\u00e9 peut \u00eatre utilis\u00e9e aussi bien sur Android que sur iOS.<\/p>\n<p>Gr\u00e2ce \u00e0 l&#8217;API WindowInsets, vous pouvez dessiner du contenu d&#8217;arri\u00e8re-plan via Compose Multiplatform derri\u00e8re l\u2019encoche, sans ajout de ligne blanche en haut de l\u2019application. Les captures d&#8217;\u00e9cran ci-dessous montrent la diff\u00e9rence que cela fait :<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-382365\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/WindowInsetsAPI.png\" alt=\"Utilisation de l'API WindowInsets pour dessiner un contenu d'arri\u00e8re-plan dans Compose Multiplatform\" width=\"623\" height=\"664\" \/><\/figure>\n<h2 class=\"wp-block-heading\">Am\u00e9liorations pour iOS<\/h2>\n<p>Cette mise jour apporte de nombreuses am\u00e9liorations pour la plateforme iOS. Le d\u00e9filement reproduit le look and feel de la plateforme, la gestion des ressources a \u00e9t\u00e9 simplifi\u00e9e et la manipulation du texte am\u00e9lior\u00e9e.<\/p>\n<h3 class=\"wp-block-heading\">D\u00e9filement naturel<\/h3>\n<p>Nous avons adapt\u00e9 le d\u00e9filement sur iOS de fa\u00e7on \u00e0 ce qu&#8217;il reproduise le d\u00e9filement natif. Supposons que nous ayons du code dans lequel le nombre et\/ou la taille des \u00e9l\u00e9ments \u00e0 afficher exc\u00e8de l&#8217;espace disponible\u00a0:<\/p>\n<pre class=\"kotlin-code\" style=\"padding: 36px 0;\" data-highlight-only=\"true\">@Composable\nfun NaturalScrolling() {\n   val items = (1..30).map { \"Item $it\" }\n   LazyColumn {\n       items(items) {\n           Text(\n               text = it,\n               fontSize = 30.sp,\n               modifier = Modifier.padding(start = 20.dp)\n           )\n       }\n   }\n}\n<\/pre>\n<p>Lors du d\u00e9filement, les \u00e9l\u00e9ments apparaissent depuis les bords de l&#8217;\u00e9cran, comme avec les applications iPhone natives\u00a0:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/NaturalScrolling-resized.png\" alt=\"Affichage des \u00e9l\u00e9ments lors du d\u00e9filement sur iOS\" data-gif-src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/NaturalScrolling-resized.gif\" \/><\/p>\n<h3 class=\"wp-block-heading\">Prise en charge de Dynamic Type<\/h3>\n<p>La fonctionnalit\u00e9 <a href=\"https:\/\/developer.apple.com\/documentation\/uikit\/uifont\/scaling_fonts_automatically\/\" target=\"_blank\" rel=\"noreferrer noopener\">Dynamic Type sur iOS<\/a> permet \u00e0 l&#8217;utilisateur de d\u00e9finir sa taille de police pr\u00e9f\u00e9r\u00e9e : plus grande pour une meilleure lisibilit\u00e9 ou plus petite pour afficher plus de contenu. La taille du texte utilis\u00e9e dans une application doit \u00eatre relative \u00e0 ce param\u00e8tre syst\u00e8me.<\/p>\n<p>Cette fonctionnalit\u00e9 est maintenant prise en charge dans Compose Multiplatform. Les incr\u00e9ments utilis\u00e9s pour le redimensionnement du texte sont les m\u00eames que ceux utilis\u00e9s dans les applications natives, le comportement sera donc identique.<\/p>\n<p>Prenons par exemple le Composable suivant\u00a0:<\/p>\n<pre class=\"kotlin-code\" style=\"padding: 36px 0;\" data-highlight-only=\"true\">@Composable\nfun DynamicType() {\n   Text(\"This is some sample text\", fontSize = 30.sp)\n}\n<\/pre>\n<p>Voici ce qui s&#8217;affiche lorsque la taille de lecture pr\u00e9f\u00e9r\u00e9e est d\u00e9finie au minimum :<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-382376\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/DynamicTypesSmall.png\" alt=\"Fonctionnalit\u00e9 Dynamic Type sur iOS dans Compose Multiplatform (petit texte)\" width=\"1266\" height=\"928\" \/><\/figure>\n<p>Et voici ce que cela donne lorsque la taille de lecture choisie est au maximum\u00a0:<\/p>\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-382387\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/DynamicTypesLarge.png\" alt=\"Fonctionnalit\u00e9 Dynamic Type sur iOS dans Compose Multiplatform (grand texte)\" width=\"1266\" height=\"928\" \/><\/figure>\n<h3 class=\"wp-block-heading\">Prise en charge des \u00e9crans \u00e0 taux de rafra\u00eechissement \u00e9lev\u00e9<\/h3>\n<p>Dans les versions pr\u00e9c\u00e9dentes, la fr\u00e9quence d&#8217;images maximale \u00e9tait de 60 FPS, Cela pouvait entra\u00eener des probl\u00e8mes de lenteur et de latence de l&#8217;interface utilisateur sur les appareils dot\u00e9s d&#8217;\u00e9crans 120\u00a0Hz. La nouvelle version prend en charge des fr\u00e9quences d&#8217;images allant jusqu&#8217;\u00e0 120 FPS.<\/p>\n<h3 class=\"wp-block-heading\">Simplification de la gestion des ressources<\/h3>\n<p>\u00c0 partir de la version 1.5.0, touts les \u00e9l\u00e9ments du dossier \u00ab\u00a0resources\u00a0\u00bb d&#8217;un ensemble de sources iOS sont copi\u00e9s dans le bundle de l&#8217;application par d\u00e9faut. Si vous placez un fichier image dans <code>src\/commonMain\/resources\/<\/code>, il sera donc copi\u00e9 dans le paquet et utilisable dans votre code.<\/p>\n<p>Si vous utilisez CocoaPods, d\u00e9sormais vous n&#8217;avez plus \u00e0 configurer ce comportement dans le fichier de build Gradle. Il n&#8217;est pas n\u00e9cessaire non plus d&#8217;appeler de nouveau <em>podInstall<\/em> pour que les ressources sont copi\u00e9es apr\u00e8s modification.<\/p>\n<p>Si vous essayez de configurer le comportement explicitement dans les scripts de build (comme indiqu\u00e9 ci-dessous), une erreur se produira\u00a0:<\/p>\n<pre class=\"kotlin-code\" style=\"padding: 36px 0;\" data-highlight-only=\"true\">kotlin {\n    cocoapods {\n        extraSpecAttributes[\"resources\"] = \"...\"\n    }\n}\n<\/pre>\n<p>Pour plus de d\u00e9tails et un guide pour la migration du code existant, <a href=\"https:\/\/github.com\/JetBrains\/compose-multiplatform\/pull\/3496\" target=\"_blank\" rel=\"noreferrer noopener\">consultez ce document.<\/a><\/p>\n<h3 class=\"wp-block-heading\">Am\u00e9lioration de TextField<\/h3>\n<p>Dans les versions pr\u00e9c\u00e9dentes, deux cas de saisie de texte pouvaient entra\u00eener un comportement ind\u00e9sirable. Pour la nouvelle version, nous avons travaill\u00e9 \u00e0 l&#8217;am\u00e9lioration de <code>TextField<\/code> afin d&#8217;\u00e9radiquer ces probl\u00e8mes.<\/p>\n<h4 class=\"wp-block-heading\">Probl\u00e8mes de capitalisation<\/h4>\n<p>Tout d&#8217;abord, <code>TextField<\/code> est maintenant capable de d\u00e9tecter si la mise en majuscule automatique de la premi\u00e8re lettre a \u00e9t\u00e9 d\u00e9sactiv\u00e9e. Cela peut notamment \u00eatre utile lors de la saisie de mots de passe. Le contr\u00f4le de ce comportement se fait via l&#8217;argument KeyboardOptions.<\/p>\n<p>Regardez le Composable ci-dessous\u00a0:<\/p>\n<pre class=\"kotlin-code\" style=\"padding: 36px 0;\" data-highlight-only=\"true\">fun TextFieldCapitalization() {\n   var text by remember { mutableStateOf(\"\") }\n   TextField(\n       value = text,\n       onValueChange = { text = it },\n       keyboardOptions = KeyboardOptions(\n           capitalization = KeyboardCapitalization.Sentences,\n           autoCorrect = false,\n           keyboardType = KeyboardType.Ascii,\n       ),\n   )\n}\n<\/pre>\n<p>L&#8217;image de gauche montre ce que l&#8217;on obtient lorsque la propri\u00e9t\u00e9 de capitalisation est d\u00e9finie sur <code>KeyboardCapitalization.None<\/code>. Sur l&#8217;image de droite, on voit le r\u00e9sultat lorsque la valeur est d\u00e9finie sur <code>KeyboardCapitalization.Sentences<\/code>.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/CapitalizationOptions-resized-2.png\" alt=\"D\u00e9mo de la capitalisation avec TextField\" data-gif-src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/CapitalizationOptions-resized-1.gif\" \/><\/p>\n<h4 class=\"wp-block-heading\">Claviers physiques<\/h4>\n<p>La deuxi\u00e8me situation concerne les claviers physiques. Dans les versions pr\u00e9c\u00e9dentes, lors de l&#8217;utilisation d&#8217;un clavier physique, appuyer sur <strong>Entr\u00e9e<\/strong> entra\u00eenait l&#8217;ajout plusieurs nouvelles lignes, et appuyer sur <strong>Retour arri\u00e8re<\/strong> d\u00e9clenchait plusieurs suppressions. Avec la nouvelle version, ces \u00e9v\u00e9nements sont d\u00e9sormais trait\u00e9s correctement.<\/p>\n<h2 class=\"wp-block-heading\">Am\u00e9liorations pour desktop<\/h2>\n<h3 class=\"wp-block-heading\">Stabilisation du framework de test<\/h3>\n<p>Cette version stabilise la prise en charge des tests sur Compose for Desktop. Jetpack Compose fournit <a href=\"https:\/\/developer.android.com\/jetpack\/compose\/testing\" target=\"_blank\" rel=\"noreferrer noopener\">un ensemble d&#8217;API de tests<\/a> pour v\u00e9rifier le comportement de votre code Compose. Ces API ont \u00e9t\u00e9 port\u00e9es sur desktop et la nouvelle version a supprim\u00e9 toutes les limitations de disponibilit\u00e9 que l&#8217;on pouvait rencontrer auparavant, vous pouvez donc d\u00e9sormais \u00e9crire des tests d&#8217;interface utilisateur complets pour votre application.<\/p>\n<p>Cr\u00e9ons et testons une interface utilisateur simple pour explorer bri\u00e8vement la fonctionnalit\u00e9 de test. Voici notre exemple de Composable\u00a0:<\/p>\n<pre class=\"kotlin-code\" style=\"padding: 36px 0;\" data-highlight-only=\"true\">@Composable\nfun App() {\n   var searchText by remember { mutableStateOf(\"cats\") }\n   val searchHistory = remember { mutableStateListOf() }\n\n\n   Column(modifier = Modifier.padding(30.dp)) {\n       TextField(\n           modifier = Modifier.testTag(\"searchText\"),\n           value = searchText,\n           onValueChange = {\n               searchText = it\n           }\n       )\n       Button(\n           modifier = Modifier.testTag(\"search\"),\n           onClick = {\n               searchHistory.add(\"You searched for: $searchText\")\n           }\n       ) {\n           Text(\"Search\")\n       }\n       LazyColumn {\n           items(searchHistory) {\n               Text(\n                   text = it,\n                   fontSize = 20.sp,\n                   modifier = Modifier.padding(start = 10.dp).testTag(\"attempt\")\n               )\n           }\n       }\n   }\n}\n<\/pre>\n<p>Cela cr\u00e9e une interface utilisateur simple qui enregistre les tentatives de recherche\u00a0:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/SearchApp-resized-1.png\" alt=\"Application de recherche utilis\u00e9e pour les tests\" data-gif-src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/SearchApp-resized-1.gif\" \/><\/p>\n<p>Vous remarquerez que <code>Modifier.testTag<\/code> est utilis\u00e9 pour attribuer des noms \u00e0 <code>TextField<\/code>, <code>Button<\/code> et aux \u00e9l\u00e9ments dans LazyColumn.<\/p>\n<p>Nous pouvons alors manipuler l&#8217;interface utilisateur dans un test JUnit\u00a0:<\/p>\n<pre class=\"kotlin-code\" style=\"padding: 36px 0;\" data-highlight-only=\"true\">class SearchAppTest {\n   @get:Rule\n   val compose = createComposeRule()\n\n\n   @Test\n   fun `Should display search attempts`() {\n       compose.setContent {\n           App()\n       }\n\n\n       val testSearches = listOf(\"cats\", \"dogs\", \"fish\", \"birds\")\n\n\n       for (text in testSearches) {\n           compose.onNodeWithTag(\"searchText\").performTextReplacement(text)\n           compose.onNodeWithTag(\"search\").performClick()\n       }\n\n\n       val lastAttempt = compose\n           .onAllNodesWithTag(\"attempt\")\n           .assertCountEquals(testSearches.size)\n           .onLast()\n\n\n       val expectedText = \"You searched for: ${testSearches.last()}\"\n       lastAttempt.assert(hasText(expectedText))\n   }\n}\n<\/pre>\n<p>En utilisant la r\u00e8gle JUnit sp\u00e9cifique \u00e0 Compose, nous\u00a0:<\/p>\n<ol>\n<li>D\u00e9finissons le contenu de l&#8217;interface utilisateur en tant qu\u2019App Composable.<\/li>\n<li>Rep\u00e9rons le champ de texte et le bouton \u00e0 l\u2019aide de <code>onNodeWithTag<\/code> .<\/li>\n<li>Entrons \u00e0 plusieurs reprises des exemples de valeurs dans le champ de texte et cliquons sur le bouton.<\/li>\n<li>Trouvons tous les n\u0153uds de texte qui ont \u00e9t\u00e9 g\u00e9n\u00e9r\u00e9s via <code>onAllNodesWithTag<\/code>.<\/li>\n<li>V\u00e9rifions que le nombre actuel de n\u0153uds de texte ont \u00e9t\u00e9 cr\u00e9\u00e9s et r\u00e9cup\u00e9rons le dernier.<\/li>\n<li>V\u00e9rifions que cette derni\u00e8re tentative contient le message attendu.<\/li>\n<\/ol>\n<h3 class=\"wp-block-heading\">Am\u00e9lioration de l&#8217;interop\u00e9rabilit\u00e9 avec Swing<\/h3>\n<p>Cette version introduit une prise en charge exp\u00e9rimentale visant \u00e0 am\u00e9liorer le rendu des panneaux de composition dans les composants Swing. Cela \u00e9vite les probl\u00e8mes de rendu transitoires lorsque les panneaux sont affich\u00e9s, masqu\u00e9s ou redimensionn\u00e9s. Cela permet \u00e9galement d&#8217;avoir une superposition correcte lors de la combinaison de composants Swing et de panneaux compose. Un composant Swing peut d\u00e9sormais \u00eatre affich\u00e9 au-dessus ou en dessous d&#8217;un <code>ComposePanel<\/code>.<\/p>\n<p>Regardez cet exemple\u00a0:<\/p>\n<pre class=\"kotlin-code\" style=\"padding: 36px 0;\" data-highlight-only=\"true\">fun main() {\n   System.setProperty(\"compose.swing.render.on.graphics\", \"true\")\n   SwingUtilities.invokeLater {\n       val composePanel = ComposePanel().apply {\n           setContent {\n               Box(modifier = Modifier.background(Color.Black).fillMaxSize())\n           }\n       }\n\n\n       val popup = object : JComponent() { ... }\n\n\n       val rightPanel = JLayeredPane().apply {\n           add(composePanel)\n           add(popup)\n           ...\n       }\n\n\n       val leftPanel = JPanel().apply { background = CYAN }\n\n\n       val splitter = JSplitPane(..., leftPanel,rightPanel)\n\n\n       JFrame().apply {\n           add(splitter)\n           setSize(600, 600)\n           isVisible = true\n       }\n   }\n}\n<\/pre>\n<p>Ce code cr\u00e9e et affiche une <code>JFrame<\/code> Swing avec le contenu suivant\u00a0:<\/p>\n<ol>\n<li>La JFrame contient un <code>JSplitPane<\/code> avec un s\u00e9parateur vertical.<\/li>\n<li>Sur la gauche du volet divis\u00e9 se trouve un <code>JPanel<\/code> standard de couleur cyan.<\/li>\n<li>\u00c0 droite se trouve un <code>JLayeredPane<\/code> compos\u00e9 de deux couches\u00a0:\n<ul>\n<li>Un <code>ComposePanel<\/code> contenant un composable <code>Box<\/code> de couleur noire<\/li>\n<li>Un composant Swing personnalis\u00e9, dans lequel le texte \u00ab\u00a0Popup\u00a0\u00bb appara\u00eet dans un rectangle blanc. Nous avons r\u00e9alis\u00e9 cela en rempla\u00e7ant la m\u00e9thode <code>paintComponent<\/code>.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>Lorsque la propri\u00e9t\u00e9 <code>compose.swing.render.on.graphics<\/code> est d\u00e9finie sur true, alors\u00a0:<\/p>\n<ul>\n<li>Le composant Swing personnalis\u00e9 s\u2019affiche au-dessus du Composable <code>Box<\/code>.<\/li>\n<li>Il n&#8217;y a pas d&#8217;artefacts graphiques de transition lorsque le curseur est d\u00e9plac\u00e9.<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/SpringInteropTrue0resized.png\" alt=\"D\u00e9mo de l'interop\u00e9rabilit\u00e9 avec Swing fonctionnant\" data-gif-src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/SpringInteropTrue0resized.gif\" \/><\/p>\n<p>Si ce flag n&#8217;avait pas \u00e9t\u00e9 d\u00e9fini, le composant personnalis\u00e9 ne serait pas visible et il pourrait y avoir des artefacts de transition lors du d\u00e9placement du curseur\u00a0:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/SpringInteropFalse.png\" alt=\"D\u00e9monstration de l'interop\u00e9rabilit\u00e9 Swing qui ne fonctionne pas\" data-gif-src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2023\/08\/SpringInteropFalse.gif\" \/><\/p>\n<p>Faites-nous part de vos commentaires sur Compose Multiplatform et participez aux discussions sur Compose Multiplatform et Jetpack Compose en rejoignant le canal Slack <a href=\"https:\/\/slack-chats.kotlinlang.org\/c\/compose\" target=\"_blank\" rel=\"noopener\">#compose<\/a> de Kotlin. Vous trouverez les discussions sur Compose Multiplatform for iOS dans le canal d\u00e9di\u00e9 <a href=\"https:\/\/slack-chats.kotlinlang.org\/c\/compose-ios\" target=\"_blank\" rel=\"noopener\">#compose-ios<\/a>.<\/p>\n<p align=\"center\"><a class=\"ek-link jb-download-button\" title=\"Essayer Compose Multiplatform 1.5.0\" href=\"https:\/\/github.com\/JetBrains\/compose-multiplatform\/releases\/tag\/v1.5.0\" target=\"_blank\" rel=\"noopener\">Essayer Compose Multiplatform 1.5.0<\/a><\/p>\n\n\n<h2 class=\"wp-block-heading\">Autres lectures et vid\u00e9os<\/h2>\n\n\n\n<ul>\n<li><a href=\"https:\/\/blog.jetbrains.com\/kotlin\/2023\/05\/compose-multiplatform-for-ios-is-in-alpha\/\" data-type=\"link\" data-id=\"https:\/\/blog.jetbrains.com\/kotlin\/2023\/05\/compose-multiplatform-for-ios-is-in-alpha\/\" target=\"_blank\" rel=\"noreferrer noopener\">Compose Multiplatform for iOS passe en phase Alpha<\/a><\/li>\n<\/ul>\n\n\n\n<ul>\n<li><a href=\"https:\/\/www.jetbrains.com\/lp\/compose-multiplatform\/\" target=\"_blank\" rel=\"noreferrer noopener\">Compose Multiplatform overview<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.youtube.com\/watch?v=5_W5YKPShZ4\" target=\"_blank\" data-type=\"link\" data-id=\"https:\/\/www.youtube.com\/watch?v=5_W5YKPShZ4\" rel=\"noreferrer noopener\">Build an iOS &amp; Android app in 100% Kotlin with Compose Multiplatform<\/a><\/li>\n<\/ul>\n\n\n<p><em>Auteur de l&#8217;article original en anglais<\/em> :<\/p>\n\n    <div class=\"about-author \">\n        <div class=\"about-author__box\">\n            <div class=\"row\">\n                <div class=\"about-author__box-img\">\n                    <img decoding=\"async\" src=\"https:\/\/secure.gravatar.com\/avatar\/?s=200&#038;r=g\" width=\"200\" height=\"200\" alt=\"\" loading=\"lazy\"  class=\"avatar avatar-200 wp-user-avatar wp-user-avatar-200 photo avatar-default\">\n                <\/div>\n                <div class=\"about-author__box-text\">\n                                                        <\/div>\n            <\/div>\n        <\/div>\n    <\/div>\n","protected":false},"author":813,"featured_media":382342,"comment_status":"closed","ping_status":"closed","template":"","categories":[956],"tags":[6481,8144,6706],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/kotlin\/386901"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/kotlin"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/types\/kotlin"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/users\/813"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/comments?post=386901"}],"version-history":[{"count":10,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/kotlin\/386901\/revisions"}],"predecessor-version":[{"id":388569,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/kotlin\/386901\/revisions\/388569"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/media\/382342"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/media?parent=386901"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/categories?post=386901"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/tags?post=386901"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/cross-post-tag?post=386901"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}