From 32aae1829371b64af6fddee84047495abc22e9ad Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Wed, 10 Jul 2019 16:40:53 +0200 Subject: [PATCH] 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 --- ui/app/app.go | 35 ++++++++++++++++++++++++++++++++++- ui/app/os_android.go | 9 ++++----- ui/app/os_ios.go | 9 ++++----- ui/app/os_js.go | 13 ++++++------- ui/app/os_macos.go | 15 +++++++-------- ui/app/os_wayland.go | 17 ++++++++--------- ui/app/os_windows.go | 15 +++++++-------- ui/gesture/gestures.go | 14 +++++++------- ui/layout/layout.go | 10 +++++----- ui/layout/list.go | 2 +- ui/measure/measure.go | 6 +++--- ui/text/editor.go | 19 ++++++++++--------- ui/ui.go | 40 ++++++---------------------------------- ui/widget/image.go | 4 ++-- 14 files changed, 104 insertions(+), 104 deletions(-) diff --git a/ui/app/app.go b/ui/app/app.go index fcbed16a..887ff58a 100644 --- a/ui/app/app.go +++ b/ui/app/app.go @@ -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))) +} diff --git a/ui/app/os_android.go b/ui/app/os_android.go index 4fb9a35e..37448dce 100644 --- a/ui/app/os_android.go +++ b/ui/app/os_android.go @@ -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, }) diff --git a/ui/app/os_ios.go b/ui/app/os_ios.go index 6fd98b42..d08a0e5b 100644 --- a/ui/app/os_ios.go +++ b/ui/app/os_ios.go @@ -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, }) diff --git a/ui/app/os_js.go b/ui/app/os_js.go index 1fcae562..5e3a087e 100644 --- a/ui/app/os_js.go +++ b/ui/app/os_js.go @@ -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), } } diff --git a/ui/app/os_macos.go b/ui/app/os_macos.go index f7e2b3fb..928d273a 100644 --- a/ui/app/os_macos.go +++ b/ui/app/os_macos.go @@ -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)) diff --git a/ui/app/os_wayland.go b/ui/app/os_wayland.go index d796ac1b..5e267ec9 100644 --- a/ui/app/os_wayland.go +++ b/ui/app/os_wayland.go @@ -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, diff --git a/ui/app/os_windows.go b/ui/app/os_windows.go index b0985189..152fa7be 100644 --- a/ui/app/os_windows.go +++ b/ui/app/os_windows.go @@ -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, } } diff --git a/ui/gesture/gestures.go b/ui/gesture/gestures.go index 237de5f9..eb68981e 100644 --- a/ui/gesture/gestures.go +++ b/ui/gesture/gestures.go @@ -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 } diff --git a/ui/layout/layout.go b/ui/layout/layout.go index aa44ff5d..09c61e97 100644 --- a/ui/layout/layout.go +++ b/ui/layout/layout.go @@ -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 diff --git a/ui/layout/list.go b/ui/layout/list.go index 0671aa3e..53e4c322 100644 --- a/ui/layout/list.go +++ b/ui/layout/list.go @@ -18,7 +18,7 @@ type scrollChild struct { } type List struct { - Config *ui.Config + Config ui.Config Inputs input.Events Axis Axis Invert bool diff --git a/ui/measure/measure.go b/ui/measure/measure.go index 8bc66083..923b03b8 100644 --- a/ui/measure/measure.go +++ b/ui/measure/measure.go @@ -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, diff --git a/ui/text/editor.go b/ui/text/editor.go index d5a30734..767c1e87 100644 --- a/ui/text/editor.go +++ b/ui/text/editor.go @@ -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 diff --git a/ui/ui.go b/ui/ui.go index b17929c0..2a7af305 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -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. diff --git a/ui/widget/image.go b/ui/widget/image.go index dc706b56..9f3a4252 100644 --- a/ui/widget/image.go +++ b/ui/widget/image.go @@ -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) }