github.com/jmigpin/editor@v1.6.0/util/uiutil/widget/scrollbar.go (about) 1 package widget 2 3 import ( 4 "image" 5 "math" 6 7 "github.com/jmigpin/editor/util/imageutil" 8 "github.com/jmigpin/editor/util/mathutil" 9 "github.com/jmigpin/editor/util/uiutil/event" 10 ) 11 12 // Used by ScrollArea. Parent of ScrollHandle. 13 type ScrollBar struct { 14 ENode 15 Handle *ScrollHandle 16 Horizontal bool 17 18 positionPercent float64 19 sizePercent float64 20 21 pressPad image.Point 22 clicking bool 23 dragging bool 24 25 sa *ScrollArea 26 27 ctx ImageContext 28 } 29 30 func NewScrollBar(ctx ImageContext, sa *ScrollArea) *ScrollBar { 31 sb := &ScrollBar{ctx: ctx, sa: sa} 32 sb.positionPercent = 0.0 33 sb.sizePercent = 1.0 34 35 sb.Handle = NewScrollHandle(ctx, sb) 36 sb.Append(sb.Handle) 37 return sb 38 } 39 40 //---------- 41 42 func (sb *ScrollBar) scrollPage(up bool) { 43 o := sb.sa.scrollable.ScrollOffset() 44 sy := sb.sa.scrollable.ScrollPageSizeY(up) 45 o = o.Add(image.Point{0, sy}) 46 sb.sa.scrollable.SetScrollOffset(o) 47 } 48 49 func (sb *ScrollBar) scrollWheel(up bool) { 50 o := sb.sa.scrollable.ScrollOffset() 51 sy := sb.sa.scrollable.ScrollWheelSizeY(up) 52 o = o.Add(image.Point{0, sy}) 53 sb.sa.scrollable.SetScrollOffset(o) 54 } 55 56 //---------- 57 58 func (sb *ScrollBar) yBoundsSizePad() (int, int, int) { 59 min := 5 60 d := sb.yaxis(sb.Bounds.Size()) 61 dpad := mathutil.Max(d-min, 0) 62 return d, dpad, min 63 } 64 65 //---------- 66 67 func (sb *ScrollBar) scrollToPoint(p *image.Point) { 68 py := float64(sb.yaxis(p.Sub(sb.pressPad).Sub(sb.Bounds.Min))) 69 _, dpad, _ := sb.yBoundsSizePad() 70 o := py / float64(dpad) 71 sb.scrollToPositionPercent(o) 72 } 73 74 func (sb *ScrollBar) scrollToPositionPercent(offsetPerc float64) { 75 size := sb.sa.scrollable.ScrollSize() 76 offset := sb.sa.scrollable.ScrollOffset() 77 offsetPerc = mathutil.LimitFloat64(offsetPerc, 0, 1) 78 *sb.yaxisPtr(&offset) = int(offsetPerc*float64(sb.yaxis(size)) + 0.5) 79 sb.sa.scrollable.SetScrollOffset(offset) 80 } 81 82 //---------- 83 84 func (sb *ScrollBar) calcPositionAndSize() { 85 pos := sb.sa.scrollable.ScrollOffset() 86 size := sb.sa.scrollable.ScrollSize() 87 vsize := sb.sa.scrollable.ScrollViewSize() 88 89 var pp, sp float64 90 91 sizey0 := sb.yaxis(size) 92 93 if sizey0 == 0 { 94 pp = 0 95 sp = 1 96 } else { 97 posy := float64(sb.yaxis(pos)) 98 sizey := float64(sizey0) 99 vsizey := float64(sb.yaxis(vsize)) 100 pp = posy / sizey 101 sp = vsizey / sizey 102 } 103 104 pp = mathutil.LimitFloat64(pp, 0, 1) 105 sp = mathutil.LimitFloat64(pp+sp, 0, 1) // add pp 106 107 sb.positionPercent = pp 108 sb.sizePercent = sp 109 } 110 111 //---------- 112 113 func (sb *ScrollBar) OnChildMarked(child Node, newMarks Marks) { 114 // paint scrollbar background if the handle is getting painted 115 if child == sb.Handle { 116 if newMarks.HasAny(MarkNeedsPaint) { 117 sb.MarkNeedsPaint() 118 } 119 } 120 } 121 122 //---------- 123 124 func (sb *ScrollBar) Layout() { 125 r := sb.Bounds 126 d, dpad, min := sb.yBoundsSizePad() 127 128 sb.calcPositionAndSize() 129 130 p := int(math.Ceil(float64(dpad) * sb.positionPercent)) 131 s := int(math.Ceil(float64(d) * sb.sizePercent)) 132 s = mathutil.Max(s, p+min) // minimum bar size (stay visible) 133 134 *sb.yaxisPtr(&r.Min) = sb.yaxis(sb.Bounds.Min) + p 135 *sb.yaxisPtr(&r.Max) = sb.yaxis(sb.Bounds.Min) + s 136 r = r.Intersect(sb.Bounds) 137 138 sb.Handle.Bounds = r 139 } 140 141 func (sb *ScrollBar) Paint() { 142 c := sb.TreeThemePaletteColor("scrollbar_bg") 143 imageutil.FillRectangle(sb.ctx.Image(), sb.Bounds, c) 144 } 145 146 //---------- 147 148 func (sb *ScrollBar) OnInputEvent(ev interface{}, p image.Point) event.Handled { 149 switch evt := ev.(type) { 150 case *event.MouseDown: 151 switch evt.Button { 152 case event.ButtonLeft: 153 sb.clicking = true 154 sb.setPressPad(&evt.Point) 155 sb.scrollToPoint(&evt.Point) 156 sb.MarkNeedsPaint() // in case it didn't move 157 case event.ButtonWheelUp: 158 sb.scrollPage(true) 159 case event.ButtonWheelDown: 160 sb.scrollPage(false) 161 } 162 case *event.MouseMove: 163 if sb.clicking { 164 sb.scrollToPoint(&evt.Point) 165 } 166 case *event.MouseUp: 167 if sb.clicking { 168 sb.clicking = false 169 sb.scrollToPoint(&evt.Point) 170 sb.MarkNeedsPaint() // in case it didn't move 171 } 172 173 case *event.MouseDragStart: 174 // take over from down/move/up to allow dragging outside bounds 175 sb.clicking = false 176 177 sb.dragging = true 178 sb.setPressPad(&evt.Point2) 179 sb.scrollToPoint(&evt.Point2) 180 case *event.MouseDragMove: 181 sb.scrollToPoint(&evt.Point) 182 case *event.MouseDragEnd: 183 sb.dragging = false 184 sb.scrollToPoint(&evt.Point) 185 sb.MarkNeedsPaint() // in case it didn't move 186 } 187 return false 188 } 189 190 func (sb *ScrollBar) setPressPad(p *image.Point) { 191 b := sb.Handle.Bounds 192 if p.In(b) { 193 // set position relative to the bar top-left 194 sb.pressPad.X = p.X - b.Min.X 195 sb.pressPad.Y = p.Y - b.Min.Y 196 } else { 197 // set position in the middle of the bar 198 sb.pressPad.X = b.Dx() / 2 199 sb.pressPad.Y = b.Dy() / 2 200 } 201 } 202 203 //---------- 204 205 func (sb *ScrollBar) yaxis(p image.Point) int { 206 if sb.Horizontal { 207 return p.X 208 } else { 209 return p.Y 210 } 211 } 212 func (sb *ScrollBar) yaxisPtr(p *image.Point) *int { 213 if sb.Horizontal { 214 return &p.X 215 } else { 216 return &p.Y 217 } 218 }