Compare commits

...

7 Commits

14 changed files with 161 additions and 54 deletions

View File

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

View File

@ -238,7 +238,7 @@ func (r *Renderer) CreateFontPath(path string, size int) (ui.Font, error) {
return &Font{font}, nil 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() m, err := source.CreateImage()
if err != nil { if err != nil {
return nil, err return nil, err
@ -252,6 +252,14 @@ func (r *Renderer) createTexture(source ui.ImageSource, keepSource bool) (ui.Tex
if err != nil { if err != nil {
return nil, err 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() defer surface.Free()
texture, err := r.renderer.CreateTextureFromSurface(surface) texture, err := r.renderer.CreateTextureFromSurface(surface)
if err != nil { if err != nil {
@ -391,6 +399,10 @@ func (r *Renderer) RenderToDisplay() {
r.renderer.SetRenderTarget(nil) 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) { func (r *Renderer) SetDrawColor(c sdl.Color) {
r.renderer.SetDrawColor(c.R, c.G, c.B, c.A) 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)) 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) 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() w, h, err := r.renderer.GetOutputSize()
if err != nil { 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 { func (r *Renderer) SystemCursor(id sdl.SystemCursor) *sdl.Cursor {

View File

@ -41,7 +41,7 @@ func BuildIconButton(icon, text string, fn func(b *Button)) *Button {
func (b *Button) desiredSize(ctx Context) geom.PointF32 { func (b *Button) desiredSize(ctx Context) geom.PointF32 {
var pad = ctx.Style().Dimensions.TextPadding 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() var w, h float32 = 0, font.Height()
icon, iconW, iconH := b.icon(ctx) icon, iconW, iconH := b.icon(ctx)
@ -155,6 +155,13 @@ func (b *Button) fillColor(p *Palette) color.Color {
return nil 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 { func (b *Button) textColor(p *Palette) color.Color {
if b.Disabled { if b.Disabled {
if b.Background != nil { if b.Background != nil {
@ -166,22 +173,19 @@ func (b *Button) textColor(p *Palette) color.Color {
} }
return b.disabledColor(p) return b.disabledColor(p)
} }
if b.Font.Color != nil {
return b.Font.Color
}
switch b.Type { switch b.Type {
case ButtonTypeContained: case ButtonTypeContained:
return p.TextOnPrimary return b.fontColor(p.TextOnPrimary)
case ButtonTypeIcon: case ButtonTypeIcon:
if b.over { if b.over {
if b.HoverColor != nil { if b.HoverColor != nil {
return b.HoverColor return b.HoverColor
} }
return p.Primary return b.fontColor(p.Primary)
} }
return p.Text return b.fontColor(p.Text)
default: default:
return p.Primary return b.fontColor(p.Primary)
} }
} }
@ -221,8 +225,7 @@ func (b *Button) Render(ctx Context) {
pos.X += iconW + pad pos.X += iconW + pad
} }
if len(b.Text) != 0 { if len(b.Text) != 0 {
fontName := b.FontName(ctx) font := b.Font_(ctx)
font := ctx.Fonts().Font(fontName)
ctx.Renderer().Text(font, geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), textColor, b.Text) ctx.Renderer().Text(font, geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), textColor, b.Text)
} }

View File

@ -23,7 +23,7 @@ func BuildCheckbox(text string, fn func(c *Checkbox)) *Checkbox {
func (c *Checkbox) desiredSize(ctx Context) geom.PointF32 { func (c *Checkbox) desiredSize(ctx Context) geom.PointF32 {
var pad = ctx.Style().Dimensions.TextPadding 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() var w, h float32 = 0, font.Height()
if len(c.Text) != 0 { if len(c.Text) != 0 {
w += pad + font.WidthOf(c.Text) w += pad + font.WidthOf(c.Text)
@ -128,8 +128,7 @@ func (c *Checkbox) Render(ctx Context) {
pos.X += iconWidth + pad pos.X += iconWidth + pad
} }
if len(c.Text) != 0 { if len(c.Text) != 0 {
var fontName = c.FontName(ctx) font := c.Font_(ctx)
var font = ctx.Fonts().Font(fontName)
ctx.Renderer().Text(font, geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), fore, c.Text) 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) 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 { func (c *ContainerBase) Handle(ctx Context, e Event) bool {
if c.ControlBase.Handle(ctx, e) { if c.ControlBase.Handle(ctx, e) {
return true return true

View File

@ -188,6 +188,11 @@ func (c *ControlBase) HandleNotify(ctx Context, e Event, notifier Notifier) bool
return false 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 { func (c *ControlBase) FontColor(ctx Context, color color.Color) color.Color {
if c.Disabled { if c.Disabled {
return ctx.Style().Palette.TextOnDisabled return ctx.Style().Palette.TextOnDisabled

View File

@ -3,6 +3,7 @@ package ui
import ( import (
"image" "image"
"github.com/nfnt/resize"
"opslag.de/schobers/zntg" "opslag.de/schobers/zntg"
) )
@ -45,3 +46,25 @@ func (s ImageSourceResource) CreateImage() (image.Image, error) {
} }
return value.(image.Image), nil 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 package ui
import ( import (
"image/color"
"opslag.de/schobers/geom" "opslag.de/schobers/geom"
) )
type Label struct { type Label struct {
ControlBase ControlBase
Text string Text string
DropShadow color.Color
desired CachedValue desired CachedValue
} }
@ -25,8 +28,7 @@ func (l *Label) hashDesiredSize(ctx Context) string {
} }
func (l *Label) desiredSize(ctx Context) interface{} { func (l *Label) desiredSize(ctx Context) interface{} {
fontName := l.FontName(ctx) font := l.Font_(ctx)
font := ctx.Fonts().Font(fontName)
width := font.WidthOf(l.Text) width := font.WidthOf(l.Text)
height := font.Height() height := font.Height()
pad := ctx.Style().Dimensions.TextPadding 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) 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) { func (l *Label) Render(ctx Context) {
l.RenderBackground(ctx) l.RenderBackground(ctx)
fontColor := l.TextColor(ctx) fontColor := l.TextColor(ctx)
fontName := l.FontName(ctx) fontName := l.FontName(ctx)
pad := ctx.Style().Dimensions.TextPadding topLeft := l.getLabelTopLeft(ctx)
bounds := l.bounds.Inset(pad) if l.DropShadow != nil {
switch l.TextAlignment { ctx.Fonts().TextAlign(fontName, topLeft.Add2D(1, 1), l.DropShadow, l.Text, 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)
} }
ctx.Fonts().TextAlign(fontName, topLeft, fontColor, l.Text, l.TextAlignment)
} }

View File

@ -23,11 +23,17 @@ type overflow struct {
ver *Scrollbar 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) 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} var o = &overflow{Proxy: Proxy{Content: content}, Background: back}
o.hor = BuildScrollbar(OrientationHorizontal, func(*Scrollbar) {}) o.hor = BuildScrollbar(OrientationHorizontal, func(*Scrollbar) {})
o.ver = BuildScrollbar(OrientationVertical, func(*Scrollbar) {}) o.ver = BuildScrollbar(OrientationVertical, func(*Scrollbar) {})
@ -152,3 +158,10 @@ func (o *overflow) Render(ctx Context) {
bar.Render(ctx) 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 fastFormatFloat32(f float32) string { return strconv.FormatFloat(float64(f), 'b', 32, 32) }
func (p *Paragraph) desiredSize(ctx Context) interface{} { func (p *Paragraph) desiredSize(ctx Context) interface{} {
fontName := p.FontName(ctx) font := p.Font_(ctx)
font := ctx.Fonts().Font(fontName)
pad := ctx.Style().Dimensions.TextPadding pad := ctx.Style().Dimensions.TextPadding
lines := p.splitInLines(ctx, p.width-2*pad) 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 { func (p *Paragraph) splitInLines(ctx Context, width float32) []string {
fontName := p.FontName(ctx) font := p.Font_(ctx)
font := ctx.Fonts().Font(fontName)
spaces := func(s string) []int { // creates a slice with indices where spaces can be found in string s spaces := func(s string) []int { // creates a slice with indices where spaces can be found in string s
var spaces []int var spaces []int

View File

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

View File

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

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

View File

@ -46,7 +46,7 @@ func RunWait(r Renderer, s *Style, view Control, wait bool) error {
ctx.Renderer().Refresh() ctx.Renderer().Refresh()
for !ctx.HasQuit() { for !ctx.HasQuit() {
var size = r.Size() 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.Arrange(ctx, bounds, geom.ZeroPtF32, nil)
overlays.Render(ctx) overlays.Render(ctx)
if ctx.HasQuit() { if ctx.HasQuit() {