4

So this is really in many ways a follow up to this question: Complete list of FrontEndResources

For a while now I've been using FrontEndResource specified appearances for my Button and Panel elements, but of late I've wanted to do more.

If we look at, say, FrontEndResource["NotebookTemplatingExpressions","ButtonDefaultAppearance"] we see that it gives us images tagged as "Default", "Hover", and "Pressed".

And yet this gives an invisible button:

Button["",
 ImageSize -> 200,
 Appearance ->
  MapThread[Rule, {
    {"Default", "Hover", "Pressed"},
    Rasterize@
       Graphics[{#, Scale[Disk[], {1, .5}]}, 
        ImageSize -> 50] & /@
     {Red, Green, Blue}
    }]
 ]

So the question is, then, how do I prepare images that will work here?

b3m2a1
  • 46,870
  • 3
  • 92
  • 239

1 Answers1

3

Simple Answer

We can get this to work by simply setting two flags on the images. They need Interleaving -> True and they need to be "Byte" images. Happily Rasterize will do this for us if we ask it to:

Consider this:

Panel["asdasdasd",
 ImageSize -> 200,
 Appearance ->
  MapThread[Rule, {
    {"Default", "Hover"},
    Rasterize[Graphics[{#, Scale[Disk[], {1, .5}]}, ImageSize -> 50], 
       "Image"] & /@
     {Red, Blue}
    }]
 ]

Extended Answer

One issue with this though is that when we expand the panel our image simply rescales. And if we look at, say,

Panel["asd",
 Appearance -> 
  FrontEndResource["NotebookTemplatingExpressions", 
   "ButtonDefaultAppearance"]
 ]

The Panel samples really nicely. And what's behind it is the little black borders we see in FrontEndResource["NotebookTemplatingExpressions","ButtonDefaultAppearance"].

Playing with it I've found the border has can't entirely box off the image, but otherwise as long as it's there it will have an effect. And this effect depends on how large the border is. The particulars of this aren't entirely clear to me yet, but the smaller it is the further from that border text seems to get placed.

Auto-formatting

With all that said, then, we can define a function to generate the appropriate sort of images--even from non-image objects (the formatting is wonky because I develop in code cells):

Options[AppearanceReadyImage] =
    Join[
        {
                BorderDimensions -> Automatic,
                Prolog -> Identity,
                ImagePadding -> None
                },
        Options@Rasterize
        ];
AppearanceReadyImage[i_?(ImageQ),ops:OptionsPattern[]]:=
    With[{img=
        ColorConvert[
            ImagePad[
                Replace[OptionValue[ImagePadding],{
                    Except[_Integer|{{_Integer,_Integer},{_Integer,_Integer}}]:>
                        Replace[OptionValue@Prolog,None->Identity]@i,
                    int_:>
                        ImagePad[
                            Replace[OptionValue@Prolog,None->Identity]@i,
                            int,GrayLevel[0,0]]
                    }],
                1,GrayLevel[0,0]],
            "RGB"
            ]},
        With[{dims=ImageDimensions@img},
            With[{
                mx=Floor@First@dims/2,my=Floor@Last@dims/2,
                x=First@dims,y=Last@dims
                },
                With[{
                    rleft=
                        Replace[
                            Replace[OptionValue@BorderDimensions,{
                                Automatic->Scaled[.5],
                                {{x_,_},_}|{x:Except[_List],_}:>
                                    Replace[x,Automatic->Scaled[.5]]
                                }],{
                            Except[_Scaled|_?NumericQ]->None,
                            w_?NumericQ:>
                                Range[Floor@-w/2,Floor@w/2],
                            Scaled[w_]:>
                                Range[Floor@-(y*w/2),Floor@(y*w/2)]
                            }],
                    rright=
                        Replace[
                            Replace[OptionValue@BorderDimensions,{
                                Automatic->Scaled[.5],
                                {{_,r_},_}|{r:Except[_List],_}:>
                                    Replace[r,Automatic->Scaled[.5]]
                                }],{
                            Except[_Scaled|_?NumericQ]->None,
                            w_?NumericQ:>
                                Range[Floor@-w/2,Floor@w/2],
                            Scaled[w_]:>
                                Range[Floor@-(y*w/2),Floor@(y*w/2)]
                            }],
                    rbottom=
                        Replace[
                            Replace[OptionValue@BorderDimensions,{
                                Automatic->Scaled[.8],
                                {_,_}|{_,x_}:>
                                    Replace[x,Automatic->Scaled[.8]]
                                }],{
                            Except[_Scaled|_?NumericQ]->None,
                            w_?NumericQ:>
                                Range[Floor@-w/2,Floor@w/2],
                            Scaled[w_]:>
                                Range[Floor@-(x*w/2),Floor@(x*w/2)]
                            }],
                    rtop=
                        Replace[
                            Replace[OptionValue@BorderDimensions,{
                                Automatic->Scaled[.5],
                                {_,{_,t_}}|{_,t_}:>
                                    Replace[t,Automatic->Scaled[.5]]
                                }],{
                            Except[_Scaled|_?NumericQ]->None,
                            w_?NumericQ:>
                                Range[Floor@-w/2,Floor@w/2],
                            Scaled[w_]:>
                                Range[Floor@-(x*w/2),Floor@(x*w/2)]
                            }]
                    },
                    Image[
                        ReplacePixelValue[
                            img,
                            Join[
                                If[rleft===None,
                                    {},
                                    Thread[{1,rleft+my}]
                                    ],
                                If[rright===None,
                                    {},
                                    Thread[{x,rright+my}]
                                    ],
                                If[rbottom===None,
                                    {},
                                    Thread[{rbottom+mx,1}]
                                    ],
                                If[rtop===None,
                                    {},
                                    Thread[{rtop+mx,y}]
                                    ]
                                ]->Black
                            ],
                            "Byte",
                            Interleaving->True
                        ]
                    ]
                ]
            ]
        ];
AppearanceReadyImage[
    e : Except[_Mouseover | _List | _Rule]?(Not@*ImageQ),
    ops : OptionsPattern[]] :=
    AppearanceReadyImage[
        Rasterize[e, "Image", FilterRules[{ops}, Options@Rasterize]],
        ops];
AppearanceReadyImage[k_ -> img_, ops : OptionsPattern[]] :=

  k -> AppearanceReadyImage[img, ops];
AppearanceReadyImage[l_List, ops : OptionsPattern[]] :=

  AppearanceReadyImage[#, ops] & /@ l;
AppearanceReadyImage[Mouseover[e1_, e2_], 
   ops : OptionsPattern[]] :=
    {
        "Default" -> AppearanceReadyImage[e1, ops],
        "Hover" -> AppearanceReadyImage[e2, ops]
        };

As far as code blocks go it's more than I'd usually want to post here, but it does let us do fun stuff. We can build buttons from Framed expressions:

Button[
 "Try me!",
 Appearance ->
  AppearanceReadyImage[
   MapThread[Rule, {
     {"Default", "Hover", "Pressed"},
     Map[
      Framed["",
        ImageSize -> 100,
        Background -> #,
        RoundingRadius -> 5,
        FrameStyle -> Directive[Thickness[.3], GrayLevel[.8]]
        ] &,
      {None, GrayLevel[.95], LightBlue}
      ]
     }],
   ImageResolution -> 750
   ]
 ]

Classic button images

Or we can make write a convenience function for LinearGradientImage that will let us make buttons of any color:

Options[GradientAppearance] = {
    ImageSize -> {10, 20},
    ImagePadding -> 1,
    FrameStyle -> Darker
    };
GradientAppearance[
    color_?ColorQ,
    center :
            _?NumericQ | _Scaled |
                {_?NumericQ | _Scaled, _?NumericQ | _Scaled} :
            {Scaled[1], Scaled[.25]},
    ops : OptionsPattern[]] :=
    With[{
            imageSize =
                OptionValue[ImageSize],
            frameColor =
                Replace[OptionValue[FrameStyle], {
                    c_?ColorQ :>
                            c,
                    e_ :>
                            e@color
                    }],
            pad = OptionValue[ImagePadding]
            },
        With[{cents =
                Replace[
                    Replace[center, {
                            n_?NumericQ :>
                                {Scaled[1], n},
                            s_Scaled :>
                                {Scaled[1], s}
                            }], {
                        Scaled[s_] :>
                            s*Last@imageSize
                        },
                1]},
                If[pad =!= None,
                    ImagePad[#, pad, frameColor],
                    Identity] &@
                    LinearGradientImage[
                        Thread@{Center, cents} ->
                        {
                                color,

        Hue[#[[1]], #[[2]], #[[3]] + .05] &@ColorConvert[color, Hue],

        Hue[#[[1]], #[[2]], #[[3]] + -.1] &@ColorConvert[color, Hue]
                                },
                        imageSize
                        ]
                ]
    ];
GradientAppearance[{c_?ColorQ}, ops : OptionsPattern[]] :=

  "Default" -> GradientAppearance[c, ops];
GradientAppearance[c : {_?ColorQ, _?ColorQ}, 
   ops : OptionsPattern[]] :=
    MapThread[
        # -> GradientAppearance[#2, ops] &, {
                {"Default", "Hover"},
                c
            }];
GradientAppearance[c : {_?ColorQ, _?ColorQ, _?ColorQ}, 
   ops : OptionsPattern[]] :=
    MapThread[
        # -> GradientAppearance[#2, #3, ops] &, {
                {"Default", "Hover", "Pressed"},
                c,
                {Scaled[.25], Scaled[.25], Scaled /@ {.25, .65}}
            }];

Putting this together we can get some very nice looking UI elements with very little code to tweak and mess about with:

Grid[
 Map[
  MapThread[
    Button[
      Style[#, White],
      ImageSize -> 100,
      Appearance ->
       #2
      ] &,
    List @@@ AppearanceReadyImage[
       GradientAppearance@{
         Hue[#, .5, .8],
         Hue[#, .5, .9],
         Hue[#, .5, .85]}
       ] // Transpose
    ] &,
  Range[0, 1, .1]
  ],
 Spacings -> {0, 0}
 ]

enter image description here

Single code-block:

Here're both large blocks in one to make copying easier:

Options[AppearanceReadyImage] =
    Join[
        {
                BorderDimensions -> Automatic,
                Prolog -> Identity,
                ImagePadding -> None
                },
        Options@Rasterize
        ];
AppearanceReadyImage[i_?(ImageQ),ops:OptionsPattern[]]:=
    With[{img=
        ColorConvert[
            ImagePad[
                Replace[OptionValue[ImagePadding],{
                    Except[_Integer|{{_Integer,_Integer},{_Integer,_Integer}}]:>
                        Replace[OptionValue@Prolog,None->Identity]@i,
                    int_:>
                        ImagePad[
                            Replace[OptionValue@Prolog,None->Identity]@i,
                            int,GrayLevel[0,0]]
                    }],
                1,GrayLevel[0,0]],
            "RGB"
            ]},
        With[{dims=ImageDimensions@img},
            With[{
                mx=Floor@First@dims/2,my=Floor@Last@dims/2,
                x=First@dims,y=Last@dims
                },
                With[{
                    rleft=
                        Replace[
                            Replace[OptionValue@BorderDimensions,{
                                Automatic->Scaled[.5],
                                {{x_,_},_}|{x:Except[_List],_}:>
                                    Replace[x,Automatic->Scaled[.5]]
                                }],{
                            Except[_Scaled|_?NumericQ]->None,
                            w_?NumericQ:>
                                Range[Floor@-w/2,Floor@w/2],
                            Scaled[w_]:>
                                Range[Floor@-(y*w/2),Floor@(y*w/2)]
                            }],
                    rright=
                        Replace[
                            Replace[OptionValue@BorderDimensions,{
                                Automatic->Scaled[.5],
                                {{_,r_},_}|{r:Except[_List],_}:>
                                    Replace[r,Automatic->Scaled[.5]]
                                }],{
                            Except[_Scaled|_?NumericQ]->None,
                            w_?NumericQ:>
                                Range[Floor@-w/2,Floor@w/2],
                            Scaled[w_]:>
                                Range[Floor@-(y*w/2),Floor@(y*w/2)]
                            }],
                    rbottom=
                        Replace[
                            Replace[OptionValue@BorderDimensions,{
                                Automatic->Scaled[.8],
                                {_,_}|{_,x_}:>
                                    Replace[x,Automatic->Scaled[.8]]
                                }],{
                            Except[_Scaled|_?NumericQ]->None,
                            w_?NumericQ:>
                                Range[Floor@-w/2,Floor@w/2],
                            Scaled[w_]:>
                                Range[Floor@-(x*w/2),Floor@(x*w/2)]
                            }],
                    rtop=
                        Replace[
                            Replace[OptionValue@BorderDimensions,{
                                Automatic->Scaled[.5],
                                {_,{_,t_}}|{_,t_}:>
                                    Replace[t,Automatic->Scaled[.5]]
                                }],{
                            Except[_Scaled|_?NumericQ]->None,
                            w_?NumericQ:>
                                Range[Floor@-w/2,Floor@w/2],
                            Scaled[w_]:>
                                Range[Floor@-(x*w/2),Floor@(x*w/2)]
                            }]
                    },
                    Image[
                        ReplacePixelValue[
                            img,
                            Join[
                                If[rleft===None,
                                    {},
                                    Thread[{1,rleft+my}]
                                    ],
                                If[rright===None,
                                    {},
                                    Thread[{x,rright+my}]
                                    ],
                                If[rbottom===None,
                                    {},
                                    Thread[{rbottom+mx,1}]
                                    ],
                                If[rtop===None,
                                    {},
                                    Thread[{rtop+mx,y}]
                                    ]
                                ]->Black
                            ],
                            "Byte",
                            Interleaving->True
                        ]
                    ]
                ]
            ]
        ];
AppearanceReadyImage[
    e : Except[_Mouseover | _List | _Rule]?(Not@*ImageQ),
    ops : OptionsPattern[]] :=
    AppearanceReadyImage[
        Rasterize[e, "Image", FilterRules[{ops}, Options@Rasterize]],
        ops];
AppearanceReadyImage[k_ -> img_, ops : OptionsPattern[]] :=

  k -> AppearanceReadyImage[img, ops];
AppearanceReadyImage[l_List, ops : OptionsPattern[]] :=

  AppearanceReadyImage[#, ops] & /@ l;
AppearanceReadyImage[Mouseover[e1_, e2_], 
   ops : OptionsPattern[]] :=
    {
        "Default" -> AppearanceReadyImage[e1, ops],
        "Hover" -> AppearanceReadyImage[e2, ops]
        };

Options[GradientAppearance] = {
    ImageSize -> {10, 20},
    ImagePadding -> 1,
    FrameStyle -> Darker
    };
GradientAppearance[
    color_?ColorQ,
    center :
            _?NumericQ | _Scaled |
                {_?NumericQ | _Scaled, _?NumericQ | _Scaled} :
            {Scaled[1], Scaled[.25]},
    ops : OptionsPattern[]] :=
    With[{
            imageSize =
                OptionValue[ImageSize],
            frameColor =
                Replace[OptionValue[FrameStyle], {
                    c_?ColorQ :>
                            c,
                    e_ :>
                            e@color
                    }],
            pad = OptionValue[ImagePadding]
            },
        With[{cents =
                Replace[
                    Replace[center, {
                            n_?NumericQ :>
                                {Scaled[1], n},
                            s_Scaled :>
                                {Scaled[1], s}
                            }], {
                        Scaled[s_] :>
                            s*Last@imageSize
                        },
                1]},
                If[pad =!= None,
                    ImagePad[#, pad, frameColor],
                    Identity] &@
                    LinearGradientImage[
                        Thread@{Center, cents} ->
                        {
                                color,

        Hue[#[[1]], #[[2]], #[[3]] + .05] &@ColorConvert[color, Hue],

        Hue[#[[1]], #[[2]], #[[3]] + -.1] &@ColorConvert[color, Hue]
                                },
                        imageSize
                        ]
                ]
    ];
GradientAppearance[{c_?ColorQ}, ops : OptionsPattern[]] :=

  "Default" -> GradientAppearance[c, ops];
GradientAppearance[c : {_?ColorQ, _?ColorQ}, 
   ops : OptionsPattern[]] :=
    MapThread[
        # -> GradientAppearance[#2, ops] &, {
                {"Default", "Hover"},
                c
            }];
GradientAppearance[c : {_?ColorQ, _?ColorQ, _?ColorQ}, 
   ops : OptionsPattern[]] :=
    MapThread[
        # -> GradientAppearance[#2, #3, ops] &, {
                {"Default", "Hover", "Pressed"},
                c,
                {Scaled[.25], Scaled[.25], Scaled /@ {.25, .65}}
            }];
b3m2a1
  • 46,870
  • 3
  • 92
  • 239