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) }