88 * it under the terms of the GNU General Public License as
99 * published by the Free Software Foundation, either version 3 of the
1010 * License, or (at your option) any later version.
11- *
11+ *
1212 * This program is distributed in the hope that it will be useful,
1313 * but WITHOUT ANY WARRANTY; without even the implied warranty of
1414 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1515 * GNU General Public License for more details.
16- *
16+ *
1717 * You should have received a copy of the GNU General Public
1818 * License along with this program. If not, see
1919 * <http://www.gnu.org/licenses/gpl-3.0.html>.
@@ -377,9 +377,56 @@ public static Icon doubleIcon(final GLYPH entry1, final GLYPH entry2, final floa
377377 return dropdownIcon (entry1 , scalingFactor , color , rightIcon );
378378 }
379379
380+ /**
381+ * Creates a two-letter icon (e.g., "CT"), analogous to {@link #doubleIcon(GLYPH, GLYPH, float, Color)} but
382+ * built from two plain characters rather than {@link GLYPH} entries. Useful for {@link JMenuItem} icons that
383+ * combine single-letter tags, such as the "C"(hannel)/"T"(frame) Path Manager tags.
384+ *
385+ * @param letter1 the first (left) letter, e.g., 'C'
386+ * @param letter2 the second (right) letter, e.g., 'T'
387+ * @param solid whether the letters are rendered using the solid (vs. regular) weight font
388+ * @param scalingFactor the icon's scaling factor relative to the default size
389+ * @param color the icon's color
390+ * @return the icon
391+ */
392+ public static Icon doubleLetterIcon (final char letter1 , final char letter2 , final boolean solid ,
393+ final float scalingFactor , final Color color ) {
394+ final float size = scalingFactor * FADerivedIcon .defSize ();
395+ final Icon leftIcon = cachedIcon (letter1 , size , color , solid );
396+ final Icon rightIcon = cachedIcon (letter2 , size , color , solid );
397+ return new DropdownIcon (leftIcon , rightIcon );
398+ }
399+
400+ /**
401+ * Convenience method for {@link #doubleLetterIcon(char, char, boolean, float, Color)} using a scaling
402+ * factor of 1f and solid font weight.
403+ *
404+ * @param letter1 the first (left) letter, e.g., 'C'
405+ * @param letter2 the second (right) letter, e.g., 'T'
406+ * @param color the icon's color
407+ * @return the icon
408+ */
409+ public static Icon doubleLetterIcon (final char letter1 , final char letter2 , final Color color ) {
410+ return doubleLetterIcon (letter1 , letter2 , true , 1f , color );
411+ }
412+
413+ /**
414+ * Assigns a two-letter icon (see {@link #doubleLetterIcon(char, char, Color)}) to a menu item, including
415+ * its disabled state.
416+ *
417+ * @param item the menu item to be assigned an icon
418+ * @param letter1 the first (left) letter, e.g., 'C'
419+ * @param letter2 the second (right) letter, e.g., 'T'
420+ * @param solid whether the letters are rendered using the solid (vs. regular) weight font
421+ */
422+ public static void assignDoubleLetterIcon (final JMenuItem item , final char letter1 , final char letter2 , final boolean solid ) {
423+ item .setIcon (doubleLetterIcon (letter1 , letter2 , solid , .75f , defaultColor ()));
424+ item .setDisabledIcon (doubleLetterIcon (letter1 , letter2 , solid , .75f , disabledColor ()));
425+ }
426+
380427 /**
381428 * Overlays a small notification dot on an icon's upper right corner within the icon's own bounds
382- * (the icon's reported size is unchanged).
429+ * (the icon's reported size is unchanged).
383430 *
384431 * @param icon the source icon
385432 * @param dotColor the dot's fill color
@@ -425,39 +472,37 @@ public static Icon dropdownMenuIcon(final GLYPH entry, final float scalingFactor
425472 }
426473
427474 private static Icon dropdownIcon (final GLYPH entry , final float scalingFactor , final Color color , final Icon rightIcon ) {
428- class DropdownIcon implements Icon {
475+ return new DropdownIcon (entry , scalingFactor , rightIcon , color );
476+ }
429477
430- static final int ICON_GAP = 2 ;
431- final Icon leftIcon ;
432- final Icon rightIcon ;
478+ private record DropdownIcon (Icon leftIcon , Icon rightIcon ) implements Icon {
433479
434- DropdownIcon (final GLYPH entry , final float scalingFactor , final Icon rightIcon ) {
435- leftIcon = cachedIcon (entry .id , scalingFactor * FADerivedIcon .defSize (), color , entry .solid );
436- this .rightIcon = rightIcon ;
437- }
480+ static final int ICON_GAP = 2 ;
438481
439- @ Override
440- public void paintIcon (final Component c , final Graphics g , int x , int y ) {
441- final Graphics2D g2 = (Graphics2D ) g ;
442- GuiUtils .setRenderingHints (g2 );
443- final int mid = getIconHeight () / 2 ;
444- final int y1 = y + mid - leftIcon .getIconHeight () / 2 ;
445- final int y2 = y + mid - rightIcon .getIconHeight () / 2 ;
446- leftIcon .paintIcon (c , g2 , x , y1 );
447- rightIcon .paintIcon (c , g2 , x + leftIcon .getIconWidth () + ICON_GAP , y2 );
448- }
482+ DropdownIcon (final GLYPH entry , final float scalingFactor , final Icon rightIcon , final Color color ) {
483+ this (cachedIcon (entry .id , scalingFactor * FADerivedIcon .defSize (), color , entry .solid ), rightIcon );
484+ }
449485
450- @ Override
451- public int getIconWidth () {
452- return leftIcon .getIconWidth () + rightIcon .getIconWidth () + ICON_GAP ;
453- }
486+ @ Override
487+ public void paintIcon (final Component c , final Graphics g , int x , int y ) {
488+ final Graphics2D g2 = (Graphics2D ) g ;
489+ GuiUtils .setRenderingHints (g2 );
490+ final int mid = getIconHeight () / 2 ;
491+ final int y1 = y + mid - leftIcon .getIconHeight () / 2 ;
492+ final int y2 = y + mid - rightIcon .getIconHeight () / 2 ;
493+ leftIcon .paintIcon (c , g2 , x , y1 );
494+ rightIcon .paintIcon (c , g2 , x + leftIcon .getIconWidth () + ICON_GAP , y2 );
495+ }
454496
455- @ Override
456- public int getIconHeight () {
457- return Math .max (leftIcon .getIconHeight (), rightIcon .getIconHeight ());
458- }
497+ @ Override
498+ public int getIconWidth () {
499+ return leftIcon .getIconWidth () + rightIcon .getIconWidth () + ICON_GAP ;
500+ }
501+
502+ @ Override
503+ public int getIconHeight () {
504+ return Math .max (leftIcon .getIconHeight (), rightIcon .getIconHeight ());
459505 }
460- return new DropdownIcon (entry , scalingFactor , rightIcon );
461506 }
462507
463508 // Returns an icon whose pixels are the average of src's pixels and tint, achieved by overlaying tint at 50%
@@ -551,9 +596,13 @@ public static void assignDoubleIcon(final JMenuItem menuItem, final GLYPH defaul
551596 menuItem .setDisabledIcon (disabledIcon );
552597 return ;
553598 }
554- final Icon rawSelectedIcon = doubleIcon (selectedIcon , secondaryIcon , .9f , selectedColor ());
555- final Icon rawDisabledSelectedIcon = doubleIcon (selectedIcon , secondaryIcon , .9f ,
556- SNTColor .average (List .of (disabledColor (), selectedColor ())));
599+ // NB: FlatLaf swaps to getSelectedIcon() whenever the item is armed (highlighted by the cursor), not only when
600+ // a JCheckBoxMenuItem is actually checked, and paints it right after filling the hover highlight.
601+ // selectedColor() is a background swatch (List.selectionBackground); using it here would color the glyph the
602+ // same as that highlight, making it disappear on hover. Keep the same foreground colors as the default/disabled
603+ // icons so the swapped glyph stays legible while armed
604+ final Icon rawSelectedIcon = doubleIcon (selectedIcon , secondaryIcon , .9f , defaultColor ());
605+ final Icon rawDisabledSelectedIcon = doubleIcon (selectedIcon , secondaryIcon , .9f , disabledColor ());
557606 // defaultIcon/selectedIcon glyphs (e.g., EYE vs EYE_SLASH) rarely share the same font-metrics
558607 final int refWidth = Math .max (icon .getIconWidth (), rawSelectedIcon .getIconWidth ());
559608 final Icon fixedIcon = fixedWidthIcon (icon , refWidth );
@@ -577,7 +626,7 @@ public static void assignDoubleIcon(final JMenuItem menuItem, final GLYPH defaul
577626 *
578627 * @param button the button to decorate
579628 * @param dotColor the dot's fill color (e.g., Color.RED)
580- * @see #notificationIcon(Icon, Color, Color)
629+ * @see #notificationIcon(Icon, Color, Color)
581630 */
582631 public static void assignNotification (final AbstractButton button , final Color dotColor ) {
583632 final Color bkgColor = button .getBackground ();
0 commit comments