2018-08-12 16:43:41 -05:00
|
|
|
// 12 august 2018
|
|
|
|
|
|
|
|
// +build OMIT
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math/rand"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/andlabs/ui"
|
|
|
|
)
|
|
|
|
|
2018-08-20 08:12:32 -05:00
|
|
|
var (
|
2018-08-20 09:09:53 -05:00
|
|
|
histogram *ui.Area
|
2018-08-20 08:12:32 -05:00
|
|
|
datapoints [10]*ui.Spinbox
|
|
|
|
colorButton *ui.ColorButton
|
2018-08-20 09:09:53 -05:00
|
|
|
|
|
|
|
currentPoint = -1
|
2018-08-20 08:12:32 -05:00
|
|
|
)
|
2018-08-12 16:43:41 -05:00
|
|
|
|
|
|
|
// some metrics
|
|
|
|
const (
|
|
|
|
xoffLeft = 20 // histogram margins
|
|
|
|
yoffTop = 20
|
|
|
|
xoffRight = 20
|
|
|
|
yoffBottom = 20
|
|
|
|
pointRadius = 5
|
|
|
|
)
|
|
|
|
|
|
|
|
// helper to quickly set a brush color
|
2018-08-26 16:26:53 -05:00
|
|
|
func mkSolidBrush(color uint32, alpha float64) *ui.DrawBrush {
|
|
|
|
brush := new(ui.DrawBrush)
|
2018-08-26 13:42:43 -05:00
|
|
|
brush.Type = ui.DrawBrushTypeSolid
|
2018-08-12 16:43:41 -05:00
|
|
|
component := uint8((color >> 16) & 0xFF)
|
|
|
|
brush.R = float64(component) / 255
|
|
|
|
component = uint8((color >> 8) & 0xFF)
|
|
|
|
brush.G = float64(component) / 255
|
|
|
|
component = uint8(color & 0xFF)
|
|
|
|
brush.B = float64(component) / 255
|
|
|
|
brush.A = alpha
|
|
|
|
return brush
|
|
|
|
}
|
|
|
|
|
|
|
|
// and some colors
|
|
|
|
// names and values from https://msdn.microsoft.com/en-us/library/windows/desktop/dd370907%28v=vs.85%29.aspx
|
|
|
|
const (
|
|
|
|
colorWhite = 0xFFFFFF
|
|
|
|
colorBlack = 0x000000
|
|
|
|
colorDodgerBlue = 0x1E90FF
|
|
|
|
)
|
|
|
|
|
|
|
|
func pointLocations(width, height float64) (xs, ys [10]float64) {
|
|
|
|
xincr := width / 9 // 10 - 1 to make the last point be at the end
|
|
|
|
yincr := height / 100
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
// get the value of the point
|
|
|
|
n := datapoints[i].Value()
|
|
|
|
// because y=0 is the top but n=0 is the bottom, we need to flip
|
|
|
|
n = 100 - n
|
|
|
|
xs[i] = xincr * float64(i)
|
|
|
|
ys[i] = yincr * float64(n)
|
|
|
|
}
|
|
|
|
return xs, ys
|
|
|
|
}
|
|
|
|
|
2018-08-26 16:26:53 -05:00
|
|
|
func constructGraph(width, height float64, extend bool) *ui.DrawPath {
|
2018-08-12 16:43:41 -05:00
|
|
|
xs, ys := pointLocations(width, height)
|
2018-08-26 16:26:53 -05:00
|
|
|
path := ui.DrawNewPath(ui.DrawFillModeWinding)
|
2018-08-12 16:43:41 -05:00
|
|
|
|
|
|
|
path.NewFigure(xs[0], ys[0])
|
|
|
|
for i := 1; i < 10; i++ {
|
|
|
|
path.LineTo(xs[i], ys[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
if extend {
|
|
|
|
path.LineTo(width, height)
|
|
|
|
path.LineTo(0, height)
|
|
|
|
path.CloseFigure()
|
|
|
|
}
|
|
|
|
|
|
|
|
path.End()
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
|
|
|
|
func graphSize(clientWidth, clientHeight float64) (graphWidth, graphHeight float64) {
|
|
|
|
return clientWidth - xoffLeft - xoffRight,
|
|
|
|
clientHeight - yoffTop - yoffBottom
|
|
|
|
}
|
|
|
|
|
|
|
|
type areaHandler struct{}
|
|
|
|
|
|
|
|
func (areaHandler) Draw(a *ui.Area, p *ui.AreaDrawParams) {
|
|
|
|
// fill the area with white
|
|
|
|
brush := mkSolidBrush(colorWhite, 1.0)
|
2018-08-26 16:26:53 -05:00
|
|
|
path := ui.DrawNewPath(ui.DrawFillModeWinding)
|
2018-08-12 16:43:41 -05:00
|
|
|
path.AddRectangle(0, 0, p.AreaWidth, p.AreaHeight)
|
|
|
|
path.End()
|
|
|
|
p.Context.Fill(path, brush)
|
|
|
|
path.Free()
|
|
|
|
|
|
|
|
graphWidth, graphHeight := graphSize(p.AreaWidth, p.AreaHeight)
|
|
|
|
|
2018-08-26 16:26:53 -05:00
|
|
|
sp := &ui.DrawStrokeParams{
|
|
|
|
Cap: ui.DrawLineCapFlat,
|
|
|
|
Join: ui.DrawLineJoinMiter,
|
2018-08-12 16:43:41 -05:00
|
|
|
Thickness: 2,
|
2018-08-26 16:26:53 -05:00
|
|
|
MiterLimit: ui.DrawDefaultMiterLimit,
|
2018-08-12 16:43:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// draw the axes
|
|
|
|
brush = mkSolidBrush(colorBlack, 1.0)
|
2018-08-26 16:26:53 -05:00
|
|
|
path = ui.DrawNewPath(ui.DrawFillModeWinding)
|
2018-08-12 16:43:41 -05:00
|
|
|
path.NewFigure(xoffLeft, yoffTop)
|
|
|
|
path.LineTo(xoffLeft, yoffTop + graphHeight)
|
|
|
|
path.LineTo(xoffLeft + graphWidth, yoffTop + graphHeight)
|
|
|
|
path.End()
|
|
|
|
p.Context.Stroke(path, brush, sp)
|
|
|
|
path.Free()
|
|
|
|
|
|
|
|
// now transform the coordinate space so (0, 0) is the top-left corner of the graph
|
2018-08-26 16:26:53 -05:00
|
|
|
m := ui.DrawNewMatrix()
|
2018-08-20 09:09:53 -05:00
|
|
|
m.Translate(xoffLeft, yoffTop)
|
2018-08-12 16:43:41 -05:00
|
|
|
p.Context.Transform(m)
|
|
|
|
|
|
|
|
// now get the color for the graph itself and set up the brush
|
2018-08-20 08:12:32 -05:00
|
|
|
graphR, graphG, graphB, graphA := colorButton.Color()
|
2018-08-26 13:42:43 -05:00
|
|
|
brush.Type = ui.DrawBrushTypeSolid
|
2018-08-20 08:12:32 -05:00
|
|
|
brush.R = graphR
|
|
|
|
brush.G = graphG
|
|
|
|
brush.B = graphB
|
2018-08-12 16:43:41 -05:00
|
|
|
// we set brush.A below to different values for the fill and stroke
|
|
|
|
|
|
|
|
// now create the fill for the graph below the graph line
|
2018-08-20 08:12:32 -05:00
|
|
|
path = constructGraph(graphWidth, graphHeight, true)
|
|
|
|
brush.A = graphA / 2
|
2018-08-12 16:43:41 -05:00
|
|
|
p.Context.Fill(path, brush)
|
|
|
|
path.Free()
|
|
|
|
|
|
|
|
// now draw the histogram line
|
2018-08-20 08:12:32 -05:00
|
|
|
path = constructGraph(graphWidth, graphHeight, false)
|
|
|
|
brush.A = graphA
|
2018-08-12 16:43:41 -05:00
|
|
|
p.Context.Stroke(path, brush, sp)
|
|
|
|
path.Free()
|
|
|
|
|
|
|
|
// now draw the point being hovered over
|
2018-08-20 09:09:53 -05:00
|
|
|
if currentPoint != -1 {
|
|
|
|
xs, ys := pointLocations(graphWidth, graphHeight)
|
2018-08-26 16:26:53 -05:00
|
|
|
path = ui.DrawNewPath(ui.DrawFillModeWinding)
|
2018-08-20 09:09:53 -05:00
|
|
|
path.NewFigureWithArc(
|
2018-08-12 16:43:41 -05:00
|
|
|
xs[currentPoint], ys[currentPoint],
|
|
|
|
pointRadius,
|
|
|
|
0, 6.23, // TODO pi
|
2018-08-20 09:09:53 -05:00
|
|
|
false)
|
|
|
|
path.End()
|
2018-08-12 16:43:41 -05:00
|
|
|
// use the same brush as for the histogram lines
|
2018-08-20 09:09:53 -05:00
|
|
|
p.Context.Fill(path, brush)
|
|
|
|
path.Free()
|
2018-08-12 16:43:41 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func inPoint(x, y float64, xtest, ytest float64) bool {
|
|
|
|
// TODO switch to using a matrix
|
|
|
|
x -= xoffLeft
|
|
|
|
y -= yoffTop
|
|
|
|
return (x >= xtest - pointRadius) &&
|
|
|
|
(x <= xtest + pointRadius) &&
|
|
|
|
(y >= ytest - pointRadius) &&
|
|
|
|
(y <= ytest + pointRadius)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (areaHandler) MouseEvent(a *ui.Area, me *ui.AreaMouseEvent) {
|
2018-08-20 09:09:53 -05:00
|
|
|
graphWidth, graphHeight := graphSize(me.AreaWidth, me.AreaHeight)
|
|
|
|
xs, ys := pointLocations(graphWidth, graphHeight)
|
2018-08-12 16:43:41 -05:00
|
|
|
|
2018-08-20 09:09:53 -05:00
|
|
|
currentPoint = -1
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
if inPoint(me.X, me.Y, xs[i], ys[i]) {
|
|
|
|
currentPoint = i
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2018-08-12 16:43:41 -05:00
|
|
|
|
|
|
|
// TODO only redraw the relevant area
|
2018-08-20 09:09:53 -05:00
|
|
|
histogram.QueueRedrawAll()
|
2018-08-12 16:43:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (areaHandler) MouseCrossed(a *ui.Area, left bool) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
func (areaHandler) DragBroken(a *ui.Area) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
func (areaHandler) KeyEvent(a *ui.Area, ke *ui.AreaKeyEvent) (handled bool) {
|
|
|
|
// reject all keys
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupUI() {
|
|
|
|
mainwin := ui.NewWindow("libui Histogram Example", 640, 480, true)
|
|
|
|
mainwin.SetMargined(true)
|
|
|
|
mainwin.OnClosing(func(*ui.Window) bool {
|
|
|
|
mainwin.Destroy()
|
|
|
|
ui.Quit()
|
|
|
|
return false
|
|
|
|
})
|
|
|
|
ui.OnShouldQuit(func() bool {
|
|
|
|
mainwin.Destroy()
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
|
|
|
|
hbox := ui.NewHorizontalBox()
|
|
|
|
hbox.SetPadded(true)
|
|
|
|
mainwin.SetChild(hbox)
|
|
|
|
|
|
|
|
vbox := ui.NewVerticalBox()
|
|
|
|
vbox.SetPadded(true)
|
|
|
|
hbox.Append(vbox, false)
|
|
|
|
|
2018-08-20 09:09:53 -05:00
|
|
|
histogram = ui.NewArea(areaHandler{})
|
2018-08-12 16:43:41 -05:00
|
|
|
|
|
|
|
rand.Seed(time.Now().Unix())
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
datapoints[i] = ui.NewSpinbox(0, 100)
|
|
|
|
datapoints[i].SetValue(rand.Intn(101))
|
|
|
|
datapoints[i].OnChanged(func(*ui.Spinbox) {
|
|
|
|
histogram.QueueRedrawAll()
|
|
|
|
})
|
|
|
|
vbox.Append(datapoints[i], false)
|
|
|
|
}
|
|
|
|
|
2018-08-19 21:19:12 -05:00
|
|
|
colorButton = ui.NewColorButton()
|
2018-08-12 16:43:41 -05:00
|
|
|
// TODO inline these
|
2018-08-20 09:09:53 -05:00
|
|
|
brush := mkSolidBrush(colorDodgerBlue, 1.0)
|
2018-08-19 21:19:12 -05:00
|
|
|
colorButton.SetColor(brush.R,
|
2018-08-12 16:43:41 -05:00
|
|
|
brush.G,
|
|
|
|
brush.B,
|
2018-08-19 21:19:12 -05:00
|
|
|
brush.A)
|
|
|
|
colorButton.OnChanged(func(*ui.ColorButton) {
|
|
|
|
histogram.QueueRedrawAll()
|
|
|
|
})
|
|
|
|
vbox.Append(colorButton, false)
|
2018-08-12 16:43:41 -05:00
|
|
|
|
|
|
|
hbox.Append(histogram, true)
|
|
|
|
|
|
|
|
mainwin.Show()
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
ui.Main(setupUI)
|
|
|
|
}
|