Compare commits

...

8 Commits

Author SHA1 Message Date
102c187566 Added generic margin. 2020-12-13 07:43:00 +01:00
11e37af9c2 Added shorthand method for retrieving default font for the control. 2020-12-13 07:40:58 +01:00
5babda0ca9 Added (optional) dropshadow for label. 2020-12-13 07:40:58 +01:00
b1cdbea90f Changed colors of button text a bit. 2020-12-13 07:40:58 +01:00
7d5168614e Added override for scrollbar color. 2020-12-13 07:40:58 +01:00
b0a13d1a3c ContainerBase now provides a DesiredSize (maximum of all children, if any). 2020-12-13 07:40:58 +01:00
cc32cf5bc3 Added scaled ImageSource. 2020-12-13 07:40:58 +01:00
0f03760e66 Added Resize & SetIcon to Renderer.
Refactored Size (on Renderer) to return geom.Point instead of geom.PointF32.
Refactored Width and Height (on Texture) to return int instead of float32.

Refactored texture dimensions to be represented by ints instead of float32s.
2020-12-13 07:40:19 +01:00
20 changed files with 190 additions and 80 deletions

View File

@ -180,7 +180,7 @@ func (r *Renderer) CreateFontPath(path string, size int) (ui.Font, error) {
return &font{f}, nil
}
func (r *Renderer) createTexture(source ui.ImageSource, keepSource bool) (ui.Texture, error) {
func (r *Renderer) createTexture(source ui.ImageSource, keepSource bool) (*texture, error) {
im, err := source.CreateImage()
if err != nil {
return nil, err
@ -314,15 +314,23 @@ func (r *Renderer) RenderToDisplay() {
r.disp.SetAsTarget()
}
func (r *Renderer) Resources() ui.Resources { return r.res }
func (r *Renderer) Size() geom.PointF32 {
return geom.PtF32(float32(r.disp.Width()), float32(r.disp.Height()))
func (r *Renderer) Resize(width, height int) {
r.disp.Resize(width, height)
}
func (r *Renderer) SetIcon(texture ui.Texture) {
bmp := r.mustGetBitmap(texture)
r.disp.SetIcon(bmp)
func (r *Renderer) Resources() ui.Resources { return r.res }
func (r *Renderer) Size() geom.Point {
return geom.Pt(r.disp.Width(), r.disp.Height())
}
func (r *Renderer) SetIcon(source ui.ImageSource) {
texture, err := r.createTexture(source, false)
if err != nil {
return
}
defer texture.Destroy()
r.disp.SetIcon(texture.bmp)
}
func (r *Renderer) SetMouseCursor(c ui.MouseCursor) {

View File

@ -20,8 +20,8 @@ func (t *texture) Destroy() error {
return nil
}
func (t *texture) Height() float32 {
return float32(t.bmp.Height())
func (t *texture) Height() int {
return t.bmp.Height()
}
func (t *texture) CreateImage() (image.Image, error) {
@ -31,6 +31,6 @@ func (t *texture) CreateImage() (image.Image, error) {
return t.source.CreateImage()
}
func (t *texture) Width() float32 {
return float32(t.bmp.Width())
func (t *texture) Width() int {
return t.bmp.Width()
}

View File

@ -238,7 +238,7 @@ func (r *Renderer) CreateFontPath(path string, size int) (ui.Font, error) {
return &Font{font}, nil
}
func (r *Renderer) createTexture(source ui.ImageSource, keepSource bool) (ui.Texture, error) {
func (r *Renderer) createSurface(source ui.ImageSource) (*sdl.Surface, error) {
m, err := source.CreateImage()
if err != nil {
return nil, err
@ -252,6 +252,14 @@ func (r *Renderer) createTexture(source ui.ImageSource, keepSource bool) (ui.Tex
if err != nil {
return nil, err
}
return surface, nil
}
func (r *Renderer) createTexture(source ui.ImageSource, keepSource bool) (ui.Texture, error) {
surface, err := r.createSurface(source)
if err != nil {
return nil, err
}
defer surface.Free()
texture, err := r.renderer.CreateTextureFromSurface(surface)
if err != nil {
@ -391,6 +399,10 @@ func (r *Renderer) RenderToDisplay() {
r.renderer.SetRenderTarget(nil)
}
func (r *Renderer) Resize(width, height int) {
r.window.SetSize(int32(width), int32(height))
}
func (r *Renderer) SetDrawColor(c sdl.Color) {
r.renderer.SetDrawColor(c.R, c.G, c.B, c.A)
}
@ -399,14 +411,27 @@ func (r *Renderer) SetDrawColorGo(c color.Color) {
r.SetDrawColor(ColorSDL(c))
}
func (r *Renderer) SetIcon(source ui.ImageSource) {
window := r.window
if window == nil {
return
}
surface, err := r.createSurface(source)
if err != nil {
return
}
defer surface.Free()
window.SetIcon(surface)
}
func (r *Renderer) SetMouseCursor(c ui.MouseCursor) { r.cursor = c }
func (r *Renderer) Size() geom.PointF32 {
func (r *Renderer) Size() geom.Point {
w, h, err := r.renderer.GetOutputSize()
if err != nil {
return geom.PtF32(geom.NaN32(), geom.NaN32())
return geom.ZeroPt
}
return geom.PtF32(float32(w), float32(h))
return geom.Pt(int(w), int(h))
}
func (r *Renderer) SystemCursor(id sdl.SystemCursor) *sdl.Cursor {
@ -493,6 +518,6 @@ func (r *Renderer) SetResourceProvider(resources ui.Resources) {
func (r *Renderer) Image() image.Image { return nil }
func (r *Renderer) Height() float32 { return r.Size().Y }
func (r *Renderer) Height() int { return r.Size().Y }
func (r *Renderer) Width() float32 { return r.Size().X }
func (r *Renderer) Width() int { return r.Size().X }

View File

@ -5,7 +5,6 @@ import (
"image/color"
"github.com/veandco/go-sdl2/sdl"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg/ui"
)
@ -21,12 +20,12 @@ type Texture struct {
var _ ui.Texture = &Texture{}
func (t *Texture) Height() float32 {
func (t *Texture) Height() int {
_, _, _, height, err := t.Texture.Query()
if err != nil {
return geom.NaN32()
return -1
}
return float32(height)
return int(height)
}
func (t *Texture) Native() *sdl.Texture { return t.Texture }
@ -44,12 +43,12 @@ func (t *Texture) Size() (int32, int32, error) {
return width, height, err
}
func (t *Texture) Width() float32 {
func (t *Texture) Width() int {
_, _, width, _, err := t.Texture.Query()
if err != nil {
return geom.NaN32()
return -1
}
return float32(width)
return int(width)
}
var _ ui.ImageSource = &TextureImageSource{}

View File

@ -41,7 +41,7 @@ func BuildIconButton(icon, text string, fn func(b *Button)) *Button {
func (b *Button) desiredSize(ctx Context) geom.PointF32 {
var pad = ctx.Style().Dimensions.TextPadding
var font = ctx.Fonts().Font(b.FontName(ctx))
var font = b.Font_(ctx)
var w, h float32 = 0, font.Height()
icon, iconW, iconH := b.icon(ctx)
@ -71,7 +71,7 @@ func (b *Button) icon(ctx Context) (Texture, float32, float32) {
return nil, 0, 0
}
icon := ctx.Textures().Texture(b.Icon)
iconW, iconH := icon.Width(), icon.Height()
iconW, iconH := float32(icon.Width()), float32(icon.Height())
if b.IconHeight != 0 {
iconW = b.IconHeight * iconW / iconH
iconH = b.IconHeight
@ -155,6 +155,13 @@ func (b *Button) fillColor(p *Palette) color.Color {
return nil
}
func (b *Button) fontColor(c color.Color) color.Color {
if b.Font.Color == nil {
return c
}
return b.Font.Color
}
func (b *Button) textColor(p *Palette) color.Color {
if b.Disabled {
if b.Background != nil {
@ -166,22 +173,19 @@ func (b *Button) textColor(p *Palette) color.Color {
}
return b.disabledColor(p)
}
if b.Font.Color != nil {
return b.Font.Color
}
switch b.Type {
case ButtonTypeContained:
return p.TextOnPrimary
return b.fontColor(p.TextOnPrimary)
case ButtonTypeIcon:
if b.over {
if b.HoverColor != nil {
return b.HoverColor
}
return p.Primary
return b.fontColor(p.Primary)
}
return p.Text
return b.fontColor(p.Text)
default:
return p.Primary
return b.fontColor(p.Primary)
}
}
@ -213,7 +217,7 @@ func (b *Button) Render(ctx Context) {
if scaled != nil { // let the renderer scale
icon = scaled
}
_, iconW = ScaleToHeight(SizeOfTexture(icon), iconH)
_, iconW = ScaleToHeight(SizeOfTexture(icon).ToF32(), iconH)
}
iconOffsetY = .5 * (boundsH - iconH)
}
@ -221,8 +225,7 @@ func (b *Button) Render(ctx Context) {
pos.X += iconW + pad
}
if len(b.Text) != 0 {
fontName := b.FontName(ctx)
font := ctx.Fonts().Font(fontName)
font := b.Font_(ctx)
ctx.Renderer().Text(font, geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), textColor, b.Text)
}

View File

@ -23,13 +23,13 @@ func BuildCheckbox(text string, fn func(c *Checkbox)) *Checkbox {
func (c *Checkbox) desiredSize(ctx Context) geom.PointF32 {
var pad = ctx.Style().Dimensions.TextPadding
var font = ctx.Fonts().Font(c.FontName(ctx))
font := c.Font_(ctx)
var w, h float32 = 0, font.Height()
if len(c.Text) != 0 {
w += pad + font.WidthOf(c.Text)
}
icon := c.getOrCreateNormalIcon(ctx)
_, iconWidth := ScaleToHeight(SizeOfTexture(icon), h)
_, iconWidth := ScaleToHeight(SizeOfTexture(icon).ToF32(), h)
w += pad + iconWidth
return geom.PtF32(w+pad, pad+h+pad)
}
@ -122,14 +122,13 @@ func (c *Checkbox) Render(ctx Context) {
if scaledIcon == nil { // let the renderer scale
scaledIcon = icon
}
_, iconWidth := ScaleToHeight(SizeOfTexture(scaledIcon), boundsH)
_, iconWidth := ScaleToHeight(SizeOfTexture(scaledIcon).ToF32(), boundsH)
rect := geom.RectRelF32(pos.X, pos.Y, iconWidth, boundsH)
ctx.Renderer().DrawTextureOptions(scaledIcon, rect, DrawOptions{Tint: iconColor})
pos.X += iconWidth + pad
}
if len(c.Text) != 0 {
var fontName = c.FontName(ctx)
var font = ctx.Fonts().Font(fontName)
font := c.Font_(ctx)
ctx.Renderer().Text(font, geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), fore, c.Text)
}
}

View File

@ -26,6 +26,15 @@ func (c *ContainerBase) Arrange(ctx Context, bounds geom.RectangleF32, offset ge
c.ControlBase.Arrange(ctx, bounds, offset, parent)
}
func (c *ContainerBase) DesiredSize(ctx Context, size geom.PointF32) geom.PointF32 {
var max geom.PointF32
for _, child := range c.Children {
s := child.DesiredSize(ctx, size)
max = geom.MaxPtF32(max, s)
}
return max
}
func (c *ContainerBase) Handle(ctx Context, e Event) bool {
if c.ControlBase.Handle(ctx, e) {
return true

View File

@ -188,6 +188,11 @@ func (c *ControlBase) HandleNotify(ctx Context, e Event, notifier Notifier) bool
return false
}
func (c *ControlBase) Font_(ctx Context) Font {
name := c.FontName(ctx)
return ctx.Fonts().Font(name)
}
func (c *ControlBase) FontColor(ctx Context, color color.Color) color.Color {
if c.Disabled {
return ctx.Style().Palette.TextOnDisabled

View File

@ -91,16 +91,18 @@ func (o *debugOverlay) renderControl(ctx Context, control Control) {
return
}
defer nameTexture.Destroy()
renderer.FillRectangle(pos.RectRel2D(nameTexture.Width(), nameTexture.Height()), color.Black)
nameTextureWidth := float32(nameTexture.Width())
nameTextureHeight := float32(nameTexture.Height())
renderer.FillRectangle(pos.RectRel2D(nameTextureWidth, nameTextureHeight), color.Black)
renderer.DrawTexturePoint(nameTexture, pos)
childPos := pos.Add2D(nameTexture.Width()+8, 0)
childPos := pos.Add2D(nameTextureWidth+ctx.Style().Dimensions.Margin, 0)
for _, child := range node.Children {
if childPos.Y == maxY {
childPos.Y = maxY + nameTexture.Height()
childPos.Y = maxY + nameTextureHeight
}
renderHoverNode(childPos, child)
maxY = childPos.Y
childPos.Y += nameTexture.Height() + 8
childPos.Y += nameTextureHeight + ctx.Style().Dimensions.Margin
}
}
renderHoverNode(geom.PtF32(4, 4), o.hoverNodes)

View File

@ -3,6 +3,7 @@ package ui
import (
"image"
"github.com/nfnt/resize"
"opslag.de/schobers/zntg"
)
@ -45,3 +46,25 @@ func (s ImageSourceResource) CreateImage() (image.Image, error) {
}
return value.(image.Image), nil
}
func ImageSourceFromResources(res Resources, name string) ImageSourceResource {
return ImageSourceResource{Resources: res, Name: name}
}
func Scaled(source ImageSource, width, height int) ImageSource {
return scaledImageSource{ImageSource: source, width: width, height: height}
}
type scaledImageSource struct {
ImageSource
width, height int
}
func (s scaledImageSource) CreateImage() (image.Image, error) {
im, err := s.ImageSource.CreateImage()
if err != nil {
return nil, err
}
return resize.Resize(uint(s.width), uint(s.height), im, resize.Bicubic), nil
}

View File

@ -1,13 +1,16 @@
package ui
import (
"image/color"
"opslag.de/schobers/geom"
)
type Label struct {
ControlBase
Text string
Text string
DropShadow color.Color
desired CachedValue
}
@ -25,8 +28,7 @@ func (l *Label) hashDesiredSize(ctx Context) string {
}
func (l *Label) desiredSize(ctx Context) interface{} {
fontName := l.FontName(ctx)
font := ctx.Fonts().Font(fontName)
font := l.Font_(ctx)
width := font.WidthOf(l.Text)
height := font.Height()
pad := ctx.Style().Dimensions.TextPadding
@ -37,18 +39,26 @@ func (l *Label) DesiredSize(ctx Context, _ geom.PointF32) geom.PointF32 {
return l.desired.GetContext(ctx, l.desiredSize, l.hashDesiredSize).(geom.PointF32)
}
func (l *Label) getLabelTopLeft(ctx Context) geom.PointF32 {
pad := ctx.Style().Dimensions.TextPadding
bounds := l.bounds.Inset(pad)
switch l.TextAlignment {
case AlignRight:
return geom.PtF32(bounds.Max.X, bounds.Min.Y)
case AlignCenter:
return geom.PtF32(.5*(bounds.Min.X+bounds.Max.X), bounds.Min.Y)
default:
return bounds.Min
}
}
func (l *Label) Render(ctx Context) {
l.RenderBackground(ctx)
fontColor := l.TextColor(ctx)
fontName := l.FontName(ctx)
pad := ctx.Style().Dimensions.TextPadding
bounds := l.bounds.Inset(pad)
switch l.TextAlignment {
case AlignLeft:
ctx.Fonts().TextAlign(fontName, bounds.Min, fontColor, l.Text, l.TextAlignment)
case AlignRight:
ctx.Fonts().TextAlign(fontName, geom.PtF32(bounds.Max.X, bounds.Min.Y), fontColor, l.Text, l.TextAlignment)
case AlignCenter:
ctx.Fonts().TextAlign(fontName, geom.PtF32(.5*(bounds.Min.X+bounds.Max.X), bounds.Min.Y), fontColor, l.Text, l.TextAlignment)
topLeft := l.getLabelTopLeft(ctx)
if l.DropShadow != nil {
ctx.Fonts().TextAlign(fontName, topLeft.Add2D(1, 1), l.DropShadow, l.Text, l.TextAlignment)
}
ctx.Fonts().TextAlign(fontName, topLeft, fontColor, l.Text, l.TextAlignment)
}

View File

@ -23,11 +23,17 @@ type overflow struct {
ver *Scrollbar
}
func Overflow(content Control) Control {
type ScrollControl interface {
Control
SetScrollbarColor(bar color.Color, hover color.Color)
}
func Overflow(content Control) ScrollControl {
return OverflowBackground(content, nil)
}
func OverflowBackground(content Control, back color.Color) Control {
func OverflowBackground(content Control, back color.Color) ScrollControl {
var o = &overflow{Proxy: Proxy{Content: content}, Background: back}
o.hor = BuildScrollbar(OrientationHorizontal, func(*Scrollbar) {})
o.ver = BuildScrollbar(OrientationVertical, func(*Scrollbar) {})
@ -152,3 +158,10 @@ func (o *overflow) Render(ctx Context) {
bar.Render(ctx)
})
}
func (o *overflow) SetScrollbarColor(bar color.Color, hover color.Color) {
o.hor.BarColor = bar
o.hor.BarHoverColor = hover
o.ver.BarColor = bar
o.ver.BarHoverColor = hover
}

View File

@ -26,8 +26,7 @@ func BuildParagraph(text string, fn func(*Paragraph)) *Paragraph {
func fastFormatFloat32(f float32) string { return strconv.FormatFloat(float64(f), 'b', 32, 32) }
func (p *Paragraph) desiredSize(ctx Context) interface{} {
fontName := p.FontName(ctx)
font := ctx.Fonts().Font(fontName)
font := p.Font_(ctx)
pad := ctx.Style().Dimensions.TextPadding
lines := p.splitInLines(ctx, p.width-2*pad)
@ -43,8 +42,7 @@ func (p *Paragraph) hashTextDesired(ctx Context) string {
}
func (p *Paragraph) splitInLines(ctx Context, width float32) []string {
fontName := p.FontName(ctx)
font := ctx.Fonts().Font(fontName)
font := p.Font_(ctx)
spaces := func(s string) []int { // creates a slice with indices where spaces can be found in string s
var spaces []int

View File

@ -32,8 +32,10 @@ type Renderer interface {
Rectangle(r geom.RectangleF32, c color.Color, thickness float32)
RenderTo(Texture)
RenderToDisplay()
Resize(width, height int)
SetIcon(source ImageSource)
SetMouseCursor(c MouseCursor)
Size() geom.PointF32
Size() geom.Point
Target() Texture
Text(font Font, p geom.PointF32, color color.Color, text string)
TextAlign(font Font, p geom.PointF32, color color.Color, text string, align HorizontalAlignment)

View File

@ -1,6 +1,8 @@
package ui
import (
"image/color"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg"
)
@ -10,6 +12,8 @@ type Scrollbar struct {
Orientation Orientation
BarColor color.Color
BarHoverColor color.Color
ContentLength float32
ContentOffset float32
@ -58,7 +62,7 @@ func (s *Scrollbar) Handle(ctx Context, e Event) bool {
func (s *Scrollbar) Render(ctx Context) {
ctx.Renderer().FillRectangle(s.bounds, zntg.RGBA(0, 0, 0, 1))
s.handle.Render(ctx)
s.handle.Render(ctx, s)
}
func (s *Scrollbar) updateBar(ctx Context) {
@ -83,12 +87,22 @@ type ScrollbarHandle struct {
ControlBase
}
func (h *ScrollbarHandle) Render(ctx Context) {
func (h *ScrollbarHandle) Render(ctx Context, s *Scrollbar) {
h.RenderBackground(ctx)
p := ctx.Style().Palette
fill := p.Primary
var fill color.Color
if h.over {
fill = p.PrimaryLight
if s.BarHoverColor == nil {
fill = p.PrimaryLight
} else {
fill = s.BarHoverColor
}
} else {
if s.BarColor == nil {
fill = p.Primary
} else {
fill = s.BarColor
}
}
ctx.Renderer().FillRectangle(h.bounds.Inset(1), fill)
}

View File

@ -12,6 +12,7 @@ var defaultPalette *Palette
var defaultStyle *Style
type Dimensions struct {
Margin float32
OutlineWidth float32
ScrollbarWidth float32
TextPadding float32
@ -67,6 +68,7 @@ type Style struct {
func DefaultDimensions() *Dimensions {
return &Dimensions{
Margin: 8.,
OutlineWidth: 2.,
ScrollbarWidth: 16.,
TextPadding: 8.,

View File

@ -57,8 +57,7 @@ func (b *TextBox) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.Poi
}
func (b *TextBox) DesiredSize(ctx Context, _ geom.PointF32) geom.PointF32 {
var fontName = b.FontName(ctx)
var font = ctx.Fonts().Font(fontName)
font := b.Font_(ctx)
var width = font.WidthOf(b.Text)
var height = font.Height()
var pad = b.pad(ctx)
@ -68,7 +67,7 @@ func (b *TextBox) DesiredSize(ctx Context, _ geom.PointF32) geom.PointF32 {
func (b *TextBox) mousePosToCaretPos(ctx Context, e MouseEvent) int {
p := b.ToControlPosition(e.Pos())
offset := p.X - b.box.bounds.Min.X
f := ctx.Fonts().Font(b.FontName(ctx))
f := b.Font_(ctx)
var carets = [3]int{0, 0, len(b.Text)}
var offsets = [3]float32{0, 0, f.WidthOf(b.Text)}
var updateCenter = func() {
@ -259,8 +258,7 @@ func (b *TextBox) Render(ctx Context) {
back = ctx.Style().Palette.Background
}
renderer.Clear(back)
fontName := b.FontName(ctx)
font := ctx.Fonts().Font(fontName)
font := b.Font_(ctx)
if b.Selection.Start != b.Selection.End {
left, right := font.WidthOf(b.Text[:b.Selection.Start]), font.WidthOf(b.Text[:b.Selection.End])
renderer.FillRectangle(geom.RectF32(left, 0, right, size.Y), style.Palette.PrimaryHighlight)

View File

@ -4,8 +4,8 @@ import "opslag.de/schobers/geom"
type Texture interface {
Destroy() error
Height() float32
Width() float32
Height() int
Width() int
}
func SizeOfTexture(t Texture) geom.PointF32 { return geom.PtF32(t.Width(), t.Height()) }
func SizeOfTexture(t Texture) geom.Point { return geom.Pt(t.Width(), t.Height()) }

View File

@ -8,7 +8,7 @@ import (
)
func ScaleTexture(render Renderer, texture Texture, scale float32) Texture {
w := uint(texture.Width() * scale)
w := uint(float32(texture.Width()) * scale)
if w == 0 {
return nil
}
@ -118,7 +118,7 @@ func (t *Textures) Scaled(texture Texture, scale float32) Texture {
}
func (t *Textures) ScaledHeight(texture Texture, height float32) (Texture, float32) {
scale := height / texture.Height()
scale := height / float32(texture.Height())
if geom.IsNaN32(scale) {
return nil, 0
}

View File

@ -46,7 +46,7 @@ func RunWait(r Renderer, s *Style, view Control, wait bool) error {
ctx.Renderer().Refresh()
for !ctx.HasQuit() {
var size = r.Size()
var bounds = geom.RectF32(0, 0, size.X, size.Y)
var bounds = geom.RectF32(0, 0, float32(size.X), float32(size.Y))
overlays.Arrange(ctx, bounds, geom.ZeroPtF32, nil)
overlays.Render(ctx)
if ctx.HasQuit() {