diff --git a/docs/widgets.md b/docs/widgets.md index 0023bdd8af..bd1ad3d296 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -192,6 +192,12 @@ If the child's width fits fully, it will not scroll. The `offset_start` and `offset_end` parameters control the position of the child in the beginning and the end of the animation. +Alignment for a child that fits fully along the horizontal/vertical axis is controlled by passing +one of the following `align` values: +- `"start"`: place child at the left/top +- `"end"`: place child at the right/bottom +- `"center"`: place child at the center + #### Attributes | Name | Type | Description | Required | | --- | --- | --- | --- | @@ -201,6 +207,7 @@ of the child in the beginning and the end of the animation. | `offset_start` | `int` | Position of child at beginning of animation | N | | `offset_end` | `int` | Position of child at end of animation | N | | `scroll_direction` | `str` | Direction to scroll, 'vertical' or 'horizontal', default is horizontal | N | +| `align` | `str` | Alignment of child that fits fully, default is 'start' | N | #### Example ``` diff --git a/render/marquee.go b/render/marquee.go index d0ce183e66..448b08732d 100644 --- a/render/marquee.go +++ b/render/marquee.go @@ -21,12 +21,19 @@ import ( // The `offset_start` and `offset_end` parameters control the position // of the child in the beginning and the end of the animation. // +// Alignment for a child that fits fully along the horizontal/vertical axis is controlled by passing +// one of the following `align` values: +// - `"start"`: place child at the left/top +// - `"end"`: place child at the right/bottom +// - `"center"`: place child at the center + // DOC(Child): Widget to potentially scroll // DOC(Width): Width of the Marquee, required for horizontal // DOC(Height): Height of the Marquee, required for vertical // DOC(OffsetStart): Position of child at beginning of animation // DOC(OffsetEnd): Position of child at end of animation // DOC(ScrollDirection): Direction to scroll, 'vertical' or 'horizontal', default is horizontal +// DOC(Align): alignment when contents fit on screen, 'start', 'center' or 'end', default is start // // EXAMPLE BEGIN // render.Marquee( @@ -44,6 +51,7 @@ type Marquee struct { OffsetStart int `starlark:"offset_start"` OffsetEnd int `starlark:"offset_end"` ScrollDirection string `starlark:"scroll_direction"` + Align string `starlark:"align"` } func (m Marquee) FrameCount() int { @@ -112,10 +120,20 @@ func (m Marquee) Paint(bounds image.Rectangle, frameIdx int) image.Image { loopIdx := cw + offstart endIdx := cw + offstart + size - offend + align := 0.0 //default is align="start" var offset int if cw <= size { // child fits entirely and we don't want to scroll it anyway offset = 0 + + //modify alignment + if m.Align=="center" { + align = 0.5 + offset = size/2 + } else if m.Align=="end" { + align = 1.0 + offset = size + } } else if frameIdx <= loopIdx { // first scroll child out of view offset = offstart - frameIdx @@ -131,10 +149,10 @@ func (m Marquee) Paint(bounds image.Rectangle, frameIdx int) image.Image { var dc *gg.Context if m.isVertical() { dc = gg.NewContext(im.Bounds().Dx(), m.Height) - dc.DrawImage(im, 0, offset) + dc.DrawImageAnchored(im,0,offset,0,align) } else { dc = gg.NewContext(m.Width, im.Bounds().Dy()) - dc.DrawImage(im, offset, 0) + dc.DrawImageAnchored(im,offset,0,align,0) } return dc.Image() diff --git a/render/marquee_test.go b/render/marquee_test.go index e83931ec18..f0ce6fd19d 100644 --- a/render/marquee_test.go +++ b/render/marquee_test.go @@ -49,6 +49,96 @@ func TestMarqueeNoScrollHorizontal(t *testing.T) { }, imv)) } +func TestMarqueeNoScrollAlignCenter(t *testing.T) { + m := Marquee{ + Width: 8, + Child: Row{ + Children: []Widget{ + Box{Width: 3, Height: 3, Color: color.RGBA{0xff, 0, 0, 0xff}}, + Box{Width: 2, Height: 2, Color: color.RGBA{0, 0xff, 0, 0xff}}, + Box{Width: 1, Height: 1, Color: color.RGBA{0, 0, 0xff, 0xff}}, + }, + }, + Align: "center", + } + + mv := Marquee{ + Height: 5, + Child: Row{ + Children: []Widget{ + Box{Width: 3, Height: 3, Color: color.RGBA{0xff, 0, 0, 0xff}}, + Box{Width: 2, Height: 2, Color: color.RGBA{0, 0xff, 0, 0xff}}, + Box{Width: 1, Height: 1, Color: color.RGBA{0, 0, 0xff, 0xff}}, + }, + }, + ScrollDirection: "vertical", + Align: "center", + } + + // Child fits so there's just 1 single frame + assert.Equal(t, 1, m.FrameCount()) + assert.Equal(t, 1, mv.FrameCount()) + im := m.Paint(image.Rect(0, 0, 100, 100), 0) + imv := mv.Paint(image.Rect(0, 0, 100, 100), 0) + assert.Equal(t, nil, checkImage([]string{ + ".rrrggb.", + ".rrrgg..", + ".rrr....", + }, im)) + assert.Equal(t, nil, checkImage([]string{ + "......", + "rrrggb", + "rrrgg.", + "rrr...", + "......", + }, imv)) +} + +func TestMarqueeNoScrollAlignEnd(t *testing.T) { + m := Marquee{ + Width: 8, + Child: Row{ + Children: []Widget{ + Box{Width: 3, Height: 3, Color: color.RGBA{0xff, 0, 0, 0xff}}, + Box{Width: 2, Height: 2, Color: color.RGBA{0, 0xff, 0, 0xff}}, + Box{Width: 1, Height: 1, Color: color.RGBA{0, 0, 0xff, 0xff}}, + }, + }, + Align: "end", + } + + mv := Marquee{ + Height: 5, + Child: Row{ + Children: []Widget{ + Box{Width: 3, Height: 3, Color: color.RGBA{0xff, 0, 0, 0xff}}, + Box{Width: 2, Height: 2, Color: color.RGBA{0, 0xff, 0, 0xff}}, + Box{Width: 1, Height: 1, Color: color.RGBA{0, 0, 0xff, 0xff}}, + }, + }, + ScrollDirection: "vertical", + Align: "end", + } + + // Child fits so there's just 1 single frame + assert.Equal(t, 1, m.FrameCount()) + assert.Equal(t, 1, mv.FrameCount()) + im := m.Paint(image.Rect(0, 0, 100, 100), 0) + imv := mv.Paint(image.Rect(0, 0, 100, 100), 0) + assert.Equal(t, nil, checkImage([]string{ + "..rrrggb", + "..rrrgg.", + "..rrr...", + }, im)) + assert.Equal(t, nil, checkImage([]string{ + "......", + "......", + "rrrggb", + "rrrgg.", + "rrr...", + }, imv)) +} + // The addition of OffsetStart and OffsetEnd changes the default // behaviour of Marquee. Passing start==width and end==0 mimics the // old default. diff --git a/runtime/modules/render_runtime/generated.go b/runtime/modules/render_runtime/generated.go index 93c9e11bdc..2a8187b16d 100644 --- a/runtime/modules/render_runtime/generated.go +++ b/runtime/modules/render_runtime/generated.go @@ -620,6 +620,7 @@ func newMarquee( offset_start starlark.Int offset_end starlark.Int scroll_direction starlark.String + align starlark.String ) if err := starlark.UnpackArgs( @@ -631,6 +632,7 @@ func newMarquee( "offset_start?", &offset_start, "offset_end?", &offset_end, "scroll_direction?", &scroll_direction, + "align?", & align, ); err != nil { return nil, fmt.Errorf("unpacking arguments for Marquee: %s", err) } @@ -659,6 +661,8 @@ func newMarquee( w.ScrollDirection = scroll_direction.GoString() + w.Align = align.GoString() + return w, nil } @@ -668,7 +672,7 @@ func (w *Marquee) AsRenderWidget() render.Widget { func (w *Marquee) AttrNames() []string { return []string{ - "child", "width", "height", "offset_start", "offset_end", "scroll_direction", + "child", "width", "height", "offset_start", "offset_end", "scroll_direction", "align", } } @@ -698,7 +702,8 @@ func (w *Marquee) Attr(name string) (starlark.Value, error) { case "scroll_direction": return starlark.String(w.ScrollDirection), nil - + case "align": + return starlark.String(w.Align), nil default: return nil, nil }