Skip to content

Commit

Permalink
Marquee: Add fix for alignment of non-scrolling child (#298)
Browse files Browse the repository at this point in the history
This allows for alignment of the child when it fits fully, this is especially useful for when text changes and may or may not fit.

It should work with other child objects, but unit tests should be used to confirm the intended behavior.

Added a unit test for "center" and "end" alignment. Basically just copied the TestMarqueeNoScrollHorizontal and lengthened width and height of the Marquee and added alignment.
  • Loading branch information
rs7q5 authored Jul 6, 2022
1 parent f204aae commit ee8b49a
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 4 deletions.
7 changes: 7 additions & 0 deletions docs/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| --- | --- | --- | --- |
Expand All @@ -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
```
Expand Down
22 changes: 20 additions & 2 deletions render/marquee.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down
90 changes: 90 additions & 0 deletions render/marquee_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 7 additions & 2 deletions runtime/modules/render_runtime/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit ee8b49a

Please sign in to comment.