Added score display
This commit is contained in:
		@@ -2,6 +2,7 @@ package ui
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"embed"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
 | 
			
		||||
	"fyne.io/fyne/v2"
 | 
			
		||||
	"fyne.io/fyne/v2/data/binding"
 | 
			
		||||
@@ -57,6 +58,7 @@ func NewBaseViewWithScreen(
 | 
			
		||||
	filesystem embed.FS,
 | 
			
		||||
	screenName string,
 | 
			
		||||
	localizer *i18n.Localizer,
 | 
			
		||||
	assetLoader func(string) (fs.File, error),
 | 
			
		||||
) *BaseView {
 | 
			
		||||
	v := NewBaseView(concreteView, viewModel)
 | 
			
		||||
 | 
			
		||||
@@ -65,7 +67,8 @@ func NewBaseViewWithScreen(
 | 
			
		||||
		filesystem,
 | 
			
		||||
		screenName,
 | 
			
		||||
		viewModel,
 | 
			
		||||
		localizer)
 | 
			
		||||
		localizer,
 | 
			
		||||
		assetLoader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		LogWindowError(v, LoadScreen, err)
 | 
			
		||||
		return nil
 | 
			
		||||
@@ -130,3 +133,7 @@ func (v *BaseView) OnHide() {
 | 
			
		||||
func (v *BaseView) GetBinding(bindingName string) binding.DataItem {
 | 
			
		||||
	return v.ViewModel.GetBinding(bindingName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (v *BaseView) GetCanvasObject() fyne.CanvasObject {
 | 
			
		||||
	return v.CanvasObject
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										34
									
								
								dialog.go
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								dialog.go
									
									
									
									
									
								
							@@ -3,6 +3,7 @@ package ui
 | 
			
		||||
import (
 | 
			
		||||
	"embed"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
 | 
			
		||||
	"fyne.io/fyne/v2"
 | 
			
		||||
	"fyne.io/fyne/v2/data/binding"
 | 
			
		||||
@@ -91,6 +92,7 @@ func NewBaseDialogWithScreen(
 | 
			
		||||
	filesystem embed.FS,
 | 
			
		||||
	screenName string,
 | 
			
		||||
	localizer *i18n.Localizer,
 | 
			
		||||
	assetLoader func(string) (fs.File, error),
 | 
			
		||||
) *BaseDialog {
 | 
			
		||||
	d := NewBaseDialog(
 | 
			
		||||
		concreteDialog, name, kind, viewModel,
 | 
			
		||||
@@ -98,7 +100,7 @@ func NewBaseDialogWithScreen(
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	d.ScreenHandler, err = NewScreenHandler(
 | 
			
		||||
		filesystem, screenName, viewModel, localizer)
 | 
			
		||||
		filesystem, screenName, viewModel, localizer, assetLoader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		LogWindowError(d, LoadScreen, err)
 | 
			
		||||
		return nil
 | 
			
		||||
@@ -138,6 +140,7 @@ func NewBaseDialogWithDataAndScreen(
 | 
			
		||||
	filesystem embed.FS,
 | 
			
		||||
	screenName string,
 | 
			
		||||
	localizer *i18n.Localizer,
 | 
			
		||||
	assetLoader func(string) (fs.File, error),
 | 
			
		||||
) *BaseDialog {
 | 
			
		||||
	d := NewBaseDialogWithData(
 | 
			
		||||
		concreteDialog, name, kind, viewModel,
 | 
			
		||||
@@ -145,7 +148,7 @@ func NewBaseDialogWithDataAndScreen(
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	d.ScreenHandler, err = NewScreenHandler(
 | 
			
		||||
		filesystem, screenName, viewModel, localizer)
 | 
			
		||||
		filesystem, screenName, viewModel, localizer, assetLoader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		LogWindowError(d, LoadScreen, err)
 | 
			
		||||
		return nil
 | 
			
		||||
@@ -248,6 +251,10 @@ func (d *BaseDialog) OnClose(confirmed bool) {
 | 
			
		||||
	d.OnHide()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *BaseDialog) GetCanvasObject() fyne.CanvasObject {
 | 
			
		||||
	return d.CanvasObj
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FormDialogItemsFn func() []*widget.FormItem
 | 
			
		||||
 | 
			
		||||
type FormDialog struct {
 | 
			
		||||
@@ -288,11 +295,12 @@ func NewFormDialogWithScreen(
 | 
			
		||||
	filesystem embed.FS,
 | 
			
		||||
	screenName string,
 | 
			
		||||
	localizer *i18n.Localizer,
 | 
			
		||||
	assetLoader func(string) (fs.File, error),
 | 
			
		||||
) *FormDialog {
 | 
			
		||||
	return &FormDialog{
 | 
			
		||||
		BaseDialog: NewBaseDialogWithScreen(
 | 
			
		||||
			concreteDialog, name, kind, viewModel, parent, confirm,
 | 
			
		||||
			filesystem, screenName, localizer),
 | 
			
		||||
			filesystem, screenName, localizer, assetLoader),
 | 
			
		||||
		confirmLabel: "OK",
 | 
			
		||||
		dismissLabel: "Cancel",
 | 
			
		||||
	}
 | 
			
		||||
@@ -328,11 +336,12 @@ func NewFormDialogWithDataAndScreen(
 | 
			
		||||
	filesystem embed.FS,
 | 
			
		||||
	screenName string,
 | 
			
		||||
	localizer *i18n.Localizer,
 | 
			
		||||
	assetLoader func(string) (fs.File, error),
 | 
			
		||||
) *FormDialog {
 | 
			
		||||
	return &FormDialog{
 | 
			
		||||
		BaseDialog: NewBaseDialogWithDataAndScreen(
 | 
			
		||||
			concreteDialog, name, kind, viewModel, parent, confirm, data,
 | 
			
		||||
			filesystem, screenName, localizer),
 | 
			
		||||
			filesystem, screenName, localizer, assetLoader),
 | 
			
		||||
		confirmLabel: "OK",
 | 
			
		||||
		dismissLabel: "Cancel",
 | 
			
		||||
	}
 | 
			
		||||
@@ -485,3 +494,20 @@ func NewDialogWithDataAndScreen(
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ShowConfirmDialog(
 | 
			
		||||
	title string,
 | 
			
		||||
	message string,
 | 
			
		||||
	lblOk string,
 | 
			
		||||
	lblCancel string,
 | 
			
		||||
	callback func(bool),
 | 
			
		||||
	parent fyne.Window,
 | 
			
		||||
) {
 | 
			
		||||
	label := widget.NewLabel(message)
 | 
			
		||||
	label.Wrapping = fyne.TextWrapBreak
 | 
			
		||||
	dlg := dialog.NewCustomConfirm(
 | 
			
		||||
		title, lblOk, lblCancel,
 | 
			
		||||
		label, callback, parent)
 | 
			
		||||
	dlg.Resize(fyne.NewSize(480, 100))
 | 
			
		||||
	dlg.Show()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										46
									
								
								screen/assets.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								screen/assets.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
package screen
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
 | 
			
		||||
	"fyne.io/fyne/v2"
 | 
			
		||||
	"fyne.io/fyne/v2/canvas"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func AssetToImage(
 | 
			
		||||
	s ScreenHandler,
 | 
			
		||||
	asset string,
 | 
			
		||||
) (
 | 
			
		||||
	img *canvas.Image,
 | 
			
		||||
	err error,
 | 
			
		||||
) {
 | 
			
		||||
	fh, err := s.LoadAsset(asset)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = fmt.Errorf("failed to load asset: %w", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	img = canvas.NewImageFromReader(fh, asset)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func AssetToResource(
 | 
			
		||||
	s ScreenHandler,
 | 
			
		||||
	asset string,
 | 
			
		||||
) (
 | 
			
		||||
	res fyne.Resource,
 | 
			
		||||
	err error,
 | 
			
		||||
) {
 | 
			
		||||
	fh, err := s.LoadAsset(asset)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = fmt.Errorf("failed to load asset: %w", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	bytes, err := io.ReadAll(fh)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = fmt.Errorf("failed to read asset: %w", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	res = fyne.NewStaticResource(asset, bytes)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										197
									
								
								screen/canvas.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								screen/canvas.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,197 @@
 | 
			
		||||
package screen
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"image/color"
 | 
			
		||||
 | 
			
		||||
	"bitbucket.org/hevanto/ui/uiwidget"
 | 
			
		||||
	"fyne.io/fyne/v2"
 | 
			
		||||
	"fyne.io/fyne/v2/canvas"
 | 
			
		||||
	"fyne.io/fyne/v2/container"
 | 
			
		||||
	"fyne.io/fyne/v2/data/binding"
 | 
			
		||||
	"fyne.io/fyne/v2/layout"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Canvas string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	Image     Canvas = "Image"
 | 
			
		||||
	Rectangle Canvas = "Rectangle"
 | 
			
		||||
	Text      Canvas = "Text"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (c Canvas) Build(
 | 
			
		||||
	e *Element,
 | 
			
		||||
	s ScreenHandler,
 | 
			
		||||
) (
 | 
			
		||||
	cnv fyne.CanvasObject,
 | 
			
		||||
	decorator fyne.CanvasObject,
 | 
			
		||||
	err error,
 | 
			
		||||
) {
 | 
			
		||||
	e.localize(s)
 | 
			
		||||
 | 
			
		||||
	switch c {
 | 
			
		||||
	case Image:
 | 
			
		||||
		cnv, err = c.BuildImageCanvas(e, s)
 | 
			
		||||
	case Rectangle:
 | 
			
		||||
		cnv, err = c.BuildRectangleCanvas(e, s)
 | 
			
		||||
	case Text:
 | 
			
		||||
		cnv, err = c.BuildTextCanvas(e, s)
 | 
			
		||||
	default:
 | 
			
		||||
		err = errors.New("invalid canvas")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	decorator = cnv
 | 
			
		||||
	if e.Decorators != nil {
 | 
			
		||||
		for _, dec := range e.Decorators {
 | 
			
		||||
			switch dec {
 | 
			
		||||
			case "Border":
 | 
			
		||||
				decorator = uiwidget.NewWidgetBorder(decorator)
 | 
			
		||||
			case "HCenter":
 | 
			
		||||
				decorator = container.NewHBox(
 | 
			
		||||
					layout.NewSpacer(),
 | 
			
		||||
					decorator,
 | 
			
		||||
					layout.NewSpacer())
 | 
			
		||||
			case "VCenter":
 | 
			
		||||
				decorator = container.NewVBox(
 | 
			
		||||
					layout.NewSpacer(),
 | 
			
		||||
					decorator,
 | 
			
		||||
					layout.NewSpacer())
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if e.Hidden {
 | 
			
		||||
		decorator.Hide()
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Canvas) BuildImageCanvas(
 | 
			
		||||
	e *Element,
 | 
			
		||||
	s ScreenHandler,
 | 
			
		||||
) (obj fyne.CanvasObject, err error) {
 | 
			
		||||
	img, err := AssetToImage(s, e.ImageName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = fmt.Errorf("failed to load image: %w", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if e.Translucency != nil {
 | 
			
		||||
		img.Translucency = *e.Translucency
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for opt, val := range e.Options {
 | 
			
		||||
		switch opt {
 | 
			
		||||
		case "scaleMode":
 | 
			
		||||
			img.ScaleMode = getImageScale(val)
 | 
			
		||||
		case "fillMode":
 | 
			
		||||
			img.FillMode = getImageFill(val)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	obj = img
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Canvas) BuildRectangleCanvas(
 | 
			
		||||
	e *Element,
 | 
			
		||||
	s ScreenHandler,
 | 
			
		||||
) (obj fyne.CanvasObject, err error) {
 | 
			
		||||
	fClr := color.RGBA{R: 255, G: 255, B: 255, A: 255}
 | 
			
		||||
	if e.FillColor != nil {
 | 
			
		||||
		fClr = color.RGBA{
 | 
			
		||||
			R: e.FillColor.Red,
 | 
			
		||||
			G: e.FillColor.Green,
 | 
			
		||||
			B: e.FillColor.Blue,
 | 
			
		||||
			A: e.FillColor.Alpha,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	rect := canvas.NewRectangle(fClr)
 | 
			
		||||
	if e.StrokeColor != nil {
 | 
			
		||||
		rect.StrokeColor = color.RGBA{
 | 
			
		||||
			R: e.StrokeColor.Red,
 | 
			
		||||
			G: e.StrokeColor.Green,
 | 
			
		||||
			B: e.StrokeColor.Blue,
 | 
			
		||||
			A: e.StrokeColor.Alpha,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	rect.StrokeWidth = 0
 | 
			
		||||
	rect.CornerRadius = 0
 | 
			
		||||
	if e.StrokeWidth != nil {
 | 
			
		||||
		rect.StrokeWidth = *e.StrokeWidth
 | 
			
		||||
	}
 | 
			
		||||
	if e.CornerRadius != nil {
 | 
			
		||||
		rect.CornerRadius = *e.CornerRadius
 | 
			
		||||
	}
 | 
			
		||||
	obj = rect
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Canvas) BuildTextCanvas(
 | 
			
		||||
	e *Element,
 | 
			
		||||
	s ScreenHandler,
 | 
			
		||||
) (obj fyne.CanvasObject, err error) {
 | 
			
		||||
	txtClr := color.RGBA{R: 255, G: 255, B: 255, A: 255}
 | 
			
		||||
	if e.TextColor != nil {
 | 
			
		||||
		txtClr = color.RGBA{
 | 
			
		||||
			R: e.TextColor.Red,
 | 
			
		||||
			G: e.TextColor.Green,
 | 
			
		||||
			B: e.TextColor.Blue,
 | 
			
		||||
			A: e.TextColor.Alpha,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	txt := canvas.NewText(e.Text, txtClr)
 | 
			
		||||
	if e.Binding != "" {
 | 
			
		||||
		bnd := s.GetBinding(e.Binding).(binding.String)
 | 
			
		||||
		bnd.AddListener(binding.NewDataListener(func() {
 | 
			
		||||
			txt.Text, _ = bnd.Get()
 | 
			
		||||
			txt.Refresh()
 | 
			
		||||
		}))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if e.TextSize != nil {
 | 
			
		||||
		txt.TextSize = *e.TextSize
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for opt, val := range e.Options {
 | 
			
		||||
		switch opt {
 | 
			
		||||
		case "alignment":
 | 
			
		||||
			txt.Alignment = getTextAlignment(val)
 | 
			
		||||
		case "textStyle":
 | 
			
		||||
			txt.TextStyle = getTextStyle(val)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	obj = txt
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getImageFill(v any) canvas.ImageFill {
 | 
			
		||||
	switch v.(string) {
 | 
			
		||||
	case "Stretch":
 | 
			
		||||
		return canvas.ImageFillStretch
 | 
			
		||||
	case "Contain":
 | 
			
		||||
		return canvas.ImageFillContain
 | 
			
		||||
	case "Original":
 | 
			
		||||
		return canvas.ImageFillOriginal
 | 
			
		||||
	}
 | 
			
		||||
	return canvas.ImageFillStretch
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getImageScale(v any) canvas.ImageScale {
 | 
			
		||||
	switch v.(string) {
 | 
			
		||||
	case "Smooth":
 | 
			
		||||
		return canvas.ImageScaleSmooth
 | 
			
		||||
	case "Pixels":
 | 
			
		||||
		return canvas.ImageScalePixels
 | 
			
		||||
	case "Fastest":
 | 
			
		||||
		return canvas.ImageScaleFastest
 | 
			
		||||
	}
 | 
			
		||||
	return canvas.ImageScaleSmooth
 | 
			
		||||
}
 | 
			
		||||
@@ -13,6 +13,18 @@ type Size struct {
 | 
			
		||||
	Height float32 `yaml:"height"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RelativeSize struct {
 | 
			
		||||
	Width  float32 `yaml:"width"`
 | 
			
		||||
	Height float32 `yaml:"height"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Color struct {
 | 
			
		||||
	Red   uint8 `yaml:"red"`
 | 
			
		||||
	Green uint8 `yaml:"green"`
 | 
			
		||||
	Blue  uint8 `yaml:"blue"`
 | 
			
		||||
	Alpha uint8 `yaml:"alpha"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Element represents the yaml description of a ui element
 | 
			
		||||
type Element struct {
 | 
			
		||||
	ID string `yaml:"id"`
 | 
			
		||||
@@ -20,6 +32,7 @@ type Element struct {
 | 
			
		||||
	Container Container `yaml:"container"`
 | 
			
		||||
	Layout    Layout    `yaml:"layout"`
 | 
			
		||||
	Widget    Widget    `yaml:"widget"`
 | 
			
		||||
	Canvas    Canvas    `yaml:"canvas"`
 | 
			
		||||
 | 
			
		||||
	// Border Layout Elements
 | 
			
		||||
	Top    *Element   `yaml:"top"`
 | 
			
		||||
@@ -45,6 +58,11 @@ type Element struct {
 | 
			
		||||
	// MinSize configuration
 | 
			
		||||
	MinSize *Size `yaml:"minSize"`
 | 
			
		||||
 | 
			
		||||
	// Relative Size configuration
 | 
			
		||||
	// Only valid for certain widgets:
 | 
			
		||||
	//	- ScoreDisplay
 | 
			
		||||
	RelativeSize *RelativeSize `yaml:"relativeSize"`
 | 
			
		||||
 | 
			
		||||
	// Widget Properties
 | 
			
		||||
	Text       string         `yaml:"text"`
 | 
			
		||||
	Localized  bool           `yaml:"localized"`
 | 
			
		||||
@@ -75,6 +93,10 @@ type Element struct {
 | 
			
		||||
	// Check Properties
 | 
			
		||||
	Checked bool `yaml:"checked"`
 | 
			
		||||
 | 
			
		||||
	// ScoreDisplay Properties
 | 
			
		||||
	Score     int `yaml:"score"`
 | 
			
		||||
	NumDigits int `yaml:"numDigits"`
 | 
			
		||||
 | 
			
		||||
	// Select Properties
 | 
			
		||||
	SelectOptionsBinding string   `yaml:"selectOptionsBinding"`
 | 
			
		||||
	SelectOptions        []string `yaml:"selectOptions"`
 | 
			
		||||
@@ -83,6 +105,19 @@ type Element struct {
 | 
			
		||||
	FixHorizontal bool `yaml:"fixHorizontal"`
 | 
			
		||||
	FixVertical   bool `yaml:"fixVertical"`
 | 
			
		||||
 | 
			
		||||
	// Canvas Properties
 | 
			
		||||
	FillColor    *Color   `yaml:"fillColor"`
 | 
			
		||||
	StrokeColor  *Color   `yaml:"strokeColor"`
 | 
			
		||||
	TextColor    *Color   `yaml:"textColor"`
 | 
			
		||||
	StrokeWidth  *float32 `yaml:"strokeWidth"`
 | 
			
		||||
	CornerRadius *float32 `yaml:"cornerRadius"`
 | 
			
		||||
	TextSize     *float32 `yaml:"textSize"`
 | 
			
		||||
	ImageName    string   `yaml:"imageName"`
 | 
			
		||||
	Translucency *float64 `yaml:"translucency"`
 | 
			
		||||
 | 
			
		||||
	// Widgets requiring a set of resources (e.g ScoreDisplay)
 | 
			
		||||
	ResourceSet map[string]string `yaml:"resourceSet"`
 | 
			
		||||
 | 
			
		||||
	// Handlers
 | 
			
		||||
	OnClicked           string `yaml:"onClicked"`
 | 
			
		||||
	OnUpClicked         string `yaml:"onUpClicked"`
 | 
			
		||||
@@ -128,6 +163,13 @@ func (e *Element) BuildUI(s ScreenHandler) (obj fyne.CanvasObject, err error) {
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if e.Canvas != "" {
 | 
			
		||||
		if baseObject, obj, err = e.Canvas.Build(e, s); err != nil {
 | 
			
		||||
			err = fmt.Errorf("failed to build canvas element: %w", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package screen
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"embed"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
@@ -35,6 +36,8 @@ type UIElement struct {
 | 
			
		||||
type ScreenHandler interface {
 | 
			
		||||
	RegisterElement(string, fyne.CanvasObject, fyne.CanvasObject)
 | 
			
		||||
 | 
			
		||||
	LoadAsset(string) (fs.File, error)
 | 
			
		||||
 | 
			
		||||
	GetLocalizer() *i18n.Localizer
 | 
			
		||||
	GetIcon(string) fyne.Resource
 | 
			
		||||
	GetBinding(string) binding.DataItem
 | 
			
		||||
@@ -133,6 +136,10 @@ func (d *TemplateScreenHandler) RegisterElement(
 | 
			
		||||
	d.elementsMap[name] = &UIElement{element, decorator}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *TemplateScreenHandler) LoadAsset(name string) (fs.File, error) {
 | 
			
		||||
	return nil, errors.New("not implemented")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *TemplateScreenHandler) GetElement(name string) *UIElement {
 | 
			
		||||
	elem, ok := d.elementsMap[name]
 | 
			
		||||
	if !ok {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@ package screen
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"bitbucket.org/hevanto/ui/uiwidget"
 | 
			
		||||
@@ -170,6 +172,9 @@ const (
 | 
			
		||||
 | 
			
		||||
	RichText Widget = "RichText"
 | 
			
		||||
 | 
			
		||||
	// ScoreDisplay Widget
 | 
			
		||||
	ScoreDisplay Widget = "ScoreDisplay"
 | 
			
		||||
 | 
			
		||||
	// Select Widget
 | 
			
		||||
	Select Widget = "Select"
 | 
			
		||||
 | 
			
		||||
@@ -232,6 +237,8 @@ func (w Widget) Build(
 | 
			
		||||
		widget, err = w.buildListWidget(e, s)
 | 
			
		||||
	case RichText:
 | 
			
		||||
		widget, err = w.buildRichTextWidget(e, s)
 | 
			
		||||
	case ScoreDisplay:
 | 
			
		||||
		widget, err = w.buildScoreDisplayWidget(e, s)
 | 
			
		||||
	case Select:
 | 
			
		||||
		widget, err = w.buildSelectWidget(e, s)
 | 
			
		||||
	case SelectEntry:
 | 
			
		||||
@@ -528,6 +535,66 @@ func (w Widget) buildRichTextWidget(
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w Widget) buildScoreDisplayWidget(
 | 
			
		||||
	e *Element,
 | 
			
		||||
	s ScreenHandler,
 | 
			
		||||
) (c fyne.CanvasObject, err error) {
 | 
			
		||||
	var sd *uiwidget.ScoreDisplay
 | 
			
		||||
 | 
			
		||||
	resources := &uiwidget.ScoreDisplayResources{}
 | 
			
		||||
	for k, v := range e.ResourceSet {
 | 
			
		||||
		switch k {
 | 
			
		||||
		case "Empty":
 | 
			
		||||
			resources.Empty, err = AssetToResource(s, v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			var digit int
 | 
			
		||||
			digit, err = strconv.Atoi(k)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Printf("failed to parse score digit: %v", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if digit > 9 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			resources.Digits[digit], err = AssetToResource(s, v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var relSize, fixedSize *fyne.Size
 | 
			
		||||
 | 
			
		||||
	if e.RelativeSize != nil {
 | 
			
		||||
		relSize = &fyne.Size{
 | 
			
		||||
			Width:  e.RelativeSize.Width,
 | 
			
		||||
			Height: e.RelativeSize.Height,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if e.ElementSize != nil {
 | 
			
		||||
		fixedSize = &fyne.Size{
 | 
			
		||||
			Width:  e.ElementSize.Width,
 | 
			
		||||
			Height: e.ElementSize.Height,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if e.Binding != "" {
 | 
			
		||||
		sd = uiwidget.NewScoreDisplayWithData(
 | 
			
		||||
			s.GetBinding(e.Binding).(binding.Int),
 | 
			
		||||
			e.NumDigits, resources, relSize, fixedSize)
 | 
			
		||||
	} else {
 | 
			
		||||
		sd = uiwidget.NewScoreDisplay(
 | 
			
		||||
			e.Score, e.NumDigits, resources,
 | 
			
		||||
			relSize, fixedSize)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c = sd
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w Widget) buildSelectWidget(
 | 
			
		||||
	e *Element,
 | 
			
		||||
	s ScreenHandler,
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ package ui
 | 
			
		||||
import (
 | 
			
		||||
	"embed"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"log"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
@@ -26,6 +27,7 @@ type ScreenHandler struct {
 | 
			
		||||
	screenDefinition *screen.Screen
 | 
			
		||||
	localizer        *i18n.Localizer
 | 
			
		||||
	viewModel        BaseViewModel
 | 
			
		||||
	assetLoader      func(string) (fs.File, error)
 | 
			
		||||
 | 
			
		||||
	// Map of screen elements that got an id assigned
 | 
			
		||||
	exportedElements map[string]*screen.UIElement
 | 
			
		||||
@@ -60,6 +62,7 @@ func NewScreenHandler(
 | 
			
		||||
	screenName string,
 | 
			
		||||
	viewModel BaseViewModel,
 | 
			
		||||
	localizer *i18n.Localizer,
 | 
			
		||||
	assetLoader func(string) (fs.File, error),
 | 
			
		||||
) (h *ScreenHandler, err error) {
 | 
			
		||||
	h = new(ScreenHandler)
 | 
			
		||||
 | 
			
		||||
@@ -72,6 +75,7 @@ func NewScreenHandler(
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	h.screenDefinition = def
 | 
			
		||||
	h.assetLoader = assetLoader
 | 
			
		||||
 | 
			
		||||
	h.exportedElements = make(map[string]*screen.UIElement)
 | 
			
		||||
	h.listItemTemplates = make(map[string]screen.ListItemTemplateFn)
 | 
			
		||||
@@ -105,6 +109,11 @@ func (h *ScreenHandler) RegisterElement(
 | 
			
		||||
	h.exportedElements[id] = &screen.UIElement{Object: elem, Decorator: decorator}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *ScreenHandler) LoadAsset(name string) (fh fs.File, err error) {
 | 
			
		||||
	fh, err = h.assetLoader(name)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegisterListItemTemplate registers a ListItemTemplate with the ScreenHandler.
 | 
			
		||||
//
 | 
			
		||||
// Use this function to register the templates for the lists used in the
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										233
									
								
								uiwidget/scoredisplay.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								uiwidget/scoredisplay.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,233 @@
 | 
			
		||||
package uiwidget
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fyne.io/fyne/v2"
 | 
			
		||||
	"fyne.io/fyne/v2/canvas"
 | 
			
		||||
	"fyne.io/fyne/v2/container"
 | 
			
		||||
	"fyne.io/fyne/v2/data/binding"
 | 
			
		||||
	"fyne.io/fyne/v2/widget"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ScoreDisplayResources struct {
 | 
			
		||||
	Empty  fyne.Resource
 | 
			
		||||
	Digits [10]fyne.Resource
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewScoreDisplayResources(
 | 
			
		||||
	empty fyne.Resource,
 | 
			
		||||
	digits [10]fyne.Resource,
 | 
			
		||||
) *ScoreDisplayResources {
 | 
			
		||||
	return &ScoreDisplayResources{
 | 
			
		||||
		Empty:  empty,
 | 
			
		||||
		Digits: digits,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ScoreDisplay struct {
 | 
			
		||||
	widget.BaseWidget
 | 
			
		||||
 | 
			
		||||
	Score int
 | 
			
		||||
 | 
			
		||||
	scoreBinding binding.Int
 | 
			
		||||
	numDigits    int
 | 
			
		||||
	digits       []*canvas.Image
 | 
			
		||||
	resources    *ScoreDisplayResources
 | 
			
		||||
	layout       *ScoreDisplayLayout
 | 
			
		||||
	cont         *fyne.Container
 | 
			
		||||
	relSize      *fyne.Size
 | 
			
		||||
	fixedSize    *fyne.Size
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewScoreDisplay(
 | 
			
		||||
	score int,
 | 
			
		||||
	numDigits int,
 | 
			
		||||
	resources *ScoreDisplayResources,
 | 
			
		||||
	relSize *fyne.Size,
 | 
			
		||||
	fizedSize *fyne.Size,
 | 
			
		||||
) *ScoreDisplay {
 | 
			
		||||
	sd := new(ScoreDisplay)
 | 
			
		||||
	sd.Score = score
 | 
			
		||||
	sd.numDigits = numDigits
 | 
			
		||||
	sd.resources = resources
 | 
			
		||||
	sd.digits = make([]*canvas.Image, numDigits)
 | 
			
		||||
	for i := 0; i < numDigits; i++ {
 | 
			
		||||
		sd.digits[i] = canvas.NewImageFromResource(resources.Empty)
 | 
			
		||||
	}
 | 
			
		||||
	sd.relSize = relSize
 | 
			
		||||
	sd.fixedSize = fizedSize
 | 
			
		||||
	sd.ExtendBaseWidget(sd)
 | 
			
		||||
	sd.updateScore()
 | 
			
		||||
	return sd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewScoreDisplayWithData(
 | 
			
		||||
	scoreBinding binding.Int,
 | 
			
		||||
	numDigits int,
 | 
			
		||||
	resources *ScoreDisplayResources,
 | 
			
		||||
	relSize *fyne.Size,
 | 
			
		||||
	fixedSize *fyne.Size,
 | 
			
		||||
) *ScoreDisplay {
 | 
			
		||||
	sd := new(ScoreDisplay)
 | 
			
		||||
	sd.scoreBinding = scoreBinding
 | 
			
		||||
	sd.numDigits = numDigits
 | 
			
		||||
	sd.resources = resources
 | 
			
		||||
	sd.digits = make([]*canvas.Image, numDigits)
 | 
			
		||||
	for i := 0; i < numDigits; i++ {
 | 
			
		||||
		sd.digits[i] = canvas.NewImageFromResource(resources.Empty)
 | 
			
		||||
	}
 | 
			
		||||
	sd.relSize = relSize
 | 
			
		||||
	sd.fixedSize = fixedSize
 | 
			
		||||
	sd.ExtendBaseWidget(sd)
 | 
			
		||||
	sd.scoreBinding.AddListener(sd)
 | 
			
		||||
	return sd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sd *ScoreDisplay) CreateRenderer() fyne.WidgetRenderer {
 | 
			
		||||
	sd.layout = NewScoreDisplayLayout(sd.relSize, sd.fixedSize)
 | 
			
		||||
	sd.cont = container.New(sd.layout)
 | 
			
		||||
	for i := sd.numDigits; i > 0; i-- {
 | 
			
		||||
		digit := sd.digits[i-1]
 | 
			
		||||
		sd.cont.Add(digit)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return widget.NewSimpleRenderer(sd.cont)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sd *ScoreDisplay) Refresh() {
 | 
			
		||||
	sd.updateScore()
 | 
			
		||||
	sd.BaseWidget.Refresh()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sd *ScoreDisplay) Size() fyne.Size {
 | 
			
		||||
	if sd.layout == nil {
 | 
			
		||||
		if sd.fixedSize != nil {
 | 
			
		||||
			return *sd.fixedSize
 | 
			
		||||
		} else if sd.relSize != nil {
 | 
			
		||||
			canvas := fyne.CurrentApp().Driver().AllWindows()[0].Canvas()
 | 
			
		||||
			if canvas == nil {
 | 
			
		||||
				return fyne.NewSize(0, 0)
 | 
			
		||||
			}
 | 
			
		||||
			size := fyne.NewSize(
 | 
			
		||||
				canvas.Size().Width*sd.relSize.Width,
 | 
			
		||||
				canvas.Size().Height*sd.relSize.Height,
 | 
			
		||||
			)
 | 
			
		||||
			return size
 | 
			
		||||
		} else {
 | 
			
		||||
			return fyne.NewSize(0, 0)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	return sd.layout.CurrentSize()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sd *ScoreDisplay) MinSize() fyne.Size {
 | 
			
		||||
	return sd.Size()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sd *ScoreDisplay) DataChanged() {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	sd.Score, err = sd.scoreBinding.Get()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	sd.Refresh()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sd *ScoreDisplay) updateScore() {
 | 
			
		||||
	aspect := float32(0)
 | 
			
		||||
	score := sd.Score
 | 
			
		||||
	for i := 0; i < sd.numDigits; i++ {
 | 
			
		||||
		digit := score % 10
 | 
			
		||||
		score /= 10
 | 
			
		||||
		if i != 0 && digit == 0 && score == 0 {
 | 
			
		||||
			sd.digits[i].Resource = sd.resources.Empty
 | 
			
		||||
		} else {
 | 
			
		||||
			sd.digits[i].Resource = sd.resources.Digits[digit]
 | 
			
		||||
		}
 | 
			
		||||
		sd.digits[i].ScaleMode = canvas.ImageScaleSmooth
 | 
			
		||||
		sd.digits[i].FillMode = canvas.ImageFillContain
 | 
			
		||||
		sd.digits[i].Resize(fyne.NewSize(200, 300))
 | 
			
		||||
		aspect += sd.digits[i].Aspect()
 | 
			
		||||
	}
 | 
			
		||||
	if sd.layout != nil {
 | 
			
		||||
		sd.layout.setAspect(aspect)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ScoreDisplayLayout struct {
 | 
			
		||||
	canvas      fyne.Canvas
 | 
			
		||||
	relSize     *fyne.Size
 | 
			
		||||
	fixedSize   *fyne.Size
 | 
			
		||||
	aspect      float32
 | 
			
		||||
	currentSize fyne.Size
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewScoreDisplayLayout(relSize *fyne.Size, fixedSize *fyne.Size) *ScoreDisplayLayout {
 | 
			
		||||
	return &ScoreDisplayLayout{
 | 
			
		||||
		relSize:   relSize,
 | 
			
		||||
		fixedSize: fixedSize,
 | 
			
		||||
		aspect:    float32(0),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sdl *ScoreDisplayLayout) setAspect(a float32) {
 | 
			
		||||
	sdl.aspect = a
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sdl *ScoreDisplayLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
 | 
			
		||||
	if sdl.fixedSize != nil {
 | 
			
		||||
		return *sdl.fixedSize
 | 
			
		||||
	}
 | 
			
		||||
	return sdl.calculateSize(objects)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sdl *ScoreDisplayLayout) Layout(objects []fyne.CanvasObject, containerSize fyne.Size) {
 | 
			
		||||
	size := sdl.calculateSize(objects)
 | 
			
		||||
	sdl.currentSize = size
 | 
			
		||||
 | 
			
		||||
	numObjects := len(objects)
 | 
			
		||||
	objSize := fyne.NewSize(
 | 
			
		||||
		size.Width/float32(numObjects),
 | 
			
		||||
		size.Height)
 | 
			
		||||
 | 
			
		||||
	pos := fyne.NewPos(0, 0)
 | 
			
		||||
	for _, o := range objects {
 | 
			
		||||
		o.Resize(objSize)
 | 
			
		||||
		o.Move(pos)
 | 
			
		||||
		pos = pos.Add(fyne.NewPos(objSize.Width, 0))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sdl *ScoreDisplayLayout) CurrentSize() fyne.Size {
 | 
			
		||||
	return sdl.currentSize
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sdl *ScoreDisplayLayout) calculateSize(objects []fyne.CanvasObject) fyne.Size {
 | 
			
		||||
	size := fyne.NewSize(0, 0)
 | 
			
		||||
	if sdl.relSize != nil {
 | 
			
		||||
		if sdl.canvas == nil && len(objects) > 0 {
 | 
			
		||||
			sdl.canvas = fyne.CurrentApp().Driver().CanvasForObject(objects[0])
 | 
			
		||||
		}
 | 
			
		||||
		if sdl.canvas != nil {
 | 
			
		||||
			size = fyne.NewSize(
 | 
			
		||||
				sdl.canvas.Size().Width*sdl.relSize.Width,
 | 
			
		||||
				sdl.canvas.Size().Height*sdl.relSize.Height,
 | 
			
		||||
			)
 | 
			
		||||
		}
 | 
			
		||||
	} else if sdl.fixedSize != nil {
 | 
			
		||||
		size = *sdl.fixedSize
 | 
			
		||||
		sdl.currentSize = size
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if sdl.aspect > 0 {
 | 
			
		||||
		curAspect := size.Width / size.Height
 | 
			
		||||
		if curAspect > sdl.aspect {
 | 
			
		||||
			size.Width = size.Height * sdl.aspect
 | 
			
		||||
		} else {
 | 
			
		||||
			size.Height = size.Width / sdl.aspect
 | 
			
		||||
		}
 | 
			
		||||
		sdl.currentSize = size
 | 
			
		||||
	}
 | 
			
		||||
	return size
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user