diff --git a/.idea/libraries/miglayout_swing.xml b/.idea/libraries/miglayout_swing.xml index 9de05a00b893..e9ccc6a176ba 100644 --- a/.idea/libraries/miglayout_swing.xml +++ b/.idea/libraries/miglayout_swing.xml @@ -1,6 +1,9 @@ + + + diff --git a/lib/annotations/migLayout/net/miginfocom/layout/annotations.xml b/lib/annotations/migLayout/net/miginfocom/layout/annotations.xml new file mode 100644 index 000000000000..b1537bcdd609 --- /dev/null +++ b/lib/annotations/migLayout/net/miginfocom/layout/annotations.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java b/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java index ce49773d7aed..5427b9800276 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java @@ -1300,16 +1300,20 @@ public abstract class DialogWrapper { JComponent centerSection = new JPanel(new BorderLayout()); root.add(centerSection, BorderLayout.CENTER); - root.setBorder(createContentPaneBorder()); - final JComponent n = createNorthPanel(); if (n != null) { centerSection.add(n, BorderLayout.NORTH); } - final JComponent c = createCenterPanel(); - if (c != null) { - centerSection.add(c, BorderLayout.CENTER); + final JComponent centerPanel = createCenterPanel(); + if (centerPanel != null) { + centerSection.add(centerPanel, BorderLayout.CENTER); + } + + boolean isVisualPaddingCompensatedOnComponentLevel = JBUI.isCompensateVisualPaddingOnComponentLevel(centerPanel); + if (isVisualPaddingCompensatedOnComponentLevel) { + // see comment about visual paddings in the MigLayoutBuilder.build + root.setBorder(createContentPaneBorder()); } final JPanel southSection = new JPanel(new BorderLayout()); @@ -1318,6 +1322,9 @@ public abstract class DialogWrapper { southSection.add(myErrorText, BorderLayout.CENTER); final JComponent south = createSouthPanel(); if (south != null) { + if (!isVisualPaddingCompensatedOnComponentLevel) { + south.setBorder(JBUI.Borders.empty(0, 12, 8, 12)); + } southSection.add(south, BorderLayout.SOUTH); } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/VisualPaddingsProvider.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/VisualPaddingsProvider.java new file mode 100644 index 000000000000..067c178bfe30 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/VisualPaddingsProvider.java @@ -0,0 +1,13 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.ide.ui.laf; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; + +public interface VisualPaddingsProvider { + @Nullable + Insets + getVisualPaddings(@NotNull Component component); +} diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaButtonPainter.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaButtonPainter.java index 1567062299be..01630327c76d 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaButtonPainter.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaButtonPainter.java @@ -1,25 +1,14 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui.laf.darcula.ui; +import com.intellij.ide.ui.laf.VisualPaddingsProvider; import com.intellij.ide.ui.laf.darcula.DarculaUIUtil; import com.intellij.util.ui.JBInsets; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.MacUIUtil; import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.border.Border; @@ -35,7 +24,7 @@ import static com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI.HELP_BUTTON_DIA /** * @author Konstantin Bulenkov */ -public class DarculaButtonPainter implements Border, UIResource { +public class DarculaButtonPainter implements Border, UIResource, VisualPaddingsProvider { private static final int myOffset = 4; @Override @@ -121,4 +110,10 @@ public class DarculaButtonPainter implements Border, UIResource { public boolean isBorderOpaque() { return false; } + + @Nullable + @Override + public Insets getVisualPaddings(@NotNull Component component) { + return JBUI.insets(3); + } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaCheckBoxBorder.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaCheckBoxBorder.java index 0d6db1b813f0..6ea532cf8ffe 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaCheckBoxBorder.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaCheckBoxBorder.java @@ -1,22 +1,11 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui.laf.darcula.ui; +import com.intellij.ide.ui.laf.VisualPaddingsProvider; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.border.Border; @@ -26,7 +15,7 @@ import java.awt.*; /** * @author Konstantin Bulenkov */ -public class DarculaCheckBoxBorder implements Border, UIResource { +public class DarculaCheckBoxBorder implements Border, UIResource, VisualPaddingsProvider { @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {} @@ -39,4 +28,27 @@ public class DarculaCheckBoxBorder implements Border, UIResource { public boolean isBorderOpaque() { return false; } + + @Nullable + @Override + public Insets getVisualPaddings(@NotNull Component component) { + if (component instanceof JRadioButton) { + if (((JRadioButton)component).getUI().getClass() == DarculaRadioButtonUI.class) { + // darcula + return JBUI.insets(0, 2, 0, 0); + } + else { + // mac light + return JBUI.insets(3, 3, 2, 0); + } + } + else if (component instanceof JCheckBox && ((JCheckBox)component).getUI().getClass() == DarculaCheckBoxUI.class) { + // darcula + return JBUI.insets(0, 2, 0, 0); + } + else { + // mac light + return JBUI.insets(3, 4, 2, 0); + } + } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaCheckBoxUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaCheckBoxUI.java index 64f19393c9d8..a4b67778fcc7 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaCheckBoxUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaCheckBoxUI.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui.laf.darcula.ui; import com.intellij.openapi.util.SystemInfo; @@ -45,6 +31,12 @@ public class DarculaCheckBoxUI extends MetalCheckBoxUI { } } + @Override + public Dimension getMinimumSize(JComponent c) { + Dimension size = super.getMinimumSize(c); + return JBUI.isCompensateVisualPaddingOnComponentLevel(c.getParent()) ? size : new Dimension(size.width, DarculaTextFieldUI.DARCULA_INPUT_HEIGHT); + } + @Override public void installDefaults(AbstractButton b) { super.installDefaults(b); b.setIconTextGap(textIconGap()); diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaComboBoxUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaComboBoxUI.java index 28d33e8d03ba..2af5c70af23f 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaComboBoxUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaComboBoxUI.java @@ -1,6 +1,7 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui.laf.darcula.ui; +import com.intellij.ide.ui.laf.VisualPaddingsProvider; import com.intellij.ide.ui.laf.darcula.DarculaUIUtil; import com.intellij.openapi.ui.ComboBoxWithWidePopup; import com.intellij.openapi.ui.ErrorBorderCapable; @@ -37,7 +38,7 @@ import static com.intellij.ide.ui.laf.darcula.DarculaUIUtil.*; * @author Konstantin Bulenkov */ @SuppressWarnings("GtkPreferredJComboBoxRenderer") -public class DarculaComboBoxUI extends BasicComboBoxUI implements Border, ErrorBorderCapable { +public class DarculaComboBoxUI extends BasicComboBoxUI implements Border, ErrorBorderCapable, VisualPaddingsProvider { public DarculaComboBoxUI() {} @@ -153,7 +154,8 @@ public class DarculaComboBoxUI extends BasicComboBoxUI implements Border, ErrorB @NotNull static Dimension getArrowButtonPreferredSize(@Nullable JComboBox comboBox) { Insets i = comboBox != null ? comboBox.getInsets() : getDefaultComboBoxInsets(); - return new Dimension(JBUI.scale(16) + i.left, JBUI.scale(22) + i.top + i.bottom); + int height = comboBox != null && JBUI.isUseCorrectInputHeightOnMacOS(comboBox) ? DarculaTextFieldUI.DARCULA_INPUT_HEIGHT : 22; + return new Dimension(JBUI.scale(16) + i.left, JBUI.scale(height) + i.top + i.bottom); } static Shape getArrowShape(Component button) { @@ -181,6 +183,9 @@ public class DarculaComboBoxUI extends BasicComboBoxUI implements Border, ErrorB @Override protected Insets getInsets() { + if (JBUI.isUseCorrectInputHeightOnMacOS(comboBox)) { + return JBUI.insets(2, 8, 2, 4).asUIResource(); + } return getDefaultComboBoxInsets().asUIResource(); } @@ -194,7 +199,9 @@ public class DarculaComboBoxUI extends BasicComboBoxUI implements Border, ErrorB Graphics2D g2 = (Graphics2D)g.create(); Rectangle r = new Rectangle(c.getSize()); - JBInsets.removeFrom(r, JBUI.insets(1)); + if (!JBUI.isUseCorrectInputHeightOnMacOS(comboBox)) { + JBInsets.removeFrom(r, JBUI.insets(1)); + } try { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); @@ -327,7 +334,9 @@ public class DarculaComboBoxUI extends BasicComboBoxUI implements Border, ErrorB g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); Rectangle r = new Rectangle(x, y, width, height); - JBInsets.removeFrom(r, JBUI.insets(1)); + if (!JBUI.isUseCorrectInputHeightOnMacOS(c)) { + JBInsets.removeFrom(r, JBUI.insets(1)); + } g2.translate(r.x, r.y); @@ -389,7 +398,7 @@ public class DarculaComboBoxUI extends BasicComboBoxUI implements Border, ErrorB Dimension abSize = arrowButton.getPreferredSize(); int editorHeight = editor != null ? editor.getPreferredSize().height + i.top + i.bottom : 0; - int height = Math.max(Math.max(editorHeight, Math.max(abSize.height, d.height)), JBUI.scale(22) + i.top + i.bottom); + int height = Math.max(Math.max(editorHeight, Math.max(abSize.height, d.height)), JBUI.scale(JBUI.isUseCorrectInputHeightOnMacOS(comboBox) ? DarculaTextFieldUI.DARCULA_INPUT_HEIGHT : 22) + i.top + i.bottom); int width = Math.max(d.width, abSize.width + JBUI.scale(10)); return new Dimension(width, height); @@ -529,4 +538,9 @@ public class DarculaComboBoxUI extends BasicComboBoxUI implements Border, ErrorB super.show(invoker, x, y); } } + + @Override + public Insets getVisualPaddings(@NotNull Component component) { + return JBUI.insets((int)bw()); + } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaPasswordFieldUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaPasswordFieldUI.java index 0d1130916297..5e3fece1e2a6 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaPasswordFieldUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaPasswordFieldUI.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui.laf.darcula.ui; import com.intellij.ide.ui.laf.darcula.DarculaUIUtil; @@ -69,7 +55,12 @@ public class DarculaPasswordFieldUI extends BasicPasswordFieldUI { public Dimension getPreferredSize(JComponent c) { Dimension size = super.getPreferredSize(c); Insets i = getComponent().getInsets(); - return new Dimension(size.width, Math.max(size.height, JBUI.scale(20) + i.top + i.bottom)); + return new Dimension(size.width, Math.max(size.height, JBUI.scale(JBUI.isCompensateVisualPaddingOnComponentLevel(c.getParent()) ? 20 : DarculaTextFieldUI.DARCULA_INPUT_HEIGHT) + i.top + i.bottom)); + } + + @Override + public Dimension getMinimumSize(JComponent c) { + return getPreferredSize(c); } @Override diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaRadioButtonUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaRadioButtonUI.java index 15c3a9020065..6cf6f598cdc9 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaRadioButtonUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaRadioButtonUI.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui.laf.darcula.ui; import com.intellij.util.ui.EmptyIcon; @@ -39,6 +25,12 @@ public class DarculaRadioButtonUI extends MetalRadioButtonUI { return new DarculaRadioButtonUI(); } + @Override + public Dimension getMinimumSize(JComponent c) { + Dimension size = super.getMinimumSize(c); + return JBUI.isCompensateVisualPaddingOnComponentLevel(c.getParent()) ? size : new Dimension(size.width, DarculaTextFieldUI.DARCULA_INPUT_HEIGHT); + } + @Override public void installDefaults(AbstractButton b) { super.installDefaults(b); b.setIconTextGap(JBUI.scale(4)); diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextBorder.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextBorder.java index 7c391c54269a..e5d8e2431e64 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextBorder.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextBorder.java @@ -1,26 +1,15 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui.laf.darcula.ui; +import com.intellij.ide.ui.laf.VisualPaddingsProvider; import com.intellij.ide.ui.laf.darcula.DarculaUIUtil; import com.intellij.openapi.ui.ErrorBorderCapable; import com.intellij.ui.ColorPanel; import com.intellij.util.ui.JBInsets; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.MacUIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.border.Border; @@ -35,13 +24,13 @@ import java.awt.geom.RoundRectangle2D; /** * @author Konstantin Bulenkov */ -public class DarculaTextBorder implements Border, UIResource, ErrorBorderCapable { +public class DarculaTextBorder implements Border, UIResource, ErrorBorderCapable, VisualPaddingsProvider { @Override public Insets getBorderInsets(Component c) { if (c instanceof JTextField && c.getParent() instanceof ColorPanel) { return JBUI.insets(3, 3, 2, 2).asUIResource(); } - Insets insets = JBUI.insets(5, 9).asUIResource(); + Insets insets = JBUI.insets(JBUI.isCompensateVisualPaddingOnComponentLevel(c.getParent()) ? 5 : (int)bw(), 9).asUIResource(); TextFieldWithPopupHandlerUI.updateBorderInsets(c, insets); return insets; } @@ -67,20 +56,27 @@ public class DarculaTextBorder implements Border, UIResource, ErrorBorderCapable g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, MacUIUtil.USE_QUARTZ ? RenderingHints.VALUE_STROKE_PURE : RenderingHints.VALUE_STROKE_NORMALIZE); - JBInsets.removeFrom(r, JBUI.insets(1)); + Container parent = c.getParent(); + // if panel layout will compensate visual paddings, paint as MacComboBoxBorder does - do not translate to avoid complicating code (and logical expectations) + if (JBUI.isCompensateVisualPaddingOnComponentLevel(parent)) { + JBInsets.removeFrom(r, JBUI.insets(1)); + } + g2.translate(r.x, r.y); Path2D border = new Path2D.Float(Path2D.WIND_EVEN_ODD); float lw = lw(g2); float bw = bw(); - border.append(new Rectangle2D.Float(bw, bw, r.width - bw * 2, r.height - bw * 2), false); - border.append(new Rectangle2D.Float(bw + lw, bw + lw, r.width - (bw + lw) * 2, r.height - (bw + lw) * 2), false); + + // paint as MacComboBoxBorder does - use passed width instead of r.width + border.append(new Rectangle2D.Float(bw, bw, width - bw * 2, height - bw * 2), false); + border.append(new Rectangle2D.Float(bw + lw, bw + lw, width - (bw + lw) * 2, height - (bw + lw) * 2), false); boolean editable = !(c instanceof JTextComponent) || ((JTextComponent)c).isEditable(); g2.setColor(getOutlineColor(c.isEnabled() && editable)); g2.fill(border); - if (c.getParent() instanceof JComboBox) return; + if (parent instanceof JComboBox) return; paint(c, g2, r.width, r.height, 0); } finally { @@ -167,4 +163,10 @@ public class DarculaTextBorder implements Border, UIResource, ErrorBorderCapable protected Color getOutlineColor(boolean enabled) { return DarculaUIUtil.getOutlineColor(enabled); } + + @Nullable + @Override + public Insets getVisualPaddings(@NotNull Component component) { + return JBUI.insets((int)bw()); + } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java index b1a6c9142776..ca1cd6100329 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui.laf.darcula.ui; import com.intellij.ide.ui.laf.darcula.DarculaUIUtil; @@ -28,19 +14,29 @@ import java.awt.geom.RoundRectangle2D; * @author Konstantin Bulenkov */ public class DarculaTextFieldUI extends TextFieldWithPopupHandlerUI { + protected static final int MACOS_LIGHT_INPUT_HEIGHT = 21; + protected static final int DARCULA_INPUT_HEIGHT = 24; @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass", "UnusedDeclaration"}) public static ComponentUI createUI(JComponent c) { return new DarculaTextFieldUI(); } + // real height without visual paddings + protected int getMinimumHeightForTextField() { + return DARCULA_INPUT_HEIGHT; + } + @Override protected int getMinimumHeight() { Insets i = getComponent().getInsets(); JComponent c = getComponent(); - return DarculaEditorTextFieldBorder.isComboBoxEditor(c) || - UIUtil.getParentOfType(JSpinner.class, c) != null ? - JBUI.scale(22) : JBUI.scale(20) + i.top + i.bottom; + if (DarculaEditorTextFieldBorder.isComboBoxEditor(c) || UIUtil.getParentOfType(JSpinner.class, c) != null) { + return JBUI.scale(JBUI.getInt("TextFieldUI.spinnerOrComboboxEditorHeight", 22)); + } + else { + return JBUI.scale(JBUI.isUseCorrectInputHeightOnMacOS(c) ? getMinimumHeightForTextField() : 22) + i.top + i.bottom; + } } @Override diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacComboBoxBorder.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacComboBoxBorder.java index adfe42f71b8f..8b06edab270d 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacComboBoxBorder.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacComboBoxBorder.java @@ -1,24 +1,12 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui.laf.intellij; import com.intellij.ui.Gray; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.MacUIUtil; import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -165,4 +153,10 @@ public class MacComboBoxBorder extends MacIntelliJTextBorder { protected boolean isSymmetric() { return false; } + + @Nullable + @Override + public Insets getVisualPaddings(@NotNull Component component) { + return JBUI.insets(3, 3, 3, 4); + } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJButtonBorder.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJButtonBorder.java index 2603f15fcfef..8da4a09e81a4 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJButtonBorder.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJButtonBorder.java @@ -1,23 +1,12 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui.laf.intellij; +import com.intellij.ide.ui.laf.VisualPaddingsProvider; import com.intellij.ide.ui.laf.darcula.DarculaUIUtil; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.border.Border; @@ -29,7 +18,7 @@ import static com.intellij.ide.ui.laf.intellij.MacIntelliJButtonUI.ARC_SIZE; /** * @author Konstantin Bulenkov */ -public class MacIntelliJButtonBorder implements Border, UIResource { +public class MacIntelliJButtonBorder implements Border, UIResource, VisualPaddingsProvider { @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { if (!c.hasFocus() || c instanceof JComponent && UIUtil.isHelpButton(c)) return; @@ -52,4 +41,10 @@ public class MacIntelliJButtonBorder implements Border, UIResource { public boolean isBorderOpaque() { return false; } + + @Nullable + @Override + public Insets getVisualPaddings(@NotNull Component component) { + return JBUI.insets(3); + } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJComboBoxUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJComboBoxUI.java index 007270171c56..a089d6152024 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJComboBoxUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJComboBoxUI.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui.laf.intellij; import com.intellij.ide.ui.laf.darcula.ui.DarculaComboBoxUI; @@ -154,8 +140,9 @@ public class MacIntelliJComboBoxUI extends DarculaComboBoxUI { int iconWidth = DEFAULT_ICON.getIconWidth() + i.right; int iconHeight = DEFAULT_ICON.getIconHeight() + i.top + i.bottom; int editorHeight = editor != null ? editor.getPreferredSize().height + i.top + i.bottom : 0; + int maxHeight = JBUI.scale(JBUI.isUseCorrectInputHeightOnMacOS(comboBox) ? MacIntelliJTextFieldUI.MACOS_LIGHT_INPUT_HEIGHT_TOTAL : 26); return new Dimension(Math.max(d.width + JBUI.scale(7), iconWidth), - Math.max(Math.max(iconHeight, editorHeight), JBUI.scale(26))); + Math.max(Math.max(iconHeight, editorHeight), maxHeight)); } @Override diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJPasswordFieldUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJPasswordFieldUI.java index d8a03f007f32..07451d17aa64 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJPasswordFieldUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJPasswordFieldUI.java @@ -1,21 +1,8 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui.laf.intellij; import com.intellij.ide.ui.laf.darcula.ui.DarculaPasswordFieldUI; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -45,7 +32,12 @@ public class MacIntelliJPasswordFieldUI extends DarculaPasswordFieldUI { if (c.getBorder() instanceof MacIntelliJTextBorder) { g.setColor(c.getBackground()); - g.fillRect(3, 3, c.getWidth() - 6, c.getHeight() - 6); + if (JBUI.isCompensateVisualPaddingOnComponentLevel(parent)) { + g.fillRect(3, 3, c.getWidth() - 6, c.getHeight() - 6); + } + else { + g.fillRect(5, 5, c.getWidth() - 6 - MacIntelliJTextFieldUI.BW, c.getHeight() - 6 - MacIntelliJTextFieldUI.BW); + } } else { super.paintBackground(g); } @@ -55,6 +47,17 @@ public class MacIntelliJPasswordFieldUI extends DarculaPasswordFieldUI { @Override public Dimension getPreferredSize(JComponent c) { Dimension size = super.getPreferredSize(c); - return new Dimension(size.width, Math.max(26, size.height)); + if (JBUI.isCompensateVisualPaddingOnComponentLevel(c.getParent())) { + return new Dimension(size.width, Math.max(26, size.height)); + } + else { + return new Dimension(size.width, MacIntelliJTextFieldUI.MACOS_LIGHT_INPUT_HEIGHT_TOTAL); + } + } + + @Override + public Dimension getMinimumSize(JComponent c) { + Dimension size = super.getMinimumSize(c); + return JBUI.isCompensateVisualPaddingOnComponentLevel(c.getParent()) ? size : new Dimension(size.width, MacIntelliJTextFieldUI.MACOS_LIGHT_INPUT_HEIGHT_TOTAL); } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextBorder.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextBorder.java index 4cf1da7fbaaa..78f53c11b19b 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextBorder.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextBorder.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui.laf.intellij; import com.intellij.ide.ui.laf.darcula.DarculaUIUtil; @@ -100,7 +86,9 @@ public class MacIntelliJTextBorder extends DarculaTextBorder { if (c instanceof JTextField && c.getParent() instanceof ColorPanel) { return JBUI.insets(3, 3, 2, 2).asUIResource(); } - Insets insets = JBUI.insets(5, 8).asUIResource(); + + int topBottom = JBUI.isCompensateVisualPaddingOnComponentLevel(c.getParent()) ? 5 : 3; + Insets insets = JBUI.insets(topBottom, 8).asUIResource(); TextFieldWithPopupHandlerUI.updateBorderInsets(c, insets); return insets; } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextFieldUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextFieldUI.java index d8683c496628..76bcd8ebf2d2 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextFieldUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextFieldUI.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui.laf.intellij; import com.intellij.ide.ui.laf.darcula.ui.DarculaEditorTextFieldBorder; @@ -29,6 +15,8 @@ import java.awt.*; * @author Sergey Malenkov */ public class MacIntelliJTextFieldUI extends DarculaTextFieldUI { + public static final int BW = 3; + public static final int MACOS_LIGHT_INPUT_HEIGHT_TOTAL = MACOS_LIGHT_INPUT_HEIGHT + (BW * 2); @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass", "UnusedDeclaration"}) public static ComponentUI createUI(final JComponent c) { @@ -47,13 +35,22 @@ public class MacIntelliJTextFieldUI extends DarculaTextFieldUI { search.bounds.x = bounds.x + (bounds.width - search.bounds.width) / 2; } + @Override + protected int getMinimumHeightForTextField() { + return DarculaTextFieldUI.MACOS_LIGHT_INPUT_HEIGHT; + } + @Override protected int getMinimumHeight() { - return DarculaEditorTextFieldBorder.isComboBoxEditor(getComponent()) ? JBUI.scale(18) : JBUI.scale(26); + JTextComponent component = getComponent(); + if (JBUI.isUseCorrectInputHeightOnMacOS(component)) { + return super.getMinimumHeight(); + } + return DarculaEditorTextFieldBorder.isComboBoxEditor(component) ? JBUI.scale(18) : JBUI.scale(26); } @Override protected float bw() { - return JBUI.scale(3); + return JBUI.scale(BW); } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf_mac.properties b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf_mac.properties index eeb97becc139..6baf9f4704db 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf_mac.properties +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf_mac.properties @@ -31,6 +31,8 @@ TextField.disabledBackground=ececec PasswordFieldUI=com.intellij.ide.ui.laf.intellij.MacIntelliJPasswordFieldUI PasswordField.border=com.intellij.ide.ui.laf.intellij.MacIntelliJTextBorder +TextFieldUI.spinnerOrComboboxEditorHeight=21 + SpinnerUI=com.intellij.ide.ui.laf.intellij.MacIntelliJSpinnerUI Spinner.border=com.intellij.ide.ui.laf.intellij.MacIntelliJSpinnerBorder diff --git a/platform/platform-impl/src/com/intellij/ui/components/components.kt b/platform/platform-impl/src/com/intellij/ui/components/components.kt index a330050af0a1..802055ccb08b 100644 --- a/platform/platform-impl/src/com/intellij/ui/components/components.kt +++ b/platform/platform-impl/src/com/intellij/ui/components/components.kt @@ -124,6 +124,9 @@ private fun setTitledBorder(title: String, panel: JPanel) { border.acceptMinimumSize(panel) } +/** + * Consider using [UI DSL](https://github.com/JetBrains/intellij-community/tree/master/platform/platform-impl/src/com/intellij/ui/layout#readme) to create panel. + */ fun dialog(title: String, panel: JComponent, resizable: Boolean = false, diff --git a/platform/platform-impl/src/com/intellij/ui/layout/Cell.kt b/platform/platform-impl/src/com/intellij/ui/layout/Cell.kt index d4fd0493971e..e08ffd5e01ef 100644 --- a/platform/platform-impl/src/com/intellij/ui/layout/Cell.kt +++ b/platform/platform-impl/src/com/intellij/ui/layout/Cell.kt @@ -26,7 +26,11 @@ import javax.swing.JButton import javax.swing.JComponent import javax.swing.JLabel +@DslMarker +annotation class CellMarker + // separate class to avoid row related methods in the `cell { } ` +@CellMarker abstract class Cell { /** * Sets how keen the component should be to grow in relation to other component **in the same cell**. Use `push` in addition if need. @@ -56,10 +60,10 @@ abstract class Cell { result() } - fun button(text: String, actionListener: (event: ActionEvent) -> Unit) { + fun button(text: String, vararg constraints: CCFlags, actionListener: (event: ActionEvent) -> Unit) { val button = JButton(BundleBase.replaceMnemonicAmpersand(text)) button.addActionListener(actionListener) - button() + button(*constraints) } fun textFieldWithBrowseButton(browseDialogTitle: String, diff --git a/platform/platform-impl/src/com/intellij/ui/layout/Row.kt b/platform/platform-impl/src/com/intellij/ui/layout/Row.kt index ac63939b6ac6..db6186470703 100644 --- a/platform/platform-impl/src/com/intellij/ui/layout/Row.kt +++ b/platform/platform-impl/src/com/intellij/ui/layout/Row.kt @@ -62,14 +62,6 @@ abstract class Row : Cell() { @PublishedApi internal abstract fun setCellMode(value: Boolean) - @Deprecated(message = "Nested row is prohibited", level = DeprecationLevel.ERROR) - fun row(label: JLabel? = null, init: Row.() -> Unit) { - } - - @Deprecated(message = "Nested noteRow is prohibited", level = DeprecationLevel.ERROR) - fun noteRow(text: String) { - } - // override here for backward compatibility @Deprecated(level = DeprecationLevel.HIDDEN, message = "deprecated") operator fun JComponent.invoke(vararg constraints: CCFlags, gapLeft: Int = 0, growPolicy: GrowPolicy? = null) { diff --git a/platform/platform-impl/src/com/intellij/ui/layout/layout.kt b/platform/platform-impl/src/com/intellij/ui/layout/layout.kt index f62b3b2da91e..9f6218f683ac 100644 --- a/platform/platform-impl/src/com/intellij/ui/layout/layout.kt +++ b/platform/platform-impl/src/com/intellij/ui/layout/layout.kt @@ -1,15 +1,17 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ui.layout +import com.intellij.openapi.util.SystemInfoRt import com.intellij.ui.components.Panel +import com.intellij.util.ui.JBUI import javax.swing.JPanel /** + * See [docs](https://github.com/JetBrains/intellij-community/tree/master/platform/platform-impl/src/com/intellij/ui/layout). + * * Claims all available space in the container for the columns ([LCFlags.fillX], if `constraints` is passed, `fillX` will be not applied - add it explicitly if need). * At least one component need to have a [Row.grow] constraint for it to fill the container. * - * See [docs](https://github.com/JetBrains/intellij-community/tree/master/platform/platform-impl/src/com/intellij/ui/layout). - * * Check `Tools -> Internal Actions -> UI -> UI DSL Debug Mode` to turn on debug painting. * * `JTextComponent`, `TextFieldWithHistory` (use [Row.textFieldWithBrowseButton]), `SeparatorComponent` and `ComponentWithBrowseButton` components automatically have [Row.growX]. @@ -21,6 +23,9 @@ inline fun panel(vararg constraints: LCFlags, title: String? = null, init: Layou builder.init() val panel = Panel(title, layout = null) + if (SystemInfoRt.isMac) { + panel.putClientProperty(JBUI.COMPENSATE_VISUAL_PADDING_KEY, false) + } builder.builder.build(panel, constraints) return panel } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/layout/layoutImpl.kt b/platform/platform-impl/src/com/intellij/ui/layout/layoutImpl.kt index cbbd3c1a9360..dffc6d8c4e65 100644 --- a/platform/platform-impl/src/com/intellij/ui/layout/layoutImpl.kt +++ b/platform/platform-impl/src/com/intellij/ui/layout/layoutImpl.kt @@ -1,19 +1,46 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ui.layout +import com.intellij.openapi.util.SystemInfoRt import com.intellij.ui.layout.migLayout.* +import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil import java.awt.Container import javax.swing.ButtonGroup import javax.swing.JLabel -// see com.intellij.uiDesigner.core.AbstractLayout.DEFAULT_HGAP and DEFAULT_VGAP -// https://docs.google.com/document/d/1DKnLkO-7_onA7_NCw669aeMH5ltNvw-QMiQHnXu8k_Y/edit - +// https://jetbrains.github.io/ui/controls/input_field/#spacing @PublishedApi -internal fun createLayoutBuilder() = LayoutBuilder(MigLayoutBuilder(horizontalGap = UIUtil.DEFAULT_HGAP, - verticalGap = UIUtil.DEFAULT_VGAP, - largeVerticalGap = UIUtil.LARGE_VGAP)) +internal fun createLayoutBuilder(): LayoutBuilder { + // https://jetbrains.github.io/ui/controls/input_field/#spacing + val isUnderIntelliJLaF = UIUtil.isUnderIntelliJLaF() + + + val labelColumnVerticalTopGap = when { + SystemInfoRt.isMac && isUnderIntelliJLaF -> 2 + else -> 4 + } + + val spacing = object : SpacingConfiguration { + override val labelColumnVerticalTopGap = JBUI.scale(labelColumnVerticalTopGap) + + override val isCompensateVisualPaddings = SystemInfoRt.isMac + + override val horizontalGap = JBUI.scale(8) + override val verticalGap = JBUI.scale(5 * 2) + override val labelColumnHorizontalGap = JBUI.scale(6) + override val largeVerticalGap = JBUI.scale(UIUtil.LARGE_VGAP) + + override val shortTextWidth = JBUI.scale(250) + override val maxShortTextWidth = JBUI.scale(350) + + override val unitSize = JBUI.scale(4) + + override val dialogTopBottom = JBUI.scale(8) + override val dialogLeftRight = JBUI.scale(12) + } + return LayoutBuilder(MigLayoutBuilder(spacing)) +} interface LayoutBuilderImpl { fun newRow(label: JLabel? = null, buttonGroup: ButtonGroup? = null, separated: Boolean = false): Row @@ -21,4 +48,51 @@ interface LayoutBuilderImpl { fun build(container: Container, layoutConstraints: Array) fun noteRow(text: String, linkHandler: ((url: String) -> Unit)? = null) +} + +internal interface SpacingConfiguration { + /** + * Horizontal space between two components (in terms of layout grid - cells). + * + * It is space between associated components (somehow relates to each other) - for example, combobox and button to delete items from combobox. + * Since in most cases components in cells will be associated, it is not a space between two independent components. + * Horizontal subgroups of components is not supported yet, that's why there is no property to define such space. + */ + val horizontalGap: Int + + /** + * Vertical space between two components (in terms of layout grid - rows). + */ + val verticalGap: Int + + /** + * Horizontal gap after label column. + */ + val labelColumnHorizontalGap: Int + + val labelColumnVerticalTopGap: Int + + val largeVerticalGap: Int + + /** + * On macOS input fields (text fields, checkboxes, buttons and so on) have focus ring that drawn outside of component border. + * If reported component dimensions will be equals to visible (when unfocused) component dimensions, focus ring will be clipped. + * + * Since LaF cannot control component environment (host component), default safe strategy is to report component dimensions including focus ring. + * But it leads to an issue - spacing specified for visible component borders, not to compensated. For example, if horizontal space must be 8px, + * this 8px must be between one visible border of component to another visible border (in the case of macOS Light theme, gray 1px borders). + * Exactly 8px. + * + * So, advanced layout engine, e.g. MigLayout, offers a way to compensate visual padding on the layout container level, not on component level, as a solution. + */ + val isCompensateVisualPaddings: Boolean + + val shortTextWidth: Int + val maxShortTextWidth: Int + + // row comment top gap or gear icon left gap + val unitSize: Int + + val dialogTopBottom: Int + val dialogLeftRight: Int } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/layout/migLayout/MigLayoutBuilder.kt b/platform/platform-impl/src/com/intellij/ui/layout/migLayout/MigLayoutBuilder.kt index 48d3e04d61f8..9f0350b06b36 100644 --- a/platform/platform-impl/src/com/intellij/ui/layout/migLayout/MigLayoutBuilder.kt +++ b/platform/platform-impl/src/com/intellij/ui/layout/migLayout/MigLayoutBuilder.kt @@ -9,19 +9,18 @@ import net.miginfocom.swing.MigLayout import java.awt.Component import java.awt.Container import javax.swing.ButtonGroup +import javax.swing.JDialog import javax.swing.JLabel -/** - * Automatically add `growX` to JTextComponent (see isAddGrowX). - * Automatically add `grow` and `push` to JPanel (see isAddGrowX). - */ -internal class MigLayoutBuilder(val horizontalGap: Int, val verticalGap: Int, val largeVerticalGap: Int) : LayoutBuilderImpl { +internal class MigLayoutBuilder(val spacing: SpacingConfiguration) : LayoutBuilderImpl { /** * Map of component to constraints shared among rows (since components are unique) */ private val componentConstraints: MutableMap = ContainerUtil.newIdentityTroveMap() private val rootRow = MigLayoutRow(parent = null, componentConstraints = componentConstraints, builder = this, indent = 0) + val defaultComponentConstraintCreator = DefaultComponentConstraintCreator(spacing) + val columnConstraints = AC() override fun newRow(label: JLabel?, buttonGroup: ButtonGroup?, separated: Boolean): Row { @@ -30,8 +29,8 @@ internal class MigLayoutBuilder(val horizontalGap: Int, val verticalGap: Int, va override fun noteRow(text: String, linkHandler: ((url: String) -> Unit)?) { val cc = CC() - cc.vertical.gapBefore = gapToBoundSize(if (rootRow.subRows == null) verticalGap else largeVerticalGap, false) - cc.vertical.gapAfter = gapToBoundSize(horizontalGap, false) + cc.vertical.gapBefore = gapToBoundSize(if (rootRow.subRows == null) spacing.verticalGap else spacing.largeVerticalGap, false) + cc.vertical.gapAfter = gapToBoundSize(spacing.verticalGap * 2, false) val row = rootRow.createChildRow(label = null, noGrid = true) row.apply { @@ -42,7 +41,10 @@ internal class MigLayoutBuilder(val horizontalGap: Int, val verticalGap: Int, va } override fun build(container: Container, layoutConstraints: Array) { - val lc = createLayoutConstraints(horizontalGap, verticalGap) + val lc = LC() + lc.gridGapX = gapToBoundSize(0, true) + lc.gridGapY = gapToBoundSize(spacing.verticalGap, false) + lc.insets("0px") if (layoutConstraints.isEmpty()) { lc.fillX() // not fillY because it leads to enormously large cells - we use cc `push` in addition to cc `grow` as a more robust and easy solution @@ -51,18 +53,49 @@ internal class MigLayoutBuilder(val horizontalGap: Int, val verticalGap: Int, va lc.apply(layoutConstraints) } - lc.noVisualPadding() + lc.isVisualPadding = spacing.isCompensateVisualPaddings lc.hideMode = 3 + if (rootRow.subRows!!.any { it.isLabeledIncludingSubRows }) { + // using columnConstraints instead of component gap allows easy debug (proper painting of debug grid) + columnConstraints.gap("${spacing.labelColumnHorizontalGap}px!", 0) + } + + for (i in 1 until columnConstraints.count) { + columnConstraints.gap("${spacing.horizontalGap}px!", i) + } + // if constraint specified only for rows 0 and 1, MigLayout will use constraint 1 for any rows with index 1+ (see LayoutUtil.getIndexSafe - use last element if index > size) val rowConstraints = AC() rowConstraints.align("top") - container.layout = MigLayout(lc, columnConstraints, rowConstraints) + + var isLayoutInsetsAdjusted = false + container.layout = object : MigLayout(lc, columnConstraints, rowConstraints) { + override fun layoutContainer(parent: Container) { + if (!isLayoutInsetsAdjusted) { + isLayoutInsetsAdjusted = true + + var topParent = parent.parent + while (topParent != null) { + if (topParent is JDialog) { + val topBottom = createUnitValue(spacing.dialogTopBottom, false) + val leftRight = createUnitValue(spacing.dialogLeftRight, true) + // since we compensate visual padding, child components should be not clipped, so, we do not use content pane DialogWrapper border (returns null), + // but instead set insets to our content panel (so, child components are not clipped) + lc.insets = arrayOf(topBottom, leftRight, topBottom, leftRight) + break + } + topParent = topParent.parent + } + } + + super.layoutContainer(parent) + } + } val isNoGrid = layoutConstraints.contains(LCFlags.noGrid) var rowIndex = 0 - fun configureComponents(row: MigLayoutRow) { val lastComponent = row.components.lastOrNull() for ((index, component) in row.components.withIndex()) { @@ -74,6 +107,8 @@ internal class MigLayoutBuilder(val horizontalGap: Int, val verticalGap: Int, va continue } + // we cannot use columnCount as an indicator of whether to use spanX/wrap or not because component can share cell with another component, + // in any case MigLayout is smart enough and unnecessary spanX/wrap doesn't harm if (component === lastComponent) { cc.spanX() cc.wrap() @@ -119,18 +154,12 @@ internal class MigLayoutBuilder(val horizontalGap: Int, val verticalGap: Int, va } internal fun gapToBoundSize(value: Int, isHorizontal: Boolean): BoundSize { - val unitValue = UnitValue(value.toFloat(), "px", isHorizontal, UnitValue.STATIC, null) + val unitValue = createUnitValue(value, isHorizontal) return BoundSize(unitValue, unitValue, null, false, null) } -// default values differs to MigLayout - IntelliJ Platform defaults are used -private fun createLayoutConstraints(gridGapX: Int, gridGapY: Int): LC { - val lc = LC() - // gap multiplied by 2 (it seems in terms of MigLayout gap is both left and right space) - lc.gridGapX = gapToBoundSize(gridGapX * 2, true) - lc.gridGapY = gapToBoundSize(gridGapY, false) - lc.insets = ConstraintParser.parseInsets("0px", true) - return lc +private fun createUnitValue(value: Int, isHorizontal: Boolean): UnitValue { + return UnitValue(value.toFloat(), "px", isHorizontal, UnitValue.STATIC, null) } private fun LC.apply(flags: Array): LC { diff --git a/platform/platform-impl/src/com/intellij/ui/layout/migLayout/MigLayoutRow.kt b/platform/platform-impl/src/com/intellij/ui/layout/migLayout/MigLayoutRow.kt index b78da450da86..3a4b1e158d43 100644 --- a/platform/platform-impl/src/com/intellij/ui/layout/migLayout/MigLayoutRow.kt +++ b/platform/platform-impl/src/com/intellij/ui/layout/migLayout/MigLayoutRow.kt @@ -3,19 +3,19 @@ package com.intellij.ui.layout.migLayout import com.intellij.CommonBundle import com.intellij.icons.AllIcons +import com.intellij.ide.ui.laf.VisualPaddingsProvider import com.intellij.openapi.ui.OnePixelDivider import com.intellij.openapi.ui.panel.ComponentPanelBuilder import com.intellij.ui.SeparatorComponent import com.intellij.ui.components.Label import com.intellij.ui.layout.* import com.intellij.util.SmartList -import com.intellij.util.ui.UIUtil import net.miginfocom.layout.CC import net.miginfocom.layout.ConstraintParser +import net.miginfocom.layout.PlatformDefaults import java.awt.Component import javax.swing.* - -private val LABEL_TOP_GAP = ConstraintParser.parseBoundSize("4px!", true, false) +import javax.swing.border.LineBorder internal class MigLayoutRow(private val parent: MigLayoutRow?, private val componentConstraints: MutableMap, @@ -39,70 +39,8 @@ internal class MigLayoutRow(private val parent: MigLayoutRow?, private var componentIndexWhenCellModeWasEnabled = -1 - fun createChildRow(label: JLabel? = null, buttonGroup: ButtonGroup? = null, separated: Boolean = false, noGrid: Boolean = false): MigLayoutRow { - if (subRows == null) { - subRows = SmartList() - } - - val subRows = subRows!! - - if (separated) { - val row = MigLayoutRow(this, componentConstraints, builder, indent = indent, noGrid = true) - subRows.add(row) - row.apply { - val separatorComponent = SeparatorComponent(0, OnePixelDivider.BACKGROUND, null) - val cc = CC() - cc.vertical.gapBefore = gapToBoundSize(builder.largeVerticalGap, false) - cc.vertical.gapAfter = gapToBoundSize(builder.verticalGap * 2, false) - componentConstraints.put(separatorComponent, cc) - separatorComponent() - } - } - - val row = MigLayoutRow(this, componentConstraints, builder, labeled = label != null, noGrid = noGrid, indent = indent + computeChildRowIndent(), buttonGroup = buttonGroup) - subRows.add(row) - - if (label != null) { - val labelComponentConstraints = CC() - labelComponentConstraints.vertical.gapBefore = LABEL_TOP_GAP - componentConstraints.put(label, labelComponentConstraints) - row.addComponent(label) - } - - return row - } - - // cell mode not tested with "gear" button, wait first user request - override fun setCellMode(value: Boolean) { - if (value) { - assert(componentIndexWhenCellModeWasEnabled == -1) - componentIndexWhenCellModeWasEnabled = components.size - } - else { - val firstComponentIndex = componentIndexWhenCellModeWasEnabled - componentIndexWhenCellModeWasEnabled = -1 - // do not add split if cell empty or contains the only component - if ((components.size - firstComponentIndex) > 1) { - val component = components.get(firstComponentIndex) - componentConstraints.getOrPut(component) { CC() }.split(components.size - firstComponentIndex) - } - } - } - - private fun computeChildRowIndent(): Int { - if (components.isEmpty()) { - return 0 - } - else { - val firstComponent = components.first() - if (firstComponent is JRadioButton || firstComponent is JCheckBox) { - return ComponentPanelBuilder.computeCommentInsets(firstComponent, true).left - } - else { - return builder.horizontalGap * 3 - } - } - } + private val spacing: SpacingConfiguration + get() = builder.spacing override var enabled: Boolean = true set(value) { @@ -148,29 +86,117 @@ internal class MigLayoutRow(private val parent: MigLayoutRow?, subRows?.forEach { it.visible = value } } + internal val isLabeledIncludingSubRows: Boolean + get() = labeled || (subRows?.any { it.isLabeledIncludingSubRows } ?: false) + + fun createChildRow(label: JLabel? = null, buttonGroup: ButtonGroup? = null, separated: Boolean = false, noGrid: Boolean = false): MigLayoutRow { + if (subRows == null) { + subRows = SmartList() + } + + val subRows = subRows!! + + if (separated) { + val row = MigLayoutRow(this, componentConstraints, builder, indent = indent, noGrid = true) + subRows.add(row) + row.apply { + val separatorComponent = SeparatorComponent(0, OnePixelDivider.BACKGROUND, null) + val cc = CC() + cc.vertical.gapBefore = gapToBoundSize(spacing.largeVerticalGap, false) + cc.vertical.gapAfter = gapToBoundSize(spacing.verticalGap * 2, false) + componentConstraints.put(separatorComponent, cc) + addComponent(separatorComponent, lazyOf(cc)) + } + } + + val row = MigLayoutRow(this, componentConstraints, builder, labeled = label != null, noGrid = noGrid, + indent = indent + computeChildRowIndent(), buttonGroup = buttonGroup) + subRows.add(row) + + if (label != null) { + val labelComponentConstraints = CC() + labelComponentConstraints.vertical.gapBefore = ConstraintParser.parseBoundSize("${spacing.labelColumnVerticalTopGap}px!", true, false) + componentConstraints.put(label, labelComponentConstraints) + row.addComponent(label, lazyOf(labelComponentConstraints)) + } + + return row + } + + // cell mode not tested with "gear" button, wait first user request + override fun setCellMode(value: Boolean) { + if (value) { + assert(componentIndexWhenCellModeWasEnabled == -1) + componentIndexWhenCellModeWasEnabled = components.size + } + else { + val firstComponentIndex = componentIndexWhenCellModeWasEnabled + componentIndexWhenCellModeWasEnabled = -1 + // do not add split if cell empty or contains the only component + if ((components.size - firstComponentIndex) > 1) { + val component = components.get(firstComponentIndex) + componentConstraints.getOrPut(component) { CC() }.split(components.size - firstComponentIndex) + } + } + } + + private fun computeChildRowIndent(): Int { + val firstComponent = components.firstOrNull() ?: return 0 + if (firstComponent is JRadioButton || firstComponent is JCheckBox) { + return getCommentLeftInset(firstComponent) + } + else { + return spacing.horizontalGap * 3 + } + } + + private fun getCommentLeftInset(component: JComponent): Int { + if (component is JTextField) { + // 1px border, better to indent comment text + return 1 + } + + // as soon as ComponentPanelBuilder will also compensate visual paddings (instead of compensating on LaF level), + // this logic will be moved into computeCommentInsets + val componentBorderVisualLeftPadding = (component.border as? VisualPaddingsProvider)?.getVisualPaddings(component)?.left ?: 0 + val insets = ComponentPanelBuilder.computeCommentInsets(component, true) + return insets.left - componentBorderVisualLeftPadding + } + override operator fun JComponent.invoke(vararg constraints: CCFlags, gapLeft: Int, growPolicy: GrowPolicy?, comment: String?) { - addComponent(this, constraints, gapLeft, growPolicy, comment) + addComponent(this, constraints.create()?.let { lazyOf(it) } ?: lazy { CC() }, gapLeft, growPolicy, comment) } // separate method to avoid JComponent as a receiver - private fun addComponent(component: JComponent, - constraints: Array? = null, - gapLeft: Int = 0, - growPolicy: GrowPolicy? = null, - comment: String? = null) { + private fun addComponent(component: JComponent, cc: Lazy, gapLeft: Int = 0, growPolicy: GrowPolicy? = null, comment: String? = null) { components.add(component) + if (!shareCellWithPreviousComponentIfNeed(component, cc)) { + // increase column index if cell mode not enabled or it is a first component of cell + if (componentIndexWhenCellModeWasEnabled == -1 || componentIndexWhenCellModeWasEnabled == (components.size - 1)) { + columnIndex++ + } + } + + setVisualPaddingsAndLabelTopGapIfNeed(component) + + // (not yet clear is it true or just some strange gaps from another source) MigLayout compensate outer visual paddings, but if there are more than one component in the cell, + // inner horizontal spacing will be not corrected (e.g. between combobox and button will be 7px horizontal gap in case of macOS IntelliJ LaF), as solution, we set horizontal gap for such components). + // if it is not a first component in the cell, compensate horizontal visual paddings using gap. + if (componentIndexWhenCellModeWasEnabled != -1 && componentIndexWhenCellModeWasEnabled < (components.size - 1)) { + cc.value.horizontal.gapBefore = gapToBoundSize(spacing.horizontalGap, true) + } + if (comment != null && comment.isNotEmpty()) { - gapAfter = "0px!" + gapAfter = "${spacing.unitSize}px!" val isParentRowLabeled = labeled // create comment in a new sibling row (developer is still able to create sub rows because rows is not stored in a flat list) parent!!.createChildRow().apply { val commentComponent = ComponentPanelBuilder.createCommentComponent(comment, true) - addComponent(commentComponent) - val commentComponentCC = CC() - commentComponentCC.horizontal.gapBefore = gapToBoundSize(ComponentPanelBuilder.computeCommentInsets(component, true).left, true) + addComponent(commentComponent, lazyOf(commentComponentCC)) + commentComponentCC.horizontal.gapBefore = gapToBoundSize(getCommentLeftInset(component), true) if (isParentRowLabeled) { commentComponentCC.skip() } @@ -182,28 +208,12 @@ internal class MigLayoutRow(private val parent: MigLayoutRow?, buttonGroup.add(component) } - val cc = constraints?.create()?.let { lazyOf(it) } ?: lazy { CC() } - createComponentConstraints(cc, component, gapLeft = gapLeft, growPolicy = growPolicy) - - // JScrollPane doesn't have visual insets (not set by layout, but part of component implementation) as TextField, Combobox and other such components, - // but it looks ugly, so, if not other components in the row and row is labeled - add left/right insets - if (component is JScrollPane && labeled && components.size == 2 && !UIUtil.isUnderWin10LookAndFeel()) { - val scrollPaneHGap = gapToBoundSize(4, true) - cc.value.horizontal.gapBefore = scrollPaneHGap - cc.value.horizontal.gapAfter = scrollPaneHGap - } + builder.defaultComponentConstraintCreator.createComponentConstraints(cc, component, gapLeft = gapLeft, growPolicy = growPolicy) if (!noGrid && indent > 0 && components.size == 1) { cc.value.horizontal.gapBefore = gapToBoundSize(indent, true) } - if (!shareCellWithPreviousComponentIfNeed(component, cc)) { - // increase column index if cell mode not enabled or it is a first component of cell - if (componentIndexWhenCellModeWasEnabled == -1 || componentIndexWhenCellModeWasEnabled == (components.size - 1)) { - columnIndex++ - } - } - // if this row is not labeled and previous row is labeled and component is a "Remember" checkbox, skip one column (since this row doesn't have a label) if (!labeled && components.size == 1 && component is JCheckBox) { val siblings = parent!!.subRows @@ -240,9 +250,26 @@ internal class MigLayoutRow(private val parent: MigLayoutRow?, } } + private fun setVisualPaddingsAndLabelTopGapIfNeed(component: JComponent) { + if (!spacing.isCompensateVisualPaddings) { + return + } + + val border = component.border + if (border is LineBorder) { + if (labeled && components.size == 2) { + componentConstraints.get(components.first())?.vertical?.gapBefore = builder.defaultComponentConstraintCreator.vertical1pxGap + } + return + } + + val paddings = (border as? VisualPaddingsProvider)?.getVisualPaddings(component) ?: return + component.putClientProperty(PlatformDefaults.VISUAL_PADDING_PROPERTY, paddings) + } + private fun shareCellWithPreviousComponentIfNeed(component: JComponent, componentCC: Lazy): Boolean { if (components.size > 1 && component is JLabel && component.icon === AllIcons.General.Gear) { - componentCC.value.horizontal.gapBefore = gapToBoundSize(0, true) + componentCC.value.horizontal.gapBefore = builder.defaultComponentConstraintCreator.horizontalUnitSizeGap if (lastComponentConstraintsWithSplit == null) { val prevComponent = components.get(components.size - 2)!! diff --git a/platform/platform-impl/src/com/intellij/ui/layout/migLayout/componentConstraints.kt b/platform/platform-impl/src/com/intellij/ui/layout/migLayout/componentConstraints.kt index ecd0381ff11a..de79debd56af 100644 --- a/platform/platform-impl/src/com/intellij/ui/layout/migLayout/componentConstraints.kt +++ b/platform/platform-impl/src/com/intellij/ui/layout/migLayout/componentConstraints.kt @@ -20,15 +20,10 @@ import javax.swing.text.JTextComponent internal fun Array.create() = if (isEmpty()) null else CC().apply(this) -private val SHORT_SHORT_TEXT_WIDTH = JBUI.scale(250) -private val MAX_SHORT_TEXT_WIDTH = JBUI.scale(350) -private val SHORT_TEXT_SIZE: BoundSize = ConstraintParser.parseBoundSize("${SHORT_SHORT_TEXT_WIDTH}px!", false, true) -private val MEDIUM_TEXT_SIZE: BoundSize = ConstraintParser.parseBoundSize("${SHORT_SHORT_TEXT_WIDTH}px::${MAX_SHORT_TEXT_WIDTH}px", false, true) - private fun CC.apply(flags: Array): CC { for (flag in flags) { when (flag) { - //CCFlags.wrap -> isWrap = true + //CCFlags.wrap -> isWrap = true CCFlags.grow -> grow() CCFlags.growX -> { growX() @@ -39,90 +34,91 @@ private fun CC.apply(flags: Array): CC { // You can however accomplish the same thing by setting a gap before and/or after the components. // That gap may have a minimum size of 0 and a preferred size of a really large value to create a "pushing" gap. // There is even a keyword for this: "push". So "gapleft push" will be the same as "align right" and work for multi-component cells as well. - //CCFlags.right -> horizontal.gapBefore = BoundSize(null, null, null, true, null) + //CCFlags.right -> horizontal.gapBefore = BoundSize(null, null, null, true, null) CCFlags.push -> push() CCFlags.pushX -> pushX() CCFlags.pushY -> pushY() - - //CCFlags.span -> span() - //CCFlags.spanX -> spanX() - //CCFlags.spanY -> spanY() - - //CCFlags.split -> split() - - //CCFlags.skip -> skip() } } return this } -internal fun createComponentConstraints(cc: Lazy, - component: Component, - gapLeft: Int = 0, - gapAfter: Int = 0, - gapTop: Int = 0, - gapBottom: Int = 0, - split: Int = -1, - growPolicy: GrowPolicy?): CC? { - if (gapLeft != 0) { - cc.value.horizontal.gapBefore = gapToBoundSize(gapLeft, true) - } - if (gapAfter != 0) { - cc.value.horizontal.gapAfter = gapToBoundSize(gapAfter, true) - } +internal class DefaultComponentConstraintCreator(private val spacing: SpacingConfiguration) { + private val shortTextSizeSpec = ConstraintParser.parseBoundSize("${spacing.shortTextWidth}px!", false, true) + private val mediumTextSizeSpec = ConstraintParser.parseBoundSize("${spacing.shortTextWidth}px::${spacing.maxShortTextWidth}px", false, true) - if (gapTop != 0) { - cc.value.vertical.gapBefore = gapToBoundSize(gapTop, false) - } - if (gapBottom != 0) { - cc.value.vertical.gapAfter = gapToBoundSize(gapBottom, false) - } + val vertical1pxGap: BoundSize = ConstraintParser.parseBoundSize("${JBUI.scale(1)}px!", true, false) - if (split != -1) { - cc.value.split = split - } + val horizontalUnitSizeGap = gapToBoundSize(spacing.unitSize, true) - if (growPolicy != null) { - applyGrowPolicy(cc.value, growPolicy) - } - else { - addGrowIfNeed(cc, component) - } - - return if (cc.isInitialized()) cc.value else null -} - -private fun addGrowIfNeed(cc: Lazy, component: Component) { - when { - component is TextFieldWithHistory || component is TextFieldWithHistoryWithBrowseButton -> { - // yes, no max width. approved by UI team (all path fields stretched to the width of the window) - cc.value.minWidth("${MAX_SHORT_TEXT_WIDTH}px") - cc.value.growX() + fun createComponentConstraints(cc: Lazy, + component: Component, + gapLeft: Int = 0, + gapAfter: Int = 0, + gapTop: Int = 0, + gapBottom: Int = 0, + split: Int = -1, + growPolicy: GrowPolicy?): CC? { + if (gapLeft != 0) { + cc.value.horizontal.gapBefore = gapToBoundSize(gapLeft, true) + } + if (gapAfter != 0) { + cc.value.horizontal.gapAfter = gapToBoundSize(gapAfter, true) } - component is JPasswordField -> { - applyGrowPolicy(cc.value, GrowPolicy.SHORT_TEXT) + if (gapTop != 0) { + cc.value.vertical.gapBefore = gapToBoundSize(gapTop, false) + } + if (gapBottom != 0) { + cc.value.vertical.gapAfter = gapToBoundSize(gapBottom, false) } - component is JTextComponent || component is SeparatorComponent || component is ComponentWithBrowseButton<*> -> { - cc.value.growX() + if (split != -1) { + cc.value.split = split } - component is JScrollPane || - (component is JPanel && component.componentCount == 1 && (component.getComponent(0) as? JComponent)?.getClientProperty( - ActionToolbar.ACTION_TOOLBAR_PROPERTY_KEY) != null) -> { - // no need to use pushX - default pushX for cell is 100. avoid to configure more than need - cc.value - .grow() - .pushY() + if (growPolicy != null) { + applyGrowPolicy(cc.value, growPolicy) + } + else { + addGrowIfNeed(cc, component, spacing) + } + + return if (cc.isInitialized()) cc.value else null + } + + private fun addGrowIfNeed(cc: Lazy, component: Component, spacing: SpacingConfiguration) { + when { + component is TextFieldWithHistory || component is TextFieldWithHistoryWithBrowseButton -> { + // yes, no max width. approved by UI team (all path fields stretched to the width of the window) + cc.value.minWidth("${spacing.maxShortTextWidth}px") + cc.value.growX() + } + + component is JPasswordField -> { + applyGrowPolicy(cc.value, GrowPolicy.SHORT_TEXT) + } + + component is JTextComponent || component is SeparatorComponent || component is ComponentWithBrowseButton<*> -> { + cc.value.growX() + } + + component is JScrollPane || + (component is JPanel && component.componentCount == 1 && (component.getComponent(0) as? JComponent)?.getClientProperty( + ActionToolbar.ACTION_TOOLBAR_PROPERTY_KEY) != null) -> { + // no need to use pushX - default pushX for cell is 100. avoid to configure more than need + cc.value + .grow() + .pushY() + } } } -} -private fun applyGrowPolicy(cc: CC, growPolicy: GrowPolicy) { - cc.horizontal.size = when (growPolicy) { - GrowPolicy.SHORT_TEXT -> SHORT_TEXT_SIZE - GrowPolicy.MEDIUM_TEXT -> MEDIUM_TEXT_SIZE + private fun applyGrowPolicy(cc: CC, growPolicy: GrowPolicy) { + cc.horizontal.size = when (growPolicy) { + GrowPolicy.SHORT_TEXT -> shortTextSizeSpec + GrowPolicy.MEDIUM_TEXT -> mediumTextSizeSpec + } } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/layout/readme.md b/platform/platform-impl/src/com/intellij/ui/layout/readme.md index a8e4b36ef2f4..c7ecaaa48792 100644 --- a/platform/platform-impl/src/com/intellij/ui/layout/readme.md +++ b/platform/platform-impl/src/com/intellij/ui/layout/readme.md @@ -1,3 +1,5 @@ +# UI DSL + Use `panel` to create UI: ```kotlin diff --git a/platform/platform-impl/src/com/intellij/ui/layout/testPanels.kt b/platform/platform-impl/src/com/intellij/ui/layout/testPanels.kt index 2a2f02de4da1..963556d310e3 100644 --- a/platform/platform-impl/src/com/intellij/ui/layout/testPanels.kt +++ b/platform/platform-impl/src/com/intellij/ui/layout/testPanels.kt @@ -3,9 +3,9 @@ package com.intellij.ui.layout import com.intellij.CommonBundle import com.intellij.openapi.ui.ComboBox +import com.intellij.ui.JBIntSpinner import com.intellij.ui.components.CheckBox import com.intellij.ui.components.JBPasswordField -import com.intellij.ui.components.JBTextField import com.intellij.ui.components.RadioButton import java.awt.Dimension import javax.swing.* @@ -23,7 +23,7 @@ fun makeSecondColumnSmaller(): JPanel { val branchCombobox = ComboBox() val diffButton = JButton("Show Diff") - val titleTextField = JBTextField() + val titleTextField = JTextField() val descriptionTextArea = JTextArea() @@ -33,8 +33,10 @@ fun makeSecondColumnSmaller(): JPanel { selectForkButton(growX) } row("Base branch:") { - branchCombobox(growX, pushX) - diffButton(growX) + cell { + branchCombobox(growX, pushX) + diffButton(growX) + } } row("Title:") { titleTextField() } row("Description:") { @@ -47,6 +49,50 @@ fun makeSecondColumnSmaller(): JPanel { return panel } +fun visualPaddingsPanelOnlyComboBox(): JPanel { + return panel { + row("Combobox:") { JComboBox(arrayOf("one", "two"))(growX) } + } +} + +fun visualPaddingsPanelOnlyButton(): JPanel { + return panel { + row("Button:") { button("label", growX) {} } + } +} + +fun visualPaddingsPanel(): JPanel { + // we use growX to test right border + return panel { + row("Text field:") { JTextField("text")() } + row("Password:") { JPasswordField("secret")() } + row("Combobox:") { JComboBox(arrayOf("one", "two"))(growX) } + row("Combobox Editable:") { + val field = JComboBox(arrayOf("one", "two")) + field.isEditable = true + field(growX) + } + row("Button:") { button("label", growX) {} } + row("CheckBox:") { CheckBox("enabled")() } + row("RadioButton:") { JRadioButton("label")() } + row("Spinner:") { JBIntSpinner(0, 0, 7)() } + // test text baseline alignment + row("All:") { + cell { + JTextField("t")() + JPasswordField("secret")() + JComboBox(arrayOf("c1", "c2"))(growX) + button("b") {} + CheckBox("c")() + JRadioButton("rb")() + } + } + row("Scroll pane:") { + scrollPane(JTextArea("first line baseline equals to label")) + } + } +} + fun alignFieldsInTheNestedGrid(): JPanel { return panel { buttonGroup { diff --git a/platform/platform-tests/testData/ui/layout/Darcula/align fields in the nested grid.yml b/platform/platform-tests/testData/ui/layout/Darcula/align fields in the nested grid.yml new file mode 100644 index 000000000000..ac35164f2c7c --- /dev/null +++ b/platform/platform-tests/testData/ui/layout/Darcula/align fields in the nested grid.yml @@ -0,0 +1,80 @@ +layoutConstraints: !!net.miginfocom.layout.LC + fillX: true + gridGapX: + min: &id001 {horizontal: true, unit: 0, value: 0.0} + preferred: *id001 + gridGapY: + min: &id002 {horizontal: false, value: 10.0} + preferred: *id002 + hideMode: 3 + insets: + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} +rowConstraints: + constraints: + - align: {horizontal: false, unit: 6, value: 0.0} + - {} + - gapAfter: + max: &id003 {horizontal: true, value: 4.0} + min: *id003 + preferred: *id003 + - {} + count: 4 +columnConstraints: + constraints: + - gapAfter: + max: &id004 {horizontal: true, value: 6.0} + min: *id004 + preferred: *id004 + - gapAfter: + max: &id005 {horizontal: true, value: 8.0} + min: *id005 + preferred: *id005 + grow: 100.0 + - {} + count: 3 +componentConstrains: + In KeePass: {spanX: 2097051, wrap: true} + 'Database:': + horizontal: + gapBefore: + min: &id006 {horizontal: true, value: 21.0} + preferred: *id006 + vertical: + gapBefore: + max: &id007 {horizontal: false, value: 4.0} + min: *id007 + preferred: *id007 + 'JTextField #2': + horizontal: {grow: 100.0} + split: 2 + 'JLabel #3': + horizontal: + gapBefore: + min: &id008 {horizontal: true, value: 4.0} + preferred: *id008 + spanX: 2097051 + wrap: true + 'Master Password:': + horizontal: + gapBefore: + min: &id009 {horizontal: true, value: 21.0} + preferred: *id009 + vertical: + gapBefore: + max: &id010 {horizontal: false, value: 4.0} + min: *id010 + preferred: *id010 + 'JBPasswordField #5': {spanX: 2097051, wrap: true} + Stored using weak encryption.: + horizontal: + gapBefore: + min: &id011 {horizontal: true, value: 1.0} + preferred: *id011 + skip: 1 + spanX: 2097051 + wrap: true +rectangles: '[0, 0, 480, 24], [0, 34, 131, 24], [137, 34, 335, 24], [137, 34, 335, + 24], [0, 68, 131, 24], [137, 68, 343, 24], [137, 96, 343, 14]' diff --git a/platform/platform-tests/testData/ui/layout/Darcula/align fields.yml b/platform/platform-tests/testData/ui/layout/Darcula/align fields.yml new file mode 100644 index 000000000000..e02ac28a30fa --- /dev/null +++ b/platform/platform-tests/testData/ui/layout/Darcula/align fields.yml @@ -0,0 +1,49 @@ +layoutConstraints: !!net.miginfocom.layout.LC + fillX: true + gridGapX: + min: &id001 {horizontal: true, unit: 0, value: 0.0} + preferred: *id001 + gridGapY: + min: &id002 {horizontal: false, value: 10.0} + preferred: *id002 + hideMode: 3 + insets: + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} +rowConstraints: + constraints: + - align: {horizontal: false, unit: 6, value: 0.0} +columnConstraints: + constraints: + - gapAfter: + max: &id003 {horizontal: true, value: 6.0} + min: *id003 + preferred: *id003 + - gapAfter: + max: &id004 {horizontal: true, value: 8.0} + min: *id004 + preferred: *id004 + grow: 100.0 + - {} + count: 3 +componentConstrains: + Create Android module: + vertical: + gapBefore: + max: &id005 {horizontal: false, value: 4.0} + min: *id005 + preferred: *id005 + 'Android module name:': + vertical: + gapBefore: + max: &id006 {horizontal: false, value: 4.0} + min: *id006 + preferred: *id006 + 'JTextField #3': + horizontal: {grow: 100.0} + spanX: 2097051 + wrap: true +rectangles: '[0, 0, 142, 24], [148, 0, 332, 24], [0, 34, 142, 24], [148, 34, 332, + 24]' diff --git a/platform/platform-tests/testData/ui/layout/Darcula/cell.yml b/platform/platform-tests/testData/ui/layout/Darcula/cell.yml new file mode 100644 index 000000000000..3e1d5c2db01f --- /dev/null +++ b/platform/platform-tests/testData/ui/layout/Darcula/cell.yml @@ -0,0 +1,67 @@ +layoutConstraints: !!net.miginfocom.layout.LC + fillX: true + gridGapX: + min: &id001 {horizontal: true, unit: 0, value: 0.0} + preferred: *id001 + gridGapY: + min: &id002 {horizontal: false, value: 10.0} + preferred: *id002 + hideMode: 3 + insets: + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} +rowConstraints: + constraints: + - align: {horizontal: false, unit: 6, value: 0.0} + gapAfter: + max: &id003 {horizontal: true, value: 4.0} + min: *id003 + preferred: *id003 + - {} + count: 2 +columnConstraints: + constraints: + - gapAfter: + max: &id004 {horizontal: true, value: 6.0} + min: *id004 + preferred: *id004 + - gapAfter: + max: &id005 {horizontal: true, value: 8.0} + min: *id005 + preferred: *id005 + grow: 100.0 + - {} + count: 3 +componentConstrains: + 'Repository:': + vertical: + gapBefore: + max: &id006 {horizontal: false, value: 4.0} + min: *id006 + preferred: *id006 + 'ComboBox #1': {split: 2} + Delete: + horizontal: + gapBefore: + min: &id007 {horizontal: true, value: 8.0} + preferred: *id007 + spanX: 2097051 + wrap: true + Use File -> Settings Repository... to configure: + horizontal: + gapBefore: + min: &id008 {horizontal: true, value: 2.0} + preferred: *id008 + skip: 1 + spanX: 2097051 + wrap: true + 'JBScrollPane #4': + horizontal: {grow: 100.0} + pushY: 100.0 + spanX: 2097051 + vertical: {grow: 100.0} + wrap: true +rectangles: '[0, 0, 70, 24], [76, 0, 396, 24], [76, 0, 396, 24], [76, 28, 404, 14], + [0, 52, 480, 258]' diff --git a/platform/platform-tests/testData/ui/layout/Darcula/note row in the dialog.yml b/platform/platform-tests/testData/ui/layout/Darcula/note row in the dialog.yml new file mode 100644 index 000000000000..a474e3439227 --- /dev/null +++ b/platform/platform-tests/testData/ui/layout/Darcula/note row in the dialog.yml @@ -0,0 +1,66 @@ +layoutConstraints: !!net.miginfocom.layout.LC + fillX: true + gridGapX: + min: &id001 {horizontal: true, unit: 0, value: 0.0} + preferred: *id001 + gridGapY: + min: &id002 {horizontal: false, value: 10.0} + preferred: *id002 + hideMode: 3 + insets: + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} +rowConstraints: + constraints: + - align: {horizontal: false, unit: 6, value: 0.0} + noGrid: true + - {} + - {} + - {noGrid: true} + count: 4 +columnConstraints: + constraints: + - gapAfter: + max: &id003 {horizontal: true, value: 6.0} + min: *id003 + preferred: *id003 + - gapAfter: + max: &id004 {horizontal: true, value: 8.0} + min: *id004 + preferred: *id004 + grow: 100.0 + - {} + count: 3 +componentConstrains: + Profiler requires access to the kernel-level API. Enter the sudo password to allow this.: + spanX: 2097051 + vertical: + gapAfter: + min: &id005 {horizontal: false, value: 20.0} + preferred: *id005 + gapBefore: + min: &id006 {horizontal: false, value: 10.0} + preferred: *id006 + wrap: true + 'Sudo password:': + vertical: + gapBefore: + max: &id007 {horizontal: false, value: 4.0} + min: *id007 + preferred: *id007 + 'JPasswordField #2': {spanX: 2097051, wrap: true} + !!binary "G1JlbWVtYmVy": {skip: 1, spanX: 2097051, wrap: true} + 'SimpleColoredComponent #4': + spanX: 2097051 + vertical: + gapAfter: + min: &id008 {horizontal: false, value: 20.0} + preferred: *id008 + gapBefore: + min: &id009 {horizontal: false, value: 12.0} + preferred: *id009 + wrap: true +rectangles: '[0, 0, 480, 62], [0, 72, 100, 24], [106, 72, 374, 24], [106, 106, 374, + 24], [0, 140, 480, 52]' diff --git a/platform/platform-tests/testData/ui/layout/Darcula/visual paddings.yml b/platform/platform-tests/testData/ui/layout/Darcula/visual paddings.yml new file mode 100644 index 000000000000..b17e2535fbf1 --- /dev/null +++ b/platform/platform-tests/testData/ui/layout/Darcula/visual paddings.yml @@ -0,0 +1,155 @@ +layoutConstraints: !!net.miginfocom.layout.LC + fillX: true + gridGapX: + min: &id001 {horizontal: true, unit: 0, value: 0.0} + preferred: *id001 + gridGapY: + min: &id002 {horizontal: false, value: 10.0} + preferred: *id002 + hideMode: 3 + insets: + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} +rowConstraints: + constraints: + - align: {horizontal: false, unit: 6, value: 0.0} +columnConstraints: + constraints: + - gapAfter: + max: &id003 {horizontal: true, value: 6.0} + min: *id003 + preferred: *id003 + - gapAfter: + max: &id004 {horizontal: true, value: 8.0} + min: *id004 + preferred: *id004 + grow: 100.0 + - {} + count: 3 +componentConstrains: + 'Text field:': + vertical: + gapBefore: + max: &id005 {horizontal: false, value: 4.0} + min: *id005 + preferred: *id005 + 'JTextField #1': + horizontal: {grow: 100.0} + spanX: 2097051 + wrap: true + 'Password:': + vertical: + gapBefore: + max: &id006 {horizontal: false, value: 4.0} + min: *id006 + preferred: *id006 + 'JPasswordField #3': {spanX: 2097051, wrap: true} + 'Combobox:': + vertical: + gapBefore: + max: &id007 {horizontal: false, value: 4.0} + min: *id007 + preferred: *id007 + 'JComboBox #5': + horizontal: {grow: 100.0} + spanX: 2097051 + wrap: true + 'Combobox Editable:': + vertical: + gapBefore: + max: &id008 {horizontal: false, value: 4.0} + min: *id008 + preferred: *id008 + 'JComboBox #7': + horizontal: {grow: 100.0} + spanX: 2097051 + wrap: true + 'Button:': + vertical: + gapBefore: + max: &id009 {horizontal: false, value: 4.0} + min: *id009 + preferred: *id009 + label: {spanX: 2097051, wrap: true} + 'CheckBox:': + vertical: + gapBefore: + max: &id010 {horizontal: false, value: 4.0} + min: *id010 + preferred: *id010 + enabled: {spanX: 2097051, wrap: true} + 'RadioButton:': + vertical: + gapBefore: + max: &id011 {horizontal: false, value: 4.0} + min: *id011 + preferred: *id011 + 'Spinner:': + vertical: + gapBefore: + max: &id012 {horizontal: false, value: 4.0} + min: *id012 + preferred: *id012 + 'JBIntSpinner #15': {spanX: 2097051, wrap: true} + 'All:': + vertical: + gapBefore: + max: &id013 {horizontal: false, value: 4.0} + min: *id013 + preferred: *id013 + 'JTextField #17': + horizontal: {grow: 100.0} + split: 6 + 'JPasswordField #18': + horizontal: + gapBefore: + min: &id014 {horizontal: true, value: 8.0} + preferred: *id014 + size: + max: &id015 {horizontal: true, value: 250.0} + min: *id015 + preferred: *id015 + 'JComboBox #19': + horizontal: + gapBefore: + min: &id016 {horizontal: true, value: 8.0} + preferred: *id016 + grow: 100.0 + b: + horizontal: + gapBefore: + min: &id017 {horizontal: true, value: 8.0} + preferred: *id017 + c: + horizontal: + gapBefore: + min: &id018 {horizontal: true, value: 8.0} + preferred: *id018 + rb: + horizontal: + gapBefore: + min: &id019 {horizontal: true, value: 8.0} + preferred: *id019 + spanX: 2097051 + wrap: true + 'Scroll pane:': + vertical: + gapBefore: + max: &id020 {horizontal: false, value: 1.0} + min: *id020 + preferred: *id020 + 'JBScrollPane #24': + horizontal: {grow: 100.0} + pushY: 100.0 + spanX: 2097051 + vertical: {grow: 100.0} + wrap: true +rectangles: '[0, 0, 124, 24], [130, 0, 518, 24], [0, 34, 124, 24], [130, 34, 518, + 24], [0, 68, 124, 24], [130, 68, 518, 24], [0, 102, 124, 24], [130, 102, 518, 24], + [0, 136, 124, 24], [130, 136, 518, 24], [0, 170, 124, 24], [130, 170, 518, 24], + [0, 204, 124, 24], [130, 204, 518, 24], [0, 238, 124, 24], [130, 238, 518, 24], + [0, 272, 124, 24], [130, 272, 510, 24], [130, 272, 510, 24], [130, 272, 510, 24], + [130, 272, 510, 24], [130, 272, 510, 24], [130, 272, 510, 24], [0, 306, 124, 21], + [130, 306, 518, 21]' diff --git a/platform/platform-tests/testData/ui/layout/IntelliJ/align fields in the nested grid.yml b/platform/platform-tests/testData/ui/layout/IntelliJ/align fields in the nested grid.yml new file mode 100644 index 000000000000..7701526f2e58 --- /dev/null +++ b/platform/platform-tests/testData/ui/layout/IntelliJ/align fields in the nested grid.yml @@ -0,0 +1,80 @@ +layoutConstraints: !!net.miginfocom.layout.LC + fillX: true + gridGapX: + min: &id001 {horizontal: true, unit: 0, value: 0.0} + preferred: *id001 + gridGapY: + min: &id002 {horizontal: false, value: 10.0} + preferred: *id002 + hideMode: 3 + insets: + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} +rowConstraints: + constraints: + - align: {horizontal: false, unit: 6, value: 0.0} + - {} + - gapAfter: + max: &id003 {horizontal: true, value: 4.0} + min: *id003 + preferred: *id003 + - {} + count: 4 +columnConstraints: + constraints: + - gapAfter: + max: &id004 {horizontal: true, value: 6.0} + min: *id004 + preferred: *id004 + - gapAfter: + max: &id005 {horizontal: true, value: 8.0} + min: *id005 + preferred: *id005 + grow: 100.0 + - {} + count: 3 +componentConstrains: + In KeePass: {spanX: 2097051, wrap: true} + 'Database:': + horizontal: + gapBefore: + min: &id006 {horizontal: true, value: 24.0} + preferred: *id006 + vertical: + gapBefore: + max: &id007 {horizontal: false, value: 2.0} + min: *id007 + preferred: *id007 + 'JTextField #2': + horizontal: {grow: 100.0} + split: 2 + 'JLabel #3': + horizontal: + gapBefore: + min: &id008 {horizontal: true, value: 4.0} + preferred: *id008 + spanX: 2097051 + wrap: true + 'Master Password:': + horizontal: + gapBefore: + min: &id009 {horizontal: true, value: 24.0} + preferred: *id009 + vertical: + gapBefore: + max: &id010 {horizontal: false, value: 2.0} + min: *id010 + preferred: *id010 + 'JBPasswordField #5': {spanX: 2097051, wrap: true} + Stored using weak encryption.: + horizontal: + gapBefore: + min: &id011 {horizontal: true, value: 1.0} + preferred: *id011 + skip: 1 + spanX: 2097051 + wrap: true +rectangles: '[0, 0, 480, 21], [0, 31, 134, 21], [140, 31, 332, 21], [140, 31, 332, + 21], [0, 62, 134, 21], [140, 62, 340, 21], [140, 87, 340, 14]' diff --git a/platform/platform-tests/testData/ui/layout/IntelliJ/align fields.yml b/platform/platform-tests/testData/ui/layout/IntelliJ/align fields.yml new file mode 100644 index 000000000000..29d168c8531a --- /dev/null +++ b/platform/platform-tests/testData/ui/layout/IntelliJ/align fields.yml @@ -0,0 +1,49 @@ +layoutConstraints: !!net.miginfocom.layout.LC + fillX: true + gridGapX: + min: &id001 {horizontal: true, unit: 0, value: 0.0} + preferred: *id001 + gridGapY: + min: &id002 {horizontal: false, value: 10.0} + preferred: *id002 + hideMode: 3 + insets: + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} +rowConstraints: + constraints: + - align: {horizontal: false, unit: 6, value: 0.0} +columnConstraints: + constraints: + - gapAfter: + max: &id003 {horizontal: true, value: 6.0} + min: *id003 + preferred: *id003 + - gapAfter: + max: &id004 {horizontal: true, value: 8.0} + min: *id004 + preferred: *id004 + grow: 100.0 + - {} + count: 3 +componentConstrains: + Create Android module: + vertical: + gapBefore: + max: &id005 {horizontal: false, value: 2.0} + min: *id005 + preferred: *id005 + 'Android module name:': + vertical: + gapBefore: + max: &id006 {horizontal: false, value: 2.0} + min: *id006 + preferred: *id006 + 'JTextField #3': + horizontal: {grow: 100.0} + spanX: 2097051 + wrap: true +rectangles: '[0, 0, 142, 21], [148, 0, 332, 21], [0, 31, 142, 21], [148, 31, 332, + 21]' diff --git a/platform/platform-tests/testData/ui/layout/IntelliJ/cell.yml b/platform/platform-tests/testData/ui/layout/IntelliJ/cell.yml new file mode 100644 index 000000000000..637e92e72f60 --- /dev/null +++ b/platform/platform-tests/testData/ui/layout/IntelliJ/cell.yml @@ -0,0 +1,67 @@ +layoutConstraints: !!net.miginfocom.layout.LC + fillX: true + gridGapX: + min: &id001 {horizontal: true, unit: 0, value: 0.0} + preferred: *id001 + gridGapY: + min: &id002 {horizontal: false, value: 10.0} + preferred: *id002 + hideMode: 3 + insets: + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} +rowConstraints: + constraints: + - align: {horizontal: false, unit: 6, value: 0.0} + gapAfter: + max: &id003 {horizontal: true, value: 4.0} + min: *id003 + preferred: *id003 + - {} + count: 2 +columnConstraints: + constraints: + - gapAfter: + max: &id004 {horizontal: true, value: 6.0} + min: *id004 + preferred: *id004 + - gapAfter: + max: &id005 {horizontal: true, value: 8.0} + min: *id005 + preferred: *id005 + grow: 100.0 + - {} + count: 3 +componentConstrains: + 'Repository:': + vertical: + gapBefore: + max: &id006 {horizontal: false, value: 2.0} + min: *id006 + preferred: *id006 + 'ComboBox #1': {split: 2} + Delete: + horizontal: + gapBefore: + min: &id007 {horizontal: true, value: 8.0} + preferred: *id007 + spanX: 2097051 + wrap: true + Use File -> Settings Repository... to configure: + horizontal: + gapBefore: + min: &id008 {horizontal: true, value: 2.0} + preferred: *id008 + skip: 1 + spanX: 2097051 + wrap: true + 'JBScrollPane #4': + horizontal: {grow: 100.0} + pushY: 100.0 + spanX: 2097051 + vertical: {grow: 100.0} + wrap: true +rectangles: '[0, 0, 70, 21], [76, 0, 396, 21], [76, 0, 396, 21], [76, 25, 404, 14], + [0, 49, 480, 261]' diff --git a/platform/platform-tests/testData/ui/layout/IntelliJ/note row in the dialog.yml b/platform/platform-tests/testData/ui/layout/IntelliJ/note row in the dialog.yml new file mode 100644 index 000000000000..6d40f1cbfe9d --- /dev/null +++ b/platform/platform-tests/testData/ui/layout/IntelliJ/note row in the dialog.yml @@ -0,0 +1,66 @@ +layoutConstraints: !!net.miginfocom.layout.LC + fillX: true + gridGapX: + min: &id001 {horizontal: true, unit: 0, value: 0.0} + preferred: *id001 + gridGapY: + min: &id002 {horizontal: false, value: 10.0} + preferred: *id002 + hideMode: 3 + insets: + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} +rowConstraints: + constraints: + - align: {horizontal: false, unit: 6, value: 0.0} + noGrid: true + - {} + - {} + - {noGrid: true} + count: 4 +columnConstraints: + constraints: + - gapAfter: + max: &id003 {horizontal: true, value: 6.0} + min: *id003 + preferred: *id003 + - gapAfter: + max: &id004 {horizontal: true, value: 8.0} + min: *id004 + preferred: *id004 + grow: 100.0 + - {} + count: 3 +componentConstrains: + Profiler requires access to the kernel-level API. Enter the sudo password to allow this.: + spanX: 2097051 + vertical: + gapAfter: + min: &id005 {horizontal: false, value: 20.0} + preferred: *id005 + gapBefore: + min: &id006 {horizontal: false, value: 10.0} + preferred: *id006 + wrap: true + 'Sudo password:': + vertical: + gapBefore: + max: &id007 {horizontal: false, value: 2.0} + min: *id007 + preferred: *id007 + 'JPasswordField #2': {spanX: 2097051, wrap: true} + !!binary "G1JlbWVtYmVy": {skip: 1, spanX: 2097051, wrap: true} + 'SimpleColoredComponent #4': + spanX: 2097051 + vertical: + gapAfter: + min: &id008 {horizontal: false, value: 20.0} + preferred: *id008 + gapBefore: + min: &id009 {horizontal: false, value: 12.0} + preferred: *id009 + wrap: true +rectangles: '[0, 0, 480, 62], [0, 72, 100, 21], [106, 72, 374, 21], [106, 103, 374, + 21], [0, 134, 480, 52]' diff --git a/platform/platform-tests/testData/ui/layout/IntelliJ/visual paddings.yml b/platform/platform-tests/testData/ui/layout/IntelliJ/visual paddings.yml new file mode 100644 index 000000000000..fdf1576dfd98 --- /dev/null +++ b/platform/platform-tests/testData/ui/layout/IntelliJ/visual paddings.yml @@ -0,0 +1,155 @@ +layoutConstraints: !!net.miginfocom.layout.LC + fillX: true + gridGapX: + min: &id001 {horizontal: true, unit: 0, value: 0.0} + preferred: *id001 + gridGapY: + min: &id002 {horizontal: false, value: 10.0} + preferred: *id002 + hideMode: 3 + insets: + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} + - {horizontal: false, unit: 0, value: 0.0} + - {horizontal: true, unit: 0, value: 0.0} +rowConstraints: + constraints: + - align: {horizontal: false, unit: 6, value: 0.0} +columnConstraints: + constraints: + - gapAfter: + max: &id003 {horizontal: true, value: 6.0} + min: *id003 + preferred: *id003 + - gapAfter: + max: &id004 {horizontal: true, value: 8.0} + min: *id004 + preferred: *id004 + grow: 100.0 + - {} + count: 3 +componentConstrains: + 'Text field:': + vertical: + gapBefore: + max: &id005 {horizontal: false, value: 2.0} + min: *id005 + preferred: *id005 + 'JTextField #1': + horizontal: {grow: 100.0} + spanX: 2097051 + wrap: true + 'Password:': + vertical: + gapBefore: + max: &id006 {horizontal: false, value: 2.0} + min: *id006 + preferred: *id006 + 'JPasswordField #3': {spanX: 2097051, wrap: true} + 'Combobox:': + vertical: + gapBefore: + max: &id007 {horizontal: false, value: 2.0} + min: *id007 + preferred: *id007 + 'JComboBox #5': + horizontal: {grow: 100.0} + spanX: 2097051 + wrap: true + 'Combobox Editable:': + vertical: + gapBefore: + max: &id008 {horizontal: false, value: 2.0} + min: *id008 + preferred: *id008 + 'JComboBox #7': + horizontal: {grow: 100.0} + spanX: 2097051 + wrap: true + 'Button:': + vertical: + gapBefore: + max: &id009 {horizontal: false, value: 2.0} + min: *id009 + preferred: *id009 + label: {spanX: 2097051, wrap: true} + 'CheckBox:': + vertical: + gapBefore: + max: &id010 {horizontal: false, value: 2.0} + min: *id010 + preferred: *id010 + enabled: {spanX: 2097051, wrap: true} + 'RadioButton:': + vertical: + gapBefore: + max: &id011 {horizontal: false, value: 2.0} + min: *id011 + preferred: *id011 + 'Spinner:': + vertical: + gapBefore: + max: &id012 {horizontal: false, value: 2.0} + min: *id012 + preferred: *id012 + 'JBIntSpinner #15': {spanX: 2097051, wrap: true} + 'All:': + vertical: + gapBefore: + max: &id013 {horizontal: false, value: 2.0} + min: *id013 + preferred: *id013 + 'JTextField #17': + horizontal: {grow: 100.0} + split: 6 + 'JPasswordField #18': + horizontal: + gapBefore: + min: &id014 {horizontal: true, value: 8.0} + preferred: *id014 + size: + max: &id015 {horizontal: true, value: 250.0} + min: *id015 + preferred: *id015 + 'JComboBox #19': + horizontal: + gapBefore: + min: &id016 {horizontal: true, value: 8.0} + preferred: *id016 + grow: 100.0 + b: + horizontal: + gapBefore: + min: &id017 {horizontal: true, value: 8.0} + preferred: *id017 + c: + horizontal: + gapBefore: + min: &id018 {horizontal: true, value: 8.0} + preferred: *id018 + rb: + horizontal: + gapBefore: + min: &id019 {horizontal: true, value: 8.0} + preferred: *id019 + spanX: 2097051 + wrap: true + 'Scroll pane:': + vertical: + gapBefore: + max: &id020 {horizontal: false, value: 1.0} + min: *id020 + preferred: *id020 + 'JBScrollPane #24': + horizontal: {grow: 100.0} + pushY: 100.0 + spanX: 2097051 + vertical: {grow: 100.0} + wrap: true +rectangles: '[0, 0, 124, 21], [130, 0, 519, 21], [0, 31, 124, 21], [130, 31, 519, + 21], [0, 62, 124, 21], [130, 62, 519, 21], [0, 93, 124, 21], [130, 93, 519, 21], + [0, 124, 124, 21], [130, 124, 519, 21], [0, 155, 124, 21], [130, 155, 519, 21], + [0, 186, 124, 21], [130, 186, 519, 21], [0, 217, 124, 21], [130, 217, 519, 21], + [0, 248, 124, 21], [130, 248, 511, 21], [130, 248, 511, 21], [130, 248, 511, 21], + [130, 248, 511, 21], [130, 248, 511, 21], [130, 248, 511, 21], [0, 279, 124, 31], + [130, 279, 519, 31]' diff --git a/platform/platform-tests/testData/ui/layout/align fields in the nested grid.yml b/platform/platform-tests/testData/ui/layout/align fields in the nested grid.yml deleted file mode 100644 index 9c6c17ec2cf9..000000000000 --- a/platform/platform-tests/testData/ui/layout/align fields in the nested grid.yml +++ /dev/null @@ -1,63 +0,0 @@ -layoutConstraints: !!net.miginfocom.layout.LC - fillX: true - gridGapX: - min: &id001 {horizontal: true, operation: 100, unit: 0, unitString: px, value: 20.0} - preferred: *id001 - gridGapY: - min: &id002 {horizontal: false, operation: 100, unit: 0, unitString: px, value: 4.0} - preferred: *id002 - hideMode: 3 - insets: - - {horizontal: false, operation: 100, unit: 0, unitString: px, value: 0.0} - - {horizontal: true, operation: 100, unit: 0, unitString: px, value: 0.0} - - {horizontal: false, operation: 100, unit: 0, unitString: px, value: 0.0} - - {horizontal: true, operation: 100, unit: 0, unitString: px, value: 0.0} - visualPadding: false -rowConstraints: - constraints: - - align: {horizontal: false, operation: 100, unit: 6, value: 0.0} - - {} - - gapAfter: - max: &id003 {horizontal: true, operation: 100, unit: 0, unitString: px, value: 0.0} - min: *id003 - preferred: *id003 - - {} - count: 4 -columnConstraints: - constraints: - - {} - - {grow: 100.0} - count: 2 -componentConstrains: - In KeePass: {spanX: 2097051, wrap: true} - 'Database:': - horizontal: - gapBefore: - min: &id004 {horizontal: true, operation: 100, unit: 0, unitString: px, value: 23.0} - preferred: *id004 - 'JTextField #2': - horizontal: {grow: 100.0} - split: 2 - 'JLabel #3': - horizontal: - gapBefore: - min: &id005 {horizontal: true, operation: 100, unit: 0, unitString: px, value: 0.0} - preferred: *id005 - spanX: 2097051 - wrap: true - 'Master Password:': - horizontal: - gapBefore: - min: &id006 {horizontal: true, operation: 100, unit: 0, unitString: px, value: 23.0} - preferred: *id006 - 'JBPasswordField #5': {spanX: 2097051, wrap: true} - Stored using weak encryption.: - horizontal: - gapBefore: - min: &id007 {horizontal: true, operation: 100, unit: 0, unitString: px, value: 4.0} - preferred: *id007 - skip: 1 - spanX: 2097051 - wrap: true -rectangles: '[0, 0, 512, 23], [0, 27, 132, 26], [152, 27, 360, 26], [152, 27, 360, - 26], [0, 57, 132, 26], [152, 57, 360, 26], [152, 83, 360, 14]' diff --git a/platform/platform-tests/testData/ui/layout/align fields.yml b/platform/platform-tests/testData/ui/layout/align fields.yml deleted file mode 100644 index 8c982081a674..000000000000 --- a/platform/platform-tests/testData/ui/layout/align fields.yml +++ /dev/null @@ -1,39 +0,0 @@ -layoutConstraints: !!net.miginfocom.layout.LC - fillX: true - gridGapX: - min: &id001 {horizontal: true, operation: 100, unit: 0, unitString: px, value: 20.0} - preferred: *id001 - gridGapY: - min: &id002 {horizontal: false, operation: 100, unit: 0, unitString: px, value: 4.0} - preferred: *id002 - hideMode: 3 - insets: - - {horizontal: false, operation: 100, unit: 0, unitString: px, value: 0.0} - - {horizontal: true, operation: 100, unit: 0, unitString: px, value: 0.0} - - {horizontal: false, operation: 100, unit: 0, unitString: px, value: 0.0} - - {horizontal: true, operation: 100, unit: 0, unitString: px, value: 0.0} - visualPadding: false -rowConstraints: - constraints: - - align: {horizontal: false, operation: 100, unit: 6, value: 0.0} -columnConstraints: - constraints: - - {} - - {grow: 100.0} - count: 2 -componentConstrains: - Create Android module: - vertical: - gapBefore: &id004 - max: &id003 {horizontal: false, operation: 100, unit: 0, unitString: px, value: 4.0} - min: *id003 - preferred: *id003 - 'Android module name:': - vertical: - gapBefore: *id004 - 'JTextField #3': - horizontal: {grow: 100.0} - spanX: 2097051 - wrap: true -rectangles: '[0, 0, 145, 23], [165, 0, 347, 23], [0, 27, 145, 26], [165, 27, 347, - 26]' diff --git a/platform/platform-tests/testData/ui/layout/cell.yml b/platform/platform-tests/testData/ui/layout/cell.yml deleted file mode 100644 index 7b82880db6c4..000000000000 --- a/platform/platform-tests/testData/ui/layout/cell.yml +++ /dev/null @@ -1,54 +0,0 @@ -layoutConstraints: !!net.miginfocom.layout.LC - fillX: true - gridGapX: - min: &id001 {horizontal: true, operation: 100, unit: 0, unitString: px, value: 20.0} - preferred: *id001 - gridGapY: - min: &id002 {horizontal: false, operation: 100, unit: 0, unitString: px, value: 4.0} - preferred: *id002 - hideMode: 3 - insets: - - {horizontal: false, operation: 100, unit: 0, unitString: px, value: 0.0} - - {horizontal: true, operation: 100, unit: 0, unitString: px, value: 0.0} - - {horizontal: false, operation: 100, unit: 0, unitString: px, value: 0.0} - - {horizontal: true, operation: 100, unit: 0, unitString: px, value: 0.0} - visualPadding: false -rowConstraints: - constraints: - - align: {horizontal: false, operation: 100, unit: 6, value: 0.0} - gapAfter: - max: &id003 {horizontal: true, operation: 100, unit: 0, unitString: px, value: 0.0} - min: *id003 - preferred: *id003 - - {} - count: 2 -columnConstraints: - constraints: - - {} - - {grow: 100.0} - count: 2 -componentConstrains: - 'Repository:': - vertical: - gapBefore: - max: &id004 {horizontal: false, operation: 100, unit: 0, unitString: px, value: 4.0} - min: *id004 - preferred: *id004 - 'ComboBox #1': {split: 2} - Delete: {spanX: 2097051, wrap: true} - Use File -> Settings Repository... to configure: - horizontal: - gapBefore: - min: &id005 {horizontal: true, operation: 100, unit: 0, unitString: px, value: 4.0} - preferred: *id005 - skip: 1 - spanX: 2097051 - wrap: true - 'JBScrollPane #4': - horizontal: {grow: 100.0} - pushY: 100.0 - spanX: 2097051 - vertical: {grow: 100.0} - wrap: true -rectangles: '[0, 0, 71, 29], [91, 0, 421, 29], [91, 0, 421, 29], [91, 29, 421, 14], - [0, 47, 512, 205]' diff --git a/platform/platform-tests/testData/ui/layout/note row in the dialog.yml b/platform/platform-tests/testData/ui/layout/note row in the dialog.yml deleted file mode 100644 index 98fd9a9dfffe..000000000000 --- a/platform/platform-tests/testData/ui/layout/note row in the dialog.yml +++ /dev/null @@ -1,59 +0,0 @@ -layoutConstraints: !!net.miginfocom.layout.LC - fillX: true - gridGapX: - min: &id001 {horizontal: true, operation: 100, unit: 0, unitString: px, value: 20.0} - preferred: *id001 - gridGapY: - min: &id002 {horizontal: false, operation: 100, unit: 0, unitString: px, value: 4.0} - preferred: *id002 - hideMode: 3 - insets: - - {horizontal: false, operation: 100, unit: 0, unitString: px, value: 0.0} - - {horizontal: true, operation: 100, unit: 0, unitString: px, value: 0.0} - - {horizontal: false, operation: 100, unit: 0, unitString: px, value: 0.0} - - {horizontal: true, operation: 100, unit: 0, unitString: px, value: 0.0} - visualPadding: false -rowConstraints: - constraints: - - align: {horizontal: false, operation: 100, unit: 6, value: 0.0} - noGrid: true - - {} - - {} - - {noGrid: true} - count: 4 -columnConstraints: - constraints: - - {} - - {grow: 100.0} - count: 2 -componentConstrains: - Profiler requires access to the kernel-level API. Enter the sudo password to allow this.: - spanX: 2097051 - vertical: - gapAfter: - min: &id003 {horizontal: false, operation: 100, unit: 0, unitString: px, value: 10.0} - preferred: *id003 - gapBefore: - min: &id004 {horizontal: false, operation: 100, unit: 0, unitString: px, value: 4.0} - preferred: *id004 - wrap: true - 'Sudo password:': - vertical: - gapBefore: - max: &id005 {horizontal: false, operation: 100, unit: 0, unitString: px, value: 4.0} - min: *id005 - preferred: *id005 - 'JPasswordField #2': {spanX: 2097051, wrap: true} - !!binary "G1JlbWVtYmVy": {skip: 1, spanX: 2097051, wrap: true} - 'SimpleColoredComponent #4': - spanX: 2097051 - vertical: - gapAfter: - min: &id006 {horizontal: false, operation: 100, unit: 0, unitString: px, value: 10.0} - preferred: *id006 - gapBefore: - min: &id007 {horizontal: false, operation: 100, unit: 0, unitString: px, value: 12.0} - preferred: *id007 - wrap: true -rectangles: '[0, 0, 512, 46], [0, 50, 99, 26], [119, 50, 393, 26], [119, 80, 393, - 23], [0, 107, 512, 42]' diff --git a/platform/platform-tests/testSrc/com/intellij/ui/layout/MigLayoutTestApp.kt b/platform/platform-tests/testSrc/com/intellij/ui/layout/MigLayoutTestApp.kt index 1ae7a1d65647..f927f1e0ebfc 100644 --- a/platform/platform-tests/testSrc/com/intellij/ui/layout/MigLayoutTestApp.kt +++ b/platform/platform-tests/testSrc/com/intellij/ui/layout/MigLayoutTestApp.kt @@ -2,11 +2,15 @@ package com.intellij.ui.layout import com.intellij.ide.ui.laf.IntelliJLaf +import com.intellij.openapi.util.SystemInfoRt import com.intellij.testFramework.runInEdtAndWait import com.intellij.ui.components.dialog import com.intellij.util.io.write +import com.intellij.util.ui.JBUI +import net.miginfocom.layout.LayoutUtil import net.miginfocom.swing.MigLayout import java.awt.Dimension +import java.awt.GraphicsEnvironment import java.nio.file.Paths import javax.swing.UIManager import javax.swing.plaf.metal.MetalLookAndFeel @@ -14,13 +18,23 @@ import javax.swing.plaf.metal.MetalLookAndFeel object MigLayoutTestApp { @JvmStatic fun main(args: Array) { -// LayoutUtil.setGlobalDebugMillis(1000) + val isDebugEnabled = true +// val isDebugEnabled = false + @Suppress("ConstantConditionIf") + if (isDebugEnabled) { + LayoutUtil.setGlobalDebugMillis(1000) + } runInEdtAndWait { UIManager.setLookAndFeel(MetalLookAndFeel()) - UIManager.setLookAndFeel(IntelliJLaf()) +// UIManager.setLookAndFeel(IntelliJLaf()) + UIManager.setLookAndFeel(DarculaLaf()) + +// val panel = visualPaddingsPanelOnlyButton() +// val panel = alignFieldsInTheNestedGrid() +// val panel = cellPanel() + val panel = visualPaddingsPanel() - val panel = noteRowInTheDialog() val dialog = dialog( title = "", panel = panel, @@ -30,9 +44,23 @@ object MigLayoutTestApp { return@dialog null } - panel.preferredSize = Dimension(512, 256) - dialog.toFront() + panel.preferredSize = Dimension(50, 50) Paths.get(System.getProperty("user.home"), "layout-dump.yml").write(configurationToJson(panel, panel.layout as MigLayout)) + + val screenDevices = GraphicsEnvironment.getLocalGraphicsEnvironment().screenDevices + if (SystemInfoRt.isMac && screenDevices != null && screenDevices.size > 1) { + // use not-Retina + for (screenDevice in screenDevices) { + if (!JBUI.isRetina(screenDevice)) { + val screenBounds = screenDevice.defaultConfiguration.bounds + dialog.setLocation(screenBounds.x, (screenBounds.height - dialog.preferredSize.height) / 2) + dialog.window.setLocation(screenBounds.x, (screenBounds.height - dialog.preferredSize.height) / 2) + break + } + } + } + +// dialog.toFront() dialog.showAndGet() // val frame = JFrame() diff --git a/platform/platform-tests/testSrc/com/intellij/ui/layout/UiDslTest.kt b/platform/platform-tests/testSrc/com/intellij/ui/layout/UiDslTest.kt index 461e8a611539..2bb3b525b0ed 100644 --- a/platform/platform-tests/testSrc/com/intellij/ui/layout/UiDslTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ui/layout/UiDslTest.kt @@ -2,7 +2,9 @@ package com.intellij.ui.layout import com.intellij.ide.ui.laf.IntelliJLaf +import com.intellij.ide.ui.laf.darcula.DarculaLaf import com.intellij.openapi.application.invokeAndWaitIfNeed +import com.intellij.openapi.util.SystemInfoRt import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.UsefulTestCase import com.intellij.testFramework.assertions.Assertions @@ -10,23 +12,26 @@ import com.intellij.util.io.exists import com.intellij.util.io.outputStream import com.intellij.util.io.sanitizeFileName import com.intellij.util.io.write +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.UIUtil import io.netty.util.internal.SystemPropertyUtil import net.miginfocom.layout.Grid import net.miginfocom.layout.LayoutUtil import net.miginfocom.swing.MigLayout import org.assertj.core.data.Offset import org.assertj.swing.assertions.Assertions.assertThat -import org.assertj.swing.edt.FailOnThreadViolationRepaintManager -import org.assertj.swing.edt.GuiActionRunner -import org.assertj.swing.fixture.FrameFixture -import org.junit.* import org.junit.Assume.assumeTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test import org.junit.rules.TestName -import java.awt.* +import java.awt.BorderLayout +import java.awt.Component +import java.awt.Dimension +import java.awt.GraphicsEnvironment import java.awt.image.BufferedImage import java.nio.file.Path import java.nio.file.Paths -import java.util.concurrent.Callable import javax.imageio.ImageIO import javax.swing.JFrame import javax.swing.JPanel @@ -40,84 +45,103 @@ import kotlin.properties.Delegates * Checkout git@github.com:develar/intellij-ui-dsl-test-snapshots.git (or create own repo) to some local dir and set env LAYOUT_IMAGE_REPO * to use image snapshots. */ -class UiDslTest { +open class UiDslTest { companion object { - @Suppress("unused") - @BeforeClass - fun setUpOnce() { - FailOnThreadViolationRepaintManager.install() - UIManager.setLookAndFeel(MetalLookAndFeel()) - UIManager.setLookAndFeel(IntelliJLaf()) - } + var currentLaf: String? = null private val imageDir: String? = System.getenv("LAYOUT_IMAGE_REPO") } - private var window: FrameFixture by Delegates.notNull() + open val lafName = "IntelliJ" @Rule @JvmField val testName = TestName() - @After - fun tearDown() { - window.cleanUp() - } - @Before fun beforeMethod() { assumeTrue(!UsefulTestCase.IS_UNDER_TEAMCITY) - } - private fun saveImage(file: Path) { - file.outputStream().use { - ImageIO.write(componentToImage(getContentPane()), "png", it) + if (currentLaf != lafName) { + currentLaf = lafName + invokeAndWaitIfNeed { + UIManager.setLookAndFeel(MetalLookAndFeel()) + val laf = if (lafName == "IntelliJ") IntelliJLaf() else DarculaLaf() + UIManager.setLookAndFeel(laf) + + if (lafName == "Darcula") { + // static init it is hell - UIUtil static init is called too early, so, call it to init properly + // (otherwise null stylesheet added and it leads to NPE on set comment text) + UIManager.getDefaults().put("javax.swing.JLabel.userStyleSheet", UIUtil.JBHtmlEditorKit.createStyleSheet()) + } + } } } - private fun getContentPane(): Container { - return window.target() + private fun saveImage(file: Path, component: Component) { + file.outputStream().use { + ImageIO.write(componentToImage(component), "png", it) + } } @Test fun `align fields in the nested grid`() { - doTest(alignFieldsInTheNestedGrid()) + doTest { alignFieldsInTheNestedGrid() } } @Test fun `align fields`() { - doTest(labelRowShouldNotGrow()) + doTest { labelRowShouldNotGrow() } } @Test fun cell() { - doTest(cellPanel()) + doTest { cellPanel() } } @Test fun `note row in the dialog`() { - doTest(noteRowInTheDialog()) + doTest { noteRowInTheDialog() } } - private fun doTest(panel: JPanel) { - val frame = GuiActionRunner.execute(Callable { + @Test + fun `visual paddings`() { + doTest { visualPaddingsPanel()} + } + + private fun doTest(panelCreator: () -> JPanel) { + var panel: JPanel by Delegates.notNull() + val frame = invokeAndWaitIfNeed { LayoutUtil.setGlobalDebugMillis(1000) - panel.background = Color.WHITE - panel.name = "test" + panel = panelCreator() val frame = JFrame("wrapper") frame.isUndecorated = true - frame.contentPane.background = Color.WHITE - frame.background = Color.WHITE frame.contentPane.add(panel, BorderLayout.CENTER) - frame.minimumSize = Dimension(512, 256) - frame - }) - window = FrameFixture(frame) - window.show() + frame.minimumSize = Dimension(480, 320) - val component = window.panel("test").target() as JPanel + val screenDevices = GraphicsEnvironment.getLocalGraphicsEnvironment().screenDevices + if (SystemInfoRt.isMac && screenDevices != null && screenDevices.size > 1) { + // use non-Retina + for (screenDevice in screenDevices) { + if (!JBUI.isRetina(screenDevice)) { + frame.setLocation(screenDevice.defaultConfiguration.bounds.x, frame.y) + break + } + } + } + + frame.pack() + frame.isVisible = true + + // clear focus from first input field + frame.requestFocusInWindow() + + frame + } + + val component = panel val layout = component.layout as MigLayout val gridField = MigLayout::class.java.getDeclaredField("grid") @@ -126,38 +150,40 @@ class UiDslTest { val rectangles = MigLayoutTestUtil.getRectangles(grid) val imageName = sanitizeFileName(testName.methodName) - val actualLayoutJson = configurationToJson(component, component.layout as MigLayout, - rectangles.joinToString(", ") { "[${it.joinToString(", ")}]" }) + val actualSerializedLayout = configurationToJson(component, component.layout as MigLayout, + rectangles.joinToString(", ") { "[${it.joinToString(", ")}]" }) try { - val expectedLayoutDataFile = Paths.get(PlatformTestUtil.getPlatformTestDataPath(), "ui", "layout", "$imageName.yml") + val expectedLayoutDataFile = Paths.get(PlatformTestUtil.getPlatformTestDataPath(), "ui", "layout", lafName, "$imageName.yml") val isUpdateSnapshots = SystemPropertyUtil.getBoolean("test.update.snapshots", false) if (!expectedLayoutDataFile.exists() || isUpdateSnapshots) { - expectedLayoutDataFile.write(actualLayoutJson) + expectedLayoutDataFile.write(actualSerializedLayout) } else { - Assertions.assertThat(actualLayoutJson).isEqualTo(expectedLayoutDataFile) + Assertions.assertThat(actualSerializedLayout).isEqualTo(expectedLayoutDataFile) } if (imageDir.isNullOrEmpty()) { return } - val imagePath = Paths.get(imageDir, "$imageName.png") + val imagePath = Paths.get(imageDir, lafName, "$imageName.png") if (!imagePath.exists()) { System.out.println("Write a new snapshot image ${imagePath.fileName}") - saveImage(imagePath) + saveImage(imagePath, frame) return } val newImage = ImageIO.read(imagePath.toFile()) + @Suppress("UnnecessaryVariable") + val snapshotComponent = frame try { - assertThat(componentToImage(getContentPane())).isEqualTo(newImage, Offset.offset(32)) + assertThat(componentToImage(snapshotComponent)).isEqualTo(newImage, Offset.offset(8)) } catch (e: AssertionError) { if (isUpdateSnapshots) { System.out.println("UPDATED snapshot image ${imagePath.fileName}") imagePath.outputStream().use { - ImageIO.write(componentToImage(getContentPane()), "png", it) + ImageIO.write(componentToImage(snapshotComponent), "png", it) } } else { @@ -167,14 +193,21 @@ class UiDslTest { } catch (e: AssertionError) { if (!imageDir.isNullOrEmpty()) { - Paths.get(imageDir, "$imageName-NEW.yml").write(actualLayoutJson) - saveImage(Paths.get(imageDir, "$imageName-NEW.png")) + Paths.get(imageDir, "$imageName-NEW.yml").write(actualSerializedLayout) + saveImage(Paths.get(imageDir, "$imageName-NEW.png"), frame) } throw e } + + frame.isVisible = false + frame.dispose() } } +class DarculaUiDslTest : UiDslTest() { + override val lafName = "Darcula" +} + private fun componentToImage(component: Component, type: Int = BufferedImage.TYPE_BYTE_GRAY): BufferedImage { return invokeAndWaitIfNeed { // we don't need retina image diff --git a/platform/platform-tests/testSrc/com/intellij/ui/layout/migLayoutDebugDumper.kt b/platform/platform-tests/testSrc/com/intellij/ui/layout/migLayoutDebugDumper.kt index bbef20d83779..2ebc1aa9eb65 100644 --- a/platform/platform-tests/testSrc/com/intellij/ui/layout/migLayoutDebugDumper.kt +++ b/platform/platform-tests/testSrc/com/intellij/ui/layout/migLayoutDebugDumper.kt @@ -34,6 +34,24 @@ private val filter by lazy { if (bean is BoundSize && property.name == "unset") { return null } + if (bean is UnitValue) { + if (property.name == "unitString") { + if (propertyValue != "px" && bean.value != 0f) { + throw RuntimeException("Only px must be used") + } + return null + } + if (property.name == "unit" && bean.value != 0f) { + if (propertyValue != UnitValue.PIXEL) { + throw RuntimeException("Only px must be used") + } + return null + } + if (property.name == "operation" && propertyValue == 100) { + // ignore static operation + return null + } + } val emptyBean = when (bean) { is AC -> emptyAC diff --git a/platform/util/src/com/intellij/util/ui/JBUI.java b/platform/util/src/com/intellij/util/ui/JBUI.java index fe9d6b511a1b..b846a66d156b 100644 --- a/platform/util/src/com/intellij/util/ui/JBUI.java +++ b/platform/util/src/com/intellij/util/ui/JBUI.java @@ -6,6 +6,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.ScalableIcon; import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.SystemInfoRt; import com.intellij.openapi.util.registry.Registry; import com.intellij.ui.border.CustomLineBorder; import com.intellij.util.LazyInitializer.NotNullValue; @@ -41,6 +42,7 @@ public class JBUI { private static final Logger LOG = Logger.getInstance("#com.intellij.util.ui.JBUI"); public static final String USER_SCALE_FACTOR_PROPERTY = "JBUI.userScaleFactor"; + public static final String COMPENSATE_VISUAL_PADDING_KEY = "compensate.visual.padding"; private static final PropertyChangeSupport PCS = new PropertyChangeSupport(new JBUI()); @@ -306,6 +308,10 @@ public class JBUI { return sysScale(); } + public static boolean isRetina(@NotNull GraphicsDevice device) { + return UIUtil.DetectRetinaKit.isOracleMacRetinaDevice(device); + } + /** * Returns the system scale factor, corresponding to the graphics. * For BufferedImage's graphics, the scale is taken from the graphics itself. @@ -490,6 +496,7 @@ public class JBUI { return new JBDimension(size.width, size.height); } + @NotNull public static JBInsets insets(int top, int left, int bottom, int right) { return new JBInsets(top, left, bottom, right); } @@ -1429,7 +1436,7 @@ public class JBUI { return color == null ? defaultColor : color; } - private static int getInt(String propertyName, int defaultValue) { + public static int getInt(String propertyName, int defaultValue) { Object value = UIManager.get(propertyName); return value instanceof Integer ? (Integer)value : defaultValue; } @@ -1438,4 +1445,22 @@ public class JBUI { Icon icon = UIManager.getIcon(propertyName); return icon == null ? defaultIcon : icon; } + + /** + * Temp method to not break IDEA LaF until changes are not reviewed. + * + * Correct input size is used now only for UI DSL. + */ + public static boolean isUseCorrectInputHeightOnMacOS(@NotNull Component component) { + if (!SystemInfoRt.isMac) { + return false; + } + + Container parent = component.getParent(); + return !isCompensateVisualPaddingOnComponentLevel(parent instanceof JComboBox ? parent.getParent() : parent); + } + + public static boolean isCompensateVisualPaddingOnComponentLevel(@Nullable Component parent) { + return !(SystemInfoRt.isMac && parent instanceof JPanel && ((JPanel)parent).getClientProperty(COMPENSATE_VISUAL_PADDING_KEY) == Boolean.FALSE); + } } \ No newline at end of file