Added score display

This commit is contained in:
Maarten Heremans
2024-04-19 16:44:36 +02:00
parent 0f3932b810
commit cb9515375a
10 changed files with 641 additions and 5 deletions

46
screen/assets.go Normal file
View 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
View 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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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,