diff --git a/canvas/canvas.go b/canvas/canvas.go index 32c757b84b..536377f466 100644 --- a/canvas/canvas.go +++ b/canvas/canvas.go @@ -1,6 +1,11 @@ package canvas -import "fyne.io/fyne/v2" +import ( + "image/color" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/internal/svg" +) // Refresh instructs the containing canvas to refresh the specified obj. func Refresh(obj fyne.CanvasObject) { @@ -15,6 +20,19 @@ func Refresh(obj fyne.CanvasObject) { } } +// RecolorSVG takes a []byte containing SVG content, and returns +// new SVG content, re-colorized to be monochrome with the given color. +// The content can be assigned to a new fyne.StaticResource with an appropriate name +// to be used in a widget.Button, canvas.Image, etc. +// +// If an error occurs, the returned content will be the original un-modified content, +// and a non-nil error is returned. +// +// Since: 2.6 +func RecolorSVG(svgContent []byte, color color.Color) ([]byte, error) { + return svg.Colorize(svgContent, color) +} + // repaint instructs the containing canvas to redraw, even if nothing changed. func repaint(obj fyne.CanvasObject) { app := fyne.CurrentApp() diff --git a/canvas/image.go b/canvas/image.go index e0dd7e13ff..c8c18a41f1 100644 --- a/canvas/image.go +++ b/canvas/image.go @@ -279,7 +279,11 @@ func (i *Image) updateReader() (io.ReadCloser, error) { th := cache.WidgetTheme(i) if th != nil { col := th.Color(res.ThemeColorName(), fyne.CurrentApp().Settings().ThemeVariant()) - content = svg.Colorize(content, col) + var err error + content, err = svg.Colorize(content, col) + if err != nil { + fyne.LogError("", err) + } } } return io.NopCloser(bytes.NewReader(content)), nil diff --git a/internal/driver/glfw/menu_darwin.go b/internal/driver/glfw/menu_darwin.go index aa5eef93ce..c1c7ba1fb2 100644 --- a/internal/driver/glfw/menu_darwin.go +++ b/internal/driver/glfw/menu_darwin.go @@ -179,9 +179,13 @@ func insertNativeMenuItem(nsMenu unsafe.Pointer, item *fyne.MenuItem, nextItemID if _, isThemed := rsc.(*theme.ThemedResource); isThemed { var r, g, b, a C.int C.getTextColorRGBA(&r, &g, &b, &a) + content, err := svg.Colorize(rsc.Content(), color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: uint8(a)}) + if err != nil { + fyne.LogError("", err) + } rsc = &fyne.StaticResource{ StaticName: rsc.Name(), - StaticContent: svg.Colorize(rsc.Content(), color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: uint8(a)}), + StaticContent: content, } } size := int(C.menuFontSize()) diff --git a/internal/svg/svg.go b/internal/svg/svg.go index a85d176689..467a9eb66e 100644 --- a/internal/svg/svg.go +++ b/internal/svg/svg.go @@ -21,23 +21,20 @@ import ( ) // Colorize creates a new SVG from a given one by replacing all fill colors by the given color. -func Colorize(src []byte, clr color.Color) []byte { +func Colorize(src []byte, clr color.Color) ([]byte, error) { rdr := bytes.NewReader(src) s, err := svgFromXML(rdr) if err != nil { - fyne.LogError("could not load SVG, falling back to static content:", err) - return src + return src, fmt.Errorf("could not load SVG, falling back to static content: %v", err) } if err := s.replaceFillColor(clr); err != nil { - fyne.LogError("could not replace fill color, falling back to static content:", err) - return src + return src, fmt.Errorf("could not replace fill color, falling back to static content: %v", err) } colorized, err := xml.Marshal(s) if err != nil { - fyne.LogError("could not marshal svg, falling back to static content:", err) - return src + return src, fmt.Errorf("could not marshal svg, falling back to static content: %v", err) } - return colorized + return colorized, nil } type Decoder struct { diff --git a/internal/svg/svg_test.go b/internal/svg/svg_test.go index 2d244b7b12..0a2eb73dea 100644 --- a/internal/svg/svg_test.go +++ b/internal/svg/svg_test.go @@ -144,7 +144,8 @@ func TestColorize(t *testing.T) { t.Run(name, func(t *testing.T) { bytes, err := os.ReadFile(filepath.Join("testdata", tt.svgFile)) require.NoError(t, err) - got := helperDrawSVG(t, Colorize(bytes, tt.color)) + content, _ := Colorize(bytes, tt.color) + got := helperDrawSVG(t, content) test.AssertImageMatches(t, tt.wantImage, got) }) } diff --git a/theme/icons.go b/theme/icons.go index 647b441940..3e62ffe168 100644 --- a/theme/icons.go +++ b/theme/icons.go @@ -1,6 +1,8 @@ package theme import ( + "image/color" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/internal/svg" ) @@ -712,7 +714,7 @@ func (res *ThemedResource) ThemeColorName() fyne.ThemeColorName { // Content returns the underlying content of the resource adapted to the current text color. func (res *ThemedResource) Content() []byte { - return svg.Colorize(unwrapResource(res.source).Content(), Color(res.ThemeColorName())) + return colorizeLogError(unwrapResource(res.source).Content(), Color(res.ThemeColorName())) } // Error returns a different resource for indicating an error. @@ -742,7 +744,7 @@ func (res *InvertedThemedResource) Name() string { // Content returns the underlying content of the resource adapted to the current background color. func (res *InvertedThemedResource) Content() []byte { clr := Color(ColorNameBackground) - return svg.Colorize(unwrapResource(res.source).Content(), clr) + return colorizeLogError(unwrapResource(res.source).Content(), clr) } // ThemeColorName returns the fyne.ThemeColorName that is used as foreground color. @@ -775,7 +777,7 @@ func (res *ErrorThemedResource) Name() string { // Content returns the underlying content of the resource adapted to the current background color. func (res *ErrorThemedResource) Content() []byte { - return svg.Colorize(unwrapResource(res.source).Content(), Color(ColorNameError)) + return colorizeLogError(unwrapResource(res.source).Content(), Color(ColorNameError)) } // Original returns the underlying resource that this error themed resource was adapted from @@ -810,7 +812,7 @@ func (res *PrimaryThemedResource) Name() string { // Content returns the underlying content of the resource adapted to the current background color. func (res *PrimaryThemedResource) Content() []byte { - return svg.Colorize(unwrapResource(res.source).Content(), Color(ColorNamePrimary)) + return colorizeLogError(unwrapResource(res.source).Content(), Color(ColorNamePrimary)) } // Original returns the underlying resource that this primary themed resource was adapted from @@ -839,7 +841,7 @@ func (res *DisabledResource) Name() string { // Content returns the disabled style content of the correct resource for the current theme func (res *DisabledResource) Content() []byte { - return svg.Colorize(unwrapResource(res.source).Content(), Color(ColorNameDisabled)) + return colorizeLogError(unwrapResource(res.source).Content(), Color(ColorNameDisabled)) } // ThemeColorName returns the fyne.ThemeColorName that is used as foreground color. @@ -1399,3 +1401,11 @@ func unwrapResource(res fyne.Resource) fyne.Resource { } } } + +func colorizeLogError(src []byte, clr color.Color) []byte { + content, err := svg.Colorize(src, clr) + if err != nil { + fyne.LogError("", err) + } + return content +}