~eliasnaur/gio

32aae1829371b64af6fddee84047495abc22e9ad — Elias Naur 1 year, 4 months ago 624ef78
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>
M ui/app/app.go => ui/app/app.go +34 -1
@@ 4,8 4,10 @@ package app

import (
	"image"
	"math"
	"os"
	"strings"
	"time"

	"gioui.org/ui"
)


@@ 15,7 17,7 @@ type Event interface {
}

type DrawEvent struct {
	Config ui.Config
	Config Config
	Size   image.Point
	// Insets is the window space taken up by
	// system decoration such as translucent


@@ 131,3 133,34 @@ func init() {
func DataDir() (string, error) {
	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)))
}

M ui/app/os_android.go => ui/app/os_android.go +4 -5
@@ 24,7 24,6 @@ import (
	"time"
	"unsafe"

	"gioui.org/ui"
	"gioui.org/ui/f32"
	"gioui.org/ui/key"
	"gioui.org/ui/pointer"


@@ 289,10 288,10 @@ func (w *window) draw(sync bool) {
			Y: int(height),
		},
		Insets: w.insets,
		Config: ui.Config{
			PxPerDp: ppdp,
			PxPerSp: w.fontScale * ppdp,
			Now:     time.Now(),
		Config: Config{
			pxPerDp: ppdp,
			pxPerSp: w.fontScale * ppdp,
			now:     time.Now(),
		},
		sync: sync,
	})

M ui/app/os_ios.go => ui/app/os_ios.go +4 -5
@@ 22,7 22,6 @@ import (
	"sync/atomic"
	"time"

	"gioui.org/ui"
	"gioui.org/ui/f32"
	"gioui.org/ui/key"
	"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)},
			Max: image.Point{X: int(right + .5), Y: int(bottom + .5)},
		},
		Config: ui.Config{
			PxPerDp: float32(dpi) * inchPrDp,
			PxPerSp: float32(sdpi) * inchPrDp,
			Now:     time.Now(),
		Config: Config{
			pxPerDp: float32(dpi) * inchPrDp,
			pxPerSp: float32(sdpi) * inchPrDp,
			now:     time.Now(),
		},
		sync: isSync,
	})

M ui/app/os_js.go => ui/app/os_js.go +6 -7
@@ 7,7 7,6 @@ import (
	"syscall/js"
	"time"

	"gioui.org/ui"
	"gioui.org/ui/f32"
	"gioui.org/ui/key"
	"gioui.org/ui/pointer"


@@ 329,13 328,13 @@ func (w *window) setTextInput(s key.TextInputState) {

func (w *window) draw(sync bool) {
	width, height, scale, cfg := w.config()
	if cfg == (ui.Config{}) {
	if cfg == (Config{}) {
		return
	}
	w.mu.Lock()
	w.scale = float32(scale)
	w.mu.Unlock()
	cfg.Now = time.Now()
	cfg.now = time.Now()
	w.w.event(DrawEvent{
		Size: image.Point{
			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")
	width, height := rect.Get("width").Float(), rect.Get("height").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)
	}
	const ppdp = 96 * inchPrDp * monitorScale
	return iw, ih, float32(scale), ui.Config{
		PxPerDp: ppdp * float32(scale),
		PxPerSp: ppdp * float32(scale),
	return iw, ih, float32(scale), Config{
		pxPerDp: ppdp * float32(scale),
		pxPerSp: ppdp * float32(scale),
	}
}


M ui/app/os_macos.go => ui/app/os_macos.go +7 -8
@@ 19,7 19,6 @@ import (
	"time"
	"unsafe"

	"gioui.org/ui"
	"gioui.org/ui/f32"
	"gioui.org/ui/key"
	"gioui.org/ui/pointer"


@@ 142,7 141,7 @@ func (w *window) draw(sync bool) {
		return
	}
	cfg := getConfig()
	cfg.Now = time.Now()
	cfg.now = time.Now()
	w.setStage(StageRunning)
	w.w.event(DrawEvent{
		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 *= monitorScale
	if ppdp < minDensity {
		ppdp = minDensity
	}
	return ui.Config{
		PxPerDp: ppdp,
		PxPerSp: ppdp,
	return Config{
		pxPerDp: ppdp,
		pxPerSp: ppdp,
	}
}



@@ 216,8 215,8 @@ func Main() {
	}
	cfg := getConfig()
	opts := singleWindow.opts
	w := cfg.Pixels(opts.Width)
	h := cfg.Pixels(opts.Height)
	w := cfg.Px(opts.Width)
	h := cfg.Px(opts.Height)
	title := C.CString(opts.Title)
	defer C.free(unsafe.Pointer(title))
	C.gio_main(view, title, C.CGFloat(w), C.CGFloat(h))

M ui/app/os_wayland.go => ui/app/os_wayland.go +8 -9
@@ 19,7 19,6 @@ import (
	"unicode/utf8"
	"unsafe"

	"gioui.org/ui"
	"gioui.org/ui/f32"
	"gioui.org/ui/key"
	"gioui.org/ui/pointer"


@@ 237,8 236,8 @@ func createNativeWindow(opts *WindowOptions) (*window, error) {
	C.free(unsafe.Pointer(title))

	_, _, cfg := w.config()
	w.width = cfg.Pixels(opts.Width)
	w.height = cfg.Pixels(opts.Height)
	w.width = cfg.Px(opts.Width)
	w.height = cfg.Px(opts.Height)
	if conn.decor != nil {
		// Request server side decorations.
		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
	return width, height, ui.Config{
		PxPerDp: w.ppdp * float32(w.scale),
		PxPerSp: w.ppsp * float32(w.scale),
	return width, height, Config{
		pxPerDp: w.ppdp * float32(w.scale),
		pxPerSp: w.ppsp * float32(w.scale),
	}
}



@@ 1036,7 1035,7 @@ func (w *window) draw(sync bool) {
		return
	}
	width, height, cfg := w.config()
	if cfg == (ui.Config{}) {
	if cfg == (Config{}) {
		return
	}
	if animating && w.lastFrameCallback == nil {


@@ 1044,7 1043,7 @@ func (w *window) draw(sync bool) {
		// Use the surface as listener data for gio_onFrameDone.
		C.gio_wl_callback_add_listener(w.lastFrameCallback, unsafe.Pointer(w.surf))
	}
	cfg.Now = time.Now()
	cfg.now = time.Now()
	w.w.event(DrawEvent{
		Size: image.Point{
			X: width,

M ui/app/os_windows.go => ui/app/os_windows.go +7 -8
@@ 13,7 13,6 @@ import (

	syscall "golang.org/x/sys/windows"

	"gioui.org/ui"
	"gioui.org/ui/f32"
	"gioui.org/ui/key"
	"gioui.org/ui/pointer"


@@ 220,8 219,8 @@ func createNativeWindow(opts *WindowOptions) (*window, error) {
	}
	defer unregisterClass(cls, hInst)
	wr := rect{
		right:  int32(cfg.Pixels(opts.Width)),
		bottom: int32(cfg.Pixels(opts.Height)),
		right:  int32(cfg.Px(opts.Width)),
		bottom: int32(cfg.Px(opts.Height)),
	}
	dwStyle := uint32(_WS_OVERLAPPEDWINDOW)
	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.height = int(r.bottom - r.top)
	cfg := configForDC(w.hdc)
	cfg.Now = time.Now()
	cfg.now = time.Now()
	w.w.event(DrawEvent{
		Size: image.Point{
			X: w.width,


@@ 487,16 486,16 @@ func convertKeyCode(code uintptr) (rune, bool) {
	return r, true
}

func configForDC(hdc syscall.Handle) ui.Config {
func configForDC(hdc syscall.Handle) Config {
	dpi := getDeviceCaps(hdc, _LOGPIXELSX)
	ppdp := float32(dpi) * inchPrDp * monitorScale
	// Force a minimum density to keep text legible and to handle bogus output geometry.
	if ppdp < minDensity {
		ppdp = minDensity
	}
	return ui.Config{
		PxPerDp: ppdp,
		PxPerSp: ppdp,
	return Config{
		pxPerDp: ppdp,
		pxPerSp: ppdp,
	}
}


M ui/gesture/gestures.go => ui/gesture/gestures.go +7 -7
@@ 133,7 133,7 @@ func (s *Scroll) Dragging() bool {
	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 {
		s.axis = axis
		return 0


@@ 165,15 165,15 @@ func (s *Scroll) Scroll(cfg *ui.Config, q input.Events, axis Axis) int {
				break
			}
			fling := s.estimator.Estimate()
			if slop, d := float32(cfg.Pixels(touchSlop)), fling.Distance; d >= slop || -slop >= d {
				if min, v := float32(cfg.Pixels(minFlingVelocity)), fling.Velocity; v >= min || -min >= v {
					max := float32(cfg.Pixels(maxFlingVelocity))
			if slop, d := float32(cfg.Px(touchSlop)), fling.Distance; d >= slop || -slop >= d {
				if min, v := float32(cfg.Px(minFlingVelocity)), fling.Velocity; v >= min || -min >= v {
					max := float32(cfg.Px(maxFlingVelocity))
					if v > max {
						v = max
					} else if v < -max {
						v = -max
					}
					s.flinger.Init(cfg.Now, v)
					s.flinger.Init(cfg.Now(), v)
				}
			}
			fallthrough


@@ 200,7 200,7 @@ func (s *Scroll) Scroll(cfg *ui.Config, q input.Events, axis Axis) int {
			v := int(math.Round(float64(val)))
			dist := s.last - v
			if e.Priority < pointer.Grabbed {
				slop := cfg.Pixels(touchSlop)
				slop := cfg.Px(touchSlop)
				if dist := dist; dist >= slop || -slop >= dist {
					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
}


M ui/layout/layout.go => ui/layout/layout.go +5 -5
@@ 96,14 96,14 @@ type Insets struct {
	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 {
		panic("must End before Begin")
	}
	in.top = c.Pixels(in.Top)
	in.right = c.Pixels(in.Right)
	in.bottom = c.Pixels(in.Bottom)
	in.left = c.Pixels(in.Left)
	in.top = c.Px(in.Top)
	in.right = c.Px(in.Right)
	in.bottom = c.Px(in.Bottom)
	in.left = c.Px(in.Left)
	in.begun = true
	in.ops = ops
	in.cs = cs

M ui/layout/list.go => ui/layout/list.go +1 -1
@@ 18,7 18,7 @@ type scrollChild struct {
}

type List struct {
	Config             *ui.Config
	Config             ui.Config
	Inputs             input.Events
	Axis               Axis
	Invert             bool

M ui/measure/measure.go => ui/measure/measure.go +3 -3
@@ 17,7 17,7 @@ import (
)

type Faces struct {
	Config      *ui.Config
	Config      ui.Config
	faceCache   map[faceKey]*textFace
	layoutCache map[layoutKey]cachedLayout
	pathCache   map[pathKey]cachedPath


@@ 102,7 102,7 @@ func (f *Faces) init() {
}

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{
		f:    f.font.Font,
		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 {
	ppem := fixed.Int26_6(f.faces.Config.Pixels(f.size) * 64)
	ppem := fixed.Int26_6(f.faces.Config.Px(f.size) * 64)
	pk := pathKey{
		f:    f.font.Font,
		ppem: ppem,

M ui/text/editor.go => ui/text/editor.go +10 -9
@@ 21,7 21,7 @@ import (
)

type Editor struct {
	Config     *ui.Config
	Config     ui.Config
	Inputs     input.Events
	Face       Face
	Alignment  Alignment


@@ 32,7 32,7 @@ type Editor struct {
	Hint         string
	HintMaterial ui.BlockOp

	oldCfg            ui.Config
	oldScale          int
	blinkStart        time.Time
	focused           bool
	rr                editBuffer


@@ 73,9 73,10 @@ func (s ChangeEvent) isEditorEvent() {}
func (s SubmitEvent) isEditorEvent() {}

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.oldCfg = cfg
		e.oldScale = scale
	}
	sbounds := e.scrollBounds()
	var smin, smax int


@@ 104,7 105,7 @@ func (e *Editor) Next() (EditorEvent, bool) {
		switch {
		case evt.Type == gesture.TypePress && evt.Source == pointer.Mouse,
			evt.Type == gesture.TypeClick && evt.Source == pointer.Touch:
			e.blinkStart = e.Config.Now
			e.blinkStart = e.Config.Now()
			e.moveCoord(image.Point{
				X: int(math.Round(float64(evt.Position.X))),
				Y: int(math.Round(float64(evt.Position.Y))),


@@ 123,7 124,7 @@ func (e *Editor) Next() (EditorEvent, bool) {
		if !ok {
			break
		}
		e.blinkStart = e.Config.Now
		e.blinkStart = e.Config.Now()
		switch ke := ke.(type) {
		case key.FocusEvent:
			e.focused = ke.Focus


@@ 153,7 154,7 @@ func (e *Editor) Next() (EditorEvent, bool) {
}

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)
}



@@ 167,7 168,7 @@ func (e *Editor) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
			break
		}
	}
	twoDp := e.Config.Dp(2)
	twoDp := e.Config.Px(ui.Dp(2))
	e.padLeft, e.padRight = twoDp, twoDp
	maxWidth := cs.Width.Max
	if e.SingleLine {


@@ 225,7 226,7 @@ func (e *Editor) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimens {
		ui.PopOp{}.Add(ops)
	}
	if e.focused {
		now := e.Config.Now
		now := e.Config.Now()
		dt := now.Sub(e.blinkStart)
		blinking := dt < maxBlinkDuration
		const timePerBlink = time.Second / blinksPerSecond

M ui/ui.go => ui/ui.go +6 -34
@@ 11,41 11,13 @@ import (
	"gioui.org/ui/internal/ops"
)

// Config contains the essential configuration for
// Config represents the essential configuration for
// updating and drawing a user interface.
type Config struct {
	// Device pixels per dp.
	PxPerDp float32
	// Device pixels per sp.
	PxPerSp float32
	// 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)))
type Config interface {
	// Now returns the current animation time.
	Now() time.Time
	// Px converts a Value to pixels.
	Px(v Value) int
}

// LayerOp represents a semantic layer of UI.

M ui/widget/image.go => ui/widget/image.go +2 -2
@@ 23,13 23,13 @@ type Image struct {
	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()
	wf, hf := float32(size.Dx()), float32(size.Dy())
	var w, h int
	if im.Scale == 0 {
		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 {
		w, h = int(wf*im.Scale+.5), int(hf*im.Scale+.5)
	}