ref: 554ea7adc9d345f391bcf1a720939ecb6c262009
parent: c7094acb596f25738d6c26a0f1b229303dba7832
author: Philip Silva <philip.silva@protonmail.com>
date: Wed Feb 3 18:24:33 EST 2021
scroll.go
--- /dev/null
+++ b/browser/scroll.go
@@ -1,0 +1,423 @@
+package browser
+
+// Original code from github.com/mjl-/duit
+//
+// Copyright 2018 Mechiel Lukkien mechiel@ueber.net
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import (
+ "fmt"
+ "image"
+
+ "9fans.net/go/draw"
+ "github.com/mjl-/duit"
+)
+
+// Scroll shows a part of its single child, typically a box, and lets you scroll the content.
+type Scroll struct {
+ Kid duit.Kid
+ Height int // < 0 means full height, 0 means as much as necessary, >0 means exactly that many lowdpi pixels
+
+ r image.Rectangle // entire ui
+ barR image.Rectangle
+ barActiveR image.Rectangle
+ childR image.Rectangle
+ offset int // current scroll offset in pixels
+ img *draw.Image // for child to draw on
+ scrollbarSize int
+ lastMouseUI duit.UI
+}
+
+var _ duit.UI = &Scroll{}
+
+// NewScroll returns a full-height scroll bar containing ui.
+func NewScroll(ui duit.UI) *Scroll {
+ return &Scroll{Height: -1, Kid: duit.Kid{UI: ui}}
+}
+
+func (ui *Scroll) Layout(dui *duit.DUI, self *duit.Kid, sizeAvail image.Point, force bool) {
+ debugLayout(dui, self)
+
+ if self.Layout == duit.Clean && !force {
+ return
+ }
+ self.Layout = duit.Clean
+ self.Draw = duit.Dirty
+ // todo: be smarter about DirtyKid
+
+ ui.scrollbarSize = dui.Scale(duit.ScrollbarSize)
+ scaledHeight := dui.Scale(ui.Height)
+ if scaledHeight > 0 && scaledHeight < sizeAvail.Y {
+ sizeAvail.Y = scaledHeight
+ }
+ ui.r = rect(sizeAvail)
+ ui.barR = ui.r
+ ui.barR.Max.X = ui.barR.Min.X + ui.scrollbarSize
+ ui.childR = ui.r
+ ui.childR.Min.X = ui.barR.Max.X
+
+ // todo: only force when sizeAvail or childR changed?
+ ui.Kid.UI.Layout(dui, &ui.Kid, image.Pt(ui.r.Dx()-ui.barR.Dx(), ui.r.Dy()), force)
+ ui.Kid.Layout = duit.Clean
+ ui.Kid.Draw = duit.Dirty
+
+ kY := ui.Kid.R.Dy()
+ if ui.r.Dy() > kY && ui.Height == 0 {
+ ui.barR.Max.Y = kY
+ ui.r.Max.Y = kY
+ ui.childR.Max.Y = kY
+ }
+ self.R = rect(ui.r.Size())
+}
+
+func (ui *Scroll) Draw(dui *duit.DUI, self *duit.Kid, img *draw.Image, orig image.Point, m draw.Mouse, force bool) {
+ debugDraw(dui, self)
+
+ if self.Draw == duit.Clean {
+ return
+ }
+ self.Draw = duit.Clean
+
+ if ui.r.Empty() {
+ return
+ }
+
+ ui.scroll(0)
+ barHover := m.In(ui.barR)
+
+ bg := dui.ScrollBGNormal
+ vis := dui.ScrollVisibleNormal
+ if barHover {
+ bg = dui.ScrollBGHover
+ vis = dui.ScrollVisibleHover
+ }
+
+ h := ui.r.Dy()
+ uih := ui.Kid.R.Dy()
+ if uih > h {
+ barR := ui.barR.Add(orig)
+ img.Draw(barR, bg, nil, image.ZP)
+ barH := h * h / uih
+ barY := ui.offset * h / uih
+ ui.barActiveR = ui.barR
+ ui.barActiveR.Min.Y += barY
+ ui.barActiveR.Max.Y = ui.barActiveR.Min.Y + barH
+ barActiveR := ui.barActiveR.Add(orig)
+ barActiveR.Max.X -= 1 // unscaled
+ img.Draw(barActiveR, vis, nil, image.ZP)
+ }
+
+ // draw child ui
+ if ui.childR.Empty() {
+ return
+ }
+ if ui.img == nil || ui.Kid.R.Size() != ui.img.R.Size() {
+ var err error
+ if ui.img != nil {
+ ui.img.Free()
+ ui.img = nil
+ }
+ ui.Kid.Draw = duit.Dirty
+ if ui.Kid.R.Dx() == 0 || ui.Kid.R.Dy() == 0 {
+ return
+ }
+ ui.img, err = dui.Display.AllocImage(ui.Kid.R, draw.ARGB32, false, dui.BackgroundColor)
+ if duitError(dui, err, "allocimage") {
+ return
+ }
+ } else if ui.Kid.Draw == duit.Dirty {
+ ui.img.Draw(ui.img.R, dui.Background, nil, image.ZP)
+ }
+ m.Point = m.Point.Add(image.Pt(-ui.childR.Min.X, ui.offset))
+ if ui.Kid.Draw != duit.Clean {
+ if force {
+ ui.Kid.Draw = duit.Dirty
+ }
+ ui.Kid.UI.Draw(dui, &ui.Kid, ui.img, image.ZP, m, ui.Kid.Draw == duit.Dirty)
+ ui.Kid.Draw = duit.Clean
+ }
+ img.Draw(ui.childR.Add(orig), ui.img, nil, image.Pt(0, ui.offset))
+}
+
+func (ui *Scroll) scroll(delta int) bool {
+ o := ui.offset
+ ui.offset += delta
+ ui.offset = maximum(0, ui.offset)
+ ui.offset = minimum(ui.offset, maximum(0, ui.Kid.R.Dy()-ui.childR.Dy()))
+ return o != ui.offset
+}
+
+func (ui *Scroll) scrollKey(k rune) (consumed bool) {
+ switch k {
+ case draw.KeyUp:
+ return ui.scroll(-50)
+ case draw.KeyDown:
+ return ui.scroll(50)
+ case draw.KeyPageUp:
+ return ui.scroll(-200)
+ case draw.KeyPageDown:
+ return ui.scroll(200)
+ }
+ return false
+}
+
+func (ui *Scroll) scrollMouse(m draw.Mouse, scrollOnly bool) (consumed bool) {
+ switch m.Buttons {
+ case duit.Button4:
+ return ui.scroll(-m.Y / 4)
+ case duit.Button5:
+ return ui.scroll(m.Y / 4)
+ }
+
+ if scrollOnly {
+ return false
+ }
+ switch m.Buttons {
+ case duit.Button1:
+ return ui.scroll(-m.Y)
+ case duit.Button2:
+ offset := m.Y * ui.Kid.R.Dy() / ui.barR.Dy()
+ offsetMax := ui.Kid.R.Dy() - ui.childR.Dy()
+ offset = maximum(0, minimum(offset, offsetMax))
+ o := ui.offset
+ ui.offset = offset
+ return o != ui.offset
+ case duit.Button3:
+ return ui.scroll(m.Y)
+ }
+ return false
+}
+
+func (ui *Scroll) result(dui *duit.DUI, self *duit.Kid, r *duit.Result, scrolled bool) {
+ if ui.Kid.Layout != duit.Clean {
+ ui.Kid.UI.Layout(dui, &ui.Kid, ui.childR.Size(), false)
+ ui.Kid.Layout = duit.Clean
+ ui.Kid.Draw = duit.Dirty
+ self.Draw = duit.Dirty
+ } else if ui.Kid.Draw != duit.Clean || scrolled {
+ self.Draw = duit.Dirty
+ }
+}
+
+func (ui *Scroll) Mouse(dui *duit.DUI, self *duit.Kid, m draw.Mouse, origM draw.Mouse, orig image.Point) (r duit.Result) {
+ if m.Point.In(ui.barR) {
+ r.Hit = ui
+ r.Consumed = ui.scrollMouse(m, false)
+ self.Draw = duit.Dirty
+ return
+ }
+ if m.Point.In(ui.childR) {
+ nOrigM := origM
+ nOrigM.Point = nOrigM.Point.Add(image.Pt(-ui.scrollbarSize, ui.offset))
+ nm := m
+ nm.Point = nm.Point.Add(image.Pt(-ui.scrollbarSize, ui.offset))
+ r = ui.Kid.UI.Mouse(dui, &ui.Kid, nm, nOrigM, image.ZP)
+ ui.warpScroll(dui, self, r.Warp, orig)
+ scrolled := false
+ if !r.Consumed {
+ scrolled = ui.scrollMouse(m, true)
+ r.Consumed = scrolled
+ }
+ ui.result(dui, self, &r, scrolled)
+ if r.Hit != ui.lastMouseUI {
+ if r.Hit != nil {
+ ui.Mark(self, r.Hit, false)
+ }
+ if ui.lastMouseUI != nil {
+ ui.Mark(self, ui.lastMouseUI, false)
+ }
+ }
+ ui.lastMouseUI = r.Hit
+ }
+ return
+}
+
+func (ui *Scroll) Key(dui *duit.DUI, self *duit.Kid, k rune, m draw.Mouse, orig image.Point) (r duit.Result) {
+ if m.Point.In(ui.barR) {
+ r.Hit = ui
+ r.Consumed = ui.scrollKey(k)
+ if r.Consumed {
+ self.Draw = duit.Dirty
+ }
+ }
+ if m.Point.In(ui.childR) {
+ m.Point = m.Point.Add(image.Pt(-ui.scrollbarSize, ui.offset))
+ r = ui.Kid.UI.Key(dui, &ui.Kid, k, m, image.ZP)
+ ui.warpScroll(dui, self, r.Warp, orig)
+ scrolled := false
+ if !r.Consumed {
+ scrolled = ui.scrollKey(k)
+ r.Consumed = scrolled
+ }
+ ui.result(dui, self, &r, scrolled)
+ }
+ return
+}
+
+func (ui *Scroll) warpScroll(dui *duit.DUI, self *duit.Kid, warp *image.Point, orig image.Point) {
+ if warp == nil {
+ return
+ }
+
+ offset := ui.offset
+ if warp.Y < ui.offset {
+ ui.offset = maximum(0, warp.Y-dui.Scale(40))
+ } else if warp.Y > ui.offset+ui.r.Dy() {
+ ui.offset = minimum(ui.Kid.R.Dy()-ui.r.Dy(), warp.Y+dui.Scale(40)-ui.r.Dy())
+ }
+ if offset != ui.offset {
+ if self != nil {
+ self.Draw = duit.Dirty
+ } else {
+ dui.MarkDraw(ui)
+ }
+ }
+ warp.Y -= ui.offset
+ warp.X += orig.X + ui.scrollbarSize
+ warp.Y += orig.Y
+}
+
+func (ui *Scroll) _focus(dui *duit.DUI, p *image.Point) *image.Point {
+ if p == nil {
+ return nil
+ }
+ pp := p.Add(ui.childR.Min)
+ p = &pp
+ ui.warpScroll(dui, nil, p, image.ZP)
+ return p
+}
+
+func (ui *Scroll) FirstFocus(dui *duit.DUI, self *duit.Kid) *image.Point {
+ p := ui.Kid.UI.FirstFocus(dui, &ui.Kid)
+ return ui._focus(dui, p)
+}
+
+func (ui *Scroll) Focus(dui *duit.DUI, self *duit.Kid, o duit.UI) *image.Point {
+ if o == ui {
+ p := image.Pt(minimum(ui.scrollbarSize/2, ui.r.Dx()), minimum(ui.scrollbarSize/2, ui.r.Dy()))
+ return &p
+ }
+ p := ui.Kid.UI.Focus(dui, &ui.Kid, o)
+ return ui._focus(dui, p)
+}
+
+func (ui *Scroll) Mark(self *duit.Kid, o duit.UI, forLayout bool) (marked bool) {
+ if self.Mark(o, forLayout) {
+ return true
+ }
+ marked = ui.Kid.UI.Mark(&ui.Kid, o, forLayout)
+ if marked {
+ if forLayout {
+ if self.Layout == duit.Clean {
+ self.Layout = duit.DirtyKid
+ }
+ } else {
+ if self.Layout == duit.Clean {
+ self.Draw = duit.DirtyKid
+ }
+ }
+ }
+ return
+}
+
+func (ui *Scroll) Print(self *duit.Kid, indent int) {
+ what := fmt.Sprintf("Scroll offset=%d childR=%v", ui.offset, ui.childR)
+ duit.PrintUI(what, self, indent)
+ ui.Kid.UI.Print(&ui.Kid, indent+1)
+}
+
+//////////////////////
+// //
+// helper functions //
+// //
+//////////////////////
+
+func pt(v int) image.Point {
+ return image.Point{v, v}
+}
+
+func rect(p image.Point) image.Rectangle {
+ return image.Rectangle{image.ZP, p}
+}
+
+func extendY(r image.Rectangle, dy int) image.Rectangle {
+ r.Max.Y += dy
+ return r
+}
+
+func insetPt(r image.Rectangle, pad image.Point) image.Rectangle {
+ r.Min = r.Min.Add(pad)
+ r.Max = r.Max.Sub(pad)
+ return r
+}
+
+func outsetPt(r image.Rectangle, pad image.Point) image.Rectangle {
+ r.Min = r.Min.Sub(pad)
+ r.Max = r.Max.Add(pad)
+ return r
+}
+
+func minimum64(a, b int64) int64 {
+ if a < b {
+ return a
+ }
+ return b
+}
+
+func maximum64(a, b int64) int64 {
+ if a > b {
+ return a
+ }
+ return b
+}
+
+func minimum(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
+
+func maximum(a, b int) int {
+ if a > b {
+ return a
+ }
+ return b
+}
+
+func debugLayout(d *duit.DUI, self *duit.Kid) {
+ if d.DebugLayout > 0 {
+ log.Printf("duit: Layout %T %s layout=%d draw=%d\n", self.UI, self.R, self.Layout, self.Draw)
+ }
+}
+
+func debugDraw(d *duit.DUI, self *duit.Kid) {
+ if d.DebugDraw > 0 {
+ log.Printf("duit: Draw %T %s layout=%d draw=%d\n", self.UI, self.R, self.Layout, self.Draw)
+ }
+}
+
+func duitError(d *duit.DUI, err error, msg string) bool {
+ if err == nil {
+ return false
+ }
+ go func() {
+ d.Error <- fmt.Errorf("%s: %s", msg, err)
+ }()
+ return true
+}