Skip to content

Commit 84e92bf

Browse files
authored
New Window and DialogWindow APIs (#2938)
1 parent 2613e71 commit 84e92bf

49 files changed

Lines changed: 7202 additions & 225 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

compose/ui/ui-unit/api/ui-unit.klib.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ final const val androidx.compose.ui.unit/MaxDimensionsAndFocusMask // androidx.c
392392
final fun <get-MaxDimensionsAndFocusMask>(): kotlin/Long // androidx.compose.ui.unit/MaxDimensionsAndFocusMask.<get-MaxDimensionsAndFocusMask>|<get-MaxDimensionsAndFocusMask>(){}[0]
393393

394394
final val androidx.compose.ui.unit/androidx_compose_ui_unit_ComposeUiUnitFlags$stableprop // androidx.compose.ui.unit/androidx_compose_ui_unit_ComposeUiUnitFlags$stableprop|#static{}androidx_compose_ui_unit_ComposeUiUnitFlags$stableprop[0]
395+
final val androidx.compose.ui.unit/androidx_compose_ui_unit_DpInsets$stableprop // androidx.compose.ui.unit/androidx_compose_ui_unit_DpInsets$stableprop|#static{}androidx_compose_ui_unit_DpInsets$stableprop[0]
395396
final val androidx.compose.ui.unit/androidx_compose_ui_unit_DpRect$stableprop // androidx.compose.ui.unit/androidx_compose_ui_unit_DpRect$stableprop|#static{}androidx_compose_ui_unit_DpRect$stableprop[0]
396397
final val androidx.compose.ui.unit/androidx_compose_ui_unit_IntRect$stableprop // androidx.compose.ui.unit/androidx_compose_ui_unit_IntRect$stableprop|#static{}androidx_compose_ui_unit_IntRect$stableprop[0]
397398
final val androidx.compose.ui.unit/center // androidx.compose.ui.unit/center|@androidx.compose.ui.unit.DpSize{}center[0]
@@ -467,6 +468,7 @@ final fun androidx.compose.ui.unit/IntRect(androidx.compose.ui.unit/IntOffset, k
467468
final fun androidx.compose.ui.unit/TextUnit(kotlin/Float, androidx.compose.ui.unit/TextUnitType): androidx.compose.ui.unit/TextUnit // androidx.compose.ui.unit/TextUnit|TextUnit(kotlin.Float;androidx.compose.ui.unit.TextUnitType){}[0]
468469
final fun androidx.compose.ui.unit/Velocity(kotlin/Float, kotlin/Float): androidx.compose.ui.unit/Velocity // androidx.compose.ui.unit/Velocity|Velocity(kotlin.Float;kotlin.Float){}[0]
469470
final fun androidx.compose.ui.unit/androidx_compose_ui_unit_ComposeUiUnitFlags$stableprop_getter(): kotlin/Int // androidx.compose.ui.unit/androidx_compose_ui_unit_ComposeUiUnitFlags$stableprop_getter|androidx_compose_ui_unit_ComposeUiUnitFlags$stableprop_getter(){}[0]
471+
final fun androidx.compose.ui.unit/androidx_compose_ui_unit_DpInsets$stableprop_getter(): kotlin/Int // androidx.compose.ui.unit/androidx_compose_ui_unit_DpInsets$stableprop_getter|androidx_compose_ui_unit_DpInsets$stableprop_getter(){}[0]
470472
final fun androidx.compose.ui.unit/androidx_compose_ui_unit_DpRect$stableprop_getter(): kotlin/Int // androidx.compose.ui.unit/androidx_compose_ui_unit_DpRect$stableprop_getter|androidx_compose_ui_unit_DpRect$stableprop_getter(){}[0]
471473
final fun androidx.compose.ui.unit/androidx_compose_ui_unit_IntRect$stableprop_getter(): kotlin/Int // androidx.compose.ui.unit/androidx_compose_ui_unit_IntRect$stableprop_getter|androidx_compose_ui_unit_IntRect$stableprop_getter(){}[0]
472474
final fun androidx.compose.ui.unit/checkArithmetic(androidx.compose.ui.unit/TextUnit) // androidx.compose.ui.unit/checkArithmetic|checkArithmetic(androidx.compose.ui.unit.TextUnit){}[0]
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2026 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package androidx.compose.ui.unit
18+
19+
import androidx.compose.runtime.Immutable
20+
import androidx.compose.ui.ExperimentalComposeUiApi
21+
22+
23+
/**
24+
* Represents a set of insets in [Dp] units.
25+
*/
26+
@ExperimentalComposeUiApi
27+
@Immutable
28+
class DpInsets(
29+
val top: Dp,
30+
val left: Dp,
31+
val bottom: Dp,
32+
val right: Dp
33+
) {
34+
35+
/**
36+
* Returns the sum of the insets.
37+
*/
38+
operator fun plus(other: DpInsets) = DpInsets(
39+
top = top + other.top,
40+
left = left + other.left,
41+
bottom = bottom + other.bottom,
42+
right = right + other.right
43+
)
44+
45+
override fun equals(other: Any?): Boolean {
46+
if (this === other) return true
47+
if (other !is DpInsets) return false
48+
49+
if (top != other.top) return false
50+
if (left != other.left) return false
51+
if (bottom != other.bottom) return false
52+
if (right != other.right) return false
53+
54+
return true
55+
}
56+
57+
override fun hashCode(): Int {
58+
var result = top.hashCode()
59+
result = 31 * result + left.hashCode()
60+
result = 31 * result + bottom.hashCode()
61+
result = 31 * result + right.hashCode()
62+
return result
63+
}
64+
}
65+
66+
/**
67+
* Returns the rectangle remaining after applying the given insets.
68+
*/
69+
@ExperimentalComposeUiApi
70+
operator fun DpRect.minus(insets: DpInsets): DpRect =
71+
DpRect(
72+
top = top + insets.top,
73+
left = left + insets.left,
74+
bottom = bottom - insets.bottom,
75+
right = right - insets.right
76+
)
77+
78+
/**
79+
* Returns the size after adding the given insets.
80+
*/
81+
@ExperimentalComposeUiApi
82+
operator fun DpSize.plus(insets: DpInsets): DpSize =
83+
DpSize(
84+
width = width + insets.left + insets.right,
85+
height = height + insets.top + insets.bottom
86+
)

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeDialog.desktop.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import androidx.compose.ui.ExperimentalComposeUiApi
2121
import androidx.compose.ui.InternalComposeUiApi
2222
import androidx.compose.ui.Modifier
2323
import androidx.compose.ui.input.key.KeyEvent
24+
import androidx.compose.ui.layout.MeasurableRootContent
2425
import androidx.compose.ui.layout.layoutId
2526
import androidx.compose.ui.semantics.dialog
2627
import androidx.compose.ui.semantics.semantics
@@ -313,6 +314,14 @@ class ComposeDialog : JDialog {
313314
return composePanel.saveState()
314315
}
315316

317+
/**
318+
* Returns an object through which the composable content of the window can be queried for its
319+
* size preferences, such as its intrinsic size.
320+
*/
321+
@ExperimentalComposeUiApi
322+
val measurableContent: MeasurableRootContent
323+
get() = composePanel.measurableContent
324+
316325
override fun dispose() {
317326
super.dispose()
318327
composePanel.dispose()

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindow.desktop.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import androidx.compose.ui.ExperimentalComposeUiApi
2121
import androidx.compose.ui.InternalComposeUiApi
2222
import androidx.compose.ui.Modifier
2323
import androidx.compose.ui.input.key.KeyEvent
24+
import androidx.compose.ui.layout.MeasurableRootContent
2425
import androidx.compose.ui.layout.layoutId
2526
import androidx.compose.ui.semantics.SemanticsOwner
2627
import androidx.compose.ui.unit.Dp
@@ -35,7 +36,7 @@ import java.awt.GraphicsConfiguration
3536
import java.awt.event.MouseListener
3637
import java.awt.event.MouseMotionListener
3738
import java.awt.event.MouseWheelListener
38-
import java.util.Locale
39+
import java.util.*
3940
import javax.swing.JFrame
4041
import kotlin.coroutines.CoroutineContext
4142
import kotlin.coroutines.EmptyCoroutineContext
@@ -196,6 +197,13 @@ class ComposeWindow @ExperimentalComposeUiApi constructor(
196197
return composePanel.saveState()
197198
}
198199

200+
/**
201+
* Returns an object through which the composable content of the window can be queried for its
202+
* size preferences, such as its intrinsic size.
203+
*/
204+
@ExperimentalComposeUiApi
205+
val measurableContent: MeasurableRootContent by composePanel::measurableContent
206+
199207
override fun dispose() {
200208
super.dispose()
201209
composePanel.dispose()

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/ComposeWindowPanel.desktop.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.compose.ui.ComposeFeatureFlags
2222
import androidx.compose.ui.LayerType
2323
import androidx.compose.ui.Modifier
2424
import androidx.compose.ui.input.key.KeyEvent
25+
import androidx.compose.ui.layout.MeasurableRootContent
2526
import androidx.compose.ui.scene.ComposeContainer
2627
import androidx.savedstate.SavedState
2728
import java.awt.Component
@@ -116,6 +117,9 @@ internal class ComposeWindowPanel(
116117
isFocusCycleRoot = true
117118
}
118119

120+
val measurableContent: MeasurableRootContent
121+
get() = composeContainer.measurableContent
122+
119123
override fun setBounds(x: Int, y: Int, width: Int, height: Int) {
120124
super.setBounds(x, y, width, height)
121125
composeContainer.setBounds(0, 0, width, height)

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingDialog.desktop.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import androidx.compose.runtime.getValue
2323
import androidx.compose.runtime.remember
2424
import androidx.compose.runtime.rememberCoroutineScope
2525
import androidx.compose.runtime.rememberUpdatedState
26+
import androidx.compose.runtime.snapshots.Snapshot
2627
import androidx.compose.ui.ExperimentalComposeUiApi
2728
import androidx.compose.ui.graphics.painter.Painter
2829
import androidx.compose.ui.input.key.KeyEvent
@@ -146,7 +147,12 @@ fun SwingDialog(
146147
// - Make the dialog displayable
147148
// - Size the dialog and the ComposeLayer correctly, so that we can draw it here
148149
if (!wasDisplayable && it.isDisplayable) {
149-
it.renderImmediately()
150+
Snapshot.withoutReadObservation {
151+
if (!it.isValid) {
152+
it.validate()
153+
}
154+
it.renderImmediately()
155+
}
150156
}
151157
},
152158
)

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/SwingWindow.desktop.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,14 @@ fun SwingWindow(
135135

136136
// If displaying for the first time, make sure we draw the first frame before making
137137
// the window visible to avoid showing the window background
138-
// It's the responsibility of setSizeSafely to
138+
// It's the responsibility of update(it) to:
139139
// - Make the window displayable
140140
// - Size the window and the ComposeLayer correctly, so that we can draw it here
141141
if (!wasDisplayable && it.isDisplayable) {
142142
Snapshot.withoutReadObservation {
143+
if (!it.isValid) {
144+
it.validate()
145+
}
143146
it.renderImmediately()
144147
}
145148
}

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/awt/Utils.desktop.kt

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ package androidx.compose.ui.awt
1818

1919
import androidx.compose.ui.geometry.Rect
2020
import androidx.compose.ui.unit.Density
21+
import androidx.compose.ui.unit.DpRect
2122
import androidx.compose.ui.unit.IntRect
23+
import androidx.compose.ui.unit.size
2224
import androidx.compose.ui.util.fastRoundToInt
2325
import java.awt.Component
2426
import java.awt.EventQueue
@@ -59,14 +61,36 @@ internal fun toAwtRectangle(
5961
return Rectangle(rleft, rtop, rwidth, rheight)
6062
}
6163

62-
internal fun IntRect.toAwtRectangle(density: Density = Density(1f)) = toAwtRectangle(
64+
internal fun IntRect.toAwtRectangle(density: Density) = toAwtRectangle(
6365
left = left.toFloat(),
6466
top = top.toFloat(),
6567
right = right.toFloat(),
6668
bottom = bottom.toFloat(),
6769
density = density.density
6870
)
6971

72+
internal fun DpRect.toAwtRectangleRounded(): Rectangle {
73+
val left = this.left.value.fastRoundToInt()
74+
val top = this.top.value.fastRoundToInt()
75+
val right = this.right.value.fastRoundToInt()
76+
val bottom = this.bottom.value.fastRoundToInt()
77+
return Rectangle(left, top, right - left, bottom - top)
78+
}
79+
80+
/**
81+
* Returns a [java.awt.Rectangle] corresponding to this [DpRect], in the given density.
82+
*
83+
* The size of the rectangle is rounded up to the nearest integer.
84+
*/
85+
internal fun DpRect.toAwtRectangleSizeRoundedUp(): Rectangle {
86+
val left = this.left.value.fastRoundToInt()
87+
val top = this.top.value.fastRoundToInt()
88+
val size = this.size
89+
val width = ceil(size.width.value).toInt()
90+
val height = ceil(size.height.value).toInt()
91+
return Rectangle(left, top, width, height)
92+
}
93+
7094
/**
7195
* Returns a [java.awt.Rectangle] corresponding to this [Rect], in the given density.
7296
*
@@ -159,4 +183,4 @@ internal class DebouncingEdtExecutor {
159183
}
160184
}
161185
}
162-
}
186+
}

0 commit comments

Comments
 (0)