blob: 6c1a665cd087fe589f6ef2864def5eaa702299ae [file] [log] [blame]
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package charts
import (
"github.com/google/gxui"
"github.com/google/gxui/math"
"github.com/google/gxui/mixins/base"
)
const zoomScale = 1.01
type InputKind int
const (
ScrollX = InputKind(iota)
ScrollY
DragX
DragY
)
type ActionKind int
const (
Nop = ActionKind(iota)
PanX
PanY
ZoomX
ZoomY
ZoomXY
)
type InputHandler func(InputKind, gxui.Orientation, gxui.MouseEvent) ActionKind
func DefaultInputHandler(input InputKind, o gxui.Orientation, ev gxui.MouseEvent) ActionKind {
switch input {
case ScrollY:
return ZoomXY
case DragX:
return PanX
case DragY:
return PanY
default:
return Nop
}
}
type ZoomWindowOuter interface {
base.ContainerOuter
Orientation() gxui.Orientation
}
type ZoomWindow struct {
outer ZoomWindowOuter
minZoomWindowSize math.Size
zoomBounds math.Rect
zoomWindow math.Rect
hScrollbar gxui.ScrollBar
vScrollbar gxui.ScrollBar
hScrollbarChild *gxui.Child
vScrollbarChild *gxui.Child
mouseDown bool
mousePos math.Point
InputHandler InputHandler
}
func (z *ZoomWindow) Init(outer ZoomWindowOuter, theme gxui.Theme) {
z.outer = outer
z.minZoomWindowSize.W = 10
z.minZoomWindowSize.H = 10
z.hScrollbar = theme.CreateScrollBar()
z.vScrollbar = theme.CreateScrollBar()
z.hScrollbar.SetOrientation(gxui.Horizontal)
z.vScrollbar.SetOrientation(gxui.Vertical)
z.hScrollbarChild = z.outer.AddChild(z.hScrollbar)
z.vScrollbarChild = z.outer.AddChild(z.vScrollbar)
z.hScrollbar.OnScroll(func(from, to int) {
w := z.zoomWindow
b := z.zoomBounds.Min.X
z.SetZoomWindow(math.CreateRect(from+b, w.Min.Y, to+b, w.Max.Y))
})
z.vScrollbar.OnScroll(func(from, to int) {
w := z.zoomWindow
b := z.zoomBounds.Min.Y
z.SetZoomWindow(math.CreateRect(w.Min.X, from+b, w.Max.X, to+b))
})
z.InputHandler = DefaultInputHandler
}
func (c *BarChart) LayoutChildren() {
s := c.Size()
hs := c.hScrollbar.DesiredSize(math.ZeroSize, s)
vs := c.vScrollbar.DesiredSize(math.ZeroSize, s)
c.hScrollbarChild.Layout(math.CreateRect(0, s.H-hs.H, s.W-vs.W, s.H))
c.vScrollbarChild.Layout(math.CreateRect(s.W-vs.W, 0, s.W, s.H-vs.H))
}
func (z *ZoomWindow) SetMinZoomWindowSize(size math.Size) {
z.minZoomWindowSize = size
z.SetZoomWindow(z.zoomWindow)
}
func (z *ZoomWindow) SetZoomWindow(zoomWindow math.Rect) {
minRect := z.minZoomWindowSize.CenteredRect().Offset(zoomWindow.Mid())
zoomWindow = zoomWindow.Union(minRect).Constrain(z.zoomBounds)
if z.zoomWindow != zoomWindow {
z.zoomWindow = zoomWindow
z.hScrollbar.SetScrollPosition(zoomWindow.Min.X-z.zoomBounds.Min.X, zoomWindow.Max.X-z.zoomBounds.Min.X)
z.vScrollbar.SetScrollPosition(zoomWindow.Min.Y-z.zoomBounds.Min.Y, zoomWindow.Max.Y-z.zoomBounds.Min.Y)
z.outer.Redraw()
}
}
func (z *ZoomWindow) SetZoomBounds(zoomBounds math.Rect) {
z.zoomBounds = zoomBounds
z.hScrollbar.SetScrollLimit(zoomBounds.W())
z.vScrollbar.SetScrollLimit(zoomBounds.H())
z.SetZoomWindow(z.zoomWindow)
}
func (z *ZoomWindow) ViewBounds() math.Rect {
bounds := z.outer.Size().Rect()
if z.vScrollbar.IsVisible() {
bounds.Max.X -= z.vScrollbar.Size().W
}
if z.hScrollbar.IsVisible() {
bounds.Max.Y -= z.hScrollbar.Size().H
}
return bounds
}
func (z *ZoomWindow) MouseDown(e gxui.MouseEvent) {
for _, c := range z.outer.Children() {
if c.Bounds().Contains(e.Point) {
return
}
}
z.mouseDown = true
z.mousePos = e.Point
}
func (z *ZoomWindow) MouseUp(e gxui.MouseEvent) {
z.mouseDown = false
}
func (z *ZoomWindow) MouseMove(e gxui.MouseEvent) {
lastPos := z.mousePos
z.mousePos = e.Point
if z.mouseDown {
delta := lastPos.Sub(e.Point).Vec2()
if delta.X != 0 {
z.handleAction(z.InputHandler(DragX, z.outer.Orientation(), e), delta.X)
}
if delta.Y != 0 {
z.handleAction(z.InputHandler(DragY, z.outer.Orientation(), e), delta.Y)
}
}
}
func (z *ZoomWindow) MouseScroll(e gxui.MouseEvent) bool {
if e.ScrollX != 0 {
z.handleAction(z.InputHandler(ScrollX, z.outer.Orientation(), e), float32(e.ScrollX))
}
if e.ScrollY != 0 {
z.handleAction(z.InputHandler(ScrollY, z.outer.Orientation(), e), float32(e.ScrollY))
}
return true
}
func (z *ZoomWindow) handleAction(action ActionKind, value float32) {
switch action {
case PanX:
value *= float32(z.zoomWindow.W()) / float32(z.ViewBounds().W())
z.SetZoomWindow(z.zoomWindow.OffsetX(int(value)))
case PanY:
value *= float32(z.zoomWindow.H()) / float32(z.ViewBounds().H())
z.SetZoomWindow(z.zoomWindow.OffsetY(int(value)))
case ZoomX, ZoomY, ZoomXY:
center := z.mousePos.Remap(z.ViewBounds(), z.zoomWindow)
scale := math.Powf(zoomScale, float32(value))
s := math.Vec2{X: 1, Y: 1}
if action != PanY {
s.X = math.Maxf(scale, float32(z.minZoomWindowSize.W)/float32(z.zoomWindow.W()))
}
if action != PanX {
s.Y = math.Maxf(scale, float32(z.minZoomWindowSize.H)/float32(z.zoomWindow.H()))
}
z.SetZoomWindow(z.zoomWindow.ScaleAt(center, s))
}
}