-
-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add icon buttons to bottom of GridViewItem when hovered
- Loading branch information
Showing
3 changed files
with
395 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,321 @@ | ||
package util | ||
|
||
// Contents of this file come from the fyne internal SVG package | ||
// Once https://github.com/fyne-io/fyne/pull/5345 is available in main, | ||
// this file can be retired, and the ColorizeSVG func can be replaced with | ||
// `canvas.ColorizeSVG` | ||
|
||
import ( | ||
"bytes" | ||
"encoding/hex" | ||
"encoding/xml" | ||
"fmt" | ||
"image/color" | ||
"io" | ||
"strconv" | ||
) | ||
|
||
// ColorizeSVG creates a new SVG from a given one by replacing all fill colors by the given color. | ||
func ColorizeSVG(src []byte, clr color.Color) ([]byte, error) { | ||
rdr := bytes.NewReader(src) | ||
s, err := svgFromXML(rdr) | ||
if err != nil { | ||
return src, fmt.Errorf("could not load SVG, falling back to static content: %v", err) | ||
} | ||
if err := s.replaceFillColor(clr); err != nil { | ||
return src, fmt.Errorf("could not replace fill color, falling back to static content: %v", err) | ||
} | ||
colorized, err := xml.Marshal(s) | ||
if err != nil { | ||
return src, fmt.Errorf("could not marshal svg, falling back to static content: %v", err) | ||
} | ||
return colorized, nil | ||
} | ||
|
||
// svg holds the unmarshaled XML from a Scalable Vector Graphic | ||
type svg struct { | ||
XMLName xml.Name `xml:"svg"` | ||
XMLNS string `xml:"xmlns,attr"` | ||
Width string `xml:"width,attr,omitempty"` | ||
Height string `xml:"height,attr,omitempty"` | ||
ViewBox string `xml:"viewBox,attr,omitempty"` | ||
Paths []*pathObj `xml:"path"` | ||
Rects []*rectObj `xml:"rect"` | ||
Circles []*circleObj `xml:"circle"` | ||
Ellipses []*ellipseObj `xml:"ellipse"` | ||
Polygons []*polygonObj `xml:"polygon"` | ||
Groups []*objGroup `xml:"g"` | ||
} | ||
|
||
type pathObj struct { | ||
XMLName xml.Name `xml:"path"` | ||
Fill string `xml:"fill,attr,omitempty"` | ||
FillOpacity string `xml:"fill-opacity,attr,omitempty"` | ||
Stroke string `xml:"stroke,attr,omitempty"` | ||
StrokeWidth string `xml:"stroke-width,attr,omitempty"` | ||
StrokeLineCap string `xml:"stroke-linecap,attr,omitempty"` | ||
StrokeLineJoin string `xml:"stroke-linejoin,attr,omitempty"` | ||
StrokeDashArray string `xml:"stroke-dasharray,attr,omitempty"` | ||
D string `xml:"d,attr"` | ||
Transform string `xml:"transform,attr,omitempty"` | ||
} | ||
|
||
type rectObj struct { | ||
XMLName xml.Name `xml:"rect"` | ||
Fill string `xml:"fill,attr,omitempty"` | ||
FillOpacity string `xml:"fill-opacity,attr,omitempty"` | ||
Stroke string `xml:"stroke,attr,omitempty"` | ||
StrokeWidth string `xml:"stroke-width,attr,omitempty"` | ||
StrokeLineCap string `xml:"stroke-linecap,attr,omitempty"` | ||
StrokeLineJoin string `xml:"stroke-linejoin,attr,omitempty"` | ||
StrokeDashArray string `xml:"stroke-dasharray,attr,omitempty"` | ||
X string `xml:"x,attr,omitempty"` | ||
Y string `xml:"y,attr,omitempty"` | ||
Width string `xml:"width,attr,omitempty"` | ||
Height string `xml:"height,attr,omitempty"` | ||
Transform string `xml:"transform,attr,omitempty"` | ||
} | ||
|
||
type circleObj struct { | ||
XMLName xml.Name `xml:"circle"` | ||
Fill string `xml:"fill,attr,omitempty"` | ||
FillOpacity string `xml:"fill-opacity,attr,omitempty"` | ||
Stroke string `xml:"stroke,attr,omitempty"` | ||
StrokeWidth string `xml:"stroke-width,attr,omitempty"` | ||
StrokeLineCap string `xml:"stroke-linecap,attr,omitempty"` | ||
StrokeLineJoin string `xml:"stroke-linejoin,attr,omitempty"` | ||
StrokeDashArray string `xml:"stroke-dasharray,attr,omitempty"` | ||
CX string `xml:"cx,attr,omitempty"` | ||
CY string `xml:"cy,attr,omitempty"` | ||
R string `xml:"r,attr,omitempty"` | ||
Transform string `xml:"transform,attr,omitempty"` | ||
} | ||
|
||
type ellipseObj struct { | ||
XMLName xml.Name `xml:"ellipse"` | ||
Fill string `xml:"fill,attr,omitempty"` | ||
FillOpacity string `xml:"fill-opacity,attr,omitempty"` | ||
Stroke string `xml:"stroke,attr,omitempty"` | ||
StrokeWidth string `xml:"stroke-width,attr,omitempty"` | ||
StrokeLineCap string `xml:"stroke-linecap,attr,omitempty"` | ||
StrokeLineJoin string `xml:"stroke-linejoin,attr,omitempty"` | ||
StrokeDashArray string `xml:"stroke-dasharray,attr,omitempty"` | ||
CX string `xml:"cx,attr,omitempty"` | ||
CY string `xml:"cy,attr,omitempty"` | ||
RX string `xml:"rx,attr,omitempty"` | ||
RY string `xml:"ry,attr,omitempty"` | ||
Transform string `xml:"transform,attr,omitempty"` | ||
} | ||
|
||
type polygonObj struct { | ||
XMLName xml.Name `xml:"polygon"` | ||
Fill string `xml:"fill,attr,omitempty"` | ||
FillOpacity string `xml:"fill-opacity,attr,omitempty"` | ||
Stroke string `xml:"stroke,attr,omitempty"` | ||
StrokeWidth string `xml:"stroke-width,attr,omitempty"` | ||
StrokeLineCap string `xml:"stroke-linecap,attr,omitempty"` | ||
StrokeLineJoin string `xml:"stroke-linejoin,attr,omitempty"` | ||
StrokeDashArray string `xml:"stroke-dasharray,attr,omitempty"` | ||
Points string `xml:"points,attr"` | ||
Transform string `xml:"transform,attr,omitempty"` | ||
} | ||
|
||
type objGroup struct { | ||
XMLName xml.Name `xml:"g"` | ||
ID string `xml:"id,attr,omitempty"` | ||
Fill string `xml:"fill,attr,omitempty"` | ||
Stroke string `xml:"stroke,attr,omitempty"` | ||
StrokeWidth string `xml:"stroke-width,attr,omitempty"` | ||
StrokeLineCap string `xml:"stroke-linecap,attr,omitempty"` | ||
StrokeLineJoin string `xml:"stroke-linejoin,attr,omitempty"` | ||
StrokeDashArray string `xml:"stroke-dasharray,attr,omitempty"` | ||
Transform string `xml:"transform,attr,omitempty"` | ||
Paths []*pathObj `xml:"path"` | ||
Circles []*circleObj `xml:"circle"` | ||
Ellipses []*ellipseObj `xml:"ellipse"` | ||
Rects []*rectObj `xml:"rect"` | ||
Polygons []*polygonObj `xml:"polygon"` | ||
Groups []*objGroup `xml:"g"` | ||
} | ||
|
||
func replacePathsFill(paths []*pathObj, hexColor string, opacity string) { | ||
for _, path := range paths { | ||
if path.Fill != "none" { | ||
path.Fill = hexColor | ||
path.FillOpacity = opacity | ||
} | ||
} | ||
} | ||
|
||
func replaceRectsFill(rects []*rectObj, hexColor string, opacity string) { | ||
for _, rect := range rects { | ||
if rect.Fill != "none" { | ||
rect.Fill = hexColor | ||
rect.FillOpacity = opacity | ||
} | ||
} | ||
} | ||
|
||
func replaceCirclesFill(circles []*circleObj, hexColor string, opacity string) { | ||
for _, circle := range circles { | ||
if circle.Fill != "none" { | ||
circle.Fill = hexColor | ||
circle.FillOpacity = opacity | ||
} | ||
} | ||
} | ||
|
||
func replaceEllipsesFill(ellipses []*ellipseObj, hexColor string, opacity string) { | ||
for _, ellipse := range ellipses { | ||
if ellipse.Fill != "none" { | ||
ellipse.Fill = hexColor | ||
ellipse.FillOpacity = opacity | ||
} | ||
} | ||
} | ||
|
||
func replacePolygonsFill(polys []*polygonObj, hexColor string, opacity string) { | ||
for _, poly := range polys { | ||
if poly.Fill != "none" { | ||
poly.Fill = hexColor | ||
poly.FillOpacity = opacity | ||
} | ||
} | ||
} | ||
|
||
func replaceGroupObjectFill(groups []*objGroup, hexColor string, opacity string) { | ||
for _, grp := range groups { | ||
replaceCirclesFill(grp.Circles, hexColor, opacity) | ||
replaceEllipsesFill(grp.Ellipses, hexColor, opacity) | ||
replacePathsFill(grp.Paths, hexColor, opacity) | ||
replaceRectsFill(grp.Rects, hexColor, opacity) | ||
replacePolygonsFill(grp.Polygons, hexColor, opacity) | ||
replaceGroupObjectFill(grp.Groups, hexColor, opacity) | ||
} | ||
} | ||
|
||
// replaceFillColor alters an svg objects fill color. Note that if an svg with multiple fill | ||
// colors is being operated upon, all fills will be converted to a single color. Mostly used | ||
// to recolor Icons to match the theme's IconColor. | ||
func (s *svg) replaceFillColor(color color.Color) error { | ||
hexColor, opacity := colorToHexAndOpacity(color) | ||
replacePathsFill(s.Paths, hexColor, opacity) | ||
replaceRectsFill(s.Rects, hexColor, opacity) | ||
replaceCirclesFill(s.Circles, hexColor, opacity) | ||
replaceEllipsesFill(s.Ellipses, hexColor, opacity) | ||
replacePolygonsFill(s.Polygons, hexColor, opacity) | ||
replaceGroupObjectFill(s.Groups, hexColor, opacity) | ||
return nil | ||
} | ||
|
||
func svgFromXML(reader io.Reader) (*svg, error) { | ||
var s svg | ||
bSlice, err := io.ReadAll(reader) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := xml.Unmarshal(bSlice, &s); err != nil { | ||
return nil, err | ||
} | ||
return &s, nil | ||
} | ||
|
||
func colorToHexAndOpacity(color color.Color) (hexStr, aStr string) { | ||
r, g, b, a := toNRGBA(color) | ||
cBytes := []byte{byte(r), byte(g), byte(b)} | ||
hexStr, aStr = "#"+hex.EncodeToString(cBytes), strconv.FormatFloat(float64(a)/0xff, 'f', 6, 64) | ||
return | ||
} | ||
|
||
// toNRGBA converts a color to RGBA values which are not premultiplied, unlike color.RGBA(). | ||
func toNRGBA(c color.Color) (r, g, b, a int) { | ||
// We use UnmultiplyAlpha with RGBA, RGBA64, and unrecognized implementations of Color. | ||
// It works for all Colors whose RGBA() method is implemented according to spec, but is only necessary for those. | ||
// Only RGBA and RGBA64 have components which are already premultiplied. | ||
switch col := c.(type) { | ||
// NRGBA and NRGBA64 are not premultiplied | ||
case color.NRGBA: | ||
r = int(col.R) | ||
g = int(col.G) | ||
b = int(col.B) | ||
a = int(col.A) | ||
case *color.NRGBA: | ||
r = int(col.R) | ||
g = int(col.G) | ||
b = int(col.B) | ||
a = int(col.A) | ||
case color.NRGBA64: | ||
r = int(col.R) >> 8 | ||
g = int(col.G) >> 8 | ||
b = int(col.B) >> 8 | ||
a = int(col.A) >> 8 | ||
case *color.NRGBA64: | ||
r = int(col.R) >> 8 | ||
g = int(col.G) >> 8 | ||
b = int(col.B) >> 8 | ||
a = int(col.A) >> 8 | ||
// Gray and Gray16 have no alpha component | ||
case *color.Gray: | ||
r = int(col.Y) | ||
g = int(col.Y) | ||
b = int(col.Y) | ||
a = 0xff | ||
case color.Gray: | ||
r = int(col.Y) | ||
g = int(col.Y) | ||
b = int(col.Y) | ||
a = 0xff | ||
case *color.Gray16: | ||
r = int(col.Y) >> 8 | ||
g = int(col.Y) >> 8 | ||
b = int(col.Y) >> 8 | ||
a = 0xff | ||
case color.Gray16: | ||
r = int(col.Y) >> 8 | ||
g = int(col.Y) >> 8 | ||
b = int(col.Y) >> 8 | ||
a = 0xff | ||
// Alpha and Alpha16 contain only an alpha component. | ||
case color.Alpha: | ||
r = 0xff | ||
g = 0xff | ||
b = 0xff | ||
a = int(col.A) | ||
case *color.Alpha: | ||
r = 0xff | ||
g = 0xff | ||
b = 0xff | ||
a = int(col.A) | ||
case color.Alpha16: | ||
r = 0xff | ||
g = 0xff | ||
b = 0xff | ||
a = int(col.A) >> 8 | ||
case *color.Alpha16: | ||
r = 0xff | ||
g = 0xff | ||
b = 0xff | ||
a = int(col.A) >> 8 | ||
default: // RGBA, RGBA64, and unknown implementations of Color | ||
r, g, b, a = unmultiplyAlpha(c) | ||
} | ||
return | ||
} | ||
|
||
// unmultiplyAlpha returns a color's RGBA components as 8-bit integers by calling c.RGBA() and then removing the alpha premultiplication. | ||
// It is only used by ToRGBA. | ||
func unmultiplyAlpha(c color.Color) (r, g, b, a int) { | ||
red, green, blue, alpha := c.RGBA() | ||
if alpha != 0 && alpha != 0xffff { | ||
red = (red * 0xffff) / alpha | ||
green = (green * 0xffff) / alpha | ||
blue = (blue * 0xffff) / alpha | ||
} | ||
// Convert from range 0-65535 to range 0-255 | ||
r = int(red >> 8) | ||
g = int(green >> 8) | ||
b = int(blue >> 8) | ||
a = int(alpha >> 8) | ||
return | ||
} |
Oops, something went wrong.