ui,ui/app: convert Config to an interface

To keep the interface slim, remove the helper methods and shorten
the essential method, Pixels, to Px.

Add and use unexported Config implementation in the app package.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-07-10 16:40:53 +02:00
parent 624ef78e1c
commit 32aae18293
14 changed files with 104 additions and 104 deletions
+34 -1
View File
@@ -4,8 +4,10 @@ package app
import ( import (
"image" "image"
"math"
"os" "os"
"strings" "strings"
"time"
"gioui.org/ui" "gioui.org/ui"
) )
@@ -15,7 +17,7 @@ type Event interface {
} }
type DrawEvent struct { type DrawEvent struct {
Config ui.Config Config Config
Size image.Point Size image.Point
// Insets is the window space taken up by // Insets is the window space taken up by
// system decoration such as translucent // system decoration such as translucent
@@ -131,3 +133,34 @@ func init() {
func DataDir() (string, error) { func DataDir() (string, error) {
return dataDir() return dataDir()
} }
// Config implements the ui.Config interface.
type Config struct {
// Device pixels per dp.
pxPerDp float32
// Device pixels per sp.
pxPerSp float32
now time.Time
}
func (c *Config) Now() time.Time {
return c.now
}
func (c *Config) Px(v ui.Value) int {
var r float32
switch v.U {
case ui.UnitPx:
r = v.V
case ui.UnitDp:
r = c.pxPerDp * v.V
case ui.UnitSp:
r = c.pxPerSp * v.V
default:
panic("unknown unit")
}
if math.IsInf(float64(r), +1) {
return ui.Inf
}
return int(math.Round(float64(r)))
}
+4 -5
View File
@@ -24,7 +24,6 @@ import (
"time" "time"
"unsafe" "unsafe"
"gioui.org/ui"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/key" "gioui.org/ui/key"
"gioui.org/ui/pointer" "gioui.org/ui/pointer"
@@ -289,10 +288,10 @@ func (w *window) draw(sync bool) {
Y: int(height), Y: int(height),
}, },
Insets: w.insets, Insets: w.insets,
Config: ui.Config{ Config: Config{
PxPerDp: ppdp, pxPerDp: ppdp,
PxPerSp: w.fontScale * ppdp, pxPerSp: w.fontScale * ppdp,
Now: time.Now(), now: time.Now(),
}, },
sync: sync, sync: sync,
}) })
+4 -5
View File
@@ -22,7 +22,6 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"gioui.org/ui"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/key" "gioui.org/ui/key"
"gioui.org/ui/pointer" "gioui.org/ui/pointer"
@@ -87,10 +86,10 @@ func onDraw(view C.CFTypeRef, dpi, sdpi, width, height C.CGFloat, sync C.int, to
Min: image.Point{X: int(left + .5), Y: int(top + .5)}, Min: image.Point{X: int(left + .5), Y: int(top + .5)},
Max: image.Point{X: int(right + .5), Y: int(bottom + .5)}, Max: image.Point{X: int(right + .5), Y: int(bottom + .5)},
}, },
Config: ui.Config{ Config: Config{
PxPerDp: float32(dpi) * inchPrDp, pxPerDp: float32(dpi) * inchPrDp,
PxPerSp: float32(sdpi) * inchPrDp, pxPerSp: float32(sdpi) * inchPrDp,
Now: time.Now(), now: time.Now(),
}, },
sync: isSync, sync: isSync,
}) })
+6 -7
View File
@@ -7,7 +7,6 @@ import (
"syscall/js" "syscall/js"
"time" "time"
"gioui.org/ui"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/key" "gioui.org/ui/key"
"gioui.org/ui/pointer" "gioui.org/ui/pointer"
@@ -329,13 +328,13 @@ func (w *window) setTextInput(s key.TextInputState) {
func (w *window) draw(sync bool) { func (w *window) draw(sync bool) {
width, height, scale, cfg := w.config() width, height, scale, cfg := w.config()
if cfg == (ui.Config{}) { if cfg == (Config{}) {
return return
} }
w.mu.Lock() w.mu.Lock()
w.scale = float32(scale) w.scale = float32(scale)
w.mu.Unlock() w.mu.Unlock()
cfg.Now = time.Now() cfg.now = time.Now()
w.w.event(DrawEvent{ w.w.event(DrawEvent{
Size: image.Point{ Size: image.Point{
X: width, X: width,
@@ -346,7 +345,7 @@ func (w *window) draw(sync bool) {
}) })
} }
func (w *window) config() (int, int, float32, ui.Config) { func (w *window) config() (int, int, float32, Config) {
rect := w.cnv.Call("getBoundingClientRect") rect := w.cnv.Call("getBoundingClientRect")
width, height := rect.Get("width").Float(), rect.Get("height").Float() width, height := rect.Get("width").Float(), rect.Get("height").Float()
scale := w.window.Get("devicePixelRatio").Float() scale := w.window.Get("devicePixelRatio").Float()
@@ -359,9 +358,9 @@ func (w *window) config() (int, int, float32, ui.Config) {
w.cnv.Set("height", ih) w.cnv.Set("height", ih)
} }
const ppdp = 96 * inchPrDp * monitorScale const ppdp = 96 * inchPrDp * monitorScale
return iw, ih, float32(scale), ui.Config{ return iw, ih, float32(scale), Config{
PxPerDp: ppdp * float32(scale), pxPerDp: ppdp * float32(scale),
PxPerSp: ppdp * float32(scale), pxPerSp: ppdp * float32(scale),
} }
} }
+7 -8
View File
@@ -19,7 +19,6 @@ import (
"time" "time"
"unsafe" "unsafe"
"gioui.org/ui"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/key" "gioui.org/ui/key"
"gioui.org/ui/pointer" "gioui.org/ui/pointer"
@@ -142,7 +141,7 @@ func (w *window) draw(sync bool) {
return return
} }
cfg := getConfig() cfg := getConfig()
cfg.Now = time.Now() cfg.now = time.Now()
w.setStage(StageRunning) w.setStage(StageRunning)
w.w.event(DrawEvent{ w.w.event(DrawEvent{
Size: image.Point{ Size: image.Point{
@@ -154,15 +153,15 @@ func (w *window) draw(sync bool) {
}) })
} }
func getConfig() ui.Config { func getConfig() Config {
ppdp := float32(C.gio_getPixelsPerDP()) ppdp := float32(C.gio_getPixelsPerDP())
ppdp *= monitorScale ppdp *= monitorScale
if ppdp < minDensity { if ppdp < minDensity {
ppdp = minDensity ppdp = minDensity
} }
return ui.Config{ return Config{
PxPerDp: ppdp, pxPerDp: ppdp,
PxPerSp: ppdp, pxPerSp: ppdp,
} }
} }
@@ -216,8 +215,8 @@ func Main() {
} }
cfg := getConfig() cfg := getConfig()
opts := singleWindow.opts opts := singleWindow.opts
w := cfg.Pixels(opts.Width) w := cfg.Px(opts.Width)
h := cfg.Pixels(opts.Height) h := cfg.Px(opts.Height)
title := C.CString(opts.Title) title := C.CString(opts.Title)
defer C.free(unsafe.Pointer(title)) defer C.free(unsafe.Pointer(title))
C.gio_main(view, title, C.CGFloat(w), C.CGFloat(h)) C.gio_main(view, title, C.CGFloat(w), C.CGFloat(h))
+8 -9
View File
@@ -19,7 +19,6 @@ import (
"unicode/utf8" "unicode/utf8"
"unsafe" "unsafe"
"gioui.org/ui"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/key" "gioui.org/ui/key"
"gioui.org/ui/pointer" "gioui.org/ui/pointer"
@@ -237,8 +236,8 @@ func createNativeWindow(opts *WindowOptions) (*window, error) {
C.free(unsafe.Pointer(title)) C.free(unsafe.Pointer(title))
_, _, cfg := w.config() _, _, cfg := w.config()
w.width = cfg.Pixels(opts.Width) w.width = cfg.Px(opts.Width)
w.height = cfg.Pixels(opts.Height) w.height = cfg.Px(opts.Height)
if conn.decor != nil { if conn.decor != nil {
// Request server side decorations. // Request server side decorations.
w.decor = C.zxdg_decoration_manager_v1_get_toplevel_decoration(conn.decor, w.topLvl) w.decor = C.zxdg_decoration_manager_v1_get_toplevel_decoration(conn.decor, w.topLvl)
@@ -1020,11 +1019,11 @@ func (w *window) updateOutputs() {
} }
} }
func (w *window) config() (int, int, ui.Config) { func (w *window) config() (int, int, Config) {
width, height := w.width*w.scale, w.height*w.scale width, height := w.width*w.scale, w.height*w.scale
return width, height, ui.Config{ return width, height, Config{
PxPerDp: w.ppdp * float32(w.scale), pxPerDp: w.ppdp * float32(w.scale),
PxPerSp: w.ppsp * float32(w.scale), pxPerSp: w.ppsp * float32(w.scale),
} }
} }
@@ -1036,7 +1035,7 @@ func (w *window) draw(sync bool) {
return return
} }
width, height, cfg := w.config() width, height, cfg := w.config()
if cfg == (ui.Config{}) { if cfg == (Config{}) {
return return
} }
if animating && w.lastFrameCallback == nil { if animating && w.lastFrameCallback == nil {
@@ -1044,7 +1043,7 @@ func (w *window) draw(sync bool) {
// Use the surface as listener data for gio_onFrameDone. // Use the surface as listener data for gio_onFrameDone.
C.gio_wl_callback_add_listener(w.lastFrameCallback, unsafe.Pointer(w.surf)) C.gio_wl_callback_add_listener(w.lastFrameCallback, unsafe.Pointer(w.surf))
} }
cfg.Now = time.Now() cfg.now = time.Now()
w.w.event(DrawEvent{ w.w.event(DrawEvent{
Size: image.Point{ Size: image.Point{
X: width, X: width,
+7 -8
View File
@@ -13,7 +13,6 @@ import (
syscall "golang.org/x/sys/windows" syscall "golang.org/x/sys/windows"
"gioui.org/ui"
"gioui.org/ui/f32" "gioui.org/ui/f32"
"gioui.org/ui/key" "gioui.org/ui/key"
"gioui.org/ui/pointer" "gioui.org/ui/pointer"
@@ -220,8 +219,8 @@ func createNativeWindow(opts *WindowOptions) (*window, error) {
} }
defer unregisterClass(cls, hInst) defer unregisterClass(cls, hInst)
wr := rect{ wr := rect{
right: int32(cfg.Pixels(opts.Width)), right: int32(cfg.Px(opts.Width)),
bottom: int32(cfg.Pixels(opts.Height)), bottom: int32(cfg.Px(opts.Height)),
} }
dwStyle := uint32(_WS_OVERLAPPEDWINDOW) dwStyle := uint32(_WS_OVERLAPPEDWINDOW)
dwExStyle := uint32(_WS_EX_APPWINDOW | _WS_EX_WINDOWEDGE) dwExStyle := uint32(_WS_EX_APPWINDOW | _WS_EX_WINDOWEDGE)
@@ -419,7 +418,7 @@ func (w *window) draw(sync bool) {
w.width = int(r.right - r.left) w.width = int(r.right - r.left)
w.height = int(r.bottom - r.top) w.height = int(r.bottom - r.top)
cfg := configForDC(w.hdc) cfg := configForDC(w.hdc)
cfg.Now = time.Now() cfg.now = time.Now()
w.w.event(DrawEvent{ w.w.event(DrawEvent{
Size: image.Point{ Size: image.Point{
X: w.width, X: w.width,
@@ -487,16 +486,16 @@ func convertKeyCode(code uintptr) (rune, bool) {
return r, true return r, true
} }
func configForDC(hdc syscall.Handle) ui.Config { func configForDC(hdc syscall.Handle) Config {
dpi := getDeviceCaps(hdc, _LOGPIXELSX) dpi := getDeviceCaps(hdc, _LOGPIXELSX)
ppdp := float32(dpi) * inchPrDp * monitorScale ppdp := float32(dpi) * inchPrDp * monitorScale
// Force a minimum density to keep text legible and to handle bogus output geometry. // Force a minimum density to keep text legible and to handle bogus output geometry.
if ppdp < minDensity { if ppdp < minDensity {
ppdp = minDensity ppdp = minDensity
} }
return ui.Config{ return Config{
PxPerDp: ppdp, pxPerDp: ppdp,
PxPerSp: ppdp, pxPerSp: ppdp,
} }
} }
+7 -7
View File
@@ -133,7 +133,7 @@ func (s *Scroll) Dragging() bool {
return s.dragging return s.dragging
} }
func (s *Scroll) Scroll(cfg *ui.Config, q input.Events, axis Axis) int { func (s *Scroll) Scroll(cfg ui.Config, q input.Events, axis Axis) int {
if s.axis != axis { if s.axis != axis {
s.axis = axis s.axis = axis
return 0 return 0
@@ -165,15 +165,15 @@ func (s *Scroll) Scroll(cfg *ui.Config, q input.Events, axis Axis) int {
break break
} }
fling := s.estimator.Estimate() fling := s.estimator.Estimate()
if slop, d := float32(cfg.Pixels(touchSlop)), fling.Distance; d >= slop || -slop >= d { if slop, d := float32(cfg.Px(touchSlop)), fling.Distance; d >= slop || -slop >= d {
if min, v := float32(cfg.Pixels(minFlingVelocity)), fling.Velocity; v >= min || -min >= v { if min, v := float32(cfg.Px(minFlingVelocity)), fling.Velocity; v >= min || -min >= v {
max := float32(cfg.Pixels(maxFlingVelocity)) max := float32(cfg.Px(maxFlingVelocity))
if v > max { if v > max {
v = max v = max
} else if v < -max { } else if v < -max {
v = -max v = -max
} }
s.flinger.Init(cfg.Now, v) s.flinger.Init(cfg.Now(), v)
} }
} }
fallthrough fallthrough
@@ -200,7 +200,7 @@ func (s *Scroll) Scroll(cfg *ui.Config, q input.Events, axis Axis) int {
v := int(math.Round(float64(val))) v := int(math.Round(float64(val)))
dist := s.last - v dist := s.last - v
if e.Priority < pointer.Grabbed { if e.Priority < pointer.Grabbed {
slop := cfg.Pixels(touchSlop) slop := cfg.Px(touchSlop)
if dist := dist; dist >= slop || -slop >= dist { if dist := dist; dist >= slop || -slop >= dist {
s.grab = true s.grab = true
} }
@@ -210,7 +210,7 @@ func (s *Scroll) Scroll(cfg *ui.Config, q input.Events, axis Axis) int {
} }
} }
} }
total += s.flinger.Tick(cfg.Now) total += s.flinger.Tick(cfg.Now())
return total return total
} }
+5 -5
View File
@@ -96,14 +96,14 @@ type Insets struct {
cs Constraints cs Constraints
} }
func (in *Insets) Begin(c *ui.Config, ops *ui.Ops, cs Constraints) Constraints { func (in *Insets) Begin(c ui.Config, ops *ui.Ops, cs Constraints) Constraints {
if in.begun { if in.begun {
panic("must End before Begin") panic("must End before Begin")
} }
in.top = c.Pixels(in.Top) in.top = c.Px(in.Top)
in.right = c.Pixels(in.Right) in.right = c.Px(in.Right)
in.bottom = c.Pixels(in.Bottom) in.bottom = c.Px(in.Bottom)
in.left = c.Pixels(in.Left) in.left = c.Px(in.Left)
in.begun = true in.begun = true
in.ops = ops in.ops = ops
in.cs = cs in.cs = cs
+1 -1
View File
@@ -18,7 +18,7 @@ type scrollChild struct {
} }
type List struct { type List struct {
Config *ui.Config Config ui.Config
Inputs input.Events Inputs input.Events
Axis Axis Axis Axis
Invert bool Invert bool
+3 -3
View File
@@ -17,7 +17,7 @@ import (
) )
type Faces struct { type Faces struct {
Config *ui.Config Config ui.Config
faceCache map[faceKey]*textFace faceCache map[faceKey]*textFace
layoutCache map[layoutKey]cachedLayout layoutCache map[layoutKey]cachedLayout
pathCache map[pathKey]cachedPath pathCache map[pathKey]cachedPath
@@ -102,7 +102,7 @@ func (f *Faces) init() {
} }
func (f *textFace) Layout(str string, opts text.LayoutOptions) *text.Layout { func (f *textFace) Layout(str string, opts text.LayoutOptions) *text.Layout {
ppem := fixed.Int26_6(f.faces.Config.Pixels(f.size) * 64) ppem := fixed.Int26_6(f.faces.Config.Px(f.size) * 64)
lk := layoutKey{ lk := layoutKey{
f: f.font.Font, f: f.font.Font,
ppem: ppem, ppem: ppem,
@@ -120,7 +120,7 @@ func (f *textFace) Layout(str string, opts text.LayoutOptions) *text.Layout {
} }
func (f *textFace) Path(str text.String) ui.BlockOp { func (f *textFace) Path(str text.String) ui.BlockOp {
ppem := fixed.Int26_6(f.faces.Config.Pixels(f.size) * 64) ppem := fixed.Int26_6(f.faces.Config.Px(f.size) * 64)
pk := pathKey{ pk := pathKey{
f: f.font.Font, f: f.font.Font,
ppem: ppem, ppem: ppem,
+10 -9
View File
@@ -21,7 +21,7 @@ import (
) )
type Editor struct { type Editor struct {
Config *ui.Config Config ui.Config
Inputs input.Events Inputs input.Events
Face Face Face Face
Alignment Alignment Alignment Alignment
@@ -32,7 +32,7 @@ type Editor struct {
Hint string Hint string
HintMaterial ui.BlockOp HintMaterial ui.BlockOp
oldCfg ui.Config oldScale int
blinkStart time.Time blinkStart time.Time
focused bool focused bool
rr editBuffer rr editBuffer
@@ -73,9 +73,10 @@ func (s ChangeEvent) isEditorEvent() {}
func (s SubmitEvent) isEditorEvent() {} func (s SubmitEvent) isEditorEvent() {}
func (e *Editor) Next() (EditorEvent, bool) { func (e *Editor) Next() (EditorEvent, bool) {
if cfg := *e.Config; cfg != e.oldCfg { // Crude configuration change detection.
if scale := e.Config.Px(ui.Sp(100)); scale != e.oldScale {
e.invalidate() e.invalidate()
e.oldCfg = cfg e.oldScale = scale
} }
sbounds := e.scrollBounds() sbounds := e.scrollBounds()
var smin, smax int var smin, smax int
@@ -104,7 +105,7 @@ func (e *Editor) Next() (EditorEvent, bool) {
switch { switch {
case evt.Type == gesture.TypePress && evt.Source == pointer.Mouse, case evt.Type == gesture.TypePress && evt.Source == pointer.Mouse,
evt.Type == gesture.TypeClick && evt.Source == pointer.Touch: evt.Type == gesture.TypeClick && evt.Source == pointer.Touch:
e.blinkStart = e.Config.Now e.blinkStart = e.Config.Now()
e.moveCoord(image.Point{ e.moveCoord(image.Point{
X: int(math.Round(float64(evt.Position.X))), X: int(math.Round(float64(evt.Position.X))),
Y: int(math.Round(float64(evt.Position.Y))), Y: int(math.Round(float64(evt.Position.Y))),
@@ -123,7 +124,7 @@ func (e *Editor) Next() (EditorEvent, bool) {
if !ok { if !ok {
break break
} }
e.blinkStart = e.Config.Now e.blinkStart = e.Config.Now()
switch ke := ke.(type) { switch ke := ke.(type) {
case key.FocusEvent: case key.FocusEvent:
e.focused = ke.Focus e.focused = ke.Focus
@@ -153,7 +154,7 @@ func (e *Editor) Next() (EditorEvent, bool) {
} }
func (e *Editor) caretWidth() fixed.Int26_6 { func (e *Editor) caretWidth() fixed.Int26_6 {
oneDp := e.Config.Dp(1) oneDp := e.Config.Px(ui.Dp(1))
return fixed.Int26_6(oneDp * 64) return fixed.Int26_6(oneDp * 64)
} }
@@ -167,7 +168,7 @@ func (e *Editor) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
break break
} }
} }
twoDp := e.Config.Dp(2) twoDp := e.Config.Px(ui.Dp(2))
e.padLeft, e.padRight = twoDp, twoDp e.padLeft, e.padRight = twoDp, twoDp
maxWidth := cs.Width.Max maxWidth := cs.Width.Max
if e.SingleLine { if e.SingleLine {
@@ -225,7 +226,7 @@ func (e *Editor) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
ui.PopOp{}.Add(ops) ui.PopOp{}.Add(ops)
} }
if e.focused { if e.focused {
now := e.Config.Now now := e.Config.Now()
dt := now.Sub(e.blinkStart) dt := now.Sub(e.blinkStart)
blinking := dt < maxBlinkDuration blinking := dt < maxBlinkDuration
const timePerBlink = time.Second / blinksPerSecond const timePerBlink = time.Second / blinksPerSecond
+6 -34
View File
@@ -11,41 +11,13 @@ import (
"gioui.org/ui/internal/ops" "gioui.org/ui/internal/ops"
) )
// Config contains the essential configuration for // Config represents the essential configuration for
// updating and drawing a user interface. // updating and drawing a user interface.
type Config struct { type Config interface {
// Device pixels per dp. // Now returns the current animation time.
PxPerDp float32 Now() time.Time
// Device pixels per sp. // Px converts a Value to pixels.
PxPerSp float32 Px(v Value) int
// The current time for animation.
Now time.Time
}
// Dp converts a value in dp units to pixels.
func (c *Config) Dp(dp float32) int {
return c.Pixels(Dp(dp))
}
// Sp converts a value in sp units to pixels.
func (c *Config) Sp(sp float32) int {
return c.Pixels(Sp(sp))
}
// Pixels converts a value to pixels.
func (c *Config) Pixels(v Value) int {
var r float32
switch v.U {
case UnitPx:
r = v.V
case UnitDp:
r = c.PxPerDp * v.V
case UnitSp:
r = c.PxPerSp * v.V
default:
panic("unknown unit")
}
return int(math.Round(float64(r)))
} }
// LayerOp represents a semantic layer of UI. // LayerOp represents a semantic layer of UI.
+2 -2
View File
@@ -23,13 +23,13 @@ type Image struct {
Scale float32 Scale float32
} }
func (im Image) Layout(c *ui.Config, ops *ui.Ops, cs layout.Constraints) layout.Dimens { func (im Image) Layout(c ui.Config, ops *ui.Ops, cs layout.Constraints) layout.Dimens {
size := im.Src.Bounds() size := im.Src.Bounds()
wf, hf := float32(size.Dx()), float32(size.Dy()) wf, hf := float32(size.Dx()), float32(size.Dy())
var w, h int var w, h int
if im.Scale == 0 { if im.Scale == 0 {
const dpPrPx = 160 / 72 const dpPrPx = 160 / 72
w, h = c.Dp(wf*dpPrPx), c.Dp(hf*dpPrPx) w, h = c.Px(ui.Dp(wf*dpPrPx)), c.Px(ui.Dp(hf*dpPrPx))
} else { } else {
w, h = int(wf*im.Scale+.5), int(hf*im.Scale+.5) w, h = int(wf*im.Scale+.5), int(hf*im.Scale+.5)
} }