diff --git a/ui/buffer.go b/ui/buffer.go index 73b7c1e..74dfe7c 100644 --- a/ui/buffer.go +++ b/ui/buffer.go @@ -45,8 +45,8 @@ type BufferControl struct { buffer Buffer } -func (c *BufferControl) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) { - c.ControlBase.Arrange(ctx, bounds, offset) +func (c *BufferControl) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) { + c.ControlBase.Arrange(ctx, bounds, offset, parent) c.buffer.Update(ctx, bounds.Size()) } diff --git a/ui/containerbase.go b/ui/containerbase.go index 7d6b006..1cd95a2 100644 --- a/ui/containerbase.go +++ b/ui/containerbase.go @@ -19,18 +19,18 @@ func (c *ContainerBase) AddChild(child ...Control) { 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 { - 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) { + c.ControlBase.Handle(ctx, e) for _, child := range c.Children { child.Handle(ctx, e) } - c.ControlBase.Handle(ctx, e) } func (c *ContainerBase) Render(ctx Context) { diff --git a/ui/control.go b/ui/control.go index ae93029..c74d3ec 100644 --- a/ui/control.go +++ b/ui/control.go @@ -5,17 +5,20 @@ import ( ) type Control interface { - Arrange(Context, geom.RectangleF32, geom.PointF32) + Arrange(Context, geom.RectangleF32, geom.PointF32, Control) DesiredSize(Context) geom.PointF32 Handle(Context, Event) Render(Context) Bounds() geom.RectangleF32 + IsInBounds(p geom.PointF32) bool + IsOver() bool Offset() geom.PointF32 OnClick(ClickFn) OnDragStart(DragStartFn) OnDragMove(DragMoveFn) OnDragEnd(DragEndFn) + Parent() Control } type RootControl interface { diff --git a/ui/controlbase.go b/ui/controlbase.go index 4cd7f76..e18cd31 100644 --- a/ui/controlbase.go +++ b/ui/controlbase.go @@ -18,6 +18,7 @@ var _ Control = &ControlBase{} type ControlBase struct { bounds geom.RectangleF32 offset geom.PointF32 + parent Control dragStart *geom.PointF32 over bool pressed bool @@ -31,55 +32,59 @@ type ControlBase struct { 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.offset = offset + c.parent = parent } -func (c *ControlBase) Bounds() geom.RectangleF32 { - return c.bounds -} +func (c *ControlBase) Bounds() geom.RectangleF32 { return c.bounds } -func (c *ControlBase) DesiredSize(Context) geom.PointF32 { - return geom.ZeroPtF32 -} - -func (c *ControlBase) mousePos(e MouseEvent) geom.PointF32 { - return e.Pos().Sub(c.offset) -} +func (c *ControlBase) DesiredSize(Context) geom.PointF32 { return geom.ZeroPtF32 } func (c *ControlBase) Handle(ctx Context, e Event) { var over = func(e MouseEvent) bool { - c.over = c.mousePos(e).In(c.bounds) - return c.over + pos := e.Pos() + 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) { case *MouseMoveEvent: - over(e.MouseEvent) + c.over = over(e.MouseEvent) if c.pressed { if c.dragStart == nil { - var start = c.mousePos(e.MouseEvent) + var start = c.ToControlPosition(e.Pos()) c.dragStart = &start if c.onDragStart != nil { c.onDragStart(ctx, c, start) } } else { var start = *c.dragStart - var move = c.mousePos(e.MouseEvent) + var move = c.ToControlPosition(e.Pos()) if c.onDragMove != nil { c.onDragMove(ctx, c, start, move) } } } case *MouseButtonDownEvent: - if over(e.MouseEvent) && e.Button == MouseButtonLeft { + c.over = over(e.MouseEvent) + if c.over && e.Button == MouseButtonLeft { c.pressed = true } case *MouseButtonUpEvent: if e.Button == MouseButtonLeft { if c.dragStart != nil { var start = *c.dragStart - var end = c.mousePos(e.MouseEvent) + var end = c.ToControlPosition(e.Pos()) c.dragStart = nil if c.onDragEnd != nil { c.onDragEnd(ctx, c, start, end) @@ -87,7 +92,7 @@ func (c *ControlBase) Handle(ctx Context, e Event) { } if c.pressed { 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 @@ -111,15 +116,26 @@ func (c *ControlBase) FontName(ctx Context) string { 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) Parent() Control { return c.parent } + func (c *ControlBase) IsPressed() bool { return c.pressed } func (c *ControlBase) Offset() geom.PointF32 { return c.offset } -func (c *ControlBase) OnClick(fn ClickFn) { - c.onClick = fn -} +func (c *ControlBase) OnClick(fn ClickFn) { c.onClick = fn } func (c *ControlBase) OnDragStart(fn DragStartFn) { c.onDragStart = fn @@ -129,6 +145,8 @@ func (c *ControlBase) OnDragMove(fn DragMoveFn) { c.onDragMove = fn } +func (c *ControlBase) OnDragEnd(fn DragEndFn) { c.onDragEnd = fn } + func (c *ControlBase) Render(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) } + +func (c *ControlBase) ToControlPosition(p geom.PointF32) geom.PointF32 { return p.Sub(c.offset) } diff --git a/ui/overflow.go b/ui/overflow.go index 75af3d0..abc2df4 100644 --- a/ui/overflow.go +++ b/ui/overflow.go @@ -15,6 +15,7 @@ type overflow struct { desired geom.PointF32 bounds geom.RectangleF32 offset geom.PointF32 + parent Control content Buffer 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.desired = o.Content.DesiredSize(ctx) o.bounds = bounds o.offset = offset + o.parent = parent var hor, ver = o.shouldScroll(bounds) var contentX, contentY float32 = 0, 0 var contentW, contentH = bounds.Dx(), bounds.Dy() if hor { - contentX -= o.hor.Offset + contentX -= o.hor.ContentOffset contentH = geom.Max32(0, contentH-o.barWidth) } if ver { - contentY -= o.ver.Offset + contentY -= o.ver.ContentOffset 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 { - o.hor.Content = 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.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) } if ver { - o.ver.Content = 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.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) } } @@ -108,15 +110,19 @@ func (o *overflow) Handle(ctx Context, e Event) { content.Min = contentO content.Max = content.Max.Add(contentO) 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) } +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) Parent() Control { return o.parent } + func (o *overflow) Render(ctx Context) { var renderer = ctx.Renderer() diff --git a/ui/proxy.go b/ui/proxy.go index 669fff5..67f6c05 100644 --- a/ui/proxy.go +++ b/ui/proxy.go @@ -8,8 +8,8 @@ type Proxy struct { Content Control } -func (p *Proxy) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) { - p.Content.Arrange(ctx, bounds, offset) +func (p *Proxy) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) { + p.Content.Arrange(ctx, bounds, offset, parent) } func (p *Proxy) DesiredSize(ctx Context) geom.PointF32 { @@ -28,6 +28,10 @@ func (p *Proxy) Bounds() geom.RectangleF32 { 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) OnClick(fn ClickFn) { @@ -45,3 +49,5 @@ func (p *Proxy) OnDragMove(fn DragMoveFn) { func (p *Proxy) OnDragEnd(fn DragEndFn) { p.Content.OnDragEnd(fn) } + +func (p *Proxy) Parent() Control { return p.Content.Parent() } diff --git a/ui/scrollbar.go b/ui/scrollbar.go index c634719..7d80ec2 100644 --- a/ui/scrollbar.go +++ b/ui/scrollbar.go @@ -9,24 +9,24 @@ type Scrollbar struct { Orientation Orientation - Content float32 - Offset float32 + ContentLength float32 + ContentOffset float32 handle ScrollbarHandle startDragOffset float32 } 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.startDragOffset = s.Offset + s.startDragOffset = s.ContentOffset }) s.handle.OnDragMove(func(_ Context, _ Control, start, move geom.PointF32) { var length = s.Orientation.SizeParallel(s.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 - 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 { fn(s) @@ -34,8 +34,8 @@ func BuildScrollbar(o Orientation, fn func(s *Scrollbar)) *Scrollbar { return s } -func (s *Scrollbar) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) { - s.ControlBase.Arrange(ctx, bounds, offset) +func (s *Scrollbar) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) { + s.ControlBase.Arrange(ctx, bounds, offset, parent) s.updateBar(ctx) } @@ -48,7 +48,7 @@ func (s *Scrollbar) Handle(ctx Context, e Event) { switch e := e.(type) { case *MouseMoveEvent: 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) @@ -63,17 +63,17 @@ func (s *Scrollbar) updateBar(ctx Context) { var width = ctx.Style().Dimensions.ScrollbarWidth var handleLength = length var handleOffset = s.Orientation.LengthParallel(s.bounds.Min) - if s.Content > length { - handleLength = geom.Max32(2*width, length*length/s.Content) - var hidden = s.Content - length - if s.Offset > hidden { - s.Offset = hidden + if s.ContentLength > length { + handleLength = geom.Max32(2*width, length*length/s.ContentLength) + var hidden = s.ContentLength - length + if s.ContentOffset > 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) } 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 { diff --git a/ui/spacing.go b/ui/spacing.go index ddd198e..8750869 100644 --- a/ui/spacing.go +++ b/ui/spacing.go @@ -7,6 +7,8 @@ import ( type Spacing struct { Proxy + bounds geom.RectangleF32 + Margin SideLengths Width *Length Height *Length @@ -21,58 +23,55 @@ func BuildSpacing(content Control, fn func(*Spacing)) *Spacing { return s } -func sumLengths(lengths ...float32) (float32, int) { - var fixed float32 - var infinite int - for _, l := range lengths { - if geom.IsNaN32(l) { - infinite++ - } else { - fixed += l +func insetMargins(bounds geom.RectangleF32, margin SideLengths, width, height float32) geom.RectangleF32 { + var zero = func(f float32) float32 { + if geom.IsNaN32(f) { + return 0 } + 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 { - var left = margin.Left.Value() - var top = margin.Top.Value() - var w, hor = sumLengths(left, size.X, margin.Right.Value()) - var h, ver = sumLengths(top, size.Y, margin.Bottom.Value()) - 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, parent Control) { + size := s.DesiredSize(ctx) + content := insetMargins(bounds, s.Margin, s.Width.Zero(size.X), s.Height.Zero(size.Y)) + s.bounds = bounds + s.Proxy.Arrange(ctx, content, offset, parent) } -func (s *Spacing) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) { - 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) Bounds() geom.RectangleF32 { return s.bounds } func (s *Spacing) Center() { s.Margin.Left = Infinite() diff --git a/ui/spacing_test.go b/ui/spacing_test.go index a321f98..2f2cf04 100644 --- a/ui/spacing_test.go +++ b/ui/spacing_test.go @@ -27,7 +27,7 @@ func TestNoStretchFillsAvailableSpace(t *testing.T) { s := BuildSpacing(m, func(s *Spacing) { 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) } @@ -36,7 +36,7 @@ func TestStretch(t *testing.T) { s := BuildSpacing(m, func(s *Spacing) { 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) } @@ -45,7 +45,7 @@ func TestCenter(t *testing.T) { s := BuildSpacing(m, func(s *Spacing) { 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) } @@ -57,6 +57,23 @@ func TestFixedMargin(t *testing.T) { s.Margin.Right = Fixed(3) 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) } + +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) +} diff --git a/ui/stackpanel.go b/ui/stackpanel.go index 16b0c92..a69d191 100644 --- a/ui/stackpanel.go +++ b/ui/stackpanel.go @@ -1,6 +1,8 @@ package ui -import "opslag.de/schobers/geom" +import ( + "opslag.de/schobers/geom" +) type StackPanel struct { ContainerBase @@ -15,7 +17,7 @@ func BuildStackPanel(o Orientation, fn func(*StackPanel)) *StackPanel { 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) var length float32 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) - p.Children[i].Arrange(ctx, p.Orientation.FlipRect(child), offset) + p.Children[i].Arrange(ctx, p.Orientation.FlipRect(child), offset, p) 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 { diff --git a/ui/textbox.go b/ui/textbox.go index e945585..2786a34 100644 --- a/ui/textbox.go +++ b/ui/textbox.go @@ -52,9 +52,9 @@ func (b *TextBox) pad(ctx Context) float32 { return ctx.Style().Dimensions.TextPadding } -func (b *TextBox) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32) { - b.ControlBase.Arrange(ctx, bounds, offset) - b.box.Arrange(ctx, bounds.Inset(b.pad(ctx)), offset) +func (b *TextBox) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) { + b.ControlBase.Arrange(ctx, bounds, offset, parent) + b.box.Arrange(ctx, bounds.Inset(b.pad(ctx)), offset, b) } 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 { - p := b.mousePos(e) + p := b.ToControlPosition(e.Pos()) offset := p.X - b.box.bounds.Min.X f := ctx.Renderer().Font(b.FontName(ctx)) var carets = [3]int{0, 0, len(b.Text)} @@ -155,77 +155,77 @@ func (b *TextBox) Handle(ctx Context, e Event) { b.Selection.SetSelectionToCaret() } } - switch { - case e.Key == KeyDelete: - if b.Selection.HasSelection() { - b.cut() - } else { - caret := b.Selection.Caret - if caret < len(b.Text) { - b.Text = b.Text[:caret] + b.Text[caret+1:] - } + switch { + case e.Key == KeyDelete: + if b.Selection.HasSelection() { + b.cut() + } else { + caret := b.Selection.Caret + if caret < len(b.Text) { + b.Text = b.Text[:caret] + b.Text[caret+1:] } - case e.Key == KeyBackspace: - if b.Selection.HasSelection() { - b.cut() - } else { - caret := b.Selection.Caret - if caret > 0 { - b.Text = b.Text[:caret-1] + b.Text[caret:] - b.Selection.Caret = caret - 1 + } + case e.Key == KeyBackspace: + if b.Selection.HasSelection() { + b.cut() + } else { + caret := b.Selection.Caret + if caret > 0 { + b.Text = b.Text[:caret-1] + b.Text[caret:] + b.Selection.Caret = caret - 1 b.Selection.SetSelectionToCaret() - } } + } case e.Key == KeyEnd: b.Selection.Caret = len(b.Text) selectAfterMove() case e.Key == KeyHome: b.Selection.Caret = 0 selectAfterMove() - case e.Key == KeyLeft: - if b.Selection.Caret > 0 { - b.Selection.MoveCaret(-1) + case e.Key == KeyLeft: + if b.Selection.Caret > 0 { + b.Selection.MoveCaret(-1) selectAfterMove() - } - case e.Key == KeyRight: - if b.Selection.Caret < len(b.Text) { - b.Selection.MoveCaret(1) + } + case e.Key == KeyRight: + if b.Selection.Caret < len(b.Text) { + b.Selection.MoveCaret(1) selectAfterMove() - } - case !unicode.IsControl(e.Character): + } + case !unicode.IsControl(e.Character): if e.Modifiers == KeyModifierNone { - b.cut() - caret := b.Selection.Caret - b.Text = fmt.Sprintf("%s%c%s", b.Text[:caret], e.Character, b.Text[caret:]) - b.Selection.Caret = caret + 1 - } else if e.Modifiers&KeyModifierControl == KeyModifierControl { - switch e.Character { - case 'a': - b.Selection.Start = 0 - b.Selection.End = len(b.Text) - b.Selection.Caret = b.Selection.End - case 'c': - DefaultClipboard.WriteText(b.selection()) - case 'v': - text, err := DefaultClipboard.ReadText() + b.cut() + caret := b.Selection.Caret + b.Text = fmt.Sprintf("%s%c%s", b.Text[:caret], e.Character, b.Text[caret:]) + b.Selection.Caret = caret + 1 + } else if e.Modifiers&KeyModifierControl == KeyModifierControl { + switch e.Character { + case 'a': + b.Selection.Start = 0 + b.Selection.End = len(b.Text) + b.Selection.Caret = b.Selection.End + case 'c': + DefaultClipboard.WriteText(b.selection()) + case 'v': + text, err := DefaultClipboard.ReadText() text = strings.Map(func(r rune) rune { if unicode.IsControl(r) { return -1 } return r }, text) - if err == nil { - b.cut() - caret := b.Selection.Caret - b.Text = b.Text[:caret] + text + b.Text[caret:] - b.Selection.Caret = caret + len(text) - } - case 'x': - DefaultClipboard.WriteText(b.cut()) + if err == nil { + b.cut() + caret := b.Selection.Caret + b.Text = b.Text[:caret] + text + b.Text[caret:] + b.Selection.Caret = caret + len(text) } + case 'x': + DefaultClipboard.WriteText(b.cut()) } } } + } if b.over { ctx.Renderer().SetMouseCursor(MouseCursorText) } diff --git a/ui/ui.go b/ui/ui.go index 91059b6..6a847d8 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -31,7 +31,7 @@ func RunWait(r Renderer, s *Style, view Control, wait bool) error { for !ctx.quit { var size = r.Size() 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) if ctx.quit { return nil