diff --git a/container/tabs_test.go b/container/tabs_test.go index c5a224d715..f50e78336a 100644 --- a/container/tabs_test.go +++ b/container/tabs_test.go @@ -3,6 +3,7 @@ package container import ( "testing" + internalTest "fyne.io/fyne/v2/internal/test" "github.com/stretchr/testify/assert" "fyne.io/fyne/v2" @@ -27,7 +28,7 @@ func TestTabButton_Icon_Change(t *testing.T) { func TestTab_ThemeChange(t *testing.T) { a := test.NewApp() defer test.NewApp() - a.Settings().SetTheme(theme.LightTheme()) + a.Settings().SetTheme(internalTest.LightTheme(theme.DefaultTheme())) tabs := NewAppTabs( NewTabItem("a", widget.NewLabel("a")), @@ -37,12 +38,12 @@ func TestTab_ThemeChange(t *testing.T) { initial := w.Canvas().Capture() - a.Settings().SetTheme(theme.DarkTheme()) + a.Settings().SetTheme(internalTest.DarkTheme(theme.DefaultTheme())) tabs.SelectIndex(1) second := w.Canvas().Capture() assert.NotEqual(t, initial, second) - a.Settings().SetTheme(theme.LightTheme()) + a.Settings().SetTheme(internalTest.LightTheme(theme.DefaultTheme())) tabs.SelectIndex(0) assert.Equal(t, initial, w.Canvas().Capture()) } diff --git a/widget/accordion.go b/widget/accordion.go index a6f0c0c3ba..47ed7dab1e 100644 --- a/widget/accordion.go +++ b/widget/accordion.go @@ -147,8 +147,10 @@ type accordionRenderer struct { } func (r *accordionRenderer) Layout(size fyne.Size) { - pad := theme.Padding() - dividerOff := (pad + theme.SeparatorThicknessSize()) / 2 + th := r.container.Theme() + pad := th.Size(theme.SizeNamePadding) + separator := th.Size(theme.SizeNameSeparatorThickness) + dividerOff := (pad + separator) / 2 x := float32(0) y := float32(0) hasOpen := 0 @@ -178,7 +180,7 @@ func (r *accordionRenderer) Layout(size fyne.Size) { if i > 0 { div.Move(fyne.NewPos(x, y-dividerOff)) } - div.Resize(fyne.NewSize(size.Width, theme.SeparatorThicknessSize())) + div.Resize(fyne.NewSize(size.Width, separator)) } h := r.headers[i] @@ -200,7 +202,8 @@ func (r *accordionRenderer) Layout(size fyne.Size) { } func (r *accordionRenderer) MinSize() fyne.Size { - pad := theme.Padding() + th := r.container.Theme() + pad := th.Size(theme.SizeNamePadding) size := fyne.Size{} r.container.propertyLock.RLock() @@ -231,6 +234,7 @@ func (r *accordionRenderer) Refresh() { } func (r *accordionRenderer) updateObjects() { + th := r.container.themeWithLock() r.container.propertyLock.RLock() defer r.container.propertyLock.RUnlock() @@ -263,10 +267,10 @@ func (r *accordionRenderer) updateObjects() { } } if ai.Open { - h.Icon = theme.MenuDropUpIcon() + h.Icon = th.Icon(theme.IconNameArrowDropUp) ai.Detail.Show() } else { - h.Icon = theme.MenuDropDownIcon() + h.Icon = th.Icon(theme.IconNameArrowDropDown) ai.Detail.Hide() } h.Refresh() diff --git a/widget/activity.go b/widget/activity.go index 58fc73a028..07b62fb431 100644 --- a/widget/activity.go +++ b/widget/activity.go @@ -35,7 +35,7 @@ func NewActivity() *Activity { func (a *Activity) MinSize() fyne.Size { a.ExtendBaseWidget(a) - return fyne.NewSquareSize(theme.IconInlineSize()) + return fyne.NewSquareSize(a.Theme().Size(theme.SizeNameInlineIcon)) } // Start the activity indicator animation @@ -62,10 +62,11 @@ func (a *Activity) Stop() { func (a *Activity) CreateRenderer() fyne.WidgetRenderer { dots := make([]fyne.CanvasObject, 3) + v := fyne.CurrentApp().Settings().ThemeVariant() for i := range dots { - dots[i] = canvas.NewCircle(theme.ForegroundColor()) + dots[i] = canvas.NewCircle(a.Theme().Color(theme.ColorNameForeground, v)) } - r := &activityRenderer{dots: dots} + r := &activityRenderer{dots: dots, parent: a} r.anim = &fyne.Animation{ Duration: time.Second * 2, RepeatCount: fyne.AnimationRepeatForever, @@ -82,8 +83,9 @@ func (a *Activity) CreateRenderer() fyne.WidgetRenderer { var _ fyne.WidgetRenderer = (*activityRenderer)(nil) type activityRenderer struct { - anim *fyne.Animation - dots []fyne.CanvasObject + anim *fyne.Animation + dots []fyne.CanvasObject + parent *Activity bound fyne.Size maxCol color.NRGBA @@ -100,7 +102,7 @@ func (a *activityRenderer) Layout(size fyne.Size) { } func (a *activityRenderer) MinSize() fyne.Size { - return fyne.NewSquareSize(theme.IconInlineSize()) + return fyne.NewSquareSize(a.parent.Theme().Size(theme.SizeNameInlineIcon)) } func (a *activityRenderer) Objects() []fyne.CanvasObject { @@ -159,6 +161,7 @@ func (a *activityRenderer) stop() { } func (a *activityRenderer) updateColor() { - rr, gg, bb, aa := theme.ForegroundColor().RGBA() + v := fyne.CurrentApp().Settings().ThemeVariant() + rr, gg, bb, aa := a.parent.Theme().Color(theme.ColorNameForeground, v).RGBA() a.maxCol = color.NRGBA{R: uint8(rr >> 8), G: uint8(gg >> 8), B: uint8(bb >> 8), A: uint8(aa >> 8)} } diff --git a/widget/button.go b/widget/button.go index 2a501e3bf8..6304a969d1 100644 --- a/widget/button.go +++ b/widget/button.go @@ -221,7 +221,7 @@ func (b *Button) applyButtonTheme(th fyne.Theme) { } func (b *Button) buttonColor() color.Color { - th := b.Theme() + th := b.themeWithLock() v := fyne.CurrentApp().Settings().ThemeVariant() switch { @@ -368,11 +368,11 @@ func (r *buttonRenderer) Refresh() { r.label.inset = fyne.NewSquareSize(th.Size(theme.SizeNameInnerPadding)) r.button.propertyLock.RLock() - defer r.button.propertyLock.RUnlock() - r.label.Segments[0].(*TextSegment).Text = r.button.Text r.updateIconAndText() r.applyTheme() + r.button.propertyLock.RUnlock() + r.background.Refresh() r.Layout(r.button.Size()) canvas.Refresh(r.button.super()) @@ -381,7 +381,7 @@ func (r *buttonRenderer) Refresh() { // applyTheme updates this button to match the current theme // must be called with the button propertyLock held func (r *buttonRenderer) applyTheme() { - r.button.applyButtonTheme(r.button.Theme()) + r.button.applyButtonTheme(r.button.themeWithLock()) r.label.Segments[0].(*TextSegment).Style.ColorName = theme.ColorNameForeground switch { case r.button.disabled.Load(): diff --git a/widget/card.go b/widget/card.go index 64d58b4486..9c000227bf 100644 --- a/widget/card.go +++ b/widget/card.go @@ -34,8 +34,10 @@ func NewCard(title, subtitle string, content fyne.CanvasObject) *Card { // CreateRenderer is a private method to Fyne which links this widget to its renderer func (c *Card) CreateRenderer() fyne.WidgetRenderer { c.ExtendBaseWidget(c) + th := c.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() - header := canvas.NewText(c.Title, theme.ForegroundColor()) + header := canvas.NewText(c.Title, th.Color(theme.ColorNameForeground, v)) header.TextStyle.Bold = true subHeader := canvas.NewText(c.Subtitle, header.Color) @@ -100,7 +102,7 @@ const ( // Layout the components of the card container. func (c *cardRenderer) Layout(size fyne.Size) { - padding := theme.Padding() + padding := c.card.Theme().Size(theme.SizeNamePadding) pos := fyne.NewSquareOffsetPos(padding / 2) size = size.Subtract(fyne.NewSquareSize(padding)) c.LayoutShadow(size, pos) @@ -157,7 +159,7 @@ func (c *cardRenderer) MinSize() fyne.Size { hasImage := c.card.Image != nil hasContent := c.card.Content != nil - padding := theme.Padding() + padding := c.card.Theme().Size(theme.SizeNamePadding) if !hasHeader && !hasSubHeader && !hasContent { // just image, or nothing if c.card.Image == nil { return fyne.NewSize(padding, padding) // empty, just space for border @@ -220,13 +222,16 @@ func (c *cardRenderer) Refresh() { // applyTheme updates this button to match the current theme func (c *cardRenderer) applyTheme() { + th := c.card.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + if c.header != nil { - c.header.TextSize = theme.TextHeadingSize() - c.header.Color = theme.ForegroundColor() + c.header.TextSize = th.Size(theme.SizeNameHeadingText) + c.header.Color = th.Color(theme.ColorNameForeground, v) } if c.subHeader != nil { - c.subHeader.TextSize = theme.TextSize() - c.subHeader.Color = theme.ForegroundColor() + c.subHeader.TextSize = th.Size(theme.SizeNameText) + c.subHeader.Color = th.Color(theme.ColorNameForeground, v) } if c.card.Content != nil { c.card.Content.Refresh() diff --git a/widget/check.go b/widget/check.go index 784f8a59e9..b03bfeedae 100644 --- a/widget/check.go +++ b/widget/check.go @@ -157,16 +157,19 @@ func (c *Check) MinSize() fyne.Size { // CreateRenderer is a private method to Fyne which links this widget to its renderer func (c *Check) CreateRenderer() fyne.WidgetRenderer { + th := c.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + c.ExtendBaseWidget(c) - bg := canvas.NewImageFromResource(theme.CheckButtonFillIcon()) - icon := canvas.NewImageFromResource(theme.CheckButtonIcon()) + bg := canvas.NewImageFromResource(th.Icon(theme.IconNameCheckButtonFill)) + icon := canvas.NewImageFromResource(th.Icon(theme.IconNameCheckButton)) c.propertyLock.RLock() defer c.propertyLock.RUnlock() - text := canvas.NewText(c.Text, theme.ForegroundColor()) + text := canvas.NewText(c.Text, th.Color(theme.ColorNameForeground, v)) text.Alignment = fyne.TextAlignLeading - focusIndicator := canvas.NewCircle(theme.BackgroundColor()) + focusIndicator := canvas.NewCircle(th.Color(theme.ColorNameBackground, v)) r := &checkRenderer{ widget.NewBaseRenderer([]fyne.CanvasObject{focusIndicator, bg, icon, text}), bg, @@ -175,10 +178,10 @@ func (c *Check) CreateRenderer() fyne.WidgetRenderer { focusIndicator, c, } - r.applyTheme() + r.applyTheme(th, v) r.updateLabel() - r.updateResource() - r.updateFocusIndicator() + r.updateResource(th) + r.updateFocusIndicator(th, v) return r } @@ -278,13 +281,16 @@ type checkRenderer struct { // MinSize calculates the minimum size of a check. // This is based on the contained text, the check icon and a standard amount of padding added. func (c *checkRenderer) MinSize() fyne.Size { - pad4 := theme.InnerPadding() * 2 - min := c.label.MinSize().Add(fyne.NewSize(theme.IconInlineSize()+pad4, pad4)) + th := c.check.Theme() + + pad4 := th.Size(theme.SizeNameInnerPadding) * 2 + min := c.label.MinSize().Add(fyne.NewSize(th.Size(theme.SizeNameInlineIcon)+pad4, pad4)) + c.check.propertyLock.RLock() text := c.check.Text c.check.propertyLock.RUnlock() if text != "" { - min.Add(fyne.NewSize(theme.Padding(), 0)) + min.Add(fyne.NewSize(th.Size(theme.SizeNamePadding), 0)) } return min @@ -292,9 +298,10 @@ func (c *checkRenderer) MinSize() fyne.Size { // Layout the components of the check widget func (c *checkRenderer) Layout(size fyne.Size) { - innerPadding := theme.InnerPadding() - borderSize := theme.InputBorderSize() - iconInlineSize := theme.IconInlineSize() + th := c.check.Theme() + innerPadding := th.Size(theme.SizeNameInnerPadding) + borderSize := th.Size(theme.SizeNameInputBorder) + iconInlineSize := th.Size(theme.SizeNameInlineIcon) focusIndicatorSize := fyne.NewSquareSize(iconInlineSize + innerPadding) c.focusIndicator.Resize(focusIndicatorSize) @@ -314,20 +321,23 @@ func (c *checkRenderer) Layout(size fyne.Size) { } // applyTheme updates this Check to the current theme -func (c *checkRenderer) applyTheme() { - c.label.Color = theme.ForegroundColor() - c.label.TextSize = theme.TextSize() +func (c *checkRenderer) applyTheme(th fyne.Theme, v fyne.ThemeVariant) { + c.label.Color = th.Color(theme.ColorNameForeground, v) + c.label.TextSize = th.Size(theme.SizeNameText) if c.check.disabled.Load() { - c.label.Color = theme.DisabledColor() + c.label.Color = th.Color(theme.ColorNameDisabled, v) } } func (c *checkRenderer) Refresh() { + th := c.check.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + c.check.propertyLock.RLock() - c.applyTheme() + c.applyTheme(th, v) c.updateLabel() - c.updateResource() - c.updateFocusIndicator() + c.updateResource(th) + c.updateFocusIndicator(th, v) c.check.propertyLock.RUnlock() canvas.Refresh(c.check.super()) } @@ -338,14 +348,14 @@ func (c *checkRenderer) updateLabel() { } // must be called while holding c.check.propertyLock for reading -func (c *checkRenderer) updateResource() { - res := theme.NewThemedResource(theme.CheckButtonIcon()) +func (c *checkRenderer) updateResource(th fyne.Theme) { + res := theme.NewThemedResource(th.Icon(theme.IconNameCheckButton)) res.ColorName = theme.ColorNameInputBorder - bgRes := theme.NewThemedResource(theme.CheckButtonFillIcon()) + bgRes := theme.NewThemedResource(th.Icon(theme.IconNameCheckButtonFill)) bgRes.ColorName = theme.ColorNameInputBackground if c.check.Checked { - res = theme.NewThemedResource(theme.CheckButtonCheckedIcon()) + res = theme.NewThemedResource(th.Icon(theme.IconNameCheckButtonChecked)) res.ColorName = theme.ColorNamePrimary bgRes.ColorName = theme.ColorNameBackground } @@ -361,13 +371,13 @@ func (c *checkRenderer) updateResource() { } // must be called while holding c.check.propertyLock for reading -func (c *checkRenderer) updateFocusIndicator() { +func (c *checkRenderer) updateFocusIndicator(th fyne.Theme, v fyne.ThemeVariant) { if c.check.disabled.Load() { c.focusIndicator.FillColor = color.Transparent } else if c.check.focused { - c.focusIndicator.FillColor = theme.FocusColor() + c.focusIndicator.FillColor = th.Color(theme.ColorNameFocus, v) } else if c.check.hovered { - c.focusIndicator.FillColor = theme.HoverColor() + c.focusIndicator.FillColor = th.Color(theme.ColorNameHover, v) } else { c.focusIndicator.FillColor = color.Transparent } diff --git a/widget/check_internal_test.go b/widget/check_internal_test.go index 6e821ea390..9eec2a0984 100644 --- a/widget/check_internal_test.go +++ b/widget/check_internal_test.go @@ -273,12 +273,14 @@ func TestCheck_Disabled(t *testing.T) { func TestCheckRenderer_ApplyTheme(t *testing.T) { check := &Check{} + v := fyne.CurrentApp().Settings().ThemeVariant() render := test.WidgetRenderer(check).(*checkRenderer) textSize := render.label.TextSize customTextSize := textSize test.WithTestTheme(t, func() { - render.applyTheme() + th := test.NewTheme() + render.applyTheme(th, v) customTextSize = render.label.TextSize }) diff --git a/widget/entry.go b/widget/entry.go index 81be55d5d6..2b71e4dbc4 100644 --- a/widget/entry.go +++ b/widget/entry.go @@ -167,18 +167,20 @@ func (e *Entry) Bind(data binding.String) { // // Implements: fyne.Widget func (e *Entry) CreateRenderer() fyne.WidgetRenderer { + th := e.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() e.ExtendBaseWidget(e) // initialise e.textProvider() e.placeholderProvider() - box := canvas.NewRectangle(theme.InputBackgroundColor()) - box.CornerRadius = theme.InputRadiusSize() + box := canvas.NewRectangle(th.Color(theme.ColorNameInputBackground, v)) + box.CornerRadius = th.Size(theme.SizeNameInputRadius) border := canvas.NewRectangle(color.Transparent) - border.StrokeWidth = theme.InputBorderSize() - border.StrokeColor = theme.InputBorderColor() - border.CornerRadius = theme.InputRadiusSize() + border.StrokeWidth = th.Size(theme.SizeNameInputBorder) + border.StrokeColor = th.Color(theme.ColorNameInputBorder, v) + border.CornerRadius = th.Size(theme.SizeNameInputRadius) cursor := canvas.NewRectangle(color.Transparent) cursor.Hide() @@ -280,7 +282,7 @@ func (e *Entry) DragEnd() { // // Implements: fyne.Draggable func (e *Entry) Dragged(d *fyne.DragEvent) { - pos := d.Position.Subtract(e.scroll.Offset).Add(fyne.NewPos(0, theme.InputBorderSize())) + pos := d.Position.Subtract(e.scroll.Offset).Add(fyne.NewPos(0, e.Theme().Size(theme.SizeNameInputBorder))) if !e.selecting { startPos := pos.Subtract(d.Dragged) e.selectRow, e.selectColumn = e.getRowCol(startPos) @@ -407,12 +409,14 @@ func (e *Entry) MinSize() fyne.Size { e.ExtendBaseWidget(e) + th := e.Theme() + iconSpace := th.Size(theme.SizeNameInlineIcon) + th.Size(theme.SizeNameLineSpacing) min := e.BaseWidget.MinSize() if e.ActionItem != nil { - min = min.Add(fyne.NewSize(theme.IconInlineSize()+theme.LineSpacing(), 0)) + min = min.Add(fyne.NewSize(iconSpace, 0)) } if e.Validator != nil { - min = min.Add(fyne.NewSize(theme.IconInlineSize()+theme.LineSpacing(), 0)) + min = min.Add(fyne.NewSize(iconSpace, 0)) } e.minCache.Store(min) @@ -518,6 +522,8 @@ func (e *Entry) SetMinRowsVisible(count int) { // SetPlaceHolder sets the text that will be displayed if the entry is otherwise empty func (e *Entry) SetPlaceHolder(text string) { + e.Theme() // setup theme cache before locking + e.propertyLock.Lock() e.PlaceHolder = text e.propertyLock.Unlock() @@ -534,8 +540,8 @@ func (e *Entry) SetText(text string) { } func (e *Entry) setText(text string, fromBinding bool) { + e.Theme() // setup theme cache before locking e.updateTextAndRefresh(text, fromBinding) - e.updateCursorAndSelection() e.propertyLock.Lock() @@ -946,11 +952,15 @@ func (e *Entry) copyToClipboard(clipboard fyne.Clipboard) { } func (e *Entry) cursorColAt(text []rune, pos fyne.Position) int { + th := e.themeWithLock() + textSize := th.Size(theme.SizeNameText) + innerPad := th.Size(theme.SizeNameInnerPadding) + for i := 0; i < len(text); i++ { str := string(text[0:i]) - wid := fyne.MeasureText(str, theme.TextSize(), e.TextStyle).Width - charWid := fyne.MeasureText(string(text[i]), theme.TextSize(), e.TextStyle).Width - if pos.X < theme.InnerPadding()+wid+(charWid/2) { + wid := fyne.MeasureText(str, textSize, e.TextStyle).Width + charWid := fyne.MeasureText(string(text[i]), textSize, e.TextStyle).Width + if pos.X < innerPad+wid+(charWid/2) { return i } } @@ -1009,12 +1019,13 @@ func (e *Entry) eraseSelection() { } func (e *Entry) getRowCol(p fyne.Position) (int, int) { - textSize := e.Theme().Size(theme.SizeNameText) + th := e.Theme() + textSize := th.Size(theme.SizeNameText) e.propertyLock.RLock() defer e.propertyLock.RUnlock() rowHeight := e.textProvider().charMinSize(e.Password, e.TextStyle, textSize).Height - row := int(math.Floor(float64(p.Y+e.scroll.Offset.Y-theme.LineSpacing()) / float64(rowHeight))) + row := int(math.Floor(float64(p.Y+e.scroll.Offset.Y-th.Size(theme.SizeNameLineSpacing)) / float64(rowHeight))) col := 0 if row < 0 { row = 0 @@ -1070,7 +1081,7 @@ func (e *Entry) placeholderProvider() *RichText { Text: e.PlaceHolder, }) text.ExtendBaseWidget(text) - text.inset = fyne.NewSize(0, theme.InputBorderSize()) + text.inset = fyne.NewSize(0, e.themeWithLock().Size(theme.SizeNameInputBorder)) e.placeholder = text return e.placeholder } @@ -1372,7 +1383,7 @@ func (e *Entry) textProvider() *RichText { text := NewRichTextWithText(e.Text) text.ExtendBaseWidget(text) - text.inset = fyne.NewSize(0, theme.InputBorderSize()) + text.inset = fyne.NewSize(0, e.themeWithLock().Size(theme.SizeNameInputBorder)) e.text = text return e.text } @@ -1572,17 +1583,19 @@ func (r *entryRenderer) Destroy() { } func (r *entryRenderer) trailingInset() float32 { + th := r.entry.Theme() xInset := float32(0) + iconSpace := th.Size(theme.SizeNameInlineIcon) + th.Size(theme.SizeNameLineSpacing) if r.entry.ActionItem != nil { - xInset = theme.IconInlineSize() + theme.LineSpacing() + xInset = iconSpace } if r.entry.Validator != nil { if r.entry.ActionItem == nil { - xInset = theme.IconInlineSize() + theme.LineSpacing() + xInset = iconSpace } else { - xInset += theme.IconInlineSize() + theme.LineSpacing() + xInset += iconSpace } } @@ -1590,39 +1603,46 @@ func (r *entryRenderer) trailingInset() float32 { } func (r *entryRenderer) Layout(size fyne.Size) { + th := r.entry.Theme() + borderSize := th.Size(theme.SizeNameInputBorder) + iconSize := th.Size(theme.SizeNameInlineIcon) + innerPad := th.Size(theme.SizeNameInnerPadding) + inputBorder := th.Size(theme.SizeNameInputBorder) + lineSpace := th.Size(theme.SizeNameLineSpacing) + // 0.5 is removed so on low DPI it rounds down on the trailing edge - r.border.Resize(fyne.NewSize(size.Width-theme.InputBorderSize()-.5, size.Height-theme.InputBorderSize()-.5)) - r.border.StrokeWidth = theme.InputBorderSize() - r.border.Move(fyne.NewSquareOffsetPos(theme.InputBorderSize() / 2)) - r.box.Resize(size.Subtract(fyne.NewSquareSize(theme.InputBorderSize() * 2))) - r.box.Move(fyne.NewSquareOffsetPos(theme.InputBorderSize())) + r.border.Resize(fyne.NewSize(size.Width-borderSize-.5, size.Height-borderSize-.5)) + r.border.StrokeWidth = borderSize + r.border.Move(fyne.NewSquareOffsetPos(borderSize / 2)) + r.box.Resize(size.Subtract(fyne.NewSquareSize(borderSize * 2))) + r.box.Move(fyne.NewSquareOffsetPos(borderSize)) actionIconSize := fyne.NewSize(0, 0) if r.entry.ActionItem != nil { - actionIconSize = fyne.NewSquareSize(theme.IconInlineSize()) + actionIconSize = fyne.NewSquareSize(iconSize) r.entry.ActionItem.Resize(actionIconSize) - r.entry.ActionItem.Move(fyne.NewPos(size.Width-actionIconSize.Width-theme.InnerPadding(), theme.InnerPadding())) + r.entry.ActionItem.Move(fyne.NewPos(size.Width-actionIconSize.Width-innerPad, innerPad)) } validatorIconSize := fyne.NewSize(0, 0) if r.entry.Validator != nil { - validatorIconSize = fyne.NewSquareSize(theme.IconInlineSize()) + validatorIconSize = fyne.NewSquareSize(iconSize) r.ensureValidationSetup() r.entry.validationStatus.Resize(validatorIconSize) if r.entry.ActionItem == nil { - r.entry.validationStatus.Move(fyne.NewPos(size.Width-validatorIconSize.Width-theme.InnerPadding(), theme.InnerPadding())) + r.entry.validationStatus.Move(fyne.NewPos(size.Width-validatorIconSize.Width-innerPad, innerPad)) } else { - r.entry.validationStatus.Move(fyne.NewPos(size.Width-validatorIconSize.Width-actionIconSize.Width-theme.InnerPadding()-theme.LineSpacing(), theme.InnerPadding())) + r.entry.validationStatus.Move(fyne.NewPos(size.Width-validatorIconSize.Width-actionIconSize.Width-innerPad-lineSpace, innerPad)) } } - r.entry.textProvider().inset = fyne.NewSize(0, theme.InputBorderSize()) - r.entry.placeholderProvider().inset = fyne.NewSize(0, theme.InputBorderSize()) - entrySize := size.Subtract(fyne.NewSize(r.trailingInset(), theme.InputBorderSize()*2)) - entryPos := fyne.NewPos(0, theme.InputBorderSize()) + r.entry.textProvider().inset = fyne.NewSize(0, inputBorder) + r.entry.placeholderProvider().inset = fyne.NewSize(0, inputBorder) + entrySize := size.Subtract(fyne.NewSize(r.trailingInset(), inputBorder*2)) + entryPos := fyne.NewPos(0, inputBorder) r.entry.propertyLock.Lock() textPos := r.entry.textPosFromRowCol(r.entry.CursorRow, r.entry.CursorColumn) @@ -1658,7 +1678,7 @@ func (r *entryRenderer) MinSize() fyne.Size { rend.(*entryContentRenderer).updateScrollDirections() } if r.scroll.Direction == widget.ScrollNone { - return r.entry.content.MinSize().Add(fyne.NewSize(0, theme.InputBorderSize()*2)) + return r.entry.content.MinSize().Add(fyne.NewSize(0, r.entry.Theme().Size(theme.SizeNameInputBorder)*2)) } innerPadding := r.entry.Theme().Size(theme.SizeNameInnerPadding) @@ -1699,12 +1719,16 @@ func (r *entryRenderer) Refresh() { r.entry.text.Refresh() r.entry.placeholder.Refresh() + th := r.entry.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + inputBorder := th.Size(theme.SizeNameInputBorder) + // correct our scroll wrappers if the wrap mode changed - entrySize := r.entry.size.Load().Subtract(fyne.NewSize(r.trailingInset(), theme.InputBorderSize()*2)) + entrySize := r.entry.size.Load().Subtract(fyne.NewSize(r.trailingInset(), inputBorder*2)) if wrapping == fyne.TextWrapOff && scroll == widget.ScrollNone && r.scroll.Content != nil { r.scroll.Hide() r.scroll.Content = nil - content.Move(fyne.NewPos(0, theme.InputBorderSize())) + content.Move(fyne.NewPos(0, inputBorder)) content.Resize(entrySize) for i, o := range r.objects { @@ -1716,7 +1740,7 @@ func (r *entryRenderer) Refresh() { } else if (wrapping != fyne.TextWrapOff || scroll != widget.ScrollNone) && r.scroll.Content == nil { r.scroll.Content = content content.Move(fyne.NewPos(0, 0)) - r.scroll.Move(fyne.NewPos(0, theme.InputBorderSize())) + r.scroll.Move(fyne.NewPos(0, inputBorder)) r.scroll.Resize(entrySize) r.scroll.Show() @@ -1729,16 +1753,16 @@ func (r *entryRenderer) Refresh() { } r.entry.updateCursorAndSelection() - r.box.FillColor = theme.InputBackgroundColor() - r.box.CornerRadius = theme.InputRadiusSize() - r.border.CornerRadius = theme.InputRadiusSize() + r.box.FillColor = th.Color(theme.ColorNameInputBackground, v) + r.box.CornerRadius = th.Size(theme.SizeNameInputRadius) + r.border.CornerRadius = r.box.CornerRadius if focusedAppearance { - r.border.StrokeColor = theme.PrimaryColor() + r.border.StrokeColor = th.Color(theme.ColorNamePrimary, v) } else { if r.entry.Disabled() { - r.border.StrokeColor = theme.DisabledColor() + r.border.StrokeColor = th.Color(theme.ColorNameDisabled, v) } else { - r.border.StrokeColor = theme.InputBorderColor() + r.border.StrokeColor = th.Color(theme.ColorNameInputBorder, v) } } if r.entry.ActionItem != nil { @@ -1747,7 +1771,7 @@ func (r *entryRenderer) Refresh() { if r.entry.Validator != nil { if !r.entry.focused && !r.entry.Disabled() && r.entry.dirty && r.entry.validationError != nil { - r.border.StrokeColor = theme.ErrorColor() + r.border.StrokeColor = th.Color(theme.ColorNameError, v) } r.ensureValidationSetup() r.entry.validationStatus.Refresh() @@ -1838,6 +1862,7 @@ func (r *entryContentRenderer) Layout(size fyne.Size) { } func (r *entryContentRenderer) MinSize() fyne.Size { + r.content.Theme() // setup theme cache before locking minSize := r.content.entry.placeholderProvider().MinSize() if r.content.entry.textProvider().len() > 0 { @@ -1886,7 +1911,9 @@ func (r *entryContentRenderer) Refresh() { } r.moveCursor() - selectionColor := theme.SelectionColor() + th := r.content.entry.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + selectionColor := th.Color(theme.ColorNameSelection, v) for _, selection := range selections { rect := selection.(*canvas.Rectangle) rect.Hidden = !focused @@ -1904,7 +1931,9 @@ func (r *entryContentRenderer) Refresh() { // require movement and resizing. The existing solution creates a new rectangle and then moves/resizes // all rectangles to comply with the occurrence order as stated above. func (r *entryContentRenderer) buildSelection() { - textSize := r.content.entry.Theme().Size(theme.SizeNameText) + th := r.content.entry.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + textSize := th.Size(theme.SizeNameText) r.content.entry.propertyLock.RLock() cursorRow, cursorCol := r.content.entry.CursorRow, r.content.entry.CursorColumn @@ -1922,7 +1951,6 @@ func (r *entryContentRenderer) buildSelection() { } provider := r.content.entry.textProvider() - th := theme.CurrentForWidget(r.content.entry) innerPad := th.Size(theme.SizeNameInnerPadding) // Convert column, row into x,y getCoordinates := func(column int, row int) (float32, float32) { @@ -1961,7 +1989,7 @@ func (r *entryContentRenderer) buildSelection() { // build a rectangle for each row and add it to r.selection for i := 0; i < rowCount; i++ { if len(r.selection) <= i { - box := canvas.NewRectangle(theme.SelectionColor()) + box := canvas.NewRectangle(th.Color(theme.ColorNameSelection, v)) r.selection = append(r.selection, box) } @@ -1986,9 +2014,12 @@ func (r *entryContentRenderer) buildSelection() { } func (r *entryContentRenderer) ensureCursorVisible() { - letter := fyne.MeasureText("e", theme.TextSize(), r.content.entry.TextStyle) - padX := letter.Width*2 + theme.LineSpacing() - padY := letter.Height - theme.LineSpacing() + th := r.content.entry.Theme() + lineSpace := th.Size(theme.SizeNameLineSpacing) + + letter := fyne.MeasureText("e", th.Size(theme.SizeNameText), r.content.entry.TextStyle) + padX := letter.Width*2 + lineSpace + padY := letter.Height - lineSpace cx := r.cursor.Position().X cy := r.cursor.Position().Y cx1 := cx - padX @@ -2024,10 +2055,10 @@ func (r *entryContentRenderer) moveCursor() { // build r.selection[] if the user has made a selection r.buildSelection() - textSize := r.content.entry.Theme().Size(theme.SizeNameText) + th := r.content.entry.Theme() + textSize := th.Size(theme.SizeNameText) r.content.entry.propertyLock.RLock() provider := r.content.entry.textProvider() - th := theme.CurrentForWidget(r.content.entry) innerPad := th.Size(theme.SizeNameInnerPadding) inputBorder := th.Size(theme.SizeNameInputBorder) size := provider.lineSizeToColumn(r.content.entry.CursorColumn, r.content.entry.CursorRow, textSize, innerPad) diff --git a/widget/entry_internal_test.go b/widget/entry_internal_test.go index 6f9139f26a..8ff5806e64 100644 --- a/widget/entry_internal_test.go +++ b/widget/entry_internal_test.go @@ -279,6 +279,7 @@ func TestEntry_EraseSelection(t *testing.T) { keyPress(&fyne.KeyEvent{Name: fyne.KeyRight}) keyPress(&fyne.KeyEvent{Name: fyne.KeyRight}) + _ = e.Theme() e.eraseSelection() e.updateText(e.textProvider().String(), false) assert.Equal(t, "Testing\nTeng\nTesting", e.Text) @@ -296,6 +297,7 @@ func TestEntry_CallbackLocking(t *testing.T) { e.propertyLock.Unlock() } + _ = e.Theme() test.Type(e, "abc123") e.selectAll() e.TypedKey(&fyne.KeyEvent{Name: fyne.KeyBackspace}) @@ -401,6 +403,7 @@ func TestEntry_Tab(t *testing.T) { e.TextStyle.Monospace = true e.SetText("a\n\tb\nc") + _ = e.Theme() r := cache.Renderer(e.textProvider()).(*textRenderer) assert.Equal(t, 3, len(r.Objects())) assert.Equal(t, "a", r.Objects()[0].(*canvas.Text).Text) diff --git a/widget/entry_password.go b/widget/entry_password.go index 6d65e0311e..912d11b594 100644 --- a/widget/entry_password.go +++ b/widget/entry_password.go @@ -19,8 +19,9 @@ type passwordRevealer struct { } func newPasswordRevealer(e *Entry) *passwordRevealer { + th := e.Theme() pr := &passwordRevealer{ - icon: canvas.NewImageFromResource(theme.VisibilityOffIcon()), + icon: canvas.NewImageFromResource(th.Icon(theme.IconNameVisibilityOff)), entry: e, } pr.ExtendBaseWidget(pr) @@ -59,21 +60,24 @@ type passwordRevealerRenderer struct { } func (r *passwordRevealerRenderer) Layout(size fyne.Size) { - r.icon.Resize(fyne.NewSize(theme.IconInlineSize(), theme.IconInlineSize())) - r.icon.Move(fyne.NewPos((size.Width-theme.IconInlineSize())/2, (size.Height-theme.IconInlineSize())/2)) + iconSize := r.entry.Theme().Size(theme.SizeNameInlineIcon) + r.icon.Resize(fyne.NewSquareSize(iconSize)) + r.icon.Move(fyne.NewPos((size.Width-iconSize)/2, (size.Height-iconSize)/2)) } func (r *passwordRevealerRenderer) MinSize() fyne.Size { - return fyne.NewSize(theme.IconInlineSize(), theme.IconInlineSize()) + iconSize := r.entry.Theme().Size(theme.SizeNameInlineIcon) + return fyne.NewSquareSize(iconSize) } func (r *passwordRevealerRenderer) Refresh() { + th := r.entry.Theme() r.entry.propertyLock.RLock() defer r.entry.propertyLock.RUnlock() if !r.entry.Password { - r.icon.Resource = theme.VisibilityIcon() + r.icon.Resource = th.Icon(theme.IconNameVisibility) } else { - r.icon.Resource = theme.VisibilityOffIcon() + r.icon.Resource = th.Icon(theme.IconNameVisibilityOff) } if r.entry.disabled.Load() { diff --git a/widget/entry_validation.go b/widget/entry_validation.go index deb46592a6..61c98baed9 100644 --- a/widget/entry_validation.go +++ b/widget/entry_validation.go @@ -82,15 +82,18 @@ type validationStatusRenderer struct { } func (r *validationStatusRenderer) Layout(size fyne.Size) { - r.icon.Resize(fyne.NewSize(theme.IconInlineSize(), theme.IconInlineSize())) - r.icon.Move(fyne.NewPos((size.Width-theme.IconInlineSize())/2, (size.Height-theme.IconInlineSize())/2)) + iconSize := r.entry.Theme().Size(theme.SizeNameInlineIcon) + r.icon.Resize(fyne.NewSquareSize(iconSize)) + r.icon.Move(fyne.NewPos((size.Width-iconSize)/2, (size.Height-iconSize)/2)) } func (r *validationStatusRenderer) MinSize() fyne.Size { - return fyne.NewSize(theme.IconInlineSize(), theme.IconInlineSize()) + iconSize := r.entry.Theme().Size(theme.SizeNameInlineIcon) + return fyne.NewSquareSize(iconSize) } func (r *validationStatusRenderer) Refresh() { + th := r.entry.Theme() r.entry.propertyLock.RLock() defer r.entry.propertyLock.RUnlock() if r.entry.disabled.Load() { @@ -99,10 +102,10 @@ func (r *validationStatusRenderer) Refresh() { } if r.entry.validationError == nil && r.entry.Text != "" { - r.icon.Resource = theme.ConfirmIcon() + r.icon.Resource = th.Icon(theme.IconNameConfirm) r.icon.Show() } else if r.entry.validationError != nil && !r.entry.focused && r.entry.dirty { - r.icon.Resource = theme.NewErrorThemedResource(theme.ErrorIcon()) + r.icon.Resource = theme.NewErrorThemedResource(th.Icon(theme.IconNameError)) r.icon.Show() } else { r.icon.Hide() diff --git a/widget/fileicon.go b/widget/fileicon.go index a2f0b86b51..0d885b3e6c 100644 --- a/widget/fileicon.go +++ b/widget/fileicon.go @@ -45,7 +45,7 @@ func (i *FileIcon) SetURI(uri fyne.URI) { func (i *FileIcon) setURI(uri fyne.URI) { if uri == nil { - i.resource = theme.FileIcon() + i.resource = i.themeWithLock().Icon(theme.IconNameFile) return } @@ -62,6 +62,9 @@ func (i *FileIcon) MinSize() fyne.Size { // CreateRenderer is a private method to Fyne which links this widget to its renderer func (i *FileIcon) CreateRenderer() fyne.WidgetRenderer { + th := i.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + i.ExtendBaseWidget(i) i.propertyLock.Lock() i.setURI(i.URI) @@ -71,13 +74,13 @@ func (i *FileIcon) CreateRenderer() fyne.WidgetRenderer { defer i.propertyLock.RUnlock() // TODO remove background when `SetSelected` is gone. - background := canvas.NewRectangle(theme.SelectionColor()) + background := canvas.NewRectangle(th.Color(theme.ColorNameSelection, v)) background.Hide() s := &fileIconRenderer{file: i, background: background} s.img = canvas.NewImageFromResource(s.file.resource) s.img.FillMode = canvas.ImageFillContain - s.ext = canvas.NewText(s.file.extension, theme.BackgroundColor()) + s.ext = canvas.NewText(s.file.extension, th.Color(theme.ColorNameBackground, v)) s.ext.Alignment = fyne.TextAlignCenter s.SetObjects([]fyne.CanvasObject{s.background, s.img, s.ext}) @@ -101,20 +104,21 @@ func (i *FileIcon) lookupIcon(uri fyne.URI) fyne.Resource { return theme.FolderIcon() } + th := i.themeWithLock() mainMimeType, _ := mime.Split(uri.MimeType()) switch mainMimeType { case "application": - return theme.FileApplicationIcon() + return th.Icon(theme.IconNameFileApplication) case "audio": - return theme.FileAudioIcon() + return th.Icon(theme.IconNameFileAudio) case "image": - return theme.FileImageIcon() + return th.Icon(theme.IconNameFileImage) case "text": - return theme.FileTextIcon() + return th.Icon(theme.IconNameFileText) case "video": - return theme.FileVideoIcon() + return th.Icon(theme.IconNameFileVideo) default: - return theme.FileIcon() + return th.Icon(theme.IconNameFile) } } @@ -143,7 +147,8 @@ type fileIconRenderer struct { } func (s *fileIconRenderer) MinSize() fyne.Size { - return fyne.NewSquareSize(theme.IconInlineSize()) + th := s.file.Theme() + return fyne.NewSquareSize(th.Size(theme.SizeNameInlineIcon)) } func (s *fileIconRenderer) Layout(size fyne.Size) { @@ -170,6 +175,9 @@ func (s *fileIconRenderer) Layout(size fyne.Size) { } func (s *fileIconRenderer) Refresh() { + th := s.file.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + s.file.propertyLock.Lock() s.file.setURI(s.file.URI) s.file.propertyLock.Unlock() @@ -181,13 +189,13 @@ func (s *fileIconRenderer) Refresh() { if s.file.Selected { s.background.Show() - s.ext.Color = theme.SelectionColor() + s.ext.Color = th.Color(theme.ColorNameSelection, v) if _, ok := s.img.Resource.(*theme.InvertedThemedResource); !ok { s.img.Resource = theme.NewInvertedThemedResource(s.img.Resource) } } else { s.background.Hide() - s.ext.Color = theme.BackgroundColor() + s.ext.Color = th.Color(theme.ColorNameBackground, v) if res, ok := s.img.Resource.(*theme.InvertedThemedResource); ok { s.img.Resource = res.Original() } diff --git a/widget/form.go b/widget/form.go index d2bf8112c9..ac4349f280 100644 --- a/widget/form.go +++ b/widget/form.go @@ -155,12 +155,15 @@ func (f *Form) createInput(item *FormItem) fyne.CanvasObject { } } - text := canvas.NewText(item.HintText, theme.PlaceHolderColor()) - text.TextSize = theme.CaptionTextSize() + th := f.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + + text := canvas.NewText(item.HintText, th.Color(theme.ColorNamePlaceHolder, v)) + text.TextSize = th.Size(theme.SizeNameCaptionText) item.helperOutput = text f.updateHelperText(item) textContainer := &fyne.Container{Objects: []fyne.CanvasObject{text}} - return &fyne.Container{Layout: formItemLayout{}, Objects: []fyne.CanvasObject{item.Widget, textContainer}} + return &fyne.Container{Layout: formItemLayout{form: f}, Objects: []fyne.CanvasObject{item.Widget, textContainer}} } func (f *Form) itemWidgetHasValidator(w fyne.CanvasObject) bool { @@ -177,10 +180,12 @@ func (f *Form) itemWidgetHasValidator(w fyne.CanvasObject) bool { } func (f *Form) createLabel(text string) *canvas.Text { + th := f.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() return &canvas.Text{Text: text, Alignment: fyne.TextAlignTrailing, - Color: theme.ForegroundColor(), - TextSize: theme.TextSize(), + Color: th.Color(theme.ColorNameForeground, v), + TextSize: th.Size(theme.SizeNameText), TextStyle: fyne.TextStyle{Bold: true}} } @@ -305,6 +310,9 @@ func (f *Form) setValidationError(err error) { } func (f *Form) updateHelperText(item *FormItem) { + th := f.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + if item.helperOutput == nil { return // testing probably, either way not rendered yet } @@ -314,26 +322,29 @@ func (f *Form) updateHelperText(item *FormItem) { } if item.validationError == nil || showHintIfError { item.helperOutput.Text = item.HintText - item.helperOutput.Color = theme.PlaceHolderColor() + item.helperOutput.Color = th.Color(theme.ColorNamePlaceHolder, v) } else { item.helperOutput.Text = item.validationError.Error() - item.helperOutput.Color = theme.ErrorColor() + item.helperOutput.Color = th.Color(theme.ColorNameError, v) } item.helperOutput.Refresh() } func (f *Form) updateLabels() { + th := f.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + for i, item := range f.Items { l := f.itemGrid.Objects[i*2].(*canvas.Text) - l.TextSize = theme.TextSize() + l.TextSize = th.Size(theme.SizeNameText) if dis, ok := item.Widget.(fyne.Disableable); ok { if dis.Disabled() { - l.Color = theme.DisabledColor() + l.Color = th.Color(theme.ColorNameDisabled, v) } else { - l.Color = theme.ForegroundColor() + l.Color = th.Color(theme.ColorNameForeground, v) } } else { - l.Color = theme.ForegroundColor() + l.Color = th.Color(theme.ColorNameForeground, v) } l.Text = item.Text @@ -345,9 +356,9 @@ func (f *Form) updateLabels() { // CreateRenderer is a private method to Fyne which links this widget to its renderer func (f *Form) CreateRenderer() fyne.WidgetRenderer { f.ExtendBaseWidget(f) - - f.cancelButton = &Button{Icon: theme.CancelIcon(), OnTapped: f.OnCancel} - f.submitButton = &Button{Icon: theme.ConfirmIcon(), OnTapped: f.OnSubmit, Importance: HighImportance} + th := f.Theme() + f.cancelButton = &Button{Icon: th.Icon(theme.IconNameCancel), OnTapped: f.OnCancel} + f.submitButton = &Button{Icon: th.Icon(theme.IconNameConfirm), OnTapped: f.OnSubmit, Importance: HighImportance} buttons := &fyne.Container{Layout: layout.NewGridLayoutWithRows(1), Objects: []fyne.CanvasObject{f.cancelButton, f.submitButton}} f.buttonBox = &fyne.Container{Layout: layout.NewBorderLayout(nil, nil, nil, buttons), Objects: []fyne.CanvasObject{buttons}} f.validationError = errFormItemInitialState // set initial state error to guarantee next error (if triggers) is always different @@ -371,20 +382,24 @@ func NewForm(items ...*FormItem) *Form { return form } -type formItemLayout struct{} +type formItemLayout struct { + form *Form +} func (f formItemLayout) Layout(objs []fyne.CanvasObject, size fyne.Size) { + innerPad := f.form.Theme().Size(theme.SizeNameInnerPadding) itemHeight := objs[0].MinSize().Height objs[0].Resize(fyne.NewSize(size.Width, itemHeight)) - objs[1].Move(fyne.NewPos(theme.InnerPadding(), itemHeight+theme.InnerPadding()/2)) + objs[1].Move(fyne.NewPos(innerPad, itemHeight+innerPad/2)) objs[1].Resize(fyne.NewSize(size.Width, objs[1].MinSize().Width)) } func (f formItemLayout) MinSize(objs []fyne.CanvasObject) fyne.Size { + innerPad := f.form.Theme().Size(theme.SizeNameInnerPadding) min0 := objs[0].MinSize() min1 := objs[1].MinSize() minWidth := fyne.Max(min0.Width, min1.Width) - return fyne.NewSize(minWidth, min0.Height+min1.Height+theme.InnerPadding()) + return fyne.NewSize(minWidth, min0.Height+min1.Height+innerPad) } diff --git a/widget/hyperlink.go b/widget/hyperlink.go index 546a4559cb..ad069450af 100644 --- a/widget/hyperlink.go +++ b/widget/hyperlink.go @@ -118,26 +118,31 @@ func (hl *Hyperlink) MouseOut() { } func (hl *Hyperlink) focusWidth() float32 { - innerPad := theme.InnerPadding() - return fyne.Min(hl.size.Load().Width, hl.textSize.Width+innerPad+theme.Padding()*2) - innerPad + th := hl.themeWithLock() + + innerPad := th.Size(theme.SizeNameInnerPadding) + return fyne.Min(hl.size.Load().Width, hl.textSize.Width+innerPad+th.Size(theme.SizeNamePadding)*2) - innerPad } func (hl *Hyperlink) focusXPos() float32 { + innerPad := hl.themeWithLock().Size(theme.SizeNameInnerPadding) + switch hl.Alignment { case fyne.TextAlignLeading: - return theme.InnerPadding() / 2 + return innerPad / 2 case fyne.TextAlignCenter: return (hl.size.Load().Width - hl.focusWidth()) / 2 case fyne.TextAlignTrailing: - return (hl.size.Load().Width - hl.focusWidth()) - theme.InnerPadding()/2 + return (hl.size.Load().Width - hl.focusWidth()) - innerPad/2 default: return 0 // unreached } } func (hl *Hyperlink) isPosOverText(pos fyne.Position) bool { - innerPad := theme.InnerPadding() - pad := theme.Padding() + th := hl.Theme() + innerPad := th.Size(theme.SizeNameInnerPadding) + pad := th.Size(theme.SizeNamePadding) // If not rendered yet provider will be nil lineCount := float32(1) if hl.provider != nil { @@ -258,6 +263,8 @@ func (hl *Hyperlink) openURL() { } func (hl *Hyperlink) syncSegments() { + th := hl.Theme() + hl.propertyLock.RLock() defer hl.propertyLock.RUnlock() @@ -272,7 +279,7 @@ func (hl *Hyperlink) syncSegments() { }, Text: hl.Text, }} - hl.textSize = fyne.MeasureText(hl.Text, theme.TextSize(), hl.TextStyle) + hl.textSize = fyne.MeasureText(hl.Text, th.Size(theme.SizeNameText), hl.TextStyle) } var _ fyne.WidgetRenderer = (*hyperlinkRenderer)(nil) @@ -289,9 +296,10 @@ func (r *hyperlinkRenderer) Destroy() { } func (r *hyperlinkRenderer) Layout(s fyne.Size) { + th := r.hl.Theme() r.hl.propertyLock.RLock() textSize := r.hl.textSize - innerPad := r.hl.Theme().Size(theme.SizeNameInnerPadding) + innerPad := th.Size(theme.SizeNameInnerPadding) w := r.hl.focusWidth() xposFocus := r.hl.focusXPos() r.hl.propertyLock.RUnlock() @@ -302,7 +310,7 @@ func (r *hyperlinkRenderer) Layout(s fyne.Size) { r.hl.provider.Resize(s) r.focus.Move(fyne.NewPos(xposFocus, innerPad/2)) r.focus.Resize(fyne.NewSize(w, textSize.Height*lineCount+innerPad)) - r.under.Move(fyne.NewPos(xposUnderline, textSize.Height*lineCount+theme.Padding()*2)) + r.under.Move(fyne.NewPos(xposUnderline, textSize.Height*lineCount+th.Size(theme.SizeNamePadding)*2)) r.under.Resize(fyne.NewSize(w-innerPad, 1)) } diff --git a/widget/icon.go b/widget/icon.go index 8a311992f5..695d42a2d5 100644 --- a/widget/icon.go +++ b/widget/icon.go @@ -16,7 +16,7 @@ type iconRenderer struct { } func (i *iconRenderer) MinSize() fyne.Size { - return fyne.NewSquareSize(theme.IconInlineSize()) + return fyne.NewSquareSize(i.image.Theme().Size(theme.SizeNameInlineIcon)) } func (i *iconRenderer) Layout(size fyne.Size) { @@ -34,7 +34,8 @@ func (i *iconRenderer) Refresh() { r := i.image.Resource if r != nil { - i.image.Resource = cache.OverrideResourceTheme(i.image.Resource, i.image) + r = cache.OverrideResourceTheme(i.image.Resource, i.image) + i.image.Resource = r } i.image.propertyLock.RLock() @@ -75,11 +76,13 @@ func (i *Icon) CreateRenderer() fyne.WidgetRenderer { i.propertyLock.RLock() defer i.propertyLock.RUnlock() - img := canvas.NewImageFromResource(i.Resource) - img.FillMode = canvas.ImageFillContain - if i.Resource != nil { - i.Resource = cache.OverrideResourceTheme(i.Resource, i) + res := i.Resource + if res != nil { + res = cache.OverrideResourceTheme(i.Resource, i) + i.Resource = res } + img := canvas.NewImageFromResource(res) + img.FillMode = canvas.ImageFillContain r := &iconRenderer{image: i, raster: img} r.SetObjects([]fyne.CanvasObject{img}) diff --git a/widget/icon_extend_test.go b/widget/icon_extend_test.go index e4c86c51c6..f404b4efdc 100644 --- a/widget/icon_extend_test.go +++ b/widget/icon_extend_test.go @@ -25,5 +25,5 @@ func TestIcon_Extended_SetResource(t *testing.T) { objs := cache.Renderer(icon).Objects() assert.Equal(t, 1, len(objs)) - assert.Equal(t, theme.ComputerIcon(), objs[0].(*canvas.Image).Resource) + assert.Equal(t, theme.ComputerIcon(), objs[0].(*canvas.Image).Resource.(*cache.WidgetResource).ThemedResource) } diff --git a/widget/icon_internal_test.go b/widget/icon_internal_test.go index 07733d66fe..4ce819b80f 100644 --- a/widget/icon_internal_test.go +++ b/widget/icon_internal_test.go @@ -4,6 +4,7 @@ import ( "testing" "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/internal/cache" "fyne.io/fyne/v2/test" "fyne.io/fyne/v2/theme" "github.com/stretchr/testify/assert" @@ -19,7 +20,7 @@ func TestNewIcon(t *testing.T) { if !ok { t.Fail() } - assert.Equal(t, theme.ConfirmIcon(), img.Resource) + assert.Equal(t, theme.ConfirmIcon(), img.Resource.(*cache.WidgetResource).ThemedResource) } func TestIcon_Nil(t *testing.T) { diff --git a/widget/menu.go b/widget/menu.go index 45e238edd8..d927490cab 100644 --- a/widget/menu.go +++ b/widget/menu.go @@ -300,7 +300,7 @@ func (r *menuRenderer) layoutActiveChild() { cp.X = c.Size().Width - absPos.X - childSize.Width } } - requiredHeight := childSize.Height - theme.Padding() + requiredHeight := childSize.Height - r.m.themeWithLock().Size(theme.SizeNamePadding) availableHeight := c.Size().Height - absPos.Y missingHeight := requiredHeight - availableHeight if missingHeight > 0 { @@ -324,7 +324,10 @@ func newMenuBox(items []fyne.CanvasObject) *menuBox { } func (b *menuBox) CreateRenderer() fyne.WidgetRenderer { - background := canvas.NewRectangle(theme.MenuBackgroundColor()) + th := b.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + + background := canvas.NewRectangle(th.Color(theme.ColorNameMenuBackground, v)) cont := &fyne.Container{Layout: layout.NewVBoxLayout(), Objects: b.items} return &menuBoxRenderer{ BaseRenderer: widget.NewBaseRenderer([]fyne.CanvasObject{background, cont}), @@ -354,7 +357,10 @@ func (r *menuBoxRenderer) MinSize() fyne.Size { } func (r *menuBoxRenderer) Refresh() { - r.background.FillColor = theme.MenuBackgroundColor() + th := r.b.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + + r.background.FillColor = th.Color(theme.ColorNameMenuBackground, v) r.background.Refresh() canvas.Refresh(r.b) } diff --git a/widget/menu_item.go b/widget/menu_item.go index 9c381509e4..eb70a3faf7 100644 --- a/widget/menu_item.go +++ b/widget/menu_item.go @@ -11,6 +11,8 @@ import ( "fyne.io/fyne/v2/theme" ) +// TODO + const ( runeModifierAlt = '⌥' runeModifierControl = '⌃' @@ -69,18 +71,21 @@ func (i *menuItem) Child() *Menu { // // Implements: fyne.Widget func (i *menuItem) CreateRenderer() fyne.WidgetRenderer { - background := canvas.NewRectangle(theme.HoverColor()) - background.CornerRadius = theme.SelectionRadiusSize() + th := i.Parent.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + + background := canvas.NewRectangle(th.Color(theme.ColorNameHover, v)) + background.CornerRadius = th.Size(theme.SizeNameSelectionRadius) background.Hide() - text := canvas.NewText(i.Item.Label, theme.ForegroundColor()) + text := canvas.NewText(i.Item.Label, th.Color(theme.ColorNameForeground, v)) text.Alignment = i.alignment objects := []fyne.CanvasObject{background, text} var expandIcon *canvas.Image if i.Item.ChildMenu != nil { - expandIcon = canvas.NewImageFromResource(theme.MenuExpandIcon()) + expandIcon = canvas.NewImageFromResource(th.Icon(theme.IconNameMenuExpand)) objects = append(objects, expandIcon) } - checkIcon := canvas.NewImageFromResource(theme.ConfirmIcon()) + checkIcon := canvas.NewImageFromResource(th.Icon(theme.IconNameConfirm)) if !i.Item.Checked { checkIcon.Hide() } @@ -91,7 +96,7 @@ func (i *menuItem) CreateRenderer() fyne.WidgetRenderer { } var shortcutTexts []*canvas.Text if s, ok := i.Item.Shortcut.(fyne.KeyboardShortcut); ok { - shortcutTexts = textsForShortcut(s) + shortcutTexts = textsForShortcut(s, th) for _, t := range shortcutTexts { objects = append(objects, t) } @@ -230,42 +235,46 @@ type menuItemRenderer struct { } func (r *menuItemRenderer) Layout(size fyne.Size) { - leftOffset := theme.InnerPadding() + r.checkSpace() + th := r.i.Parent.Theme() + innerPad := th.Size(theme.SizeNameInnerPadding) + inlineIcon := th.Size(theme.SizeNameInlineIcon) + + leftOffset := innerPad + r.checkSpace() rightOffset := size.Width - iconSize := fyne.NewSize(theme.IconInlineSize(), theme.IconInlineSize()) - iconTopOffset := (size.Height - theme.IconInlineSize()) / 2 + iconSize := fyne.NewSquareSize(inlineIcon) + iconTopOffset := (size.Height - inlineIcon) / 2 if r.expandIcon != nil { - rightOffset -= theme.IconInlineSize() + rightOffset -= inlineIcon r.expandIcon.Resize(iconSize) r.expandIcon.Move(fyne.NewPos(rightOffset, iconTopOffset)) } - rightOffset -= theme.InnerPadding() + rightOffset -= innerPad textHeight := r.text.MinSize().Height for i := len(r.shortcutTexts) - 1; i >= 0; i-- { text := r.shortcutTexts[i] text.Resize(text.MinSize()) rightOffset -= text.MinSize().Width - text.Move(fyne.NewPos(rightOffset, theme.InnerPadding()+(textHeight-text.Size().Height))) + text.Move(fyne.NewPos(rightOffset, innerPad+(textHeight-text.Size().Height))) if i == 0 { - rightOffset -= theme.InnerPadding() + rightOffset -= innerPad } } r.checkIcon.Resize(iconSize) - r.checkIcon.Move(fyne.NewPos(theme.InnerPadding(), iconTopOffset)) + r.checkIcon.Move(fyne.NewPos(innerPad, iconTopOffset)) if r.icon != nil { r.icon.Resize(iconSize) r.icon.Move(fyne.NewPos(leftOffset, iconTopOffset)) - leftOffset += theme.IconInlineSize() - leftOffset += theme.InnerPadding() + leftOffset += inlineIcon + leftOffset += innerPad } r.text.Resize(fyne.NewSize(rightOffset-leftOffset, textHeight)) - r.text.Move(fyne.NewPos(leftOffset, theme.InnerPadding())) + r.text.Move(fyne.NewPos(leftOffset, innerPad)) r.background.Resize(size) } @@ -275,30 +284,37 @@ func (r *menuItemRenderer) MinSize() fyne.Size { return r.minSize } - minSize := r.text.MinSize().AddWidthHeight(theme.InnerPadding()*2+r.checkSpace(), theme.InnerPadding()*2) + th := r.i.Parent.Theme() + innerPad := th.Size(theme.SizeNameInnerPadding) + inlineIcon := th.Size(theme.SizeNameInlineIcon) + innerPad2 := innerPad * 2 + + minSize := r.text.MinSize().AddWidthHeight(innerPad2+r.checkSpace(), innerPad2) if r.expandIcon != nil { - minSize = minSize.AddWidthHeight(theme.IconInlineSize(), 0) + minSize = minSize.AddWidthHeight(inlineIcon, 0) } if r.icon != nil { - minSize = minSize.AddWidthHeight(theme.IconInlineSize()+theme.InnerPadding(), 0) + minSize = minSize.AddWidthHeight(inlineIcon+innerPad, 0) } if r.shortcutTexts != nil { var textWidth float32 for _, text := range r.shortcutTexts { textWidth += text.MinSize().Width } - minSize = minSize.AddWidthHeight(textWidth+theme.InnerPadding(), 0) + minSize = minSize.AddWidthHeight(textWidth+innerPad, 0) } r.minSize = minSize return r.minSize } func (r *menuItemRenderer) updateVisuals() { - r.background.CornerRadius = theme.SelectionRadiusSize() + th := r.i.Parent.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + r.background.CornerRadius = th.Size(theme.SizeNameSelectionRadius) if fyne.CurrentDevice().IsMobile() { r.background.Hide() } else if r.i.isActive() { - r.background.FillColor = theme.FocusColor() + r.background.FillColor = th.Color(theme.ColorNameFocus, v) r.background.Show() } else { r.background.Hide() @@ -315,8 +331,8 @@ func (r *menuItemRenderer) updateVisuals() { } else { r.checkIcon.Hide() } - r.updateIcon(r.checkIcon, theme.ConfirmIcon()) - r.updateIcon(r.expandIcon, theme.MenuExpandIcon()) + r.updateIcon(r.checkIcon, th.Icon(theme.IconNameConfirm)) + r.updateIcon(r.expandIcon, th.Icon(theme.IconNameMenuExpand)) r.updateIcon(r.icon, r.i.Item.Icon) } @@ -333,10 +349,12 @@ func (r *menuItemRenderer) checkSpace() float32 { } func (r *menuItemRenderer) minSizeUnchanged() bool { + th := r.i.Parent.Theme() + return !r.minSize.IsZero() && - r.text.TextSize == theme.TextSize() && - (r.expandIcon == nil || r.expandIcon.Size().Width == theme.IconInlineSize()) && - r.lastThemePadding == theme.InnerPadding() + r.text.TextSize == th.Size(theme.SizeNameText) && + (r.expandIcon == nil || r.expandIcon.Size().Width == th.Size(theme.SizeNameInlineIcon)) && + r.lastThemePadding == th.Size(theme.SizeNameInnerPadding) } func (r *menuItemRenderer) updateIcon(img *canvas.Image, rsc fyne.Resource) { @@ -351,26 +369,30 @@ func (r *menuItemRenderer) updateIcon(img *canvas.Image, rsc fyne.Resource) { } func (r *menuItemRenderer) refreshText(text *canvas.Text, shortcut bool) { - text.TextSize = theme.TextSize() + th := r.i.Parent.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + + text.TextSize = th.Size(theme.SizeNameText) if r.i.Item.Disabled { - text.Color = theme.DisabledColor() + text.Color = th.Color(theme.ColorNameDisabled, v) } else { if shortcut { - text.Color = shortcutColor() + text.Color = shortcutColor(th) } else { - text.Color = theme.ForegroundColor() + text.Color = th.Color(theme.ColorNameForeground, v) } } text.Refresh() } -func shortcutColor() color.Color { - r, g, b, a := theme.ForegroundColor().RGBA() +func shortcutColor(th fyne.Theme) color.Color { + v := fyne.CurrentApp().Settings().ThemeVariant() + r, g, b, a := th.Color(theme.ColorNameForeground, v).RGBA() a = uint32(float32(a) * 0.95) return color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: uint8(a)} } -func textsForShortcut(s fyne.KeyboardShortcut) (texts []*canvas.Text) { +func textsForShortcut(s fyne.KeyboardShortcut, th fyne.Theme) (texts []*canvas.Text) { b := strings.Builder{} mods := s.Mod() if mods&fyne.KeyModifierControl != 0 { @@ -389,7 +411,7 @@ func textsForShortcut(s fyne.KeyboardShortcut) (texts []*canvas.Text) { if r != 0 { b.WriteRune(r) } - shortColor := shortcutColor() + shortColor := shortcutColor(th) t := canvas.NewText(b.String(), shortColor) t.TextStyle.Symbol = true texts = append(texts, t) diff --git a/widget/popup.go b/widget/popup.go index f2753b34f7..f15d1cf37e 100644 --- a/widget/popup.go +++ b/widget/popup.go @@ -98,10 +98,13 @@ func (p *PopUp) MinSize() fyne.Size { // CreateRenderer is a private method to Fyne which links this widget to its renderer func (p *PopUp) CreateRenderer() fyne.WidgetRenderer { + th := p.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + p.ExtendBaseWidget(p) - background := canvas.NewRectangle(theme.OverlayBackgroundColor()) + background := canvas.NewRectangle(th.Color(theme.ColorNameOverlayBackground, v)) if p.modal { - underlay := canvas.NewRectangle(theme.ShadowColor()) + underlay := canvas.NewRectangle(th.Color(theme.ColorNameShadow, v)) objects := []fyne.CanvasObject{underlay, background, p.Content} return &modalPopUpRenderer{ widget.NewShadowingRenderer(objects, widget.DialogLevel), @@ -173,11 +176,13 @@ type popUpBaseRenderer struct { } func (r *popUpBaseRenderer) padding() fyne.Size { - return fyne.NewSize(theme.InnerPadding(), theme.InnerPadding()) + th := r.popUp.Theme() + return fyne.NewSquareSize(th.Size(theme.SizeNameInnerPadding)) } func (r *popUpBaseRenderer) offset() fyne.Position { - return fyne.NewPos(theme.InnerPadding()/2, theme.InnerPadding()/2) + th := r.popUp.Theme() + return fyne.NewSquareOffsetPos(th.Size(theme.SizeNameInnerPadding) / 2) } type popUpRenderer struct { @@ -214,7 +219,9 @@ func (r *popUpRenderer) MinSize() fyne.Size { } func (r *popUpRenderer) Refresh() { - r.background.FillColor = theme.OverlayBackgroundColor() + th := r.popUp.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + r.background.FillColor = th.Color(theme.ColorNameOverlayBackground, v) expectedContentSize := r.popUp.innerSize.Max(r.popUp.MinSize()).Subtract(r.padding()) shouldRelayout := r.popUp.Content.Size() != expectedContentSize @@ -259,8 +266,10 @@ func (r *modalPopUpRenderer) MinSize() fyne.Size { } func (r *modalPopUpRenderer) Refresh() { - r.underlay.FillColor = theme.ShadowColor() - r.background.FillColor = theme.OverlayBackgroundColor() + th := r.popUp.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + r.underlay.FillColor = th.Color(theme.ColorNameShadow, v) + r.background.FillColor = th.Color(theme.ColorNameOverlayBackground, v) expectedContentSize := r.popUp.innerSize.Max(r.popUp.MinSize()).Subtract(r.padding()) shouldLayout := r.popUp.Content.Size() != expectedContentSize diff --git a/widget/progressbar.go b/widget/progressbar.go index 135fb9f159..cfae197db9 100644 --- a/widget/progressbar.go +++ b/widget/progressbar.go @@ -23,6 +23,8 @@ type progressRenderer struct { // MinSize calculates the minimum size of a progress bar. // This is simply the "100%" label size plus padding. func (p *progressRenderer) MinSize() fyne.Size { + th := p.progress.Theme() + var tsize fyne.Size if text := p.progress.TextFormatter; text != nil { tsize = fyne.MeasureText(text(), p.label.TextSize, p.label.TextStyle) @@ -30,7 +32,7 @@ func (p *progressRenderer) MinSize() fyne.Size { tsize = fyne.MeasureText("100%", p.label.TextSize, p.label.TextStyle) } - padding := theme.InnerPadding() * 2 + padding := th.Size(theme.SizeNameInnerPadding) * 2 return fyne.NewSize(tsize.Width+padding, tsize.Height+padding) } @@ -64,15 +66,18 @@ func (p *progressRenderer) Layout(size fyne.Size) { // applyTheme updates the progress bar to match the current theme func (p *progressRenderer) applyTheme() { - primaryColor := theme.PrimaryColor() - inputRadius := theme.InputRadiusSize() + th := p.progress.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + + primaryColor := th.Color(theme.ColorNamePrimary, v) + inputRadius := th.Size(theme.SizeNameInputRadius) p.background.FillColor = progressBlendColor(primaryColor) p.background.CornerRadius = inputRadius p.bar.FillColor = primaryColor p.bar.CornerRadius = inputRadius - p.label.Color = theme.BackgroundColor() - p.label.TextSize = theme.TextSize() + p.label.Color = th.Color(theme.ColorNameBackground, v) + p.label.TextSize = th.Size(theme.SizeNameText) } func (p *progressRenderer) Refresh() { @@ -128,8 +133,10 @@ func (p *ProgressBar) CreateRenderer() fyne.WidgetRenderer { p.Max = 1.0 } - cornerRadius := theme.InputRadiusSize() - primaryColor := theme.PrimaryColor() + th := p.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + cornerRadius := th.Size(theme.SizeNameInputRadius) + primaryColor := th.Color(theme.ColorNamePrimary, v) renderer := &progressRenderer{ background: canvas.Rectangle{ @@ -142,7 +149,7 @@ func (p *ProgressBar) CreateRenderer() fyne.WidgetRenderer { }, label: canvas.Text{ Text: "0%", - Color: theme.BackgroundColor(), + Color: th.Color(theme.ColorNameBackground, v), Alignment: fyne.TextAlignCenter, }, progress: p, diff --git a/widget/progressbarinfinite.go b/widget/progressbarinfinite.go index 7d1619f610..86fb38d066 100644 --- a/widget/progressbarinfinite.go +++ b/widget/progressbarinfinite.go @@ -27,10 +27,12 @@ type infProgressRenderer struct { // MinSize calculates the minimum size of a progress bar. func (p *infProgressRenderer) MinSize() fyne.Size { + th := p.progress.Theme() + innerPad2 := th.Size(theme.SizeNameInnerPadding) * 2 // this is to create the same size infinite progress bar as regular progress bar - text := fyne.MeasureText("100%", theme.TextSize(), fyne.TextStyle{}) + text := fyne.MeasureText("100%", th.Size(theme.SizeNameText), fyne.TextStyle{}) - return fyne.NewSize(text.Width+theme.InnerPadding()*2, text.Height+theme.InnerPadding()*2) + return fyne.NewSize(text.Width+innerPad2, text.Height+innerPad2) } func (p *infProgressRenderer) updateBar(done float32) { @@ -65,8 +67,11 @@ func (p *infProgressRenderer) Refresh() { if p.isRunning() { return // we refresh from the goroutine } - cornerRadius := theme.InputRadiusSize() - primaryColor := theme.PrimaryColor() + + th := p.progress.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + cornerRadius := th.Size(theme.SizeNameInputRadius) + primaryColor := th.Color(theme.ColorNamePrimary, v) p.background.FillColor = progressBlendColor(primaryColor) p.background.CornerRadius = cornerRadius @@ -160,9 +165,11 @@ func (p *ProgressBarInfinite) MinSize() fyne.Size { // CreateRenderer is a private method to Fyne which links this widget to its renderer func (p *ProgressBarInfinite) CreateRenderer() fyne.WidgetRenderer { p.ExtendBaseWidget(p) + th := p.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() - primaryColor := theme.PrimaryColor() - cornerRadius := theme.InputRadiusSize() + primaryColor := th.Color(theme.ColorNamePrimary, v) + cornerRadius := th.Size(theme.SizeNameInputRadius) render := &infProgressRenderer{ background: canvas.Rectangle{ diff --git a/widget/radio_group.go b/widget/radio_group.go index 084a1afe83..51944a6740 100644 --- a/widget/radio_group.go +++ b/widget/radio_group.go @@ -241,7 +241,7 @@ func (r *radioGroupRenderer) updateItems(refresh bool) { changed = true } - if refresh && changed { + if refresh || changed { item.Refresh() } } diff --git a/widget/radio_item.go b/widget/radio_item.go index 6325635da1..8fd24d5acd 100644 --- a/widget/radio_item.go +++ b/widget/radio_item.go @@ -37,8 +37,10 @@ type radioItem struct { // // Implements: fyne.Widget func (i *radioItem) CreateRenderer() fyne.WidgetRenderer { - r := &radioItemRenderer{item: i, label: canvas.Text{Alignment: fyne.TextAlignLeading}} - r.SetObjects([]fyne.CanvasObject{&r.focusIndicator, &r.icon, &r.over, &r.label}) + txt := canvas.Text{Alignment: fyne.TextAlignLeading} + txt.TextSize = i.Theme().Size(theme.SizeNameText) + r := &radioItemRenderer{item: i, label: &txt} + r.SetObjects([]fyne.CanvasObject{&r.focusIndicator, &r.icon, &r.over, &txt}) r.update() return r } @@ -142,13 +144,14 @@ type radioItemRenderer struct { focusIndicator canvas.Circle icon, over canvas.Image - label canvas.Text + label *canvas.Text } func (r *radioItemRenderer) Layout(size fyne.Size) { - innerPadding := theme.InnerPadding() - borderSize := theme.InputBorderSize() - iconInlineSize := theme.IconInlineSize() + th := r.item.Theme() + innerPadding := th.Size(theme.SizeNameInnerPadding) + borderSize := th.Size(theme.SizeNameInputBorder) + iconInlineSize := th.Size(theme.SizeNameInlineIcon) focusIndicatorSize := fyne.NewSquareSize(iconInlineSize + innerPadding) r.focusIndicator.Resize(focusIndicatorSize) @@ -156,7 +159,7 @@ func (r *radioItemRenderer) Layout(size fyne.Size) { labelSize := fyne.NewSize(size.Width, size.Height) r.label.Resize(labelSize) - r.label.Move(fyne.NewPos(focusIndicatorSize.Width+theme.Padding(), 0)) + r.label.Move(fyne.NewPos(focusIndicatorSize.Width+th.Size(theme.SizeNamePadding), 0)) iconPos := fyne.NewPos(innerPadding/2+borderSize, (size.Height-iconInlineSize)/2) iconSize := fyne.NewSquareSize(iconInlineSize) @@ -167,10 +170,11 @@ func (r *radioItemRenderer) Layout(size fyne.Size) { } func (r *radioItemRenderer) MinSize() fyne.Size { - inPad := theme.InnerPadding() * 2 + th := r.item.Theme() + inPad := th.Size(theme.SizeNameInnerPadding) * 2 return r.label.MinSize(). - AddWidthHeight(inPad+theme.IconInlineSize()+theme.Padding(), inPad) + AddWidthHeight(inPad+th.Size(theme.SizeNameInlineIcon)+th.Size(theme.SizeNamePadding), inPad) } func (r *radioItemRenderer) Refresh() { @@ -179,17 +183,20 @@ func (r *radioItemRenderer) Refresh() { } func (r *radioItemRenderer) update() { + th := r.item.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + r.label.Text = r.item.Label - r.label.TextSize = theme.TextSize() + r.label.TextSize = th.Size(theme.SizeNameText) if r.item.Disabled() { - r.label.Color = theme.DisabledColor() + r.label.Color = th.Color(theme.ColorNameDisabled, v) } else { - r.label.Color = theme.ForegroundColor() + r.label.Color = th.Color(theme.ColorNameForeground, v) } - out := theme.NewThemedResource(theme.RadioButtonIcon()) + out := theme.NewThemedResource(th.Icon(theme.IconNameRadioButton)) out.ColorName = theme.ColorNameInputBorder - in := theme.NewThemedResource(theme.RadioButtonFillIcon()) + in := theme.NewThemedResource(th.Icon(theme.IconNameRadioButtonFill)) in.ColorName = theme.ColorNameInputBackground if r.item.Selected { in.ColorName = theme.ColorNamePrimary @@ -209,9 +216,9 @@ func (r *radioItemRenderer) update() { if r.item.Disabled() { r.focusIndicator.FillColor = color.Transparent } else if r.item.focused { - r.focusIndicator.FillColor = theme.FocusColor() + r.focusIndicator.FillColor = th.Color(theme.ColorNameFocus, v) } else if r.item.hovered { - r.focusIndicator.FillColor = theme.HoverColor() + r.focusIndicator.FillColor = th.Color(theme.ColorNameHover, v) } else { r.focusIndicator.FillColor = color.Transparent } diff --git a/widget/richtext.go b/widget/richtext.go index e447c1df89..007b8e3c67 100644 --- a/widget/richtext.go +++ b/widget/richtext.go @@ -560,9 +560,9 @@ func (r *textRenderer) Layout(size fyne.Size) { // MinSize calculates the minimum size of a rich text widget. // This is based on the contained text with a standard amount of padding added. func (r *textRenderer) MinSize() fyne.Size { - textSize := r.obj.Theme().Size(theme.SizeNameText) - r.obj.propertyLock.RLock() th := r.obj.Theme() + textSize := th.Size(theme.SizeNameText) + r.obj.propertyLock.RLock() innerPad := th.Size(theme.SizeNameInnerPadding) bounds := r.obj.rowBounds @@ -618,7 +618,7 @@ func (r *textRenderer) calculateMin(bounds []rowBoundary, wrap fyne.TextWrap, ob innerPad := th.Size(theme.SizeNameInnerPadding) // Accessing the theme here is slow, so we cache the value - lineSpacing := theme.SizeForWidget(theme.SizeNameLineSpacing, r.obj) + lineSpacing := th.Size(theme.SizeNameLineSpacing) i := 0 for row, bound := range bounds { diff --git a/widget/richtext_objects.go b/widget/richtext_objects.go index 2a2b4dd957..09019d9b56 100644 --- a/widget/richtext_objects.go +++ b/widget/richtext_objects.go @@ -131,7 +131,7 @@ func (h *HyperlinkSegment) Visual() fyne.CanvasObject { link := NewHyperlink(h.Text, h.URL) link.Alignment = h.Alignment link.OnTapped = h.OnTapped - return &fyne.Container{Layout: &unpadTextWidgetLayout{}, Objects: []fyne.CanvasObject{link}} + return &fyne.Container{Layout: &unpadTextWidgetLayout{parent: link}, Objects: []fyne.CanvasObject{link}} } // Update applies the current state of this hyperlink segment to an existing visual. @@ -525,10 +525,12 @@ func (r *richImageLayout) MinSize(_ []fyne.CanvasObject) fyne.Size { } type unpadTextWidgetLayout struct { + parent fyne.Widget } func (u *unpadTextWidgetLayout) Layout(o []fyne.CanvasObject, s fyne.Size) { - pad := theme.InnerPadding() * -1 + innerPad := theme.SizeForWidget(theme.SizeNameInnerPadding, u.parent) + pad := innerPad * -1 pad2 := pad * -2 o[0].Move(fyne.NewPos(pad, pad)) @@ -536,6 +538,7 @@ func (u *unpadTextWidgetLayout) Layout(o []fyne.CanvasObject, s fyne.Size) { } func (u *unpadTextWidgetLayout) MinSize(o []fyne.CanvasObject) fyne.Size { - pad := theme.InnerPadding() * 2 + innerPad := theme.SizeForWidget(theme.SizeNameInnerPadding, u.parent) + pad := innerPad * 2 return o[0].MinSize().Subtract(fyne.NewSize(pad, pad)) } diff --git a/widget/select.go b/widget/select.go index 906773ee84..e8e4eea21c 100644 --- a/widget/select.go +++ b/widget/select.go @@ -58,14 +58,15 @@ func (s *Select) ClearSelected() { func (s *Select) CreateRenderer() fyne.WidgetRenderer { s.ExtendBaseWidget(s) th := s.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() s.propertyLock.RLock() - icon := NewIcon(theme.MenuDropDownIcon()) + icon := NewIcon(th.Icon(theme.IconNameArrowDropDown)) if s.PlaceHolder == "" { s.PlaceHolder = defaultPlaceHolder } txtProv := NewRichTextWithText(s.Selected) - txtProv.inset = fyne.NewSquareSize(theme.Padding()) + txtProv.inset = fyne.NewSquareSize(th.Size(theme.SizeNamePadding)) txtProv.ExtendBaseWidget(txtProv) txtProv.Truncation = fyne.TextTruncateEllipsis if s.disabled.Load() { @@ -78,9 +79,9 @@ func (s *Select) CreateRenderer() fyne.WidgetRenderer { s.tapAnim.Curve = fyne.AnimationEaseOut objects := []fyne.CanvasObject{background, tapBG, txtProv, icon} r := &selectRenderer{icon, txtProv, background, objects, s} - background.FillColor = r.bgColor() - background.CornerRadius = theme.InputRadiusSize() - r.updateIcon() + background.FillColor = r.bgColor(th, v) + background.CornerRadius = th.Size(theme.SizeNameInputRadius) + r.updateIcon(th) s.propertyLock.RUnlock() // updateLabel and some text handling isn't quite right, resolve in text refactor for 2.0 r.updateLabel() return r @@ -236,7 +237,7 @@ func (s *Select) TypedRune(_ rune) { func (s *Select) popUpPos() fyne.Position { buttonPos := fyne.CurrentApp().Driver().AbsolutePositionForObject(s.super()) - return buttonPos.Add(fyne.NewPos(0, s.Size().Height-theme.InputBorderSize())) + return buttonPos.Add(fyne.NewPos(0, s.Size().Height-s.Theme().Size(theme.SizeNameInputBorder))) } func (s *Select) showPopUp() { @@ -298,38 +299,48 @@ func (s *selectRenderer) Destroy() {} // Layout the components of the button widget func (s *selectRenderer) Layout(size fyne.Size) { + th := s.combo.Theme() + pad := th.Size(theme.SizeNamePadding) + iconSize := th.Size(theme.SizeNameInlineIcon) + innerPad := th.Size(theme.SizeNameInnerPadding) s.background.Resize(fyne.NewSize(size.Width, size.Height)) - s.label.inset = fyne.NewSize(theme.Padding(), theme.Padding()) + s.label.inset = fyne.NewSquareSize(pad) - iconPos := fyne.NewPos(size.Width-theme.IconInlineSize()-theme.InnerPadding(), (size.Height-theme.IconInlineSize())/2) - labelSize := fyne.NewSize(iconPos.X-theme.Padding(), s.label.MinSize().Height) + iconPos := fyne.NewPos(size.Width-iconSize-innerPad, (size.Height-iconSize)/2) + labelSize := fyne.NewSize(iconPos.X-pad, s.label.MinSize().Height) s.label.Resize(labelSize) - s.label.Move(fyne.NewPos(theme.Padding(), (size.Height-labelSize.Height)/2)) + s.label.Move(fyne.NewPos(pad, (size.Height-labelSize.Height)/2)) - s.icon.Resize(fyne.NewSize(theme.IconInlineSize(), theme.IconInlineSize())) + s.icon.Resize(fyne.NewSquareSize(iconSize)) s.icon.Move(iconPos) } // MinSize calculates the minimum size of a select button. // This is based on the selected text, the drop icon and a standard amount of padding added. func (s *selectRenderer) MinSize() fyne.Size { + th := s.combo.Theme() + innerPad := th.Size(theme.SizeNameInnerPadding) + s.combo.propertyLock.RLock() defer s.combo.propertyLock.RUnlock() - minPlaceholderWidth := fyne.MeasureText(s.combo.PlaceHolder, theme.TextSize(), fyne.TextStyle{}).Width + minPlaceholderWidth := fyne.MeasureText(s.combo.PlaceHolder, th.Size(theme.SizeNameText), fyne.TextStyle{}).Width min := s.label.MinSize() min.Width = minPlaceholderWidth - min = min.Add(fyne.NewSize(theme.InnerPadding()*3, theme.InnerPadding())) - return min.Add(fyne.NewSize(theme.IconInlineSize()+theme.InnerPadding(), 0)) + min = min.Add(fyne.NewSize(innerPad*3, innerPad)) + return min.Add(fyne.NewSize(th.Size(theme.SizeNameInlineIcon)+innerPad, 0)) } func (s *selectRenderer) Refresh() { + th := s.combo.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + s.combo.propertyLock.RLock() s.updateLabel() - s.updateIcon() - s.background.FillColor = s.bgColor() - s.background.CornerRadius = theme.InputRadiusSize() + s.updateIcon(th) + s.background.FillColor = s.bgColor(th, v) + s.background.CornerRadius = s.combo.themeWithLock().Size(theme.SizeNameInputRadius) s.combo.propertyLock.RUnlock() s.Layout(s.combo.Size()) @@ -343,24 +354,25 @@ func (s *selectRenderer) Refresh() { canvas.Refresh(s.combo.super()) } -func (s *selectRenderer) bgColor() color.Color { +func (s *selectRenderer) bgColor(th fyne.Theme, v fyne.ThemeVariant) color.Color { if s.combo.disabled.Load() { - return theme.DisabledButtonColor() + return th.Color(theme.ColorNameDisabledButton, v) } if s.combo.focused { - return theme.FocusColor() + return th.Color(theme.ColorNameFocus, v) } if s.combo.hovered { - return theme.HoverColor() + return th.Color(theme.ColorNameHover, v) } - return theme.InputBackgroundColor() + return th.Color(theme.ColorNameInputBackground, v) } -func (s *selectRenderer) updateIcon() { +func (s *selectRenderer) updateIcon(th fyne.Theme) { + icon := th.Icon(theme.IconNameArrowDropDown) if s.combo.disabled.Load() { - s.icon.Resource = theme.NewDisabledResource(theme.MenuDropDownIcon()) + s.icon.Resource = theme.NewDisabledResource(icon) } else { - s.icon.Resource = theme.MenuDropDownIcon() + s.icon.Resource = icon } s.icon.Refresh() } diff --git a/widget/select_entry.go b/widget/select_entry.go index 4c2bedaf59..806d17c8c0 100644 --- a/widget/select_entry.go +++ b/widget/select_entry.go @@ -98,7 +98,7 @@ func (e *SelectEntry) SetOptions(options []string) { func (e *SelectEntry) popUpPos() fyne.Position { entryPos := fyne.CurrentApp().Driver().AbsolutePositionForObject(e.super()) - return entryPos.Add(fyne.NewPos(0, e.Size().Height-theme.InputBorderSize())) + return entryPos.Add(fyne.NewPos(0, e.Size().Height-e.Theme().Size(theme.SizeNameInputBorder))) } func (e *SelectEntry) setupDropDown() *Button { @@ -110,6 +110,6 @@ func (e *SelectEntry) setupDropDown() *Button { e.popUp.Resize(fyne.NewSize(e.Size().Width, e.popUp.MinSize().Height)) }) dropDownButton.Importance = LowImportance - dropDownButton.SetIcon(theme.MenuDropDownIcon()) + dropDownButton.SetIcon(e.Theme().Icon(theme.IconNameArrowDropDown)) return dropDownButton } diff --git a/widget/separator.go b/widget/separator.go index 1483fddf12..8f61de80ef 100644 --- a/widget/separator.go +++ b/widget/separator.go @@ -1,6 +1,8 @@ package widget import ( + "image/color" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/theme" @@ -13,6 +15,8 @@ var _ fyne.Widget = (*Separator)(nil) // Since: 1.4 type Separator struct { BaseWidget + + invert bool } // NewSeparator creates a new separator. @@ -29,7 +33,16 @@ func NewSeparator() *Separator { // Implements: fyne.Widget func (s *Separator) CreateRenderer() fyne.WidgetRenderer { s.ExtendBaseWidget(s) - bar := canvas.NewRectangle(theme.SeparatorColor()) + th := s.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + var col color.Color + if s.invert { + col = th.Color(theme.ColorNameForeground, v) + } else { + col = th.Color(theme.ColorNameSeparator, v) + } + bar := canvas.NewRectangle(col) + return &separatorRenderer{ WidgetRenderer: NewSimpleRenderer(bar), bar: bar, @@ -42,7 +55,7 @@ func (s *Separator) CreateRenderer() fyne.WidgetRenderer { // Implements: fyne.Widget func (s *Separator) MinSize() fyne.Size { s.ExtendBaseWidget(s) - t := theme.SeparatorThicknessSize() + t := s.Theme().Size(theme.SizeNameSeparatorThickness) return fyne.NewSize(t, t) } @@ -55,11 +68,18 @@ type separatorRenderer struct { } func (r *separatorRenderer) MinSize() fyne.Size { - t := theme.SeparatorThicknessSize() + t := r.d.Theme().Size(theme.SizeNameSeparatorThickness) return fyne.NewSize(t, t) } func (r *separatorRenderer) Refresh() { - r.bar.FillColor = theme.SeparatorColor() + th := r.d.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + + if r.d.invert { + r.bar.FillColor = th.Color(theme.ColorNameForeground, v) + } else { + r.bar.FillColor = th.Color(theme.ColorNameSeparator, v) + } canvas.Refresh(r.d) } diff --git a/widget/slider.go b/widget/slider.go index 7e553545d2..309cdff882 100644 --- a/widget/slider.go +++ b/widget/slider.go @@ -239,7 +239,8 @@ func (s *Slider) endOffset(inlineIconSize, innerPadding float32) float32 { } func (s *Slider) getRatio(e *fyne.PointEvent) float64 { - pad := s.endOffset(theme.IconInlineSize(), theme.InnerPadding()) + th := s.Theme() + pad := s.endOffset(th.Size(theme.SizeNameInlineIcon), th.Size(theme.SizeNameInnerPadding)) x := e.Position.X y := e.Position.Y @@ -346,9 +347,12 @@ func (s *Slider) Disabled() bool { // CreateRenderer links this widget to its renderer. func (s *Slider) CreateRenderer() fyne.WidgetRenderer { s.ExtendBaseWidget(s) - track := canvas.NewRectangle(theme.InputBackgroundColor()) - active := canvas.NewRectangle(theme.ForegroundColor()) - thumb := &canvas.Circle{FillColor: theme.ForegroundColor()} + th := s.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + + track := canvas.NewRectangle(th.Color(theme.ColorNameInputBackground, v)) + active := canvas.NewRectangle(th.Color(theme.ColorNameForeground, v)) + thumb := &canvas.Circle{FillColor: th.Color(theme.ColorNameForeground, v)} focusIndicator := &canvas.Circle{FillColor: color.Transparent} objects := []fyne.CanvasObject{track, active, thumb, focusIndicator} @@ -422,18 +426,21 @@ type sliderRenderer struct { // Refresh updates the widget state for drawing. func (s *sliderRenderer) Refresh() { - s.track.FillColor = theme.InputBackgroundColor() + th := s.slider.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + + s.track.FillColor = th.Color(theme.ColorNameInputBackground, v) if s.slider.disabled { - s.thumb.FillColor = theme.DisabledColor() + s.thumb.FillColor = th.Color(theme.ColorNameDisabled, v) } else { - s.thumb.FillColor = theme.ForegroundColor() + s.thumb.FillColor = th.Color(theme.ColorNameForeground, v) } s.active.FillColor = s.thumb.FillColor if s.slider.focused && !s.slider.disabled { - s.focusIndicator.FillColor = theme.FocusColor() + s.focusIndicator.FillColor = th.Color(theme.ColorNameFocus, v) } else if s.slider.hovered && !s.slider.disabled { - s.focusIndicator.FillColor = theme.HoverColor() + s.focusIndicator.FillColor = th.Color(theme.ColorNameHover, v) } else { s.focusIndicator.FillColor = color.Transparent } @@ -447,10 +454,12 @@ func (s *sliderRenderer) Refresh() { // Layout the components of the widget. func (s *sliderRenderer) Layout(size fyne.Size) { - inputBorderSize := theme.InputBorderSize() + th := s.slider.Theme() + + inputBorderSize := th.Size(theme.SizeNameInputBorder) trackWidth := inputBorderSize * 2 - inlineIconSize := theme.IconInlineSize() - innerPadding := theme.InnerPadding() + inlineIconSize := th.Size(theme.SizeNameInlineIcon) + innerPadding := th.Size(theme.SizeNameInnerPadding) diameter := s.slider.buttonDiameter(inlineIconSize) endPad := s.slider.endOffset(inlineIconSize, innerPadding) @@ -500,7 +509,7 @@ func (s *sliderRenderer) Layout(size fyne.Size) { // MinSize calculates the minimum size of a widget. func (s *sliderRenderer) MinSize() fyne.Size { - dia := s.slider.buttonDiameter(theme.IconInlineSize()) + dia := s.slider.buttonDiameter(s.slider.Theme().Size(theme.SizeNameInlineIcon)) s1, s2 := minLongSide+dia, dia switch s.slider.Orientation { diff --git a/widget/textgrid.go b/widget/textgrid.go index 6b68a99707..38735f72a3 100644 --- a/widget/textgrid.go +++ b/widget/textgrid.go @@ -287,8 +287,10 @@ func (t *TextGrid) CreateRenderer() fyne.WidgetRenderer { render := &textGridRenderer{text: t} render.updateCellSize() + th := t.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() TextGridStyleDefault = &CustomTextGridStyle{} - TextGridStyleWhitespace = &CustomTextGridStyle{FGColor: theme.DisabledColor()} + TextGridStyleWhitespace = &CustomTextGridStyle{FGColor: th.Color(theme.ColorNameDisabled, v)} return render } @@ -341,7 +343,10 @@ type textGridRenderer struct { } func (t *textGridRenderer) appendTextCell(str rune) { - text := canvas.NewText(string(str), theme.ForegroundColor()) + th := t.text.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + + text := canvas.NewText(string(str), th.Color(theme.ColorNameForeground, v)) text.TextStyle.Monospace = true bg := canvas.NewRectangle(color.Transparent) @@ -363,9 +368,12 @@ func (t *textGridRenderer) setCellRune(str rune, pos int, style, rowStyle TextGr str = ' ' } + th := t.text.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + text := t.objects[pos*2+1].(*canvas.Text) - text.TextSize = theme.TextSize() - fg := theme.ForegroundColor() + text.TextSize = th.Size(theme.SizeNameText) + fg := th.Color(theme.ColorNameForeground, v) if style != nil && style.TextColor() != nil { fg = style.TextColor() } else if rowStyle != nil && rowStyle.TextColor() != nil { @@ -536,7 +544,9 @@ func (t *textGridRenderer) Refresh() { // theme could change text size t.updateCellSize() - TextGridStyleWhitespace = &CustomTextGridStyle{FGColor: theme.DisabledColor()} + th := t.text.Theme() + v := fyne.CurrentApp().Settings().ThemeVariant() + TextGridStyleWhitespace = &CustomTextGridStyle{FGColor: th.Color(theme.ColorNameDisabled, v)} t.updateGridSize(t.text.Size()) t.refreshGrid() } @@ -567,11 +577,12 @@ func (t *textGridRenderer) refresh(obj fyne.CanvasObject) { } func (t *textGridRenderer) updateCellSize() { - size := fyne.MeasureText("M", theme.TextSize(), fyne.TextStyle{Monospace: true}) + th := t.text.Theme() + size := fyne.MeasureText("M", th.Size(theme.SizeNameText), fyne.TextStyle{Monospace: true}) // round it for seamless background - size.Width = float32(math.Round(float64((size.Width)))) - size.Height = float32(math.Round(float64((size.Height)))) + size.Width = float32(math.Round(float64(size.Width))) + size.Height = float32(math.Round(float64(size.Height))) t.cellSize = size } diff --git a/widget/toolbar.go b/widget/toolbar.go index 9f1f6c5f96..7a5f075e40 100644 --- a/widget/toolbar.go +++ b/widget/toolbar.go @@ -5,7 +5,6 @@ import ( "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/internal/widget" "fyne.io/fyne/v2/layout" - "fyne.io/fyne/v2/theme" ) // ToolbarItem represents any interface element that can be added to a toolbar @@ -86,7 +85,7 @@ type ToolbarSeparator struct { // ToolbarObject gets the visible line object for this ToolbarSeparator func (t *ToolbarSeparator) ToolbarObject() fyne.CanvasObject { - return canvas.NewRectangle(theme.ForegroundColor()) + return &Separator{invert: true} } // NewToolbarSeparator returns a new separator item for a Toolbar to assist with ToolbarItem grouping @@ -152,13 +151,6 @@ func (r *toolbarRenderer) Layout(size fyne.Size) { func (r *toolbarRenderer) Refresh() { r.resetObjects() - for i, item := range r.toolbar.Items { - if _, ok := item.(*ToolbarSeparator); ok { - rect := r.items[i].(*canvas.Rectangle) - rect.FillColor = theme.ForegroundColor() - } - } - canvas.Refresh(r.toolbar) } diff --git a/widget/widget.go b/widget/widget.go index 071774369e..c5335485d8 100644 --- a/widget/widget.go +++ b/widget/widget.go @@ -136,14 +136,21 @@ func (w *BaseWidget) Refresh() { // // Since: 2.5 func (w *BaseWidget) Theme() fyne.Theme { - w.propertyLock.RLock() + w.propertyLock.Lock() + defer w.propertyLock.Unlock() + return w.themeWithLock() +} + +func (w *BaseWidget) themeWithLock() fyne.Theme { cached := w.themeCache - w.propertyLock.RUnlock() if cached == nil { - cached = theme.CurrentForWidget(w.super()) - w.propertyLock.Lock() + cached = cache.WidgetTheme(w.super()) + // don't cache the default as it may change + if cached == nil { + return theme.Current() + } + w.themeCache = cached - w.propertyLock.Unlock() } return cached