Added Parent to Controls.

Fixed issue where events of partially and invisible controls where incorrectly handled.
This commit is contained in:
Sander Schobers 2019-04-11 23:38:32 +02:00
parent 3e7e2ab682
commit dbc017507c
12 changed files with 217 additions and 164 deletions

View File

@ -45,8 +45,8 @@ type BufferControl struct {
buffer Buffer buffer Buffer
} }
func (c *BufferControl) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) { func (c *BufferControl) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
c.ControlBase.Arrange(ctx, bounds, offset) c.ControlBase.Arrange(ctx, bounds, offset, parent)
c.buffer.Update(ctx, bounds.Size()) c.buffer.Update(ctx, bounds.Size())
} }

View File

@ -19,18 +19,18 @@ func (c *ContainerBase) AddChild(child ...Control) {
c.Children = append(c.Children, child...) c.Children = append(c.Children, child...)
} }
func (c *ContainerBase) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) { func (c *ContainerBase) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
for _, child := range c.Children { for _, child := range c.Children {
child.Arrange(ctx, bounds, offset) child.Arrange(ctx, bounds, offset, c)
} }
c.ControlBase.Arrange(ctx, bounds, offset) c.ControlBase.Arrange(ctx, bounds, offset, parent)
} }
func (c *ContainerBase) Handle(ctx Context, e Event) { func (c *ContainerBase) Handle(ctx Context, e Event) {
c.ControlBase.Handle(ctx, e)
for _, child := range c.Children { for _, child := range c.Children {
child.Handle(ctx, e) child.Handle(ctx, e)
} }
c.ControlBase.Handle(ctx, e)
} }
func (c *ContainerBase) Render(ctx Context) { func (c *ContainerBase) Render(ctx Context) {

View File

@ -5,17 +5,20 @@ import (
) )
type Control interface { type Control interface {
Arrange(Context, geom.RectangleF32, geom.PointF32) Arrange(Context, geom.RectangleF32, geom.PointF32, Control)
DesiredSize(Context) geom.PointF32 DesiredSize(Context) geom.PointF32
Handle(Context, Event) Handle(Context, Event)
Render(Context) Render(Context)
Bounds() geom.RectangleF32 Bounds() geom.RectangleF32
IsInBounds(p geom.PointF32) bool
IsOver() bool
Offset() geom.PointF32 Offset() geom.PointF32
OnClick(ClickFn) OnClick(ClickFn)
OnDragStart(DragStartFn) OnDragStart(DragStartFn)
OnDragMove(DragMoveFn) OnDragMove(DragMoveFn)
OnDragEnd(DragEndFn) OnDragEnd(DragEndFn)
Parent() Control
} }
type RootControl interface { type RootControl interface {

View File

@ -18,6 +18,7 @@ var _ Control = &ControlBase{}
type ControlBase struct { type ControlBase struct {
bounds geom.RectangleF32 bounds geom.RectangleF32
offset geom.PointF32 offset geom.PointF32
parent Control
dragStart *geom.PointF32 dragStart *geom.PointF32
over bool over bool
pressed bool pressed bool
@ -31,55 +32,59 @@ type ControlBase struct {
Font FontStyle Font FontStyle
} }
func (c *ControlBase) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) { func (c *ControlBase) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
c.bounds = bounds c.bounds = bounds
c.offset = offset c.offset = offset
c.parent = parent
} }
func (c *ControlBase) Bounds() geom.RectangleF32 { func (c *ControlBase) Bounds() geom.RectangleF32 { return c.bounds }
return c.bounds
}
func (c *ControlBase) DesiredSize(Context) geom.PointF32 { func (c *ControlBase) DesiredSize(Context) geom.PointF32 { return geom.ZeroPtF32 }
return geom.ZeroPtF32
}
func (c *ControlBase) mousePos(e MouseEvent) geom.PointF32 {
return e.Pos().Sub(c.offset)
}
func (c *ControlBase) Handle(ctx Context, e Event) { func (c *ControlBase) Handle(ctx Context, e Event) {
var over = func(e MouseEvent) bool { var over = func(e MouseEvent) bool {
c.over = c.mousePos(e).In(c.bounds) pos := e.Pos()
return c.over if !c.IsInBounds(pos) {
return false
}
parent := c.Parent()
for parent != nil {
if !parent.IsInBounds(pos) {
return false
}
parent = parent.Parent()
}
return true
} }
switch e := e.(type) { switch e := e.(type) {
case *MouseMoveEvent: case *MouseMoveEvent:
over(e.MouseEvent) c.over = over(e.MouseEvent)
if c.pressed { if c.pressed {
if c.dragStart == nil { if c.dragStart == nil {
var start = c.mousePos(e.MouseEvent) var start = c.ToControlPosition(e.Pos())
c.dragStart = &start c.dragStart = &start
if c.onDragStart != nil { if c.onDragStart != nil {
c.onDragStart(ctx, c, start) c.onDragStart(ctx, c, start)
} }
} else { } else {
var start = *c.dragStart var start = *c.dragStart
var move = c.mousePos(e.MouseEvent) var move = c.ToControlPosition(e.Pos())
if c.onDragMove != nil { if c.onDragMove != nil {
c.onDragMove(ctx, c, start, move) c.onDragMove(ctx, c, start, move)
} }
} }
} }
case *MouseButtonDownEvent: case *MouseButtonDownEvent:
if over(e.MouseEvent) && e.Button == MouseButtonLeft { c.over = over(e.MouseEvent)
if c.over && e.Button == MouseButtonLeft {
c.pressed = true c.pressed = true
} }
case *MouseButtonUpEvent: case *MouseButtonUpEvent:
if e.Button == MouseButtonLeft { if e.Button == MouseButtonLeft {
if c.dragStart != nil { if c.dragStart != nil {
var start = *c.dragStart var start = *c.dragStart
var end = c.mousePos(e.MouseEvent) var end = c.ToControlPosition(e.Pos())
c.dragStart = nil c.dragStart = nil
if c.onDragEnd != nil { if c.onDragEnd != nil {
c.onDragEnd(ctx, c, start, end) c.onDragEnd(ctx, c, start, end)
@ -87,7 +92,7 @@ func (c *ControlBase) Handle(ctx Context, e Event) {
} }
if c.pressed { if c.pressed {
if c.onClick != nil { if c.onClick != nil {
c.onClick(ctx, c, c.mousePos(e.MouseEvent), e.Button) c.onClick(ctx, c, c.ToControlPosition(e.Pos()), e.Button)
} }
} }
c.pressed = false c.pressed = false
@ -111,15 +116,26 @@ func (c *ControlBase) FontName(ctx Context) string {
return name return name
} }
func (c *ControlBase) IsInBounds(p geom.PointF32) bool {
bounds := c.bounds
if bounds.Min.X < 0 {
bounds.Min.X = 0
}
if bounds.Min.Y < 0 {
bounds.Min.Y = 0
}
return c.ToControlPosition(p).In(c.bounds)
}
func (c *ControlBase) IsOver() bool { return c.over } func (c *ControlBase) IsOver() bool { return c.over }
func (c *ControlBase) Parent() Control { return c.parent }
func (c *ControlBase) IsPressed() bool { return c.pressed } func (c *ControlBase) IsPressed() bool { return c.pressed }
func (c *ControlBase) Offset() geom.PointF32 { return c.offset } func (c *ControlBase) Offset() geom.PointF32 { return c.offset }
func (c *ControlBase) OnClick(fn ClickFn) { func (c *ControlBase) OnClick(fn ClickFn) { c.onClick = fn }
c.onClick = fn
}
func (c *ControlBase) OnDragStart(fn DragStartFn) { func (c *ControlBase) OnDragStart(fn DragStartFn) {
c.onDragStart = fn c.onDragStart = fn
@ -129,6 +145,8 @@ func (c *ControlBase) OnDragMove(fn DragMoveFn) {
c.onDragMove = fn c.onDragMove = fn
} }
func (c *ControlBase) OnDragEnd(fn DragEndFn) { c.onDragEnd = fn }
func (c *ControlBase) Render(Context) {} func (c *ControlBase) Render(Context) {}
func (c *ControlBase) RenderBackground(ctx Context) { func (c *ControlBase) RenderBackground(ctx Context) {
@ -146,3 +164,5 @@ func (c *ControlBase) RenderOutline(ctx Context) {
} }
ctx.Renderer().Rectangle(c.bounds.Inset(.5*width), color, width) ctx.Renderer().Rectangle(c.bounds.Inset(.5*width), color, width)
} }
func (c *ControlBase) ToControlPosition(p geom.PointF32) geom.PointF32 { return p.Sub(c.offset) }

View File

@ -15,6 +15,7 @@ type overflow struct {
desired geom.PointF32 desired geom.PointF32
bounds geom.RectangleF32 bounds geom.RectangleF32
offset geom.PointF32 offset geom.PointF32
parent Control
content Buffer content Buffer
hor *Scrollbar hor *Scrollbar
@ -58,31 +59,32 @@ func (o *overflow) doOnVisibleBars(fn func(bar *Scrollbar)) {
} }
} }
func (o *overflow) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) { func (o *overflow) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
o.barWidth = ctx.Style().Dimensions.ScrollbarWidth o.barWidth = ctx.Style().Dimensions.ScrollbarWidth
o.desired = o.Content.DesiredSize(ctx) o.desired = o.Content.DesiredSize(ctx)
o.bounds = bounds o.bounds = bounds
o.offset = offset o.offset = offset
o.parent = parent
var hor, ver = o.shouldScroll(bounds) var hor, ver = o.shouldScroll(bounds)
var contentX, contentY float32 = 0, 0 var contentX, contentY float32 = 0, 0
var contentW, contentH = bounds.Dx(), bounds.Dy() var contentW, contentH = bounds.Dx(), bounds.Dy()
if hor { if hor {
contentX -= o.hor.Offset contentX -= o.hor.ContentOffset
contentH = geom.Max32(0, contentH-o.barWidth) contentH = geom.Max32(0, contentH-o.barWidth)
} }
if ver { if ver {
contentY -= o.ver.Offset contentY -= o.ver.ContentOffset
contentW = geom.Max32(0, contentW-o.barWidth) contentW = geom.Max32(0, contentW-o.barWidth)
} }
o.Content.Arrange(ctx, geom.RectF32(contentX, contentY, contentW, contentH), offset.Add(bounds.Min)) o.Content.Arrange(ctx, geom.RectF32(contentX, contentY, contentW, contentH), offset.Add(bounds.Min), o)
if hor { if hor {
o.hor.Content = o.desired.X o.hor.ContentLength = o.desired.X
o.hor.Arrange(ctx, geom.RectF32(bounds.Min.X, bounds.Min.Y+contentH, bounds.Min.X+contentW, bounds.Max.Y), offset) o.hor.Arrange(ctx, geom.RectF32(bounds.Min.X, bounds.Min.Y+contentH, bounds.Min.X+contentW, bounds.Max.Y), offset, o)
} }
if ver { if ver {
o.ver.Content = o.desired.Y o.ver.ContentLength = o.desired.Y
o.ver.Arrange(ctx, geom.RectF32(bounds.Min.X+contentW, bounds.Min.Y, bounds.Max.X, bounds.Max.Y), offset) o.ver.Arrange(ctx, geom.RectF32(bounds.Min.X+contentW, bounds.Min.Y, bounds.Max.X, bounds.Max.Y), offset, o)
} }
} }
@ -108,15 +110,19 @@ func (o *overflow) Handle(ctx Context, e Event) {
content.Min = contentO content.Min = contentO
content.Max = content.Max.Add(contentO) content.Max = content.Max.Add(contentO)
if e.MouseWheel != 0 && e.Pos().In(content) { if e.MouseWheel != 0 && e.Pos().In(content) {
o.ver.Offset = geom.Max32(0, geom.Min32(o.ver.Content-content.Dy(), o.ver.Offset-36*e.MouseWheel)) o.ver.ContentOffset = geom.Max32(0, geom.Min32(o.ver.ContentLength-content.Dy(), o.ver.ContentOffset-36*e.MouseWheel))
} }
} }
} }
o.Content.Handle(ctx, e) o.Content.Handle(ctx, e)
} }
func (o *overflow) IsInBounds(p geom.PointF32) bool { return p.Sub(o.offset).In(o.bounds) }
func (o *overflow) Offset() geom.PointF32 { return o.offset } func (o *overflow) Offset() geom.PointF32 { return o.offset }
func (o *overflow) Parent() Control { return o.parent }
func (o *overflow) Render(ctx Context) { func (o *overflow) Render(ctx Context) {
var renderer = ctx.Renderer() var renderer = ctx.Renderer()

View File

@ -8,8 +8,8 @@ type Proxy struct {
Content Control Content Control
} }
func (p *Proxy) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) { func (p *Proxy) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
p.Content.Arrange(ctx, bounds, offset) p.Content.Arrange(ctx, bounds, offset, parent)
} }
func (p *Proxy) DesiredSize(ctx Context) geom.PointF32 { func (p *Proxy) DesiredSize(ctx Context) geom.PointF32 {
@ -28,6 +28,10 @@ func (p *Proxy) Bounds() geom.RectangleF32 {
return p.Content.Bounds() return p.Content.Bounds()
} }
func (p *Proxy) IsInBounds(pt geom.PointF32) bool { return p.Content.IsInBounds(pt) }
func (p *Proxy) IsOver() bool { return p.Content.IsOver() }
func (p *Proxy) Offset() geom.PointF32 { return p.Content.Offset() } func (p *Proxy) Offset() geom.PointF32 { return p.Content.Offset() }
func (p *Proxy) OnClick(fn ClickFn) { func (p *Proxy) OnClick(fn ClickFn) {
@ -45,3 +49,5 @@ func (p *Proxy) OnDragMove(fn DragMoveFn) {
func (p *Proxy) OnDragEnd(fn DragEndFn) { func (p *Proxy) OnDragEnd(fn DragEndFn) {
p.Content.OnDragEnd(fn) p.Content.OnDragEnd(fn)
} }
func (p *Proxy) Parent() Control { return p.Content.Parent() }

View File

@ -9,24 +9,24 @@ type Scrollbar struct {
Orientation Orientation Orientation Orientation
Content float32 ContentLength float32
Offset float32 ContentOffset float32
handle ScrollbarHandle handle ScrollbarHandle
startDragOffset float32 startDragOffset float32
} }
func BuildScrollbar(o Orientation, fn func(s *Scrollbar)) *Scrollbar { func BuildScrollbar(o Orientation, fn func(s *Scrollbar)) *Scrollbar {
var s = &Scrollbar{Orientation: o, Content: 0, Offset: 0} var s = &Scrollbar{Orientation: o, ContentLength: 0, ContentOffset: 0}
s.handle.OnDragStart(func(_ Context, _ Control, _ geom.PointF32) { s.handle.OnDragStart(func(_ Context, _ Control, _ geom.PointF32) {
s.startDragOffset = s.Offset s.startDragOffset = s.ContentOffset
}) })
s.handle.OnDragMove(func(_ Context, _ Control, start, move geom.PointF32) { s.handle.OnDragMove(func(_ Context, _ Control, start, move geom.PointF32) {
var length = s.Orientation.SizeParallel(s.bounds) var length = s.Orientation.SizeParallel(s.bounds)
var handleMaxOffset = length - s.Orientation.SizeParallel(s.handle.bounds) var handleMaxOffset = length - s.Orientation.SizeParallel(s.handle.bounds)
var hidden = s.Content - length var hidden = s.ContentLength - length
var offset = (s.Orientation.LengthParallel(move) - s.Orientation.LengthParallel(start)) / handleMaxOffset var offset = (s.Orientation.LengthParallel(move) - s.Orientation.LengthParallel(start)) / handleMaxOffset
s.Offset = geom.Max32(0, geom.Min32(s.startDragOffset+offset*hidden, hidden)) s.ContentOffset = geom.Max32(0, geom.Min32(s.startDragOffset+offset*hidden, hidden))
}) })
if fn != nil { if fn != nil {
fn(s) fn(s)
@ -34,8 +34,8 @@ func BuildScrollbar(o Orientation, fn func(s *Scrollbar)) *Scrollbar {
return s return s
} }
func (s *Scrollbar) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) { func (s *Scrollbar) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
s.ControlBase.Arrange(ctx, bounds, offset) s.ControlBase.Arrange(ctx, bounds, offset, parent)
s.updateBar(ctx) s.updateBar(ctx)
} }
@ -48,7 +48,7 @@ func (s *Scrollbar) Handle(ctx Context, e Event) {
switch e := e.(type) { switch e := e.(type) {
case *MouseMoveEvent: case *MouseMoveEvent:
if e.MouseWheel != 0 && e.Pos().Sub(s.offset).In(s.bounds) { if e.MouseWheel != 0 && e.Pos().Sub(s.offset).In(s.bounds) {
s.Offset = geom.Max32(0, geom.Min32(s.Content-s.Orientation.SizeParallel(s.bounds), s.Offset-36*e.MouseWheel)) s.ContentOffset = geom.Max32(0, geom.Min32(s.ContentLength-s.Orientation.SizeParallel(s.bounds), s.ContentOffset-36*e.MouseWheel))
} }
} }
s.ControlBase.Handle(ctx, e) s.ControlBase.Handle(ctx, e)
@ -63,17 +63,17 @@ func (s *Scrollbar) updateBar(ctx Context) {
var width = ctx.Style().Dimensions.ScrollbarWidth var width = ctx.Style().Dimensions.ScrollbarWidth
var handleLength = length var handleLength = length
var handleOffset = s.Orientation.LengthParallel(s.bounds.Min) var handleOffset = s.Orientation.LengthParallel(s.bounds.Min)
if s.Content > length { if s.ContentLength > length {
handleLength = geom.Max32(2*width, length*length/s.Content) handleLength = geom.Max32(2*width, length*length/s.ContentLength)
var hidden = s.Content - length var hidden = s.ContentLength - length
if s.Offset > hidden { if s.ContentOffset > hidden {
s.Offset = hidden s.ContentOffset = hidden
} }
var offset = geom.Min32(1, s.Offset/hidden) var offset = geom.Min32(1, s.ContentOffset/hidden)
handleOffset = handleOffset + offset*(length-handleLength) handleOffset = handleOffset + offset*(length-handleLength)
} }
var min = s.Orientation.Pt(handleOffset, s.Orientation.LengthPerpendicular(s.bounds.Max)-width) var min = s.Orientation.Pt(handleOffset, s.Orientation.LengthPerpendicular(s.bounds.Max)-width)
s.handle.Arrange(ctx, s.Orientation.Rect(min, handleLength, width), s.offset) s.handle.Arrange(ctx, s.Orientation.Rect(min, handleLength, width), s.offset, s)
} }
type ScrollbarHandle struct { type ScrollbarHandle struct {

View File

@ -7,6 +7,8 @@ import (
type Spacing struct { type Spacing struct {
Proxy Proxy
bounds geom.RectangleF32
Margin SideLengths Margin SideLengths
Width *Length Width *Length
Height *Length Height *Length
@ -21,58 +23,55 @@ func BuildSpacing(content Control, fn func(*Spacing)) *Spacing {
return s return s
} }
func sumLengths(lengths ...float32) (float32, int) { func insetMargins(bounds geom.RectangleF32, margin SideLengths, width, height float32) geom.RectangleF32 {
var fixed float32 var zero = func(f float32) float32 {
var infinite int if geom.IsNaN32(f) {
for _, l := range lengths { return 0
if geom.IsNaN32(l) {
infinite++
} else {
fixed += l
} }
return f
} }
return fixed, infinite var nan = func(f float32) int {
if geom.IsNaN32(f) {
return 1
}
return 0
}
var partial = func(f, p float32) float32 {
if geom.IsNaN32(f) {
return p
}
return f
}
var inset = func(min, max, marginMin, marginMax, length float32) (float32, float32) {
var available = max - min
var margins = zero(marginMin) + zero(marginMax)
if margins > available {
return min + zero(marginMin)*available/margins, max - zero(marginMax)*available/margins
}
var fixed = margins + zero(length)
if fixed > available {
return min + zero(marginMin), max - zero(marginMax)
}
var nans = nan(marginMin) + nan(length) + nan(marginMax)
var part float32 = 0
if nans > 0 {
part = (available - fixed) / float32(nans)
}
return min + partial(marginMin, part), max - partial(marginMax, part)
}
bounds.Min.X, bounds.Max.X = inset(bounds.Min.X, bounds.Max.X, margin.Left.Value(), margin.Right.Value(), width)
bounds.Min.Y, bounds.Max.Y = inset(bounds.Min.Y, bounds.Max.Y, margin.Top.Value(), margin.Bottom.Value(), height)
return bounds
} }
func addMargin(bounds geom.PointF32, size geom.PointF32, margin SideLengths) geom.RectangleF32 { func (s *Spacing) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
var left = margin.Left.Value() size := s.DesiredSize(ctx)
var top = margin.Top.Value() content := insetMargins(bounds, s.Margin, s.Width.Zero(size.X), s.Height.Zero(size.Y))
var w, hor = sumLengths(left, size.X, margin.Right.Value()) s.bounds = bounds
var h, ver = sumLengths(top, size.Y, margin.Bottom.Value()) s.Proxy.Arrange(ctx, content, offset, parent)
var rem = geom.PtF32(geom.Max32(0, bounds.X-w), geom.Max32(0, bounds.Y-h))
if geom.IsNaN32(left) {
left = rem.X / float32(hor)
} else if rem.X == 0 {
left = (left * bounds.X) / w
}
if geom.IsNaN32(size.X) {
size.X = rem.X / float32(hor)
} else if rem.X == 0 {
size.X = (size.X * bounds.X) / w
} else if hor == 0 { // nothing stretches
size.X += rem.X
}
if geom.IsNaN32(top) {
top = rem.Y / float32(ver)
} else if rem.Y == 0 {
top = (top * bounds.Y) / h
}
if geom.IsNaN32(size.Y) {
size.Y = rem.Y / float32(ver)
} else if rem.Y == 0 {
size.Y = (size.Y * bounds.Y) / h
} else if ver == 0 { // nothing stretches
size.Y += rem.Y
}
return geom.RectF32(left, top, left+size.X, top+size.Y)
} }
func (s *Spacing) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) { func (s *Spacing) Bounds() geom.RectangleF32 { return s.bounds }
var size = s.Proxy.DesiredSize(ctx)
var w, h = s.Width.Zero(size.X), s.Height.Zero(size.Y)
var content = addMargin(geom.PtF32(bounds.Dx(), bounds.Dy()), geom.PtF32(w, h), s.Margin).Add(bounds.Min)
s.Proxy.Arrange(ctx, content, offset)
}
func (s *Spacing) Center() { func (s *Spacing) Center() {
s.Margin.Left = Infinite() s.Margin.Left = Infinite()

View File

@ -27,7 +27,7 @@ func TestNoStretchFillsAvailableSpace(t *testing.T) {
s := BuildSpacing(m, func(s *Spacing) { s := BuildSpacing(m, func(s *Spacing) {
s.SetSize(0, 0) s.SetSize(0, 0)
}) })
s.Arrange(nil, geom.RectF32(31, 37, 73, 79), geom.ZeroPtF32) s.Arrange(nil, geom.RectF32(31, 37, 73, 79), geom.ZeroPtF32, nil)
assert.Equal(t, geom.RectF32(31, 37, 73, 79), m.bounds) assert.Equal(t, geom.RectF32(31, 37, 73, 79), m.bounds)
} }
@ -36,7 +36,7 @@ func TestStretch(t *testing.T) {
s := BuildSpacing(m, func(s *Spacing) { s := BuildSpacing(m, func(s *Spacing) {
s.SetSize(geom.NaN32(), geom.NaN32()) s.SetSize(geom.NaN32(), geom.NaN32())
}) })
s.Arrange(nil, geom.RectF32(31, 37, 73, 79), geom.ZeroPtF32) s.Arrange(nil, geom.RectF32(31, 37, 73, 79), geom.ZeroPtF32, nil)
assert.Equal(t, geom.RectF32(31, 37, 73, 79), m.bounds) assert.Equal(t, geom.RectF32(31, 37, 73, 79), m.bounds)
} }
@ -45,7 +45,7 @@ func TestCenter(t *testing.T) {
s := BuildSpacing(m, func(s *Spacing) { s := BuildSpacing(m, func(s *Spacing) {
s.Center() s.Center()
}) })
s.Arrange(nil, geom.RectF32(31, 37, 73, 79), geom.ZeroPtF32) s.Arrange(nil, geom.RectF32(31, 37, 73, 79), geom.ZeroPtF32, nil)
assert.Equal(t, geom.RectF32(51, 56.5, 53, 59.5), m.bounds) assert.Equal(t, geom.RectF32(51, 56.5, 53, 59.5), m.bounds)
} }
@ -57,6 +57,23 @@ func TestFixedMargin(t *testing.T) {
s.Margin.Right = Fixed(3) s.Margin.Right = Fixed(3)
s.Margin.Bottom = Fixed(5) s.Margin.Bottom = Fixed(5)
}) })
s.Arrange(nil, geom.RectF32(31, 37, 73, 79), geom.ZeroPtF32) s.Arrange(nil, geom.RectF32(31, 37, 73, 79), geom.ZeroPtF32, nil)
assert.Equal(t, geom.RectF32(38, 39, 70, 74), m.bounds) assert.Equal(t, geom.RectF32(38, 39, 70, 74), m.bounds)
} }
func TestRightAlign(t *testing.T) {
m := &Mock{Size: &geom.PointF32{X: 2, Y: 3}}
s := Margins(m, geom.NaN32(), 0, 0, 0)
s.Arrange(nil, geom.RectF32(31, 37, 73, 79), geom.ZeroPtF32, nil)
assert.Equal(t, geom.RectF32(71, 37, 73, 79), m.bounds)
}
func TestRightAlignFixedWidth(t *testing.T) {
m := &Mock{Size: &geom.PointF32{X: 2, Y: 3}}
s := BuildSpacing(m, func(s *Spacing) {
s.Margin.Left = Infinite()
s.Width = Fixed(5)
})
s.Arrange(nil, geom.RectF32(31, 37, 73, 79), geom.ZeroPtF32, nil)
assert.Equal(t, geom.RectF32(68, 37, 73, 79), m.bounds)
}

View File

@ -1,6 +1,8 @@
package ui package ui
import "opslag.de/schobers/geom" import (
"opslag.de/schobers/geom"
)
type StackPanel struct { type StackPanel struct {
ContainerBase ContainerBase
@ -15,7 +17,7 @@ func BuildStackPanel(o Orientation, fn func(*StackPanel)) *StackPanel {
return p return p
} }
func (p *StackPanel) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) { func (p *StackPanel) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
bounds = p.Orientation.FlipRect(bounds) bounds = p.Orientation.FlipRect(bounds)
var length float32 var length float32
var stretch int var stretch int
@ -41,10 +43,10 @@ func (p *StackPanel) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.
} }
} }
var child = geom.RectF32(bounds.Min.X, bounds.Min.Y+childOffset, bounds.Max.X, bounds.Min.Y+childOffset+height) var child = geom.RectF32(bounds.Min.X, bounds.Min.Y+childOffset, bounds.Max.X, bounds.Min.Y+childOffset+height)
p.Children[i].Arrange(ctx, p.Orientation.FlipRect(child), offset) p.Children[i].Arrange(ctx, p.Orientation.FlipRect(child), offset, p)
childOffset += height childOffset += height
} }
p.ControlBase.Arrange(ctx, p.Orientation.FlipRect(bounds), offset) p.ControlBase.Arrange(ctx, p.Orientation.FlipRect(bounds), offset, parent)
} }
func (p *StackPanel) DesiredSize(ctx Context) geom.PointF32 { func (p *StackPanel) DesiredSize(ctx Context) geom.PointF32 {

View File

@ -52,9 +52,9 @@ func (b *TextBox) pad(ctx Context) float32 {
return ctx.Style().Dimensions.TextPadding return ctx.Style().Dimensions.TextPadding
} }
func (b *TextBox) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) { func (b *TextBox) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
b.ControlBase.Arrange(ctx, bounds, offset) b.ControlBase.Arrange(ctx, bounds, offset, parent)
b.box.Arrange(ctx, bounds.Inset(b.pad(ctx)), offset) b.box.Arrange(ctx, bounds.Inset(b.pad(ctx)), offset, b)
} }
func (b *TextBox) DesiredSize(ctx Context) geom.PointF32 { func (b *TextBox) DesiredSize(ctx Context) geom.PointF32 {
@ -67,7 +67,7 @@ func (b *TextBox) DesiredSize(ctx Context) geom.PointF32 {
} }
func (b *TextBox) mousePosToCaretPos(ctx Context, e MouseEvent) int { func (b *TextBox) mousePosToCaretPos(ctx Context, e MouseEvent) int {
p := b.mousePos(e) p := b.ToControlPosition(e.Pos())
offset := p.X - b.box.bounds.Min.X offset := p.X - b.box.bounds.Min.X
f := ctx.Renderer().Font(b.FontName(ctx)) f := ctx.Renderer().Font(b.FontName(ctx))
var carets = [3]int{0, 0, len(b.Text)} var carets = [3]int{0, 0, len(b.Text)}
@ -155,77 +155,77 @@ func (b *TextBox) Handle(ctx Context, e Event) {
b.Selection.SetSelectionToCaret() b.Selection.SetSelectionToCaret()
} }
} }
switch { switch {
case e.Key == KeyDelete: case e.Key == KeyDelete:
if b.Selection.HasSelection() { if b.Selection.HasSelection() {
b.cut() b.cut()
} else { } else {
caret := b.Selection.Caret caret := b.Selection.Caret
if caret < len(b.Text) { if caret < len(b.Text) {
b.Text = b.Text[:caret] + b.Text[caret+1:] b.Text = b.Text[:caret] + b.Text[caret+1:]
}
} }
case e.Key == KeyBackspace: }
if b.Selection.HasSelection() { case e.Key == KeyBackspace:
b.cut() if b.Selection.HasSelection() {
} else { b.cut()
caret := b.Selection.Caret } else {
if caret > 0 { caret := b.Selection.Caret
b.Text = b.Text[:caret-1] + b.Text[caret:] if caret > 0 {
b.Selection.Caret = caret - 1 b.Text = b.Text[:caret-1] + b.Text[caret:]
b.Selection.Caret = caret - 1
b.Selection.SetSelectionToCaret() b.Selection.SetSelectionToCaret()
}
} }
}
case e.Key == KeyEnd: case e.Key == KeyEnd:
b.Selection.Caret = len(b.Text) b.Selection.Caret = len(b.Text)
selectAfterMove() selectAfterMove()
case e.Key == KeyHome: case e.Key == KeyHome:
b.Selection.Caret = 0 b.Selection.Caret = 0
selectAfterMove() selectAfterMove()
case e.Key == KeyLeft: case e.Key == KeyLeft:
if b.Selection.Caret > 0 { if b.Selection.Caret > 0 {
b.Selection.MoveCaret(-1) b.Selection.MoveCaret(-1)
selectAfterMove() selectAfterMove()
} }
case e.Key == KeyRight: case e.Key == KeyRight:
if b.Selection.Caret < len(b.Text) { if b.Selection.Caret < len(b.Text) {
b.Selection.MoveCaret(1) b.Selection.MoveCaret(1)
selectAfterMove() selectAfterMove()
} }
case !unicode.IsControl(e.Character): case !unicode.IsControl(e.Character):
if e.Modifiers == KeyModifierNone { if e.Modifiers == KeyModifierNone {
b.cut() b.cut()
caret := b.Selection.Caret caret := b.Selection.Caret
b.Text = fmt.Sprintf("%s%c%s", b.Text[:caret], e.Character, b.Text[caret:]) b.Text = fmt.Sprintf("%s%c%s", b.Text[:caret], e.Character, b.Text[caret:])
b.Selection.Caret = caret + 1 b.Selection.Caret = caret + 1
} else if e.Modifiers&KeyModifierControl == KeyModifierControl { } else if e.Modifiers&KeyModifierControl == KeyModifierControl {
switch e.Character { switch e.Character {
case 'a': case 'a':
b.Selection.Start = 0 b.Selection.Start = 0
b.Selection.End = len(b.Text) b.Selection.End = len(b.Text)
b.Selection.Caret = b.Selection.End b.Selection.Caret = b.Selection.End
case 'c': case 'c':
DefaultClipboard.WriteText(b.selection()) DefaultClipboard.WriteText(b.selection())
case 'v': case 'v':
text, err := DefaultClipboard.ReadText() text, err := DefaultClipboard.ReadText()
text = strings.Map(func(r rune) rune { text = strings.Map(func(r rune) rune {
if unicode.IsControl(r) { if unicode.IsControl(r) {
return -1 return -1
} }
return r return r
}, text) }, text)
if err == nil { if err == nil {
b.cut() b.cut()
caret := b.Selection.Caret caret := b.Selection.Caret
b.Text = b.Text[:caret] + text + b.Text[caret:] b.Text = b.Text[:caret] + text + b.Text[caret:]
b.Selection.Caret = caret + len(text) b.Selection.Caret = caret + len(text)
}
case 'x':
DefaultClipboard.WriteText(b.cut())
} }
case 'x':
DefaultClipboard.WriteText(b.cut())
} }
} }
} }
}
if b.over { if b.over {
ctx.Renderer().SetMouseCursor(MouseCursorText) ctx.Renderer().SetMouseCursor(MouseCursorText)
} }

View File

@ -31,7 +31,7 @@ func RunWait(r Renderer, s *Style, view Control, wait bool) error {
for !ctx.quit { for !ctx.quit {
var size = r.Size() var size = r.Size()
var bounds = geom.RectF32(0, 0, size.X, size.Y) var bounds = geom.RectF32(0, 0, size.X, size.Y)
view.Arrange(ctx, bounds, geom.ZeroPtF32) view.Arrange(ctx, bounds, geom.ZeroPtF32, nil)
view.Render(ctx) view.Render(ctx)
if ctx.quit { if ctx.quit {
return nil return nil