Added Fonts() to context similarly as Textures().

- Fonts are now managed by context instead of the implementation specific renderers.
This commit is contained in:
Sander Schobers 2020-05-15 15:42:24 +02:00
parent 02ee819a99
commit 3591e22c97
18 changed files with 215 additions and 153 deletions

View File

@ -22,8 +22,9 @@ func newFont(f *allg5.Font) *font {
return &font{f} return &font{f}
} }
func (f *font) Destroy() { func (f *font) Destroy() error {
f.Font.Destroy() f.Font.Destroy()
return nil
} }
func (f *font) Height() float32 { func (f *font) Height() float32 {

View File

@ -47,7 +47,7 @@ func NewRenderer(w, h int, opts allg5.NewDisplayOptions) (*Renderer, error) {
}) })
clean = nil clean = nil
return &Renderer{disp, eq, nil, map[string]*font{}, user, &ui.OSResources{}, ui.KeyState{}, ui.KeyModifierNone, ui.MouseCursorDefault}, nil return &Renderer{disp, eq, nil, user, &ui.OSResources{}, ui.KeyState{}, ui.KeyModifierNone, ui.MouseCursorDefault}, nil
} }
// Renderer implements ui.Renderer using Allegro 5. // Renderer implements ui.Renderer using Allegro 5.
@ -55,7 +55,6 @@ type Renderer struct {
disp *allg5.Display disp *allg5.Display
eq *allg5.EventQueue eq *allg5.EventQueue
unh func(allg5.Event) unh func(allg5.Event)
ft map[string]*font
user *allg5.UserEventSource user *allg5.UserEventSource
res ui.Resources res ui.Resources
@ -149,10 +148,6 @@ func (r *Renderer) Refresh() {
func (r *Renderer) Destroy() error { func (r *Renderer) Destroy() error {
r.user.Destroy() r.user.Destroy()
r.eq.Destroy() r.eq.Destroy()
for _, f := range r.ft {
f.Destroy()
}
r.ft = nil
r.disp.Destroy() r.disp.Destroy()
r.res.Destroy() r.res.Destroy()
return nil return nil
@ -164,6 +159,18 @@ func (r *Renderer) Clear(c color.Color) {
allg5.ClearToColor(newColor(c)) allg5.ClearToColor(newColor(c))
} }
func (r *Renderer) CreateFontPath(path string, size int) (ui.Font, error) {
path, err := r.res.FetchResource(path)
if err != nil {
return nil, err
}
f, err := allg5.LoadTTFFont(path, size)
if err != nil {
return nil, err
}
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) (ui.Texture, error) {
im, err := source.CreateImage() im, err := source.CreateImage()
if err != nil { if err != nil {
@ -237,10 +244,6 @@ func (r *Renderer) FillRectangle(rect geom.RectangleF32, c color.Color) {
allg5.DrawFilledRectangle(rect.Min.X, rect.Min.Y, rect.Max.X, rect.Max.Y, newColor(c)) allg5.DrawFilledRectangle(rect.Min.X, rect.Min.Y, rect.Max.X, rect.Max.Y, newColor(c))
} }
func (r *Renderer) Font(name string) ui.Font {
return r.ft[name]
}
func (r *Renderer) mustGetBitmap(t ui.Texture) *allg5.Bitmap { func (r *Renderer) mustGetBitmap(t ui.Texture) *allg5.Bitmap {
texture, ok := t.(*texture) texture, ok := t.(*texture)
if !ok { if !ok {
@ -255,33 +258,6 @@ func (r *Renderer) Rectangle(rect geom.RectangleF32, c color.Color, thickness fl
allg5.DrawRectangle(minX, minY, maxX, maxY, newColor(c), thickness) allg5.DrawRectangle(minX, minY, maxX, maxY, newColor(c), thickness)
} }
func (r *Renderer) RegisterFont(name, path string, size int) error {
path, err := r.res.FetchResource(path)
if err != nil {
return err
}
font, err := allg5.LoadTTFFont(path, size)
if err != nil {
return err
}
var prev = r.ft[name]
if prev != nil {
prev.Destroy()
}
r.ft[name] = newFont(font)
return nil
}
func (r *Renderer) RegisterFonts(path string, fonts ...FontDefinition) error {
for _, f := range fonts {
err := r.RegisterFont(path, f.Name, f.Size)
if err != nil {
return err
}
}
return nil
}
func (r *Renderer) RenderTo(texture ui.Texture) { func (r *Renderer) RenderTo(texture ui.Texture) {
bmp := r.mustGetBitmap(texture) bmp := r.mustGetBitmap(texture)
bmp.SetAsTarget() bmp.SetAsTarget()
@ -325,20 +301,20 @@ func (r *Renderer) Target() ui.Texture {
return &texture{allg5.CurrentTarget(), nil} return &texture{allg5.CurrentTarget(), nil}
} }
func (r *Renderer) text(p geom.PointF32, font string, c color.Color, t string, align allg5.HorizontalAlignment) { func (r *Renderer) text(f ui.Font, p geom.PointF32, c color.Color, t string, align allg5.HorizontalAlignment) {
var f = r.ft[font] font, ok := f.(*font)
if f == nil { if !ok {
return return
} }
x, y := snap(p) x, y := snap(p)
f.Draw(x, y, newColor(c), align, t) font.Draw(x, y, newColor(c), align, t)
} }
func (r *Renderer) Text(p geom.PointF32, font string, c color.Color, t string) { func (r *Renderer) Text(font ui.Font, p geom.PointF32, c color.Color, t string) {
r.text(p, font, c, t, allg5.AlignLeft) r.text(font, p, c, t, allg5.AlignLeft)
} }
func (r *Renderer) TextAlign(p geom.PointF32, font string, c color.Color, t string, align ui.HorizontalAlignment) { func (r *Renderer) TextAlign(font ui.Font, p geom.PointF32, c color.Color, t string, align ui.HorizontalAlignment) {
var alignment = allg5.AlignLeft var alignment = allg5.AlignLeft
switch align { switch align {
case ui.AlignCenter: case ui.AlignCenter:
@ -346,7 +322,7 @@ func (r *Renderer) TextAlign(p geom.PointF32, font string, c color.Color, t stri
case ui.AlignRight: case ui.AlignRight:
alignment = allg5.AlignRight alignment = allg5.AlignRight
} }
r.text(p, font, c, t, alignment) r.text(font, p, c, t, alignment)
} }
// Utility functions // Utility functions

View File

@ -22,7 +22,7 @@ func HexColor(s string) (color.RGBA, error) {
if len(match[4]) > 0 { if len(match[4]) > 0 {
a = values[3] a = values[3]
} }
return color.RGBA{R: uint8(values[0]), G: uint8(values[1]), B: uint8(values[2]), A: uint8(a)}, nil return RGBA(uint8(values[0]), uint8(values[1]), uint8(values[2]), uint8(a)), nil
} }
// HexToInt tries to convert a string with hexadecimal characters to an integer. // HexToInt tries to convert a string with hexadecimal characters to an integer.
@ -64,3 +64,11 @@ func MustHexColor(s string) color.RGBA {
} }
return color return color
} }
// RGB creates an opaque color with the specified red, green and red values.
func RGB(r, g, b byte) color.RGBA { return RGBA(r, g, b, 0xff) }
// RGB creates a color with the specified red, green, red and alpha values.
func RGBA(r, g, b, a byte) color.RGBA {
return color.RGBA{R: r, G: g, B: b, A: a}
}

View File

@ -9,6 +9,11 @@ type Font struct {
*ttf.Font *ttf.Font
} }
func (f *Font) Destroy() error {
f.Font.Close()
return nil
}
func (f *Font) Height() float32 { func (f *Font) Height() float32 {
return float32(f.Font.Height()) return float32(f.Font.Height())
} }

View File

@ -22,7 +22,6 @@ type Renderer struct {
window *sdl.Window window *sdl.Window
renderer *sdl.Renderer renderer *sdl.Renderer
refresh uint32 refresh uint32
fonts map[string]*Font
resources ui.Resources resources ui.Resources
mouse geom.PointF32 mouse geom.PointF32
@ -88,7 +87,6 @@ func NewRenderer(title string, width, height int32, opts NewRendererOptions) (*R
window: window, window: window,
renderer: renderer, renderer: renderer,
refresh: refresh, refresh: refresh,
fonts: map[string]*Font{},
resources: &ui.OSResources{}, resources: &ui.OSResources{},
cursors: map[sdl.SystemCursor]*sdl.Cursor{}, cursors: map[sdl.SystemCursor]*sdl.Cursor{},
}, nil }, nil
@ -205,9 +203,6 @@ func (r *Renderer) Refresh() {
// Lifetime // Lifetime
func (r *Renderer) Destroy() error { func (r *Renderer) Destroy() error {
for _, f := range r.fonts {
f.Close()
}
r.renderer.Destroy() r.renderer.Destroy()
r.window.Destroy() r.window.Destroy()
ttf.Quit() ttf.Quit()
@ -226,6 +221,18 @@ func (r *Renderer) Clear(c color.Color) {
r.renderer.Clear() r.renderer.Clear()
} }
func (r *Renderer) CreateFontPath(path string, size int) (ui.Font, error) {
path, err := r.resources.FetchResource(path)
if err != nil {
return nil, err
}
font, err := ttf.OpenFont(path, size)
if err != nil {
return nil, err
}
return &Font{font}, nil
}
func (r *Renderer) createTexture(source ui.ImageSource, keepSource bool) (ui.Texture, error) { func (r *Renderer) createTexture(source ui.ImageSource, keepSource bool) (ui.Texture, error) {
m, err := source.CreateImage() m, err := source.CreateImage()
if err != nil { if err != nil {
@ -307,10 +314,6 @@ func (r *Renderer) FillRectangle(rect geom.RectangleF32, c color.Color) {
r.renderer.FillRect(SDLRectanglePtr(rect)) r.renderer.FillRect(SDLRectanglePtr(rect))
} }
func (r *Renderer) Font(name string) ui.Font {
return r.fonts[name]
}
func (r *Renderer) Rectangle(rect geom.RectangleF32, c color.Color, thickness float32) { func (r *Renderer) Rectangle(rect geom.RectangleF32, c color.Color, thickness float32) {
r.SetDrawColorGo(c) r.SetDrawColorGo(c)
if rect.Dx() == 0 { // SDL doesn't draw a 1 px wide line when Dx() == 0 && thickness == 1 if rect.Dx() == 0 { // SDL doesn't draw a 1 px wide line when Dx() == 0 && thickness == 1
@ -333,19 +336,6 @@ func (r *Renderer) Rectangle(rect geom.RectangleF32, c color.Color, thickness fl
} }
} }
func (r *Renderer) RegisterFont(name, path string, size int) error {
path, err := r.resources.FetchResource(path)
if err != nil {
return err
}
font, err := ttf.OpenFont(path, size)
if err != nil {
return err
}
r.fonts[name] = &Font{font}
return nil
}
func (r *Renderer) RenderTo(t ui.Texture) { func (r *Renderer) RenderTo(t ui.Texture) {
texture, ok := t.(sdlTexture) texture, ok := t.(sdlTexture)
if ok { if ok {
@ -397,8 +387,8 @@ func (r *Renderer) Target() ui.Texture {
return &Texture{target} return &Texture{target}
} }
func (r *Renderer) Text(p geom.PointF32, font string, color color.Color, text string) { func (r *Renderer) Text(font ui.Font, p geom.PointF32, color color.Color, text string) {
f := r.Font(font).(*Font) f := font.(*Font)
surface, err := f.RenderUTF8Blended(text, ColorSDL(color)) surface, err := f.RenderUTF8Blended(text, ColorSDL(color))
if err != nil { if err != nil {
@ -414,16 +404,16 @@ func (r *Renderer) Text(p geom.PointF32, font string, color color.Color, text st
r.DrawTexture(&Texture{texture}, p) r.DrawTexture(&Texture{texture}, p)
} }
func (r *Renderer) TextAlign(p geom.PointF32, font string, color color.Color, text string, align ui.HorizontalAlignment) { func (r *Renderer) TextAlign(font ui.Font, p geom.PointF32, color color.Color, text string, align ui.HorizontalAlignment) {
switch align { switch align {
case ui.AlignLeft: case ui.AlignLeft:
r.Text(p, font, color, text) r.Text(font, p, color, text)
case ui.AlignCenter: case ui.AlignCenter:
width := r.Font(font).(*Font).WidthOf(text) width := font.WidthOf(text)
r.Text(p.Add2D(-.5*width, 0), font, color, text) r.Text(font, p.Add2D(-.5*width, 0), color, text)
case ui.AlignRight: case ui.AlignRight:
width := r.Font(font).(*Font).WidthOf(text) width := font.WidthOf(text)
r.Text(p.Add2D(-width, 0), font, color, text) r.Text(font, p.Add2D(-width, 0), color, text)
} }
} }

View File

@ -36,7 +36,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.Renderer().Font(b.FontName(ctx)) var font = ctx.Fonts().Font(b.FontName(ctx))
var w, h float32 = 0, font.Height() var w, h float32 = 0, font.Height()
if len(b.Text) != 0 { if len(b.Text) != 0 {
w += pad + font.WidthOf(b.Text) w += pad + font.WidthOf(b.Text)
@ -144,9 +144,9 @@ func (b *Button) Render(ctx Context) {
pos.X += iconWidth + pad pos.X += iconWidth + pad
} }
if len(b.Text) != 0 { if len(b.Text) != 0 {
var fontName = b.FontName(ctx) fontName := b.FontName(ctx)
var font = ctx.Renderer().Font(fontName) font := ctx.Fonts().Font(fontName)
ctx.Renderer().Text(geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), fontName, textColor, b.Text) ctx.Renderer().Text(font, geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), textColor, b.Text)
} }
if b.Type == ButtonTypeOutlined { if b.Type == ButtonTypeOutlined {

View File

@ -25,7 +25,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.Renderer().Font(c.FontName(ctx)) var font = ctx.Fonts().Font(c.FontName(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,7 +128,7 @@ func (c *Checkbox) Render(ctx Context) {
} }
if len(c.Text) != 0 { if len(c.Text) != 0 {
var fontName = c.FontName(ctx) var fontName = c.FontName(ctx)
var font = ctx.Renderer().Font(fontName) var font = ctx.Fonts().Font(fontName)
ctx.Renderer().Text(geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), fontName, fore, c.Text) ctx.Renderer().Text(font, geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), fore, c.Text)
} }
} }

View File

@ -2,6 +2,7 @@ package ui
type Context interface { type Context interface {
Animate() Animate()
Fonts() *Fonts
HasQuit() bool HasQuit() bool
Quit() Quit()
Renderer() Renderer Renderer() Renderer
@ -15,14 +16,32 @@ var _ EventTarget = &context{}
type context struct { type context struct {
animate bool animate bool
quit chan struct{} quit chan struct{}
r Renderer renderer Renderer
view Control view Control
fonts *Fonts
textures *Textures textures *Textures
style *Style style *Style
} }
func newContext(r Renderer, s *Style, view Control) *context {
return &context{
quit: make(chan struct{}),
renderer: r,
style: s,
view: view,
fonts: NewFonts(r),
textures: NewTextures(r)}
}
func (c *context) Animate() { c.animate = true } func (c *context) Animate() { c.animate = true }
func (c *context) Destroy() {
c.fonts.Destroy()
c.textures.Destroy()
}
func (c *context) Fonts() *Fonts { return c.fonts }
func (c *context) HasQuit() bool { func (c *context) HasQuit() bool {
select { select {
case <-c.quit: case <-c.quit:
@ -32,7 +51,7 @@ func (c *context) HasQuit() bool {
} }
} }
func (c *context) Renderer() Renderer { return c.r } func (c *context) Renderer() Renderer { return c.renderer }
func (c *context) Style() *Style { return c.style } func (c *context) Style() *Style { return c.style }

View File

@ -16,7 +16,12 @@ type basic struct {
} }
func (b *basic) Init(ctx ui.Context) error { func (b *basic) Init(ctx ui.Context) error {
_, err := ctx.Textures().CreateTexturePath("plus", "../resources/images/plus.png", true) _, err := ctx.Fonts().CreateFontPath("default", "../resources/font/OpenSans-Regular.ttf", 14)
if err != nil {
return err
}
_, err = ctx.Textures().CreateTexturePath("plus", "../resources/images/plus.png", true)
if err != nil { if err != nil {
return err return err
} }
@ -73,11 +78,6 @@ func run() error {
} }
defer render.Destroy() defer render.Destroy()
err = render.RegisterFont("default", "../resources/font/OpenSans-Regular.ttf", 14)
if err != nil {
return err
}
return ui.RunWait(render, ui.DefaultStyle(), &basic{}, true) return ui.RunWait(render, ui.DefaultStyle(), &basic{}, true)
} }

View File

@ -7,6 +7,7 @@ import (
) )
type Font interface { type Font interface {
Destroy() error
Height() float32 Height() float32
Measure(t string) geom.RectangleF32 Measure(t string) geom.RectangleF32
WidthOf(t string) float32 WidthOf(t string) float32

70
ui/fonts.go Normal file
View File

@ -0,0 +1,70 @@
package ui
import (
"image/color"
"opslag.de/schobers/geom"
)
type Fonts struct {
render Renderer
fonts map[string]Font
}
func NewFonts(render Renderer) *Fonts {
return &Fonts{render, map[string]Font{}}
}
func (f *Fonts) AddFont(name string, font Font) {
curr := f.fonts[name]
if curr != nil {
curr.Destroy()
}
f.fonts[name] = font
}
func (f *Fonts) createFont(name string, create func() (Font, error)) (Font, error) {
font, err := create()
if err != nil {
return nil, err
}
f.AddFont(name, font)
return font, nil
}
func (f *Fonts) CreateFontPath(name, path string, size int) (Font, error) {
return f.createFont(name, func() (Font, error) {
return f.render.CreateFontPath(path, size)
})
}
func (f *Fonts) Destroy() {
for _, font := range f.fonts {
font.Destroy()
}
f.fonts = nil
}
func (f *Fonts) Font(name string) Font {
font, ok := f.fonts[name]
if ok {
return font
}
return nil
}
func (f *Fonts) Text(fontName string, p geom.PointF32, color color.Color, text string) {
font := f.Font(fontName)
if font == nil {
return
}
f.render.Text(font, p, color, text)
}
func (f *Fonts) TextAlign(fontName string, p geom.PointF32, color color.Color, text string, align HorizontalAlignment) {
font := f.Font(fontName)
if font == nil {
return
}
f.render.TextAlign(font, p, color, text, align)
}

View File

@ -35,7 +35,7 @@ func (l *Label) hashContent(ctx Context) string {
func (l *Label) desiredSize(ctx Context) interface{} { func (l *Label) desiredSize(ctx Context) interface{} {
fontName := l.FontName(ctx) fontName := l.FontName(ctx)
font := ctx.Renderer().Font(fontName) 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
@ -49,16 +49,16 @@ func (l *Label) DesiredSize(ctx Context) geom.PointF32 {
func (l *Label) Render(ctx Context) { func (l *Label) Render(ctx Context) {
l.RenderBackground(ctx) l.RenderBackground(ctx)
c := l.FontColor(ctx) fontColor := l.FontColor(ctx)
f := l.FontName(ctx) fontName := l.FontName(ctx)
pad := ctx.Style().Dimensions.TextPadding pad := ctx.Style().Dimensions.TextPadding
bounds := l.bounds.Inset(pad) bounds := l.bounds.Inset(pad)
switch l.TextAlignment { switch l.TextAlignment {
case AlignLeft: case AlignLeft:
ctx.Renderer().TextAlign(bounds.Min, f, c, l.Text, l.TextAlignment) ctx.Fonts().TextAlign(fontName, bounds.Min, fontColor, l.Text, l.TextAlignment)
case AlignRight: case AlignRight:
ctx.Renderer().TextAlign(geom.PtF32(bounds.Max.X, bounds.Min.Y), f, c, l.Text, l.TextAlignment) ctx.Fonts().TextAlign(fontName, geom.PtF32(bounds.Max.X, bounds.Min.Y), fontColor, l.Text, l.TextAlignment)
case AlignCenter: case AlignCenter:
ctx.Renderer().TextAlign(geom.PtF32(.5*(bounds.Min.X+bounds.Max.X), bounds.Min.Y), f, c, l.Text, l.TextAlignment) ctx.Fonts().TextAlign(fontName, geom.PtF32(.5*(bounds.Min.X+bounds.Max.X), bounds.Min.Y), fontColor, l.Text, l.TextAlignment)
} }
} }

View File

@ -17,6 +17,7 @@ type Renderer interface {
// Drawing // Drawing
Clear(c color.Color) Clear(c color.Color)
CreateFontPath(path string, size int) (Font, error)
CreateTexture(m ImageSource) (Texture, error) CreateTexture(m ImageSource) (Texture, error)
CreateTextureGo(m image.Image, source bool) (Texture, error) CreateTextureGo(m image.Image, source bool) (Texture, error)
CreateTexturePath(path string, source bool) (Texture, error) CreateTexturePath(path string, source bool) (Texture, error)
@ -25,16 +26,14 @@ type Renderer interface {
DrawTexture(t Texture, p geom.PointF32) DrawTexture(t Texture, p geom.PointF32)
DrawTextureOptions(t Texture, p geom.PointF32, opts DrawOptions) DrawTextureOptions(t Texture, p geom.PointF32, opts DrawOptions)
FillRectangle(r geom.RectangleF32, c color.Color) FillRectangle(r geom.RectangleF32, c color.Color)
Font(name string) Font
Rectangle(r geom.RectangleF32, c color.Color, thickness float32) Rectangle(r geom.RectangleF32, c color.Color, thickness float32)
RegisterFont(name, path string, size int) error
RenderTo(Texture) RenderTo(Texture)
RenderToDisplay() RenderToDisplay()
SetMouseCursor(c MouseCursor) SetMouseCursor(c MouseCursor)
Size() geom.PointF32 Size() geom.PointF32
Target() Texture Target() Texture
Text(p geom.PointF32, font string, color color.Color, text string) Text(font Font, p geom.PointF32, color color.Color, text string)
TextAlign(p geom.PointF32, font string, color color.Color, text string, align HorizontalAlignment) TextAlign(font Font, p geom.PointF32, color color.Color, text string, align HorizontalAlignment)
// Resources // Resources
Resources() Resources Resources() Resources

View File

@ -2,6 +2,7 @@ package ui
import ( import (
"opslag.de/schobers/geom" "opslag.de/schobers/geom"
"opslag.de/schobers/zntg"
) )
type Scrollbar struct { type Scrollbar struct {
@ -55,7 +56,7 @@ func (s *Scrollbar) Handle(ctx Context, e Event) {
} }
func (s *Scrollbar) Render(ctx Context) { func (s *Scrollbar) Render(ctx Context) {
ctx.Renderer().FillRectangle(s.bounds, RGBA(0, 0, 0, 1)) ctx.Renderer().FillRectangle(s.bounds, zntg.RGBA(0, 0, 0, 1))
s.handle.Render(ctx) s.handle.Render(ctx)
} }

View File

@ -1,6 +1,10 @@
package ui package ui
import "image/color" import (
"image/color"
"opslag.de/schobers/zntg"
)
var defaultDimensions *Dimensions var defaultDimensions *Dimensions
var defaultFonts *Fonts var defaultFonts *Fonts
@ -13,7 +17,7 @@ type Dimensions struct {
TextPadding float32 TextPadding float32
} }
type Fonts struct { type FontNames struct {
Default string Default string
} }
@ -44,60 +48,44 @@ type Palette struct {
type Style struct { type Style struct {
Dimensions *Dimensions Dimensions *Dimensions
Fonts *Fonts Fonts *FontNames
Palette *Palette Palette *Palette
} }
func DefaultDimensions() *Dimensions { func DefaultDimensions() *Dimensions {
if defaultDimensions == nil { return &Dimensions{
defaultDimensions = &Dimensions{ OutlineWidth: 2.,
OutlineWidth: 2., ScrollbarWidth: 16.,
ScrollbarWidth: 16., TextPadding: 8.,
TextPadding: 8.,
}
} }
return defaultDimensions
} }
func DefaultFonts() *Fonts { func DefaultFontNames() *FontNames {
if defaultFonts == nil { return &FontNames{
defaultFonts = &Fonts{ Default: "default",
Default: "default",
}
} }
return defaultFonts
} }
func DefaultPalette() *Palette { func DefaultPalette() *Palette {
if defaultPalette == nil { return &Palette{
defaultPalette = &Palette{ Background: color.White,
Background: color.White, Primary: zntg.MustHexColor(`#3F51B5`),
Primary: RGBA(0x3F, 0x51, 0xB5, 0xFF), PrimaryDark: zntg.MustHexColor(`#002884`),
PrimaryDark: RGBA(0x00, 0x28, 0x84, 0xFF), PrimaryHighlight: zntg.MustHexColor(`#E8EAF6`),
PrimaryHighlight: RGBA(0xE8, 0xEA, 0xF6, 0xFF), PrimaryLight: zntg.MustHexColor(`#757CE8`),
PrimaryLight: RGBA(0x75, 0x7C, 0xE8, 0xFF), ShadedBackground: zntg.MustHexColor(`#FAFAFA`),
ShadedBackground: RGBA(0xFA, 0xFA, 0xFA, 0xFF), Text: color.Black,
Text: color.Black, TextDisabled: zntg.MustHexColor(`#BDBDBD`),
TextDisabled: RGBA(0xBD, 0xBD, 0xBD, 0xFF), TextNegative: zntg.MustHexColor(`#FF4336`),
TextNegative: RGBA(0xFF, 0x43, 0x36, 0xFF), TextOnPrimary: color.White,
TextOnPrimary: color.White, TextPositive: zntg.MustHexColor(`#4CAF50`),
TextPositive: RGBA(0x4C, 0xAF, 0x50, 0xFF),
}
} }
return defaultPalette
} }
func DefaultStyle() *Style { func DefaultStyle() *Style {
if defaultStyle == nil { return &Style{
defaultStyle = &Style{ Dimensions: DefaultDimensions(),
Dimensions: DefaultDimensions(), Fonts: DefaultFontNames(),
Fonts: DefaultFonts(), Palette: DefaultPalette(),
Palette: DefaultPalette(),
}
} }
return defaultStyle
}
func RGBA(r, g, b, a byte) *color.RGBA {
return &color.RGBA{R: r, G: g, B: b, A: a}
} }

View File

@ -58,7 +58,7 @@ func (b *TextBox) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.Poi
func (b *TextBox) DesiredSize(ctx Context) geom.PointF32 { func (b *TextBox) DesiredSize(ctx Context) geom.PointF32 {
var fontName = b.FontName(ctx) var fontName = b.FontName(ctx)
var font = ctx.Renderer().Font(fontName) 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 +68,7 @@ func (b *TextBox) DesiredSize(ctx Context) 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.Renderer().Font(b.FontName(ctx)) f := ctx.Fonts().Font(b.FontName(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() {
@ -234,7 +234,6 @@ func (b *TextBox) Render(ctx Context) {
b.RenderOutline(ctx) b.RenderOutline(ctx)
c := b.FontColor(ctx) c := b.FontColor(ctx)
f := b.FontName(ctx)
style := ctx.Style() style := ctx.Style()
var caretWidth float32 = 1 var caretWidth float32 = 1
b.box.RenderFn(ctx, func(_ Context, size geom.PointF32) { b.box.RenderFn(ctx, func(_ Context, size geom.PointF32) {
@ -244,14 +243,16 @@ 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 := ctx.Fonts().Font(fontName)
if b.Selection.Start != b.Selection.End { if b.Selection.Start != b.Selection.End {
left, right := renderer.Font(f).WidthOf(b.Text[:b.Selection.Start]), renderer.Font(f).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)
} }
renderer.Text(geom.ZeroPtF32, f, c, b.Text) renderer.Text(font, geom.ZeroPtF32, c, b.Text)
const interval = 500 * time.Millisecond const interval = 500 * time.Millisecond
if b.Focus && (time.Since(b.blink)/interval)%2 == 0 { if b.Focus && (time.Since(b.blink)/interval)%2 == 0 {
var w = renderer.Font(f).WidthOf(b.Text[:b.Selection.Caret]) var w = font.WidthOf(b.Text[:b.Selection.Caret])
var caret = w + .5*caretWidth var caret = w + .5*caretWidth
renderer.Rectangle(geom.RectF32(caret, 0, caret, size.Y), c, caretWidth) renderer.Rectangle(geom.RectF32(caret, 0, caret, size.Y), c, caretWidth)
} }

View File

@ -13,7 +13,9 @@ func Run(r Renderer, s *Style, view Control) error {
// RunWait runs the application loop and conditionally waits on events before rendering. // RunWait runs the application loop and conditionally waits on events before rendering.
func RunWait(r Renderer, s *Style, view Control, wait bool) error { func RunWait(r Renderer, s *Style, view Control, wait bool) error {
ctx := &context{quit: make(chan struct{}), r: r, style: s, view: view, textures: NewTextures(r)} ctx := newContext(r, s, view)
defer ctx.Destroy()
root, ok := view.(RootControl) root, ok := view.(RootControl)
if ok { if ok {
err := root.Init(ctx) err := root.Init(ctx)

View File

@ -4,6 +4,7 @@ import (
"image/color" "image/color"
"opslag.de/schobers/geom" "opslag.de/schobers/geom"
"opslag.de/schobers/zntg"
) )
func Background(content Control, c color.Color) Control { func Background(content Control, c color.Color) Control {
@ -37,7 +38,7 @@ type shadow struct {
func (s *shadow) Render(ctx Context) { func (s *shadow) Render(ctx Context) {
s.Proxy.Render(ctx) s.Proxy.Render(ctx)
b := s.Bounds() b := s.Bounds()
shadow := RGBA(0xBD, 0xBD, 0xBD, 0x2F) shadow := zntg.RGBA(0xBD, 0xBD, 0xBD, 0x2F)
ctx.Renderer().FillRectangle(geom.RectF32(b.Min.X, b.Min.Y, b.Max.X, b.Min.Y+3), shadow) ctx.Renderer().FillRectangle(geom.RectF32(b.Min.X, b.Min.Y, b.Max.X, b.Min.Y+3), shadow)
ctx.Renderer().FillRectangle(geom.RectF32(b.Min.X, b.Min.Y, b.Max.X, b.Min.Y+2), shadow) ctx.Renderer().FillRectangle(geom.RectF32(b.Min.X, b.Min.Y, b.Max.X, b.Min.Y+2), shadow)
ctx.Renderer().FillRectangle(geom.RectF32(b.Min.X, b.Min.Y, b.Max.X, b.Min.Y+1), shadow) ctx.Renderer().FillRectangle(geom.RectF32(b.Min.X, b.Min.Y, b.Max.X, b.Min.Y+1), shadow)