diff --git a/ui/checkbox.go b/ui/checkbox.go new file mode 100644 index 0000000..bab6205 --- /dev/null +++ b/ui/checkbox.go @@ -0,0 +1,114 @@ +package ui + +import ( + "opslag.de/schobers/geom" +) + +type Checkbox struct { + ControlBase + + Selected bool + Text string +} + +func BuildCheckbox(text string, fn func(c *Checkbox)) *Checkbox { + var c = &Checkbox{Text: text} + if fn != nil { + fn(c) + } + return c +} + +func (c *Checkbox) desiredSize(ctx Context) geom.PointF32 { + var pad = ctx.Style().Dimensions.TextPadding + var font = ctx.Renderer().Font(c.FontName(ctx)) + var w, h float32 = 0, font.Height() + if len(c.Text) != 0 { + w += pad + font.WidthOf(c.Text) + } + icon, _ := ctx.Images().ScaledHeight(c.icon(ctx, false), h) + w += pad + icon.Width() + return geom.PtF32(w+pad, pad+h+pad) +} + +func (c *Checkbox) icon(ctx Context, selected bool) Image { + if selected { + return GetOrCreateIcon(ctx, "ui-default-checkbox-selected", c.selectedIcon) + } + return GetOrCreateIcon(ctx, "ui-default-checkbox", c.normalIcon) +} + +func (c *Checkbox) iconBorder() geom.PolygonF32 { + return geom.PolF32( + geom.PtF32(48, 80), + geom.PtF32(400, 80), + geom.PtF32(400, 432), + geom.PtF32(48, 432), + ) +} + +func (c *Checkbox) normalIcon() IconPixelTestFn { + border := c.iconBorder() + return func(pt geom.PointF32) bool { + return pt.DistanceToPolygon(border) < 48 && !pt.InPolygon(border) + } +} + +func (c *Checkbox) selectedIcon() IconPixelTestFn { + border := c.iconBorder() + check := geom.PointsF32{ + geom.PtF32(84, 256), + geom.PtF32(180, 352), + geom.PtF32(352, 150), + } + return func(pt geom.PointF32) bool { + if pt.DistanceToPolygon(border) < 48 || pt.InPolygon(border) { + return pt.DistanceToLines(check) > 24 + } + return false + } +} + +func (c *Checkbox) DesiredSize(ctx Context) geom.PointF32 { return c.desiredSize(ctx) } + +func (c *Checkbox) Handle(ctx Context, e Event) { + switch e := e.(type) { + case *MouseButtonDownEvent: + if e.Button == MouseButtonLeft && c.over { + c.Selected = !c.Selected + } + } + c.ControlBase.Handle(ctx, e) + if c.over { + ctx.Renderer().SetMouseCursor(MouseCursorPointer) + } +} + +func (c *Checkbox) Render(ctx Context) { + var style = ctx.Style() + var palette = style.Palette + fore := c.FontColor(ctx) + size := c.desiredSize(ctx) + bounds := c.bounds + deltaX, deltaY := bounds.Dx()-size.X, bounds.Dy()-size.Y + bounds.Min.X += .5 * deltaX + bounds.Min.Y += .5 * deltaY + + var pad = style.Dimensions.TextPadding + bounds = bounds.Inset(pad) + pos := bounds.Min + icon, _ := ctx.Images().ScaledHeight(c.icon(ctx, c.Selected), bounds.Dy()) + if icon != nil { + iconColor := fore + if c.Selected { + iconColor = palette.Primary + } + ctx.Renderer().DrawImageOptions(icon, geom.PtF32(pos.X, pos.Y+.5*(bounds.Dy()-icon.Height())), DrawOptions{Tint: iconColor}) + pos.X += icon.Width() + pad + } + if len(c.Text) != 0 { + var fontName = c.FontName(ctx) + var font = ctx.Renderer().Font(fontName) + ctx.Renderer().Text(geom.PtF32(pos.X, pos.Y+.5*(bounds.Dy()-font.Height())), fontName, fore, c.Text) + } +} diff --git a/ui/examples/01_basic/basic.go b/ui/examples/01_basic/basic.go index 8b8f2f6..c8a1c8d 100644 --- a/ui/examples/01_basic/basic.go +++ b/ui/examples/01_basic/basic.go @@ -55,6 +55,12 @@ func run() error { ctx.Quit() }) }), 8), + ui.BuildStackPanel(ui.OrientationHorizontal, func(p *ui.StackPanel) { + p.Children = []ui.Control{ + &ui.Checkbox{}, + ui.BuildCheckbox("Check me!", nil), + } + }), ui.Stretch(&ui.Label{Text: "Content"}), ui.StretchWidth(ui.BuildTextBox(func(b *ui.TextBox) { b.Text = "Type here..." diff --git a/ui/icon.go b/ui/icon.go new file mode 100644 index 0000000..8500a75 --- /dev/null +++ b/ui/icon.go @@ -0,0 +1,52 @@ +package ui + +import ( + "image" + "image/color" + + "opslag.de/schobers/geom" +) + +type IconPixelTestFn func(geom.PointF32) bool + +func IconSize() geom.Point { + return geom.Pt(448, 512) +} + +func CreateIcon(ctx Context, test IconPixelTestFn) Image { + icon := DrawIcon(test) + im, err := ctx.Renderer().CreateImage(icon) + if err != nil { + return nil + } + return im +} + +func DrawIcon(test IconPixelTestFn) image.Image { + size := IconSize() + r := image.Rect(0, 0, size.X, size.Y) + icon := image.NewRGBA(r) + for y := 0; y < size.Y; y++ { + for x := 0; x < size.X; x++ { + pt := geom.PtF32(float32(x), float32(y)) + if test(pt) { + icon.Set(x, y, color.White) + } + } + } + return icon +} + +func GetOrCreateIcon(ctx Context, name string, testFactory func() IconPixelTestFn) Image { + im := ctx.Images().Image(name) + if im != nil { + return im + } + test := testFactory() + im = CreateIcon(ctx, test) + if im == nil { + return nil + } + ctx.Images().AddImage(name, im) + return im +}