UI DSL: visual paddings on a container level — macOS IntelliJ and macOS Darcula

This commit is contained in:
Vladimir Krivosheev
2018-03-30 09:55:42 +02:00
parent 2c507a25c1
commit de47dadf4d
47 changed files with 1594 additions and 705 deletions
+3
View File
@@ -1,6 +1,9 @@
<component name="libraryTable">
<library name="miglayout-swing" type="repository">
<properties maven-id="com.miglayout:miglayout-swing:5.1" />
<ANNOTATIONS>
<root url="file://$PROJECT_DIR$/lib/annotations/migLayout" />
</ANNOTATIONS>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/miglayout/miglayout-swing/5.1/miglayout-swing-5.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/miglayout/miglayout-core/5.1/miglayout-core-5.1.jar!/" />
@@ -0,0 +1,5 @@
<root>
<item name='net.miginfocom.layout.ConstraintParser net.miginfocom.layout.BoundSize parseBoundSize(java.lang.String, boolean, boolean)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root>
@@ -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);
}
@@ -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);
}
@@ -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);
}
}
@@ -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);
}
}
}
@@ -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());
@@ -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());
}
}
@@ -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
@@ -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));
@@ -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());
}
}
@@ -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
@@ -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);
}
}
@@ -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);
}
}
@@ -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
@@ -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);
}
}
@@ -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;
}
@@ -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);
}
}
@@ -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
@@ -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,
@@ -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,
@@ -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) {
@@ -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
}
@@ -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<out LCFlags>)
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
}
@@ -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<Component, CC> = 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<out LCFlags>) {
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<out LCFlags>): LC {
@@ -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<Component, CC>,
@@ -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<out CCFlags>? = null,
gapLeft: Int = 0,
growPolicy: GrowPolicy? = null,
comment: String? = null) {
private fun addComponent(component: JComponent, cc: Lazy<CC>, 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<CC>): 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)!!
@@ -20,15 +20,10 @@ import javax.swing.text.JTextComponent
internal fun Array<out CCFlags>.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<out CCFlags>): 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<out CCFlags>): 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<CC>,
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<CC>, 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<CC>,
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<CC>, 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
}
}
}
@@ -1,3 +1,5 @@
# UI DSL
Use `panel` to create UI:
```kotlin
@@ -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<String>()
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<String>(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<String>(arrayOf("one", "two"))(growX) }
row("Combobox Editable:") {
val field = JComboBox<String>(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<String>(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 {
@@ -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]'
@@ -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]'
@@ -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]'
@@ -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]'
@@ -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]'
@@ -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]'
@@ -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]'
@@ -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]'
@@ -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]'
@@ -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]'
@@ -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]'
@@ -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]'
@@ -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]'
@@ -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]'
@@ -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<String>) {
// 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()
@@ -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
@@ -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
@@ -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);
}
}