mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2026-02-10 22:17:13 -06:00
Theme Editor: added "Pick Color from Screen" action to "Edit" menu that allows picking a color from anywhere on screen and insert/change it at caret position
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright 2021 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.themeeditor;
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Robot;
|
||||
import java.awt.Window;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.function.Consumer;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JWindow;
|
||||
import javax.swing.SwingUtilities;
|
||||
import com.formdev.flatlaf.icons.FlatAbstractIcon;
|
||||
import com.formdev.flatlaf.ui.FlatEmptyBorder;
|
||||
import com.formdev.flatlaf.ui.FlatLineBorder;
|
||||
import com.formdev.flatlaf.util.HSLColor;
|
||||
import com.formdev.flatlaf.util.UIScale;
|
||||
|
||||
/**
|
||||
* @author Karl Tauber
|
||||
*/
|
||||
class FlatColorPipette
|
||||
{
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @throws AWTException if platform does not allow using robot (e.g. if headless)
|
||||
* @throws UnsupportedOperationException if platform does not support translucent window
|
||||
*/
|
||||
static void pick( Window owner, boolean modal,
|
||||
Consumer<Color> hoverCallback, Consumer<Color> pickCallback )
|
||||
throws AWTException, UnsupportedOperationException
|
||||
{
|
||||
InvisiblePickWindow pickWindow = new InvisiblePickWindow( owner, modal, hoverCallback, pickCallback );
|
||||
pickWindow.setVisible( true );
|
||||
}
|
||||
|
||||
//---- class InvisiblePickWindow ------------------------------------------
|
||||
|
||||
/**
|
||||
* An invisible window used to receive mouse and keyboard events for the whole screen.
|
||||
*/
|
||||
private static class InvisiblePickWindow
|
||||
extends JDialog
|
||||
{
|
||||
private final Consumer<Color> hoverCallback;
|
||||
private final Consumer<Color> pickCallback;
|
||||
|
||||
private final Robot robot;
|
||||
private final Magnifier magnifier;
|
||||
|
||||
private int lastX;
|
||||
private int lastY;
|
||||
private Color lastHoverColor;
|
||||
|
||||
InvisiblePickWindow( Window owner, boolean modal,
|
||||
Consumer<Color> hoverCallback, Consumer<Color> pickCallback )
|
||||
throws AWTException, UnsupportedOperationException
|
||||
{
|
||||
super( owner, modal ? ModalityType.APPLICATION_MODAL : ModalityType.MODELESS );
|
||||
this.hoverCallback = hoverCallback;
|
||||
this.pickCallback = pickCallback;
|
||||
|
||||
setAlwaysOnTop( true );
|
||||
setUndecorated( true );
|
||||
setOpacity( 0.005f );
|
||||
setBounds( owner.getGraphicsConfiguration().getBounds() );
|
||||
|
||||
robot = new Robot( owner.getGraphicsConfiguration().getDevice() );
|
||||
magnifier = new Magnifier( this, robot );
|
||||
|
||||
MouseAdapter mouseListener = new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseMoved( MouseEvent e ) {
|
||||
lastX = e.getX();
|
||||
lastY = e.getY();
|
||||
|
||||
// get color at mouse location
|
||||
// (temporary change opacity to zero to get correct color from robot)
|
||||
float oldOpacity = getOpacity();
|
||||
setOpacity( 0 );
|
||||
Color color = robot.getPixelColor( lastX, lastY );
|
||||
setOpacity( oldOpacity );
|
||||
|
||||
hover( color );
|
||||
magnifier.update( lastX, lastY, color );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e ) {
|
||||
dispose();
|
||||
|
||||
if( SwingUtilities.isLeftMouseButton( e ) )
|
||||
pick( robot.getPixelColor( e.getX(), e.getY() ) );
|
||||
else
|
||||
pick( null );
|
||||
}
|
||||
};
|
||||
|
||||
KeyAdapter keyListener = new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed( KeyEvent e ) {
|
||||
switch( e.getKeyCode() ) {
|
||||
case KeyEvent.VK_ESCAPE: dispose(); pick( null ); break;
|
||||
|
||||
// move mouse one pixel using arrow keys
|
||||
case KeyEvent.VK_LEFT: robot.mouseMove( lastX - 1, lastY ); break;
|
||||
case KeyEvent.VK_RIGHT: robot.mouseMove( lastX + 1, lastY ); break;
|
||||
case KeyEvent.VK_UP: robot.mouseMove( lastX, lastY - 1 ); break;
|
||||
case KeyEvent.VK_DOWN: robot.mouseMove( lastX, lastY + 1 ); break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
addMouseListener( mouseListener );
|
||||
addMouseMotionListener( mouseListener );
|
||||
addKeyListener( keyListener );
|
||||
|
||||
magnifier.addMouseListener( mouseListener );
|
||||
magnifier.addMouseMotionListener( mouseListener );
|
||||
magnifier.addKeyListener( keyListener );
|
||||
|
||||
magnifier.pack();
|
||||
}
|
||||
|
||||
private void hover( Color color ) {
|
||||
if( hoverCallback == null || color == null || color.equals( lastHoverColor ) )
|
||||
return;
|
||||
|
||||
lastHoverColor = color;
|
||||
|
||||
EventQueue.invokeLater( () -> {
|
||||
hoverCallback.accept( color );
|
||||
} );
|
||||
}
|
||||
|
||||
private void pick( Color color ) {
|
||||
EventQueue.invokeLater( () -> {
|
||||
pickCallback.accept( color );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
//---- class Magnifier ----------------------------------------------------
|
||||
|
||||
private static class Magnifier
|
||||
extends JWindow
|
||||
{
|
||||
private final Window owner;
|
||||
private final Robot robot;
|
||||
|
||||
private final MagnifierView view;
|
||||
private final JLabel infoLabel;
|
||||
|
||||
private final int zoom;
|
||||
private final int pixels = 16;
|
||||
private Color colorAtMouse;
|
||||
private BufferedImage image;
|
||||
|
||||
public Magnifier( Window owner, Robot robot ) {
|
||||
super( owner );
|
||||
this.owner = owner;
|
||||
this.robot = robot;
|
||||
|
||||
zoom = UIScale.scale( 16 );
|
||||
|
||||
getRootPane().setBorder( new FlatLineBorder( new Insets( 2, 2, 2, 2 ), Color.red, 2 ) );
|
||||
|
||||
view = new MagnifierView();
|
||||
view.setPreferredSize( new Dimension( pixels * zoom, pixels * zoom ) );
|
||||
|
||||
infoLabel = new JLabel( "#" );
|
||||
infoLabel.setIcon( new ColorIcon() );
|
||||
infoLabel.setBorder( new FlatEmptyBorder( 4, 4, 4, 4 ) );
|
||||
|
||||
add( view, BorderLayout.CENTER );
|
||||
add( infoLabel, BorderLayout.SOUTH );
|
||||
}
|
||||
|
||||
void update( int x, int y, Color colorAtXY ) {
|
||||
colorAtMouse = colorAtXY;
|
||||
|
||||
// capture screen at mouse location
|
||||
image = robot.createScreenCapture( new Rectangle( x - (pixels / 2), y - (pixels / 2), pixels, pixels ) );
|
||||
|
||||
// update color in info label
|
||||
HSLColor hslColor = new HSLColor( colorAtMouse );
|
||||
int hue = Math.round( hslColor.getHue() );
|
||||
int saturation = Math.round( hslColor.getSaturation() );
|
||||
int luminance = Math.round( hslColor.getLuminance() );
|
||||
infoLabel.setText( String.format( "#%06x HSL %d %d %d",
|
||||
colorAtMouse.getRGB() & 0xffffff, hue, saturation, luminance ) );
|
||||
|
||||
// place bottom-right to mouse location
|
||||
int mx = x + UIScale.scale( 32 );
|
||||
int my = y + UIScale.scale( 32 );
|
||||
|
||||
// make sure that it is within screen
|
||||
if( mx + getWidth() > owner.getX() + owner.getWidth() )
|
||||
mx = x - getWidth() - UIScale.scale( 32 );
|
||||
if( my + getHeight() > owner.getY() + owner.getHeight() )
|
||||
my = y - getHeight() - UIScale.scale( 32 );
|
||||
|
||||
setLocation( mx, my );
|
||||
setVisible( true );
|
||||
repaint();
|
||||
}
|
||||
|
||||
//---- class MagnifierView ----
|
||||
|
||||
private class MagnifierView
|
||||
extends JComponent
|
||||
{
|
||||
@Override
|
||||
public void paint( Graphics g ) {
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
|
||||
if( image != null )
|
||||
g.drawImage( image, 0, 0, width, height, null );
|
||||
|
||||
int xy = (pixels / 2) * zoom;
|
||||
g.setColor( Color.red );
|
||||
((Graphics2D)g).setStroke( new BasicStroke( 2 ) );
|
||||
g.drawRect( xy - 1, xy - 1, zoom + 2, zoom + 2 );
|
||||
}
|
||||
}
|
||||
|
||||
//---- class ColorIcon ----
|
||||
|
||||
private class ColorIcon
|
||||
extends FlatAbstractIcon
|
||||
{
|
||||
private static final int WIDTH = 64;
|
||||
private static final int HEIGHT = 16;
|
||||
|
||||
public ColorIcon() {
|
||||
super( WIDTH, HEIGHT, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintIcon( Component c, Graphics2D g ) {
|
||||
g.setColor( colorAtMouse );
|
||||
g.fillRect( 0, 0, WIDTH, HEIGHT );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import org.fife.ui.rtextarea.RTextAreaUI;
|
||||
import org.fife.ui.rtextarea.RUndoManager;
|
||||
import com.formdev.flatlaf.UIDefaultsLoaderAccessor;
|
||||
import com.formdev.flatlaf.themeeditor.FlatSyntaxTextAreaActions.InsertColorAction;
|
||||
import com.formdev.flatlaf.themeeditor.FlatSyntaxTextAreaActions.PickColorAction;
|
||||
import com.formdev.flatlaf.themeeditor.FlatSyntaxTextAreaActions.DuplicateLinesAction;
|
||||
import com.formdev.flatlaf.themeeditor.FlatSyntaxTextAreaActions.IncrementNumberAction;
|
||||
|
||||
@@ -68,6 +69,7 @@ class FlatSyntaxTextArea
|
||||
actionMap.put( FlatSyntaxTextAreaActions.incrementNumberAction, new IncrementNumberAction( FlatSyntaxTextAreaActions.incrementNumberAction, true ) );
|
||||
actionMap.put( FlatSyntaxTextAreaActions.decrementNumberAction, new IncrementNumberAction( FlatSyntaxTextAreaActions.decrementNumberAction, false ) );
|
||||
actionMap.put( FlatSyntaxTextAreaActions.insertColorAction, new InsertColorAction( FlatSyntaxTextAreaActions.insertColorAction ) );
|
||||
actionMap.put( FlatSyntaxTextAreaActions.pickColorAction, new PickColorAction( FlatSyntaxTextAreaActions.pickColorAction ) );
|
||||
|
||||
// add editor key strokes
|
||||
InputMap inputMap = getInputMap();
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.formdev.flatlaf.themeeditor;
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Point;
|
||||
@@ -43,6 +44,7 @@ class FlatSyntaxTextAreaActions
|
||||
static final String incrementNumberAction = "FlatLaf.IncrementNumberAction";
|
||||
static final String decrementNumberAction = "FlatLaf.DecrementNumberAction";
|
||||
static final String insertColorAction = "FlatLaf.InsertColorAction";
|
||||
static final String pickColorAction = "FlatLaf.PickColorAction";
|
||||
|
||||
static int[] findColorAt( RTextArea textArea, int position ) {
|
||||
try {
|
||||
@@ -384,4 +386,76 @@ class FlatSyntaxTextAreaActions
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
|
||||
//---- class PickColorAction ----------------------------------------------
|
||||
|
||||
static class PickColorAction
|
||||
extends RecordableTextAction
|
||||
{
|
||||
PickColorAction( String name ) {
|
||||
super( name );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformedImpl( ActionEvent e, RTextArea textArea ) {
|
||||
try {
|
||||
// find current color at caret
|
||||
int caretPosition = textArea.getCaretPosition();
|
||||
int start;
|
||||
int len = 0;
|
||||
String oldStr;
|
||||
int[] result = findColorAt( textArea, caretPosition );
|
||||
if( result != null ) {
|
||||
start = result[0];
|
||||
len = result[1];
|
||||
|
||||
oldStr = textArea.getText( start, len );
|
||||
} else {
|
||||
start = caretPosition;
|
||||
oldStr = "";
|
||||
}
|
||||
|
||||
AtomicInteger length = new AtomicInteger( len );
|
||||
AtomicBoolean changed = new AtomicBoolean();
|
||||
|
||||
// show pipette color picker
|
||||
Window window = SwingUtilities.windowForComponent( textArea );
|
||||
FlatColorPipette.pick( window, true,
|
||||
color -> {
|
||||
// update editor immediately for live preview
|
||||
String str = colorToString( color );
|
||||
((FlatSyntaxTextArea)textArea).runWithoutUndo( () -> {
|
||||
textArea.replaceRange( str, start, start + length.get() );
|
||||
} );
|
||||
length.set( str.length() );
|
||||
changed.set( true );
|
||||
},
|
||||
color -> {
|
||||
// restore original string
|
||||
((FlatSyntaxTextArea)textArea).runWithoutUndo( () -> {
|
||||
textArea.replaceRange( oldStr, start, start + length.get() );
|
||||
} );
|
||||
length.set( oldStr.length() );
|
||||
|
||||
// update editor
|
||||
if( color != null ) {
|
||||
String newStr = colorToString( color );
|
||||
try {
|
||||
if( !newStr.equals( textArea.getText( start, length.get() ) ) )
|
||||
textArea.replaceRange( newStr, start, start + length.get() );
|
||||
} catch( BadLocationException ex ) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
} );
|
||||
} catch( BadLocationException | IndexOutOfBoundsException | NumberFormatException | UnsupportedOperationException | AWTException ex ) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMacroID() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,6 +657,12 @@ class FlatThemeFileEditor
|
||||
themeEditorPane.notifyTextAreaAction( FlatSyntaxTextAreaActions.insertColorAction );
|
||||
}
|
||||
|
||||
private void pickColor() {
|
||||
FlatThemeEditorPane themeEditorPane = (FlatThemeEditorPane) tabbedPane.getSelectedComponent();
|
||||
if( themeEditorPane != null )
|
||||
themeEditorPane.notifyTextAreaAction( FlatSyntaxTextAreaActions.pickColorAction );
|
||||
}
|
||||
|
||||
private void showHidePreview() {
|
||||
boolean show = previewMenuItem.isSelected();
|
||||
for( FlatThemeEditorPane themeEditorPane : getThemeEditorPanes() )
|
||||
@@ -888,6 +894,7 @@ class FlatThemeFileEditor
|
||||
editMenu = new JMenu();
|
||||
findMenuItem = new JMenuItem();
|
||||
insertColorMenuItem = new JMenuItem();
|
||||
pickColorMenuItem = new JMenuItem();
|
||||
viewMenu = new JMenu();
|
||||
previewMenuItem = new JCheckBoxMenuItem();
|
||||
lightLafMenuItem = new JRadioButtonMenuItem();
|
||||
@@ -986,6 +993,12 @@ class FlatThemeFileEditor
|
||||
insertColorMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G, KeyEvent.CTRL_DOWN_MASK));
|
||||
insertColorMenuItem.addActionListener(e -> insertColor());
|
||||
editMenu.add(insertColorMenuItem);
|
||||
|
||||
//---- pickColorMenuItem ----
|
||||
pickColorMenuItem.setText("Pick Color from Screen");
|
||||
pickColorMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G, KeyEvent.CTRL_DOWN_MASK|KeyEvent.SHIFT_DOWN_MASK));
|
||||
pickColorMenuItem.addActionListener(e -> pickColor());
|
||||
editMenu.add(pickColorMenuItem);
|
||||
}
|
||||
menuBar.add(editMenu);
|
||||
|
||||
@@ -1150,6 +1163,7 @@ class FlatThemeFileEditor
|
||||
private JMenu editMenu;
|
||||
private JMenuItem findMenuItem;
|
||||
private JMenuItem insertColorMenuItem;
|
||||
private JMenuItem pickColorMenuItem;
|
||||
private JMenu viewMenu;
|
||||
private JCheckBoxMenuItem previewMenuItem;
|
||||
private JRadioButtonMenuItem lightLafMenuItem;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
JFDML JFormDesigner: "7.0.4.0.360" Java: "16" encoding: "UTF-8"
|
||||
JFDML JFormDesigner: "7.0.5.0.382" Java: "16" encoding: "UTF-8"
|
||||
|
||||
new FormModel {
|
||||
contentType: "form/swing"
|
||||
@@ -112,6 +112,12 @@ new FormModel {
|
||||
"accelerator": static javax.swing.KeyStroke getKeyStroke( 71, 130, false )
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "insertColor", false ) )
|
||||
} )
|
||||
add( new FormComponent( "javax.swing.JMenuItem" ) {
|
||||
name: "pickColorMenuItem"
|
||||
"text": "Pick Color from Screen"
|
||||
"accelerator": &KeyStroke0 static javax.swing.KeyStroke getKeyStroke( 71, 195, false )
|
||||
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "pickColor", false ) )
|
||||
} )
|
||||
} )
|
||||
add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) {
|
||||
name: "viewMenu"
|
||||
|
||||
Reference in New Issue
Block a user