diff --git a/app/os_js.go b/app/os_js.go index c540b44b2..5f3521cc4 100644 --- a/app/os_js.go +++ b/app/os_js.go @@ -373,6 +373,10 @@ func (w *window) keyEvent(e js.Value, ks key.State) { State: ks, } w.w.Event(cmd) + + if cmd.Name == key.NameTab { + e.Call("preventDefault") + } } } diff --git a/internal/stroke/stroke.go b/internal/stroke/stroke.go index daf472d2b..437528989 100644 --- a/internal/stroke/stroke.go +++ b/internal/stroke/stroke.go @@ -677,13 +677,14 @@ func SplitCubic(from, ctrl0, ctrl1, to f32.Point, quads []QuadSegment) []QuadSeg if h := hull.Dy(); h > l { l = h } - approxCubeTo(&quads, 0, l*0.001, from, ctrl0, ctrl1, to) + maxDist := l * 0.001 + approxCubeTo(&quads, 0, maxDist*maxDist, from, ctrl0, ctrl1, to) return quads } // approxCubeTo approximates a cubic Bézier by a series of quadratic // curves. -func approxCubeTo(quads *[]QuadSegment, splits int, maxDist float32, from, ctrl0, ctrl1, to f32.Point) int { +func approxCubeTo(quads *[]QuadSegment, splits int, maxDistSq float32, from, ctrl0, ctrl1, to f32.Point) int { // The idea is from // https://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html // where a quadratic approximates a cubic by eliminating its t³ term @@ -708,7 +709,11 @@ func approxCubeTo(quads *[]QuadSegment, splits int, maxDist float32, from, ctrl0 // and use the midpoint between the two curves Q1 and Q2 as control point: // // C = (3ctrl0 - pen + 3ctrl1 - to)/4 - c := ctrl0.Mul(3).Sub(from).Add(ctrl1.Mul(3)).Sub(to).Mul(1.0 / 4.0) + // using, q0 := 3ctrl0 - pen, q1 := 3ctrl1 - to + // C = (q0 + q1)/4 + q0 := ctrl0.Mul(3).Sub(from) + q1 := ctrl1.Mul(3).Sub(to) + c := q0.Add(q1).Mul(1.0 / 4.0) const maxSplits = 32 if splits >= maxSplits { *quads = append(*quads, QuadSegment{From: from, Ctrl: c, To: to}) @@ -717,12 +722,14 @@ func approxCubeTo(quads *[]QuadSegment, splits int, maxDist float32, from, ctrl0 // The maximum distance between the cubic P and its approximation Q given t // can be shown to be // - // d = sqrt(3)/36*|to - 3ctrl1 + 3ctrl0 - pen| + // d = sqrt(3)/36 * |to - 3ctrl1 + 3ctrl0 - pen| + // reusing, q0 := 3ctrl0 - pen, q1 := 3ctrl1 - to + // d = sqrt(3)/36 * |-q1 + q0| // // To save a square root, compare d² with the squared tolerance. - v := to.Sub(ctrl1.Mul(3)).Add(ctrl0.Mul(3)).Sub(from) + v := q0.Sub(q1) d2 := (v.X*v.X + v.Y*v.Y) * 3 / (36 * 36) - if d2 <= maxDist*maxDist { + if d2 <= maxDistSq { *quads = append(*quads, QuadSegment{From: from, Ctrl: c, To: to}) return splits } @@ -735,7 +742,7 @@ func approxCubeTo(quads *[]QuadSegment, splits int, maxDist float32, from, ctrl0 c12 := c1.Add(c2.Sub(c1).Mul(t)) c0112 := c01.Add(c12.Sub(c01).Mul(t)) splits++ - splits = approxCubeTo(quads, splits, maxDist, from, c0, c01, c0112) - splits = approxCubeTo(quads, splits, maxDist, c0112, c12, c2, to) + splits = approxCubeTo(quads, splits, maxDistSq, from, c0, c01, c0112) + splits = approxCubeTo(quads, splits, maxDistSq, c0112, c12, c2, to) return splits } diff --git a/internal/stroke/stroke_test.go b/internal/stroke/stroke_test.go new file mode 100644 index 000000000..b6ffc5479 --- /dev/null +++ b/internal/stroke/stroke_test.go @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package stroke + +import ( + "strconv" + "testing" + + "gioui.org/internal/f32" +) + +func BenchmarkSplitCubic(b *testing.B) { + type scenario struct { + segments int + from, ctrl0, ctrl1, to f32.Point + } + + scenarios := []scenario{ + { + segments: 4, + from: f32.Pt(0, 0), + ctrl0: f32.Pt(10, 10), + ctrl1: f32.Pt(10, 10), + to: f32.Pt(20, 0), + }, + { + segments: 8, + from: f32.Pt(-145.90305, 703.21277), + ctrl0: f32.Pt(-940.20215, 606.05994), + ctrl1: f32.Pt(74.58341, 405.815), + to: f32.Pt(104.35474, -241.543), + }, + { + segments: 16, + from: f32.Pt(770.35626, 639.77765), + ctrl0: f32.Pt(735.57135, 545.07837), + ctrl1: f32.Pt(286.7138, 853.7052), + to: f32.Pt(286.7138, 890.5413), + }, + { + segments: 33, + from: f32.Pt(0, 0), + ctrl0: f32.Pt(0, 0), + ctrl1: f32.Pt(100, 100), + to: f32.Pt(100, 100), + }, + } + + for _, s := range scenarios { + s := s + b.Run(strconv.Itoa(s.segments), func(b *testing.B) { + from, ctrl0, ctrl1, to := s.from, s.ctrl0, s.ctrl1, s.to + quads := make([]QuadSegment, s.segments) + b.ResetTimer() + for i := 0; i < b.N; i++ { + quads = SplitCubic(from, ctrl0, ctrl1, to, quads[:0]) + } + if len(quads) != s.segments { + // this is just for checking that we are benchmarking similar splits + // when splitting algorithm splits differently, then it's fine to adjust the + // parameters to give appropriate number of segments. + b.Fatalf("expected %d but got %d", s.segments, len(quads)) + } + }) + } +} diff --git a/widget/editor.go b/widget/editor.go index 69820e8d1..0669ff5b3 100644 --- a/widget/editor.go +++ b/widget/editor.go @@ -319,6 +319,9 @@ func (e *Editor) processKey(gtx layout.Context) { switch ke := ke.(type) { case key.FocusEvent: e.focused = ke.Focus + if e.focused { + key.SoftKeyboardOp{Show: true}.Add(gtx.Ops) + } // Reset IME state. e.ime.imeState = imeState{} case key.Event: