From 04f63bb6b6554825e14f7baa65be3261b98b11f2 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Fri, 10 May 2024 15:48:20 +0200 Subject: [PATCH] Fix scaling in the IDE once again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turns out the previous change was wrong — the issue was mostly due to us taking some JBDimensions and not un-scaling them before using their values. To better match the line width in Swing, I also tweaked the border modifier logic to not round up line widths, as that was causing lines to be drawn thicker than they should. This is not a full fix, since Swing does some tweaks in DarculaNewUIUtils#paintRectangle, and it probably just draws things slightly differently. However, this does considerably improve things at most IDE zoom levels, at the cost of some minor fuzziness at 110% zoom. --- .../jewel/foundation/modifier/Border.kt | 2 +- .../bridge/BridgePainterHintsProvider.kt | 1 - .../org/jetbrains/jewel/bridge/BridgeUtils.kt | 24 ++- .../jewel/bridge/ToolWindowExtensions.kt | 3 +- .../jewel/bridge/theme/IntUiBridge.kt | 44 ++--- .../jewel/bridge/theme/SwingBridgeTheme.kt | 4 +- .../samples/ideplugin/SwingComparisonTab.kt | 172 ------------------ 7 files changed, 41 insertions(+), 209 deletions(-) delete mode 100644 samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/SwingComparisonTab.kt diff --git a/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/modifier/Border.kt b/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/modifier/Border.kt index af287dd598..492fd3dbc0 100644 --- a/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/modifier/Border.kt +++ b/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/modifier/Border.kt @@ -98,7 +98,7 @@ private fun Modifier.drawBorderWithAlignment( drawContent() val strokeWidthPx = - min(if (width == Dp.Hairline) 1f else ceil(width.toPx()), ceil(size.minDimension / 2)) + min(if (width == Dp.Hairline) 1f else width.toPx(), size.minDimension / 2) .coerceAtLeast(1f) val expandWidthPx = expand.takeOrElse { 0.dp }.toPx() diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt index 47782dd827..f950a362b5 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgePainterHintsProvider.kt @@ -5,7 +5,6 @@ import androidx.compose.ui.graphics.Color import com.intellij.ide.ui.UITheme import com.intellij.openapi.diagnostic.thisLogger import com.intellij.ui.NewUI -import org.jetbrains.jewel.bridge.theme.isNewUiTheme import org.jetbrains.jewel.foundation.InternalJewelApi import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.foundation.util.inDebugMode diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt index 4e085ca669..1139db1a1f 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/BridgeUtils.kt @@ -19,10 +19,12 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.isUnspecified import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.takeOrElse +import com.intellij.ide.ui.LafManager import com.intellij.ide.ui.UISettingsUtils import com.intellij.openapi.diagnostic.Logger import com.intellij.ui.JBColor import com.intellij.ui.JBColor.marker +import com.intellij.ui.NewUI import com.intellij.ui.scale.JBUIScale.scale import com.intellij.util.ui.JBDimension import com.intellij.util.ui.JBFont @@ -118,7 +120,8 @@ public fun JBInsets.toPaddingValues(): PaddingValues = * instance, this function delegates to the specific [toDpSize] for it, * which is scaling-aware. */ -public fun Dimension.toDpSize(): DpSize = DpSize(width.dp, height.dp) +public fun Dimension.toDpSize(): DpSize = + if (this is JBDimension) toDpSize() else DpSize(width.dp, height.dp) /** * Converts a [JBDimension] to [DpSize], in a scaling-aware way. This means @@ -195,9 +198,22 @@ internal operator fun TextUnit.plus(delta: Float): TextUnit = else -> this } -internal fun retrieveIdeaDensity(sourceDensity: Density): Density { +internal fun scaleDensityWithIdeScale(sourceDensity: Density): Density { val ideaScale = UISettingsUtils.getInstance().currentIdeScale - val fontScale = sourceDensity.fontScale * ideaScale + val density = sourceDensity.density * ideaScale - return Density(sourceDensity.density, fontScale) + return Density(density, sourceDensity.fontScale) +} + +internal fun isNewUiTheme(): Boolean { + if (!NewUI.isEnabled()) return false + + val lafName = lafName() + return lafName == "Light" || lafName == "Dark" || lafName == "Light with Light Header" +} + +@Suppress("UnstableApiUsage") +internal fun lafName(): String { + val lafInfo = LafManager.getInstance().currentUIThemeLookAndFeel + return lafInfo.name } diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/ToolWindowExtensions.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/ToolWindowExtensions.kt index 1292895c0c..65666ba82b 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/ToolWindowExtensions.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/ToolWindowExtensions.kt @@ -1,11 +1,12 @@ package org.jetbrains.jewel.bridge import androidx.compose.runtime.Composable +import com.intellij.openapi.util.NlsContexts.TabTitle import com.intellij.openapi.wm.ToolWindow import org.jetbrains.jewel.foundation.enableNewSwingCompositing public fun ToolWindow.addComposeTab( - tabDisplayName: String, + @TabTitle tabDisplayName: String, isLockable: Boolean = true, isCloseable: Boolean = false, content: @Composable ToolWindowScope.() -> Unit, diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt index e24936a83f..33d7d29124 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/IntUiBridge.kt @@ -13,19 +13,19 @@ import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.isSpecified import androidx.compose.ui.unit.takeOrElse -import com.intellij.ide.ui.LafManager import com.intellij.ide.ui.laf.darcula.DarculaUIUtil import com.intellij.ide.ui.laf.intellij.IdeaPopupMenuUI import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.util.registry.Registry import com.intellij.ui.JBColor -import com.intellij.ui.NewUI import com.intellij.util.ui.DirProvider import com.intellij.util.ui.JBUI import com.intellij.util.ui.NamedColorUtil import org.jetbrains.jewel.bridge.bridgePainterProvider import org.jetbrains.jewel.bridge.createVerticalBrush import org.jetbrains.jewel.bridge.dp +import org.jetbrains.jewel.bridge.isNewUiTheme +import org.jetbrains.jewel.bridge.lafName import org.jetbrains.jewel.bridge.readFromLaF import org.jetbrains.jewel.bridge.retrieveArcAsCornerSizeOrDefault import org.jetbrains.jewel.bridge.retrieveArcAsCornerSizeWithFallbacks @@ -36,6 +36,7 @@ import org.jetbrains.jewel.bridge.retrieveIntAsDpOrUnspecified import org.jetbrains.jewel.bridge.retrieveTextStyle import org.jetbrains.jewel.bridge.toComposeColor import org.jetbrains.jewel.bridge.toComposeColorOrUnspecified +import org.jetbrains.jewel.bridge.toDpSize import org.jetbrains.jewel.bridge.toPaddingValues import org.jetbrains.jewel.foundation.GlobalColors import org.jetbrains.jewel.foundation.GlobalMetrics @@ -227,13 +228,13 @@ private fun readDefaultButtonStyle(): ButtonStyle { borderHovered = normalBorder, ) - val minimumSize = JBUI.CurrentTheme.Button.minimumSize() + val minimumSize = JBUI.CurrentTheme.Button.minimumSize().toDpSize() return ButtonStyle( colors = colors, metrics = ButtonMetrics( cornerSize = retrieveArcAsCornerSizeWithFallbacks("Button.default.arc", "Button.arc"), padding = PaddingValues(horizontal = 14.dp), // see DarculaButtonUI.HORIZONTAL_PADDING - minSize = DpSize(minimumSize.width.dp, minimumSize.height.dp), + minSize = DpSize(minimumSize.width, minimumSize.height), borderWidth = DarculaUIUtil.LW.dp, ), ) @@ -270,14 +271,14 @@ private fun readOutlinedButtonStyle(): ButtonStyle { borderHovered = normalBorder, ) - val minimumSize = JBUI.CurrentTheme.Button.minimumSize() + val minimumSize = JBUI.CurrentTheme.Button.minimumSize().toDpSize() return ButtonStyle( colors = colors, metrics = ButtonMetrics( cornerSize = CornerSize(DarculaUIUtil.BUTTON_ARC.dp / 2), padding = PaddingValues(horizontal = 14.dp), // see DarculaButtonUI.HORIZONTAL_PADDING - minSize = DpSize(minimumSize.width.dp, minimumSize.height.dp), + minSize = DpSize(minimumSize.width, minimumSize.height), borderWidth = DarculaUIUtil.LW.dp, ), ) @@ -468,13 +469,13 @@ private fun readDefaultDropdownStyle( iconTintHovered = Color.Unspecified, ) - val minimumSize = JBUI.CurrentTheme.ComboBox.minimumSize() + val minimumSize = JBUI.CurrentTheme.ComboBox.minimumSize().toDpSize() val arrowWidth = JBUI.CurrentTheme.Component.ARROW_AREA_WIDTH.dp return DropdownStyle( colors = colors, metrics = DropdownMetrics( - arrowMinSize = DpSize(arrowWidth, minimumSize.height.dp), - minSize = DpSize(minimumSize.width.dp + arrowWidth, minimumSize.height.dp), + arrowMinSize = DpSize(arrowWidth, minimumSize.height), + minSize = DpSize(minimumSize.width + arrowWidth, minimumSize.height), cornerSize = CornerSize(DarculaUIUtil.COMPONENT_ARC.dp / 2), contentPadding = retrieveInsetsAsPaddingValues("ComboBox.padding"), borderWidth = DarculaUIUtil.LW.dp, @@ -517,15 +518,15 @@ private fun readUndecoratedDropdownStyle( ) val arrowWidth = JBUI.CurrentTheme.Component.ARROW_AREA_WIDTH.dp - val minimumSize = JBUI.CurrentTheme.Button.minimumSize() + val minimumSize = JBUI.CurrentTheme.Button.minimumSize().toDpSize() return DropdownStyle( colors = colors, metrics = DropdownMetrics( - arrowMinSize = DpSize(arrowWidth, minimumSize.height.dp), - minSize = DpSize(minimumSize.width.dp + arrowWidth, minimumSize.height.dp), + arrowMinSize = DpSize(arrowWidth, minimumSize.height), + minSize = DpSize(minimumSize.width + arrowWidth, minimumSize.height), cornerSize = CornerSize(JBUI.CurrentTheme.MainToolbar.Dropdown.hoverArc().dp), - contentPadding = JBUI.CurrentTheme.MainToolbar.Dropdown.borderInsets().toPaddingValues(), + contentPadding = PaddingValues(3.dp), // from com.intellij.ide.ui.laf.darcula.ui.DarculaComboBoxUI.getDefaultComboBoxInsets borderWidth = 0.dp, ), icons = DropdownIcons(chevronDown = bridgePainterProvider("general/chevron-down.svg")), @@ -852,13 +853,13 @@ private fun readTextFieldStyle(textFieldStyle: TextStyle): TextFieldStyle { placeholder = NamedColorUtil.getInactiveTextColor().toComposeColor(), ) - val minimumSize = JBUI.CurrentTheme.TextField.minimumSize() + val minimumSize = JBUI.CurrentTheme.TextField.minimumSize().toDpSize() return TextFieldStyle( colors = colors, metrics = TextFieldMetrics( cornerSize = CornerSize(DarculaUIUtil.COMPONENT_ARC.dp / 2), contentPadding = PaddingValues(horizontal = 9.dp, vertical = 2.dp), - minSize = DpSize(minimumSize.width.dp, minimumSize.height.dp), + minSize = DpSize(minimumSize.width, minimumSize.height), borderWidth = DarculaUIUtil.LW.dp, ), textStyle = textFieldStyle, @@ -1056,16 +1057,3 @@ private fun readIconButtonStyle(): IconButtonStyle = borderHovered = retrieveColorOrUnspecified("ActionButton.hoverBorderColor"), ), ) - -internal fun isNewUiTheme(): Boolean { - if (!NewUI.isEnabled()) return false - - val lafName = lafName() - return lafName == "Light" || lafName == "Dark" || lafName == "Light with Light Header" -} - -@Suppress("UnstableApiUsage") -private fun lafName(): String { - val lafInfo = LafManager.getInstance().currentUIThemeLookAndFeel - return lafInfo.name -} diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/SwingBridgeTheme.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/SwingBridgeTheme.kt index 2a17ca4071..ca8a7012a3 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/SwingBridgeTheme.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/theme/SwingBridgeTheme.kt @@ -8,7 +8,7 @@ import androidx.compose.ui.platform.LocalDensity import com.intellij.openapi.components.service import org.jetbrains.jewel.bridge.BridgePainterHintsProvider import org.jetbrains.jewel.bridge.SwingBridgeService -import org.jetbrains.jewel.bridge.retrieveIdeaDensity +import org.jetbrains.jewel.bridge.scaleDensityWithIdeScale import org.jetbrains.jewel.foundation.ExperimentalJewelApi import org.jetbrains.jewel.ui.ComponentStyling import org.jetbrains.jewel.ui.painter.LocalPainterHintsProvider @@ -29,7 +29,7 @@ public fun SwingBridgeTheme(content: @Composable () -> Unit) { ) { CompositionLocalProvider( LocalPainterHintsProvider provides BridgePainterHintsProvider(themeData.themeDefinition.isDark), - LocalDensity provides retrieveIdeaDensity(LocalDensity.current), + LocalDensity provides scaleDensityWithIdeScale(LocalDensity.current), ) { content() } diff --git a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/SwingComparisonTab.kt b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/SwingComparisonTab.kt deleted file mode 100644 index b22f728b51..0000000000 --- a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/SwingComparisonTab.kt +++ /dev/null @@ -1,172 +0,0 @@ -package org.jetbrains.jewel.samples.ideplugin - -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.calculateEndPadding -import androidx.compose.foundation.layout.calculateStartPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI -import com.intellij.ui.JBColor -import com.intellij.ui.components.JBLabel -import com.intellij.ui.components.JBScrollPane -import com.intellij.ui.dsl.builder.AlignY -import com.intellij.ui.dsl.builder.COLUMNS_SHORT -import com.intellij.ui.dsl.builder.Panel -import com.intellij.ui.dsl.builder.Row -import com.intellij.ui.dsl.builder.RowLayout -import com.intellij.ui.dsl.builder.panel -import com.intellij.util.ui.JBFont -import com.intellij.util.ui.JBUI -import com.intellij.util.ui.components.BorderLayoutPanel -import icons.JewelIcons -import org.jetbrains.jewel.bridge.JewelComposePanel -import org.jetbrains.jewel.bridge.medium -import org.jetbrains.jewel.foundation.theme.JewelTheme -import org.jetbrains.jewel.ui.component.DefaultButton -import org.jetbrains.jewel.ui.component.Icon -import org.jetbrains.jewel.ui.component.OutlinedButton -import org.jetbrains.jewel.ui.component.Text -import org.jetbrains.jewel.ui.component.TextArea -import org.jetbrains.jewel.ui.component.TextField -import org.jetbrains.jewel.ui.component.Typography -import org.jetbrains.jewel.ui.theme.textAreaStyle - -internal class SwingComparisonTabPanel : BorderLayoutPanel() { - - private val mainContent = panel { - buttonsRow() - separator() - labelsRows() - separator() - iconsRow() - separator() - textFieldsRow() - separator() - textAreasRow() - }.apply { - border = JBUI.Borders.empty(0, 10) - isOpaque = false - } - - private val scrollingContainer = - JBScrollPane( - mainContent, - JBScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, - JBScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED, - ) - - init { - addToCenter(scrollingContainer) - scrollingContainer.border = null - scrollingContainer.isOpaque = false - isOpaque = false - } - - private fun Panel.buttonsRow() { - row("Buttons:") { - button("Swing Button") {}.align(AlignY.CENTER) - compose { OutlinedButton({}) { Text("Compose Button") } } - - button("Default Swing Button") {}.align(AlignY.CENTER) - .applyToComponent { putClientProperty(DarculaButtonUI.DEFAULT_STYLE_KEY, true) } - compose { DefaultButton({}) { Text("Default Compose Button") } } - }.layout(RowLayout.PARENT_GRID) - } - - private fun Panel.labelsRows() { - row("Labels:") { - label("Swing label").align(AlignY.CENTER) - compose { Text("Compose label") } - }.layout(RowLayout.PARENT_GRID) - - row("Comments:") { - comment("Swing comment").align(AlignY.CENTER) - compose { - Text("Compose comment", style = Typography.medium(), color = JewelTheme.globalColors.text.info) - } - }.layout(RowLayout.PARENT_GRID) - } - - private fun Panel.iconsRow() { - row("Icons:") { - cell( - JBLabel(JewelIcons.ToolWindowIcon).apply { border = JBUI.Borders.customLine(JBColor.RED) }, - ).align(AlignY.CENTER) - - compose { - Icon( - "icons/jewel-tool-window.svg", - null, - this@SwingComparisonTabPanel.javaClass, - Modifier.border(1.dp, Color.Red), - ) - } - }.layout(RowLayout.PARENT_GRID) - } - - private fun Panel.textFieldsRow() { - row("Text fields:") { - textField().align(AlignY.CENTER) - - compose { - var text by remember { mutableStateOf("") } - TextField(text, { text = it }) - } - }.layout(RowLayout.PARENT_GRID) - } - - private fun Panel.textAreasRow() { - row("Text areas:") { - textArea().align(AlignY.CENTER).applyToComponent { rows = 3 } - - compose { - var text by remember { mutableStateOf("") } - val metrics = remember(JBFont.label(), LocalDensity.current) { getFontMetrics(JBFont.label()) } - val charWidth = remember(metrics.widths) { - // Same logic as in JTextArea - metrics.charWidth('m') - } - val lineHeight = metrics.height - - val width = remember(charWidth) { (COLUMNS_SHORT * charWidth) } - val height = remember(lineHeight) { (3 * lineHeight) } - - val contentPadding = JewelTheme.textAreaStyle.metrics.contentPadding - TextArea( - text, - { text = it }, - Modifier.size( - width = width.dp + contentPadding.horizontal(LocalLayoutDirection.current), - height = height.dp + contentPadding.vertical(), - ), - ) - } - }.layout(RowLayout.PARENT_GRID) - } - - private fun PaddingValues.vertical(): Dp = calculateTopPadding() + calculateBottomPadding() - - private fun PaddingValues.horizontal(layoutDirection: LayoutDirection): Dp = - calculateStartPadding(layoutDirection) + calculateEndPadding(layoutDirection) - - private fun Row.compose(content: @Composable () -> Unit) = - cell( - JewelComposePanel { - Box(Modifier.padding(8.dp)) { content() } - }.apply { isOpaque = false }, - ) -}