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 parent Control drag Dragable over bool pressed bool clicked ControlClickedEvents dragEnded DragEndedEvents dragMoved DragMovedEvents dragStarted DragStartedEvents Background color.Color Font FontStyle TextAlignment HorizontalAlignment 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) 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) 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) ToControlPosition(p geom.PointF32) geom.PointF32 { return p.Sub(c.offset) }