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