diff --git a/CHANGELOG.md b/CHANGELOG.md index 55b38635..043eb143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ FlatLaf Change Log - Added API to register packages or folders where FlatLaf searches for application specific properties files with custom UI defaults (see `FlatLaf.registerCustomDefaultsSource(...)` methods). +- Demo: Show hint popups to guide users to some features of the FlatLaf Demo + application. - Extras: `FlatSVGIcon` now allows specifying `ClassLoader` that is used to load SVG file. (issue #163) diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java index 8ddd8618..d9ea0c10 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java @@ -20,11 +20,13 @@ import java.awt.*; import java.awt.event.*; import java.util.ArrayList; import java.util.Arrays; +import java.util.prefs.Preferences; import javax.swing.*; import javax.swing.text.DefaultEditorKit; import javax.swing.text.StyleContext; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.demo.HintManager.Hint; import com.formdev.flatlaf.demo.extras.*; import com.formdev.flatlaf.demo.intellijthemes.*; import com.formdev.flatlaf.extras.FlatAnimatedLafChange; @@ -57,6 +59,35 @@ class DemoFrame if( tabIndex >= 0 && tabIndex < tabbedPane.getTabCount() && tabIndex != tabbedPane.getSelectedIndex() ) tabbedPane.setSelectedIndex( tabIndex ); + + SwingUtilities.invokeLater( () -> { + showHints(); + } ); + } + + private void showHints() { + Hint fontMenuHint = new Hint( + "Use 'Font' menu to increase/decrease font size or try different fonts.", + fontMenu, SwingConstants.BOTTOM, "hint.fontMenu", null ); + + Hint optionsMenuHint = new Hint( + "Use 'Options' menu to try out various FlatLaf options.", + optionsMenu, SwingConstants.BOTTOM, "hint.optionsMenu", fontMenuHint ); + + Hint themesHint = new Hint( + "Use 'Themes' list to try out various themes.", + themesPanel, SwingConstants.LEFT, "hint.themesPanel", optionsMenuHint ); + + HintManager.showHint( themesHint ); + } + + private void clearHints() { + HintManager.hideAllHints(); + + Preferences state = DemoPrefs.getState(); + state.remove( "hint.fontMenu" ); + state.remove( "hint.optionsMenu" ); + state.remove( "hint.themesPanel" ); } private void exitActionPerformed() { @@ -110,6 +141,11 @@ class DemoFrame System.setProperty( "flatlaf.animatedLafChange", String.valueOf( animatedLafChangeMenuItem.isSelected() ) ); } + private void showHintsChanged() { + clearHints(); + showHints(); + } + private void fontFamilyChanged( ActionEvent e ) { String fontFamily = e.getActionCommand(); @@ -251,12 +287,13 @@ class DemoFrame JMenuItem restoreFontMenuItem = new JMenuItem(); JMenuItem incrFontMenuItem = new JMenuItem(); JMenuItem decrFontMenuItem = new JMenuItem(); - JMenu optionsMenu = new JMenu(); + optionsMenu = new JMenu(); windowDecorationsCheckBoxMenuItem = new JCheckBoxMenuItem(); menuBarEmbeddedCheckBoxMenuItem = new JCheckBoxMenuItem(); underlineMenuSelectionMenuItem = new JCheckBoxMenuItem(); alwaysShowMnemonicsMenuItem = new JCheckBoxMenuItem(); animatedLafChangeMenuItem = new JCheckBoxMenuItem(); + JMenuItem showHintsMenuItem = new JMenuItem(); JMenu helpMenu = new JMenu(); JMenuItem aboutMenuItem = new JMenuItem(); JToolBar toolBar1 = new JToolBar(); @@ -276,7 +313,7 @@ class DemoFrame OptionPanePanel optionPanePanel = new OptionPanePanel(); ExtrasPanel extrasPanel1 = new ExtrasPanel(); controlBar = new ControlBar(); - IJThemesPanel themesPanel = new IJThemesPanel(); + themesPanel = new IJThemesPanel(); //======== this ======== setTitle("FlatLaf Demo"); @@ -516,6 +553,11 @@ class DemoFrame animatedLafChangeMenuItem.setSelected(true); animatedLafChangeMenuItem.addActionListener(e -> animatedLafChangeChanged()); optionsMenu.add(animatedLafChangeMenuItem); + + //---- showHintsMenuItem ---- + showHintsMenuItem.setText("Show hints"); + showHintsMenuItem.addActionListener(e -> showHintsChanged()); + optionsMenu.add(showHintsMenuItem); } menuBar1.add(optionsMenu); @@ -631,6 +673,7 @@ class DemoFrame // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables private JMenu fontMenu; + private JMenu optionsMenu; private JCheckBoxMenuItem windowDecorationsCheckBoxMenuItem; private JCheckBoxMenuItem menuBarEmbeddedCheckBoxMenuItem; private JCheckBoxMenuItem underlineMenuSelectionMenuItem; @@ -638,5 +681,6 @@ class DemoFrame private JCheckBoxMenuItem animatedLafChangeMenuItem; private JTabbedPane tabbedPane; private ControlBar controlBar; + private IJThemesPanel themesPanel; // JFormDesigner - End of variables declaration //GEN-END:variables } diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd index 12f8061c..d7f1ffd6 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "7.0.2.0.298" Java: "14.0.2" encoding: "UTF-8" +JFDML JFormDesigner: "7.0.2.0.298" Java: "14" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -114,6 +114,9 @@ new FormModel { } ) add( new FormComponent( "com.formdev.flatlaf.demo.intellijthemes.IJThemesPanel" ) { name: "themesPanel" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } }, new FormLayoutConstraints( class java.lang.String ) { "value": "East" } ) @@ -322,6 +325,9 @@ new FormModel { add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { name: "optionsMenu" "text": "Options" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } add( new FormComponent( "javax.swing.JCheckBoxMenuItem" ) { name: "windowDecorationsCheckBoxMenuItem" "text": "Window decorations" @@ -365,6 +371,11 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "animatedLafChangeChanged", false ) ) } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "showHintsMenuItem" + "text": "Show hints" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showHintsChanged", false ) ) + } ) } ) add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { name: "helpMenu" diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java index 8d544bc8..db92091c 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/FlatLafDemo.java @@ -19,6 +19,7 @@ package com.formdev.flatlaf.demo; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.SwingUtilities; +import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.extras.FlatInspector; import com.formdev.flatlaf.util.SystemInfo; @@ -42,6 +43,9 @@ public class FlatLafDemo JFrame.setDefaultLookAndFeelDecorated( true ); JDialog.setDefaultLookAndFeelDecorated( true ); + // application specific UI defaults + FlatLaf.registerCustomDefaultsSource( "com.formdev.flatlaf.demo" ); + // set look and feel DemoPrefs.initLaf( args ); diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/HintManager.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/HintManager.java new file mode 100644 index 00000000..9a336f37 --- /dev/null +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/HintManager.java @@ -0,0 +1,219 @@ +/* + * Copyright 2020 FormDev Software GmbH + * + * 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 + * + * https://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. + */ + +package com.formdev.flatlaf.demo; + +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.util.ArrayList; +import java.util.List; +import javax.swing.*; +import com.formdev.flatlaf.ui.FlatDropShadowBorder; +import com.formdev.flatlaf.ui.FlatPopupMenuBorder; +import com.formdev.flatlaf.ui.FlatUIUtils; +import com.formdev.flatlaf.util.UIScale; +import net.miginfocom.swing.*; + +/** + * @author Karl Tauber + */ +class HintManager +{ + private static final List hintPanels = new ArrayList<>(); + + static void showHint( Hint hint ) { + // check whether user already closed the hint + if( DemoPrefs.getState().getBoolean( hint.prefsKey, false ) ) { + if( hint.nextHint != null ) + showHint( hint.nextHint ); + return; + } + + HintPanel hintPanel = new HintPanel( hint ); + hintPanel.showHint(); + + hintPanels.add( hintPanel ); + } + + static void hideAllHints() { + HintPanel[] hintPanels2 = hintPanels.toArray( new HintPanel[hintPanels.size()] ); + for( HintPanel hintPanel : hintPanels2 ) + hintPanel.hideHint(); + } + + //---- class HintPanel ---------------------------------------------------- + + static class Hint + { + private final String message; + private final Component owner; + private final int position; + private final String prefsKey; + private final Hint nextHint; + + Hint( String message, Component owner, int position, String prefsKey, Hint nextHint ) { + this.message = message; + this.owner = owner; + this.position = position; + this.prefsKey = prefsKey; + this.nextHint = nextHint; + } + } + + //---- class HintPanel ---------------------------------------------------- + + private static class HintPanel + extends JPanel + { + private final Hint hint; + + private JPanel popup; + + private HintPanel( Hint hint ) { + this.hint = hint; + + initComponents(); + + hintLabel.setText( "" + hint.message + "" ); + + // grab all mouse events to avoid that components overlapped + // by the hint panel receive them + addMouseListener( new MouseAdapter() {} ); + } + + @Override + public void updateUI() { + super.updateUI(); + + setBackground( UIManager.getColor( "HintPanel.backgroundColor" ) ); + setBorder( new FlatPopupMenuBorder() ); + } + + void showHint() { + JRootPane rootPane = SwingUtilities.getRootPane( hint.owner ); + if( rootPane == null ) + return; + + JLayeredPane layeredPane = rootPane.getLayeredPane(); + + // create a popup panel that has a drop shadow + popup = new JPanel( new BorderLayout() ) { + @Override + public void updateUI() { + super.updateUI(); + + setBorder( new FlatDropShadowBorder( + UIManager.getColor( "Popup.dropShadowColor" ), + UIManager.getInsets( "Popup.dropShadowInsets" ), + FlatUIUtils.getUIFloat( "Popup.dropShadowOpacity", 0.5f ) ) ); + + // use invokeLater because at this time the UI delegates + // of child components are not yet updated + EventQueue.invokeLater( () -> { + validate(); + setSize( getPreferredSize() ); + } ); + } + }; + popup.setOpaque( false ); + popup.add( this ); + + // calculate x/y location for hint popup + Point pt = SwingUtilities.convertPoint( hint.owner, 0, 0, layeredPane ); + int x = pt.x; + int y = pt.y; + Dimension size = popup.getPreferredSize(); + int gap = UIScale.scale( 6 ); + + switch( hint.position ) { + case SwingConstants.LEFT: + x -= size.width + gap; + break; + + case SwingConstants.TOP: + y -= size.height + gap; + break; + + case SwingConstants.RIGHT: + x += hint.owner.getWidth() + gap; + break; + + case SwingConstants.BOTTOM: + y += hint.owner.getHeight() + gap; + break; + } + + // set hint popup size and show it + popup.setBounds( x, y, size.width, size.height ); + layeredPane.add( popup, JLayeredPane.POPUP_LAYER ); + } + + void hideHint() { + if( popup != null ) { + Container parent = popup.getParent(); + if( parent != null ) { + parent.remove( popup ); + parent.repaint( popup.getX(), popup.getY(), popup.getWidth(), popup.getHeight() ); + } + } + + hintPanels.remove( this ); + } + + private void gotIt() { + // hide hint + hideHint(); + + // remember that user closed the hint + DemoPrefs.getState().putBoolean( hint.prefsKey, true ); + + // show next hint (if any) + if( hint.nextHint != null ) + HintManager.showHint( hint.nextHint ); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + hintLabel = new JLabel(); + gotItButton = new JButton(); + + //======== this ======== + setLayout(new MigLayout( + "insets dialog,hidemode 3", + // columns + "[::200,fill]", + // rows + "[]para" + + "[]")); + + //---- hintLabel ---- + hintLabel.setText("hint"); + add(hintLabel, "cell 0 0"); + + //---- gotItButton ---- + gotItButton.setText("Got it!"); + gotItButton.setFocusable(false); + gotItButton.addActionListener(e -> gotIt()); + add(gotItButton, "cell 0 1,alignx right,growx 0"); + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JLabel hintLabel; + private JButton gotItButton; + // JFormDesigner - End of variables declaration //GEN-END:variables + } +} diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/HintManager.jfd b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/HintManager.jfd new file mode 100644 index 00000000..a7fc21ac --- /dev/null +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/HintManager.jfd @@ -0,0 +1,34 @@ +JFDML JFormDesigner: "7.0.2.0.298" Java: "14" encoding: "UTF-8" + +new FormModel { + contentType: "form/swing" + root: new FormRoot { + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets dialog,hidemode 3" + "$columnConstraints": "[::200,fill]" + "$rowConstraints": "[]para[]" + } ) { + name: "panel" + auxiliary() { + "JavaCodeGenerator.className": "HintPanel" + } + add( new FormComponent( "javax.swing.JLabel" ) { + name: "hintLabel" + "text": "hint" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "gotItButton" + "text": "Got it!" + "focusable": false + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "gotIt", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1,alignx right,growx 0" + } ) + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 0 ) + "size": new java.awt.Dimension( 400, 300 ) + } ) + } +} diff --git a/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/FlatDarkLaf.properties b/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/FlatDarkLaf.properties new file mode 100644 index 00000000..cb56e9fa --- /dev/null +++ b/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/FlatDarkLaf.properties @@ -0,0 +1,17 @@ +# +# Copyright 2020 FormDev Software GmbH +# +# 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 +# +# https://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. +# + +HintPanel.backgroundColor=darken(#ffffe1,80%) diff --git a/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/FlatLightLaf.properties b/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/FlatLightLaf.properties new file mode 100644 index 00000000..07b41420 --- /dev/null +++ b/flatlaf-demo/src/main/resources/com/formdev/flatlaf/demo/FlatLightLaf.properties @@ -0,0 +1,17 @@ +# +# Copyright 2020 FormDev Software GmbH +# +# 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 +# +# https://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. +# + +HintPanel.backgroundColor=#ffffe1