529 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			529 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package basicElements
 | |
| 
 | |
| import "image"
 | |
| import "git.tebibyte.media/sashakoshka/tomo/input"
 | |
| import "git.tebibyte.media/sashakoshka/tomo/theme"
 | |
| import "git.tebibyte.media/sashakoshka/tomo/config"
 | |
| import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | |
| import "git.tebibyte.media/sashakoshka/tomo/artist"
 | |
| import "git.tebibyte.media/sashakoshka/tomo/elements"
 | |
| import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | |
| 
 | |
| // ScrollContainer is a container that is capable of holding a scrollable
 | |
| // element.
 | |
| type ScrollContainer struct {
 | |
| 	*core.Core
 | |
| 	core core.CoreControl
 | |
| 	focused bool
 | |
| 	
 | |
| 	child elements.Scrollable
 | |
| 	childWidth, childHeight int
 | |
| 	
 | |
| 	horizontal struct {
 | |
| 		theme theme.Wrapped
 | |
| 		exists bool
 | |
| 		enabled bool
 | |
| 		dragging bool
 | |
| 		dragOffset int
 | |
| 		gutter image.Rectangle
 | |
| 		track image.Rectangle
 | |
| 		bar image.Rectangle
 | |
| 	}
 | |
| 
 | |
| 	vertical struct {
 | |
| 		theme theme.Wrapped
 | |
| 		exists bool
 | |
| 		enabled bool
 | |
| 		dragging bool
 | |
| 		dragOffset int
 | |
| 		gutter image.Rectangle
 | |
| 		track image.Rectangle
 | |
| 		bar image.Rectangle
 | |
| 	}
 | |
| 	
 | |
| 	config config.Wrapped
 | |
| 	theme  theme.Wrapped
 | |
| 
 | |
| 	onFocusRequest func () (granted bool)
 | |
| 	onFocusMotionRequest func (input.KeynavDirection) (granted bool)
 | |
| }
 | |
| 
 | |
| // NewScrollContainer creates a new scroll container with the specified scroll
 | |
| // bars.
 | |
| func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) {
 | |
| 	element = &ScrollContainer { }
 | |
| 	element.theme.Case = theme.C("basic", "scrollContainer")
 | |
| 	element.horizontal.theme.Case = theme.C("basic", "scrollBarHorizontal")
 | |
| 	element.vertical.theme.Case   = theme.C("basic", "scrollBarVertical")
 | |
| 	
 | |
| 	element.Core, element.core = core.NewCore(element.handleResize)
 | |
| 	element.horizontal.exists = horizontal
 | |
| 	element.vertical.exists   = vertical
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) handleResize () {
 | |
| 	element.recalculate()
 | |
| 	element.resizeChildToFit()
 | |
| 	element.draw()
 | |
| }
 | |
| 
 | |
| // Adopt adds a scrollable element to the scroll container. The container can
 | |
| // only contain one scrollable element at a time, and when a new one is adopted
 | |
| // it replaces the last one.
 | |
| func (element *ScrollContainer) Adopt (child elements.Scrollable) {
 | |
| 	// disown previous child if it exists
 | |
| 	if element.child != nil {
 | |
| 		element.clearChildEventHandlers(child)
 | |
| 	}
 | |
| 
 | |
| 	// adopt new child
 | |
| 	element.child = child
 | |
| 	if child != nil {
 | |
| 		if child0, ok := child.(elements.Themeable); ok {
 | |
| 			child0.SetTheme(element.theme.Theme)
 | |
| 		}
 | |
| 		if child0, ok := child.(elements.Configurable); ok {
 | |
| 			child0.SetConfig(element.config.Config)
 | |
| 		}
 | |
| 		child.OnDamage(element.childDamageCallback)
 | |
| 		child.OnMinimumSizeChange(element.updateMinimumSize)
 | |
| 		child.OnScrollBoundsChange(element.childScrollBoundsChangeCallback)
 | |
| 		if newChild, ok := child.(elements.Focusable); ok {
 | |
| 			newChild.OnFocusRequest (
 | |
| 				element.childFocusRequestCallback)
 | |
| 			newChild.OnFocusMotionRequest (
 | |
| 				element.childFocusMotionRequestCallback)
 | |
| 		}
 | |
| 
 | |
| 		element.updateMinimumSize()
 | |
| 		
 | |
| 		element.horizontal.enabled,
 | |
| 		element.vertical.enabled = element.child.ScrollAxes()
 | |
| 
 | |
| 		if element.core.HasImage() {
 | |
| 			element.resizeChildToFit()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // SetTheme sets the element's theme.
 | |
| func (element *ScrollContainer) SetTheme (new theme.Theme) {
 | |
| 	if new == element.theme.Theme { return }
 | |
| 	element.theme.Theme = new
 | |
| 	if child, ok := element.child.(elements.Themeable); ok {
 | |
| 		child.SetTheme(element.theme.Theme)
 | |
| 	}
 | |
| 	if element.core.HasImage() {
 | |
| 		element.recalculate()
 | |
| 		element.resizeChildToFit()
 | |
| 		element.draw()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // SetConfig sets the element's configuration.
 | |
| func (element *ScrollContainer) SetConfig (new config.Config) {
 | |
| 	if new == element.config.Config { return }
 | |
| 	element.config.Config = new
 | |
| 	if child, ok := element.child.(elements.Configurable); ok {
 | |
| 		child.SetConfig(element.config.Config)
 | |
| 	}
 | |
| 	if element.core.HasImage() {
 | |
| 		element.recalculate()
 | |
| 		element.resizeChildToFit()
 | |
| 		element.draw()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
 | |
| 	if child, ok := element.child.(elements.KeyboardTarget); ok {
 | |
| 		child.HandleKeyDown(key, modifiers)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
 | |
| 	if child, ok := element.child.(elements.KeyboardTarget); ok {
 | |
| 		child.HandleKeyUp(key, modifiers)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) HandleMouseDown (x, y int, button input.Button) {
 | |
| 	velocity := element.config.ScrollVelocity()
 | |
| 	point := image.Pt(x, y)
 | |
| 	if point.In(element.horizontal.bar) {
 | |
| 		element.horizontal.dragging = true
 | |
| 		element.horizontal.dragOffset =
 | |
| 			x - element.horizontal.bar.Min.X +
 | |
| 			element.Bounds().Min.X
 | |
| 		element.dragHorizontalBar(point)
 | |
| 		
 | |
| 	} else if point.In(element.horizontal.gutter) {
 | |
| 		switch button {
 | |
| 		case input.ButtonLeft:
 | |
| 			element.horizontal.dragging = true
 | |
| 			element.horizontal.dragOffset =
 | |
| 				element.horizontal.bar.Dx() / 2 +
 | |
| 				element.Bounds().Min.X
 | |
| 			element.dragHorizontalBar(point)
 | |
| 		case input.ButtonMiddle:
 | |
| 			viewport := element.child.ScrollViewportBounds().Dx()
 | |
| 			if x > element.horizontal.bar.Min.X {
 | |
| 				element.scrollChildBy(viewport, 0)
 | |
| 			} else {
 | |
| 				element.scrollChildBy(-viewport, 0)
 | |
| 			}
 | |
| 		case input.ButtonRight:
 | |
| 			if x > element.horizontal.bar.Min.X {
 | |
| 				element.scrollChildBy(velocity, 0)
 | |
| 			} else {
 | |
| 				element.scrollChildBy(-velocity, 0)
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 	} else if point.In(element.vertical.bar) {
 | |
| 		element.vertical.dragging = true
 | |
| 		element.vertical.dragOffset =
 | |
| 			y - element.vertical.bar.Min.Y +
 | |
| 				element.Bounds().Min.Y
 | |
| 		element.dragVerticalBar(point)
 | |
| 		
 | |
| 	} else if point.In(element.vertical.gutter) {
 | |
| 		switch button {
 | |
| 		case input.ButtonLeft:
 | |
| 			element.vertical.dragging = true
 | |
| 			element.vertical.dragOffset =
 | |
| 				element.vertical.bar.Dy() / 2 +
 | |
| 				element.Bounds().Min.Y
 | |
| 			element.dragVerticalBar(point)
 | |
| 		case input.ButtonMiddle:
 | |
| 			viewport := element.child.ScrollViewportBounds().Dy()
 | |
| 			if y > element.vertical.bar.Min.Y {
 | |
| 				element.scrollChildBy(0, viewport)
 | |
| 			} else {
 | |
| 				element.scrollChildBy(0, -viewport)
 | |
| 			}
 | |
| 		case input.ButtonRight:
 | |
| 			if y > element.vertical.bar.Min.Y {
 | |
| 				element.scrollChildBy(0, velocity)
 | |
| 			} else {
 | |
| 				element.scrollChildBy(0, -velocity)
 | |
| 			}
 | |
| 		}
 | |
| 		
 | |
| 	} else if child, ok := element.child.(elements.MouseTarget); ok {
 | |
| 		child.HandleMouseDown(x, y, button)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) HandleMouseUp (x, y int, button input.Button) {
 | |
| 	if element.horizontal.dragging {
 | |
| 		element.horizontal.dragging = false
 | |
| 		element.drawHorizontalBar()
 | |
| 		element.core.DamageRegion(element.horizontal.bar)
 | |
| 		
 | |
| 	} else if element.vertical.dragging {
 | |
| 		element.vertical.dragging = false
 | |
| 		element.drawVerticalBar()
 | |
| 		element.core.DamageRegion(element.vertical.bar)
 | |
| 		
 | |
| 	} else if child, ok := element.child.(elements.MouseTarget); ok {
 | |
| 		child.HandleMouseUp(x, y, button)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) HandleMouseMove (x, y int) {
 | |
| 	if element.horizontal.dragging {
 | |
| 		element.dragHorizontalBar(image.Pt(x, y))
 | |
| 		
 | |
| 	} else if element.vertical.dragging {
 | |
| 		element.dragVerticalBar(image.Pt(x, y))
 | |
| 		
 | |
| 	} else if child, ok := element.child.(elements.MouseTarget); ok {
 | |
| 		child.HandleMouseMove(x, y)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) HandleMouseScroll (
 | |
| 	x, y int,
 | |
| 	deltaX, deltaY float64,
 | |
| ) {
 | |
| 	element.scrollChildBy(int(deltaX), int(deltaY))
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) scrollChildBy (x, y int) {
 | |
| 	if element.child == nil { return }
 | |
| 	scrollPoint :=
 | |
| 		element.child.ScrollViewportBounds().Min.
 | |
| 		Add(image.Pt(x, y))
 | |
| 	element.child.ScrollTo(scrollPoint)
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) Focused () (focused bool) {
 | |
| 	return element.focused
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) Focus () {
 | |
| 	if element.onFocusRequest != nil {
 | |
| 		if element.onFocusRequest() {
 | |
| 			element.focused = true
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) HandleFocus (
 | |
| 	direction input.KeynavDirection,
 | |
| ) (
 | |
| 	accepted bool,
 | |
| ) {
 | |
| 	if child, ok := element.child.(elements.Focusable); ok {
 | |
| 		element.focused = child.HandleFocus(direction)
 | |
| 		return element.focused
 | |
| 	} else {
 | |
| 		element.focused = false
 | |
| 		return false
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) HandleUnfocus () {
 | |
| 	if child, ok := element.child.(elements.Focusable); ok {
 | |
| 		child.HandleUnfocus()
 | |
| 	}
 | |
| 	element.focused = false
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) OnFocusRequest (callback func () (granted bool)) {
 | |
| 	element.onFocusRequest = callback
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) OnFocusMotionRequest (
 | |
| 	callback func (direction input.KeynavDirection) (granted bool),
 | |
| ) {
 | |
| 	element.onFocusMotionRequest = callback
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) childDamageCallback (region canvas.Canvas) {
 | |
| 	element.core.DamageRegion(artist.Paste(element.core, region, image.Point { }))
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) childFocusRequestCallback () (granted bool) {
 | |
| 	if element.onFocusRequest != nil {
 | |
| 		element.focused = element.onFocusRequest()
 | |
| 		return element.focused
 | |
| 	} else {
 | |
| 		return false
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) childFocusMotionRequestCallback (
 | |
| 	direction input.KeynavDirection,
 | |
| ) (
 | |
| 	granted bool,
 | |
| ) {
 | |
| 	if element.onFocusMotionRequest == nil { return }
 | |
| 	return element.onFocusMotionRequest(direction)
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) clearChildEventHandlers (child elements.Scrollable) {
 | |
| 	child.DrawTo(nil)
 | |
| 	child.OnDamage(nil)
 | |
| 	child.OnMinimumSizeChange(nil)
 | |
| 	child.OnScrollBoundsChange(nil)
 | |
| 	if child0, ok := child.(elements.Focusable); ok {
 | |
| 		child0.OnFocusRequest(nil)
 | |
| 		child0.OnFocusMotionRequest(nil)
 | |
| 		if child0.Focused() {
 | |
| 			child0.HandleUnfocus()
 | |
| 		}
 | |
| 	}
 | |
| 	if child0, ok := child.(elements.Flexible); ok {
 | |
| 		child0.OnFlexibleHeightChange(nil)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) resizeChildToFit () {
 | |
| 	childBounds := image.Rect (
 | |
| 		0, 0,
 | |
| 		element.childWidth,
 | |
| 		element.childHeight).Add(element.Bounds().Min)
 | |
| 	element.child.DrawTo(canvas.Cut(element.core, childBounds))
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) recalculate () {
 | |
| 	horizontal := &element.horizontal
 | |
| 	vertical   := &element.vertical
 | |
| 	
 | |
| 	gutterInsetHorizontal := horizontal.theme.Inset(theme.PatternGutter)
 | |
| 	gutterInsetVertical   := vertical.theme.Inset(theme.PatternGutter)
 | |
| 
 | |
| 	bounds     := element.Bounds()
 | |
| 	thicknessHorizontal :=
 | |
| 		element.config.HandleWidth() +
 | |
| 		gutterInsetHorizontal[3] +
 | |
| 		gutterInsetHorizontal[1]
 | |
| 	thicknessVertical :=
 | |
| 		element.config.HandleWidth() +
 | |
| 		gutterInsetVertical[3] +
 | |
| 		gutterInsetVertical[1]
 | |
| 
 | |
| 	// calculate child size
 | |
| 	element.childWidth  = bounds.Dx()
 | |
| 	element.childHeight = bounds.Dy()
 | |
| 
 | |
| 	// reset bounds
 | |
| 	horizontal.gutter = image.Rectangle { }
 | |
| 	vertical.gutter   = image.Rectangle { }
 | |
| 	horizontal.bar    = image.Rectangle { }
 | |
| 	vertical.bar      = image.Rectangle { }
 | |
| 
 | |
| 	// if enabled, give substance to the gutters
 | |
| 	if horizontal.exists {
 | |
| 		horizontal.gutter.Min.X = bounds.Min.X
 | |
| 		horizontal.gutter.Min.Y = bounds.Max.Y - thicknessHorizontal
 | |
| 		horizontal.gutter.Max.X = bounds.Max.X
 | |
| 		horizontal.gutter.Max.Y = bounds.Max.Y
 | |
| 		if vertical.exists {
 | |
| 			horizontal.gutter.Max.X -= thicknessVertical
 | |
| 		}
 | |
| 		element.childHeight -= thicknessHorizontal
 | |
| 		horizontal.track = gutterInsetHorizontal.Apply(horizontal.gutter)
 | |
| 	}
 | |
| 	if vertical.exists {
 | |
| 		vertical.gutter.Min.X = bounds.Max.X - thicknessVertical
 | |
| 		vertical.gutter.Max.X = bounds.Max.X
 | |
| 		vertical.gutter.Min.Y = bounds.Min.Y
 | |
| 		vertical.gutter.Max.Y = bounds.Max.Y
 | |
| 		if horizontal.exists {
 | |
| 			vertical.gutter.Max.Y -= thicknessHorizontal
 | |
| 		}
 | |
| 		element.childWidth -= thicknessVertical
 | |
| 		vertical.track = gutterInsetVertical.Apply(vertical.gutter)
 | |
| 	}
 | |
| 
 | |
| 	// if enabled, calculate the positions of the bars
 | |
| 	contentBounds  := element.child.ScrollContentBounds()
 | |
| 	viewportBounds := element.child.ScrollViewportBounds()
 | |
| 	if horizontal.exists && horizontal.enabled {
 | |
| 		horizontal.bar.Min.Y = horizontal.track.Min.Y
 | |
| 		horizontal.bar.Max.Y = horizontal.track.Max.Y
 | |
| 
 | |
| 		scale := float64(horizontal.track.Dx()) /
 | |
| 			float64(contentBounds.Dx())
 | |
| 		horizontal.bar.Min.X = int(float64(viewportBounds.Min.X) * scale)
 | |
| 		horizontal.bar.Max.X = int(float64(viewportBounds.Max.X) * scale)
 | |
| 		
 | |
| 		horizontal.bar.Min.X += horizontal.track.Min.X
 | |
| 		horizontal.bar.Max.X += horizontal.track.Min.X
 | |
| 	}
 | |
| 	if vertical.exists && vertical.enabled {
 | |
| 		vertical.bar.Min.X = vertical.track.Min.X
 | |
| 		vertical.bar.Max.X = vertical.track.Max.X
 | |
| 
 | |
| 		scale := float64(vertical.track.Dy()) /
 | |
| 			float64(contentBounds.Dy())
 | |
| 		vertical.bar.Min.Y = int(float64(viewportBounds.Min.Y) * scale)
 | |
| 		vertical.bar.Max.Y = int(float64(viewportBounds.Max.Y) * scale)
 | |
| 		
 | |
| 		vertical.bar.Min.Y += vertical.track.Min.Y
 | |
| 		vertical.bar.Max.Y += vertical.track.Min.Y
 | |
| 	}
 | |
| 
 | |
| 	// if the scroll bars are out of bounds, don't display them.
 | |
| 	if horizontal.bar.Dx() >= horizontal.track.Dx() {
 | |
| 		horizontal.bar = image.Rectangle { }
 | |
| 	}
 | |
| 	if vertical.bar.Dy() >= vertical.track.Dy() {
 | |
| 		vertical.bar = image.Rectangle { }
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) draw () {
 | |
| 	deadPattern := element.theme.Pattern (
 | |
| 		theme.PatternDead, theme.PatternState { })
 | |
| 	artist.FillRectangle (
 | |
| 		element.core, deadPattern,
 | |
| 		image.Rect (
 | |
| 			element.vertical.gutter.Min.X,
 | |
| 			element.horizontal.gutter.Min.Y,
 | |
| 			element.vertical.gutter.Max.X,
 | |
| 			element.horizontal.gutter.Max.Y))
 | |
| 	element.drawHorizontalBar()
 | |
| 	element.drawVerticalBar()
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) drawHorizontalBar () {
 | |
| 	state := theme.PatternState {
 | |
| 		Disabled: !element.horizontal.enabled,
 | |
| 		Pressed:  element.horizontal.dragging,
 | |
| 	}
 | |
| 	gutterPattern := element.horizontal.theme.Pattern(theme.PatternGutter, state)
 | |
| 	artist.FillRectangle(element.core, gutterPattern, element.horizontal.gutter)
 | |
| 	
 | |
| 	handlePattern := element.horizontal.theme.Pattern(theme.PatternHandle, state)
 | |
| 	artist.FillRectangle(element.core, handlePattern, element.horizontal.bar)
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) drawVerticalBar () {
 | |
| 	state := theme.PatternState {
 | |
| 		Disabled: !element.vertical.enabled,
 | |
| 		Pressed:  element.vertical.dragging,
 | |
| 	}
 | |
| 	gutterPattern := element.vertical.theme.Pattern(theme.PatternGutter, state)
 | |
| 	artist.FillRectangle(element.core, gutterPattern, element.vertical.gutter)
 | |
| 	
 | |
| 	handlePattern := element.vertical.theme.Pattern(theme.PatternHandle, state)
 | |
| 	artist.FillRectangle(element.core, handlePattern, element.vertical.bar)
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) dragHorizontalBar (mousePosition image.Point) {
 | |
| 	scrollX :=
 | |
| 		float64(element.child.ScrollContentBounds().Dx()) /
 | |
| 		float64(element.horizontal.track.Dx()) *
 | |
| 		float64(mousePosition.X - element.horizontal.dragOffset)
 | |
| 	scrollY := element.child.ScrollViewportBounds().Min.Y
 | |
| 	element.child.ScrollTo(image.Pt(int(scrollX), scrollY))
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) dragVerticalBar (mousePosition image.Point) {
 | |
| 	scrollY :=
 | |
| 		float64(element.child.ScrollContentBounds().Dy()) /
 | |
| 		float64(element.vertical.track.Dy()) *
 | |
| 		float64(mousePosition.Y - element.vertical.dragOffset)
 | |
| 	scrollX := element.child.ScrollViewportBounds().Min.X
 | |
| 	element.child.ScrollTo(image.Pt(scrollX, int(scrollY)))
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) updateMinimumSize () {
 | |
| 	gutterInsetHorizontal := element.horizontal.theme.Inset(theme.PatternGutter)
 | |
| 	gutterInsetVertical   := element.vertical.theme.Inset(theme.PatternGutter)
 | |
| 
 | |
| 	thicknessHorizontal :=
 | |
| 		element.config.HandleWidth() +
 | |
| 		gutterInsetHorizontal[3] +
 | |
| 		gutterInsetHorizontal[1]
 | |
| 	thicknessVertical :=
 | |
| 		element.config.HandleWidth() +
 | |
| 		gutterInsetVertical[3] +
 | |
| 		gutterInsetVertical[1]
 | |
| 	
 | |
| 	width  := thicknessHorizontal
 | |
| 	height := thicknessVertical
 | |
| 	if element.child != nil {
 | |
| 		childWidth, childHeight := element.child.MinimumSize()
 | |
| 		width  += childWidth
 | |
| 		height += childHeight
 | |
| 	}
 | |
| 	element.core.SetMinimumSize(width, height)
 | |
| }
 | |
| 
 | |
| func (element *ScrollContainer) childScrollBoundsChangeCallback () {
 | |
| 	element.horizontal.enabled,
 | |
| 	element.vertical.enabled = element.child.ScrollAxes()
 | |
| 	if element.core.HasImage() {
 | |
| 		element.recalculate()
 | |
| 		element.drawHorizontalBar()
 | |
| 		element.drawVerticalBar()
 | |
| 		element.core.DamageRegion(element.horizontal.gutter)
 | |
| 		element.core.DamageRegion(element.vertical.gutter)
 | |
| 	}
 | |
| }
 |