zntg/ui/controlbase.go

307 lines
7.1 KiB
Go

package ui
import (
"image/color"
"opslag.de/schobers/geom"
)
type ControlClickedArgs struct {
Position geom.PointF32
Button MouseButton
}
type ControlClickedEventHandler interface {
AddHandler(func(Context, ControlClickedArgs)) uint
}
type ControlClickedEvents struct {
Events
}
func (e *ControlClickedEvents) AddHandler(handler func(Context, ControlClickedArgs)) uint {
return e.Events.AddHandler(func(ctx Context, state interface{}) {
args := state.(ControlClickedArgs)
handler(ctx, args)
})
}
type DragEndedArgs struct {
Start geom.PointF32
End geom.PointF32
}
type DragEndedEventHandler interface {
AddHandler(func(Context, DragEndedArgs)) uint
}
type DragEndedEvents struct {
Events
}
func (e *DragEndedEvents) AddHandler(handler func(Context, DragEndedArgs)) uint {
return e.Events.AddHandler(func(ctx Context, state interface{}) {
args := state.(DragEndedArgs)
handler(ctx, args)
})
}
type DragMovedArgs struct {
Start geom.PointF32
Current geom.PointF32
}
type DragMovedEventHandler interface {
AddHandler(func(Context, DragMovedArgs)) uint
}
type DragMovedEvents struct {
Events
}
func (e *DragMovedEvents) AddHandler(handler func(Context, DragMovedArgs)) uint {
return e.Events.AddHandler(func(ctx Context, state interface{}) {
args := state.(DragMovedArgs)
handler(ctx, args)
})
}
type DragStartedArgs struct {
Start geom.PointF32
}
type DragStartedEventHandler interface {
AddHandler(func(Context, DragStartedArgs)) uint
}
type DragStartedEvents struct {
Events
}
func (e *DragStartedEvents) AddHandler(handler func(Context, DragStartedArgs)) uint {
return e.Events.AddHandler(func(ctx Context, state interface{}) {
args := state.(DragStartedArgs)
handler(ctx, args)
})
}
var _ Control = &ControlBase{}
type ControlBase struct {
bounds geom.RectangleF32
offset geom.PointF32
self Control
parent Control
drag Dragable
over bool
pressed bool
clicked ControlClickedEvents
dragEnded DragEndedEvents
dragMoved DragMovedEvents
dragStarted DragStartedEvents
Background color.Color
Font FontStyle
TextAlignment HorizontalAlignment
TextPadding SideLengths
Disabled bool
Tooltip string
}
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) BoundsUnclipped(ctx Context, path ControlPath) geom.RectangleF32 {
return path.BoundsUnclipped(ctx, c)
}
func (c *ControlBase) ControlClicked() ControlClickedEventHandler { return &c.clicked }
func (c *ControlBase) DesiredSize(Context, geom.PointF32) geom.PointF32 { return geom.ZeroPtF32 }
func (c *ControlBase) Disable() { c.Disabled = true }
func (c *ControlBase) DragEnded() DragEndedEventHandler { return &c.dragEnded }
func (c *ControlBase) DragMoved() DragMovedEventHandler { return &c.dragMoved }
func (c *ControlBase) DragStarted() DragStartedEventHandler { return &c.dragStarted }
func (c *ControlBase) Enable() { c.Disabled = false }
func (c *ControlBase) Handle(ctx Context, e Event) bool { return c.HandleNotify(ctx, e, c) }
func (c *ControlBase) HandleNotify(ctx Context, e Event, notifier Notifier) bool {
defer func() {
if c.Tooltip != "" && c.over {
ctx.ShowTooltip(c.Tooltip)
}
}()
var over = func(e MouseEvent) bool {
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:
c.over = over(e.MouseEvent)
if c.pressed {
if start, ok := c.drag.IsDragging(); ok {
var move = c.ToControlPosition(e.Pos())
c.drag.Move(move)
return notifier.Notify(ctx, DragMovedArgs{Start: start, Current: move})
}
var start = c.ToControlPosition(e.Pos())
c.drag.Start(start)
return notifier.Notify(ctx, DragStartedArgs{Start: start})
}
case *MouseLeaveEvent:
c.over = false
case *MouseButtonDownEvent:
c.over = over(e.MouseEvent)
if c.over && e.Button == MouseButtonLeft {
c.pressed = true
return notifier.Notify(ctx, ControlClickedArgs{Position: e.Pos(), Button: e.Button})
}
case *MouseButtonUpEvent:
if e.Button == MouseButtonLeft {
c.pressed = false
if start, ok := c.drag.IsDragging(); ok {
var end = c.ToControlPosition(e.Pos())
c.drag.Cancel()
return notifier.Notify(ctx, DragEndedArgs{Start: start, End: end})
}
}
}
return false
}
func (c *ControlBase) ActualFont(ctx Context) Font {
name := c.FontName(ctx)
return ctx.Fonts().Font(name)
}
func (c *ControlBase) ActualTextPadding(ctx Context) Sides {
return c.TextPadding.Zero(ctx.Style().Dimensions.TextPadding)
}
func (c *ControlBase) FontColor(ctx Context, color color.Color) color.Color {
if c.Disabled {
return ctx.Style().Palette.TextOnDisabled
}
var text = c.Font.Color
if text == nil {
text = color
}
return text
}
func (c *ControlBase) FontName(ctx Context) string {
var name = c.Font.Name
if len(name) == 0 {
name = ctx.Style().Fonts.Default
}
return name
}
func (c *ControlBase) IsDisabled() bool { return c.Disabled }
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) IsPressed() bool { return c.pressed }
func (c *ControlBase) Notify(ctx Context, state interface{}) bool {
switch state.(type) {
case ControlClickedArgs:
return c.clicked.Notify(ctx, state)
case DragEndedArgs:
return c.dragEnded.Notify(ctx, state)
case DragMovedArgs:
return c.dragMoved.Notify(ctx, state)
case DragStartedArgs:
return c.dragStarted.Notify(ctx, state)
default:
return false
}
}
func (c *ControlBase) Parent() Control { return c.parent }
func (c *ControlBase) Offset() geom.PointF32 { return c.offset }
func (c *ControlBase) OutlineColor(ctx Context) color.Color {
return c.FontColor(ctx, ctx.Style().Palette.Primary)
}
func (c *ControlBase) Render(Context) {}
func (c *ControlBase) RenderBackground(ctx Context) {
if c.Background != nil {
ctx.Renderer().FillRectangle(c.bounds, c.Background)
}
}
func (c *ControlBase) RenderOutline(ctx Context) {
c.RenderOutlineDefault(ctx, nil)
}
func (c *ControlBase) RenderOutlineDefault(ctx Context, color color.Color) {
style := ctx.Style()
width := style.Dimensions.OutlineWidth
if color == nil {
color = c.OutlineColor(ctx)
}
ctx.Renderer().Rectangle(c.bounds.Inset(.5*width), color, width)
}
func (c *ControlBase) TextColor(ctx Context) color.Color {
return c.FontColor(ctx, ctx.Style().Palette.Text)
}
func (c *ControlBase) ScrollIntoView(ctx Context, path ControlPath) {
if c.parent == nil {
return
}
c.parent.ScrollIntoView(ctx, path.Prepend(c))
}
func (c *ControlBase) Self() Control {
if c.self == nil {
return c
}
return c.self
}
func (c *ControlBase) SetSelf(self Control) { c.self = self }
func (c *ControlBase) ToControlPosition(p geom.PointF32) geom.PointF32 { return p.Sub(c.offset) }