202 lines
4.9 KiB
Go
202 lines
4.9 KiB
Go
package widgets
|
|
|
|
import (
|
|
"aego/core/mathx"
|
|
"aego/platform/input"
|
|
"aego/ui/core"
|
|
)
|
|
|
|
func VirtualList(c *core.Context, id core.ID, r mathx.Rect, count int, rowH float32, scroll *float32, fn func(i int, row mathx.Rect)) {
|
|
th := &c.Theme
|
|
content := float32(count) * rowH
|
|
maxOff := max(0, content-r.H)
|
|
if c.MouseOver(id, r) {
|
|
if w := c.ScrollY(rowH * 3); w != 0 {
|
|
*scroll -= w
|
|
}
|
|
}
|
|
*scroll = mathx.Clamp(*scroll, 0, maxOff)
|
|
inner := r
|
|
if content > r.H {
|
|
inner.W -= th.ScrollbarW
|
|
}
|
|
c.PushClip(inner)
|
|
if rowH > 0 && count > 0 {
|
|
first := int(*scroll / rowH)
|
|
last := min(count, int((*scroll+r.H)/rowH)+1)
|
|
for i := first; i < last; i++ {
|
|
fn(i, mathx.Rect{X: inner.X, Y: inner.Y + float32(i)*rowH - *scroll, W: inner.W, H: rowH})
|
|
}
|
|
}
|
|
c.PopClip()
|
|
if content <= r.H {
|
|
return
|
|
}
|
|
track := mathx.Rect{X: inner.X + inner.W, Y: r.Y, W: th.ScrollbarW, H: r.H}
|
|
c.Rect(track, th.PanelAlt)
|
|
thumbH := max(20, r.H*r.H/content)
|
|
ty := r.Y + (*scroll/maxOff)*(r.H-thumbH)
|
|
thumb := mathx.Rect{X: track.X + 1, Y: ty, W: track.W - 2, H: thumbH}
|
|
tid := core.IDSeq(id, 1)
|
|
if d, ok := c.Drag(tid, thumb); ok && d.Y != 0 && r.H > thumbH {
|
|
*scroll = mathx.Clamp(*scroll+d.Y*maxOff/(r.H-thumbH), 0, maxOff)
|
|
}
|
|
col := th.Widget
|
|
if c.Hot() == tid || c.Active() == tid {
|
|
col = th.WidgetHot
|
|
}
|
|
c.RoundRect(thumb, (track.W-2)/2, col)
|
|
}
|
|
|
|
func EnsureVisible(scroll *float32, index int, rowH, viewH float32) {
|
|
top := float32(index) * rowH
|
|
if top < *scroll {
|
|
*scroll = top
|
|
}
|
|
if top+rowH > *scroll+viewH {
|
|
*scroll = top + rowH - viewH
|
|
}
|
|
}
|
|
|
|
type TreeRow struct {
|
|
Depth int
|
|
Label string
|
|
HasChildren bool
|
|
Open bool
|
|
Selected bool
|
|
Focused bool
|
|
Dim bool
|
|
Icon string
|
|
Marker bool
|
|
Guides bool
|
|
Last bool
|
|
Lines uint64
|
|
}
|
|
|
|
type RowEvent struct {
|
|
Pressed bool
|
|
Released bool
|
|
DoubleClicked bool
|
|
Toggled bool
|
|
RightClicked bool
|
|
}
|
|
|
|
func TreeRowRect(c *core.Context, id core.ID, r mathx.Rect, row TreeRow) RowEvent {
|
|
th := &c.Theme
|
|
var ev RowEvent
|
|
x := r.X + float32(row.Depth)*th.Indent
|
|
arrow := mathx.Rect{X: x, Y: r.Y, W: th.Indent + 2, H: r.H}
|
|
arrowID := id ^ 0x5bd1e995
|
|
if row.HasChildren && c.Pressed(arrowID, arrow) {
|
|
ev.Toggled = true
|
|
} else if c.Pressed(id, r) {
|
|
ev.Pressed = true
|
|
ev.DoubleClicked = c.DoubleClicked(id)
|
|
}
|
|
if c.Active() == id && c.MouseReleased(input.MouseLeft) && c.Hit(r) {
|
|
ev.Released = true
|
|
}
|
|
if c.RightClicked(id, r) {
|
|
ev.RightClicked = true
|
|
}
|
|
switch {
|
|
case row.Selected && row.Focused:
|
|
c.Rect(r, th.Selection)
|
|
case row.Selected:
|
|
c.Rect(r, th.SelectionDim)
|
|
case c.Hot() == id:
|
|
c.Rect(r, th.WidgetHot)
|
|
}
|
|
if row.Guides && row.Depth > 0 {
|
|
treeGuides(c, r, x, row)
|
|
}
|
|
if row.HasChildren {
|
|
col := th.TextDim
|
|
if c.Hot() == arrowID {
|
|
col = th.Text
|
|
}
|
|
Chevron(c, arrow, row.Open, col)
|
|
}
|
|
col, tint := th.Text, th.TextDim
|
|
if row.Dim {
|
|
col, tint = th.TextDisabled, th.TextDisabled
|
|
}
|
|
lx := x + th.Indent + 4
|
|
ir := mathx.Rect{X: lx, Y: c.TextY(r) + c.LineHeight()/2 - 8, W: 16, H: 16}
|
|
switch {
|
|
case HasIcon(c, row.Icon):
|
|
IconAt(c, ir, row.Icon, tint)
|
|
lx += 20
|
|
case row.Marker:
|
|
c.RoundRect(mathx.Rect{X: ir.X + 5, Y: ir.Y + 5, W: 6, H: 6}, 3, th.TextDisabled)
|
|
lx += 20
|
|
}
|
|
c.Text(row.Label, mathx.Vec2{X: lx, Y: c.TextY(r)}, col)
|
|
return ev
|
|
}
|
|
|
|
func treeGuides(c *core.Context, r mathx.Rect, x float32, row TreeRow) {
|
|
th := &c.Theme
|
|
col := th.TreeGuide
|
|
mid := float32(int(r.Y + r.H/2))
|
|
for d := 0; d < row.Depth-1 && d < 64; d++ {
|
|
if row.Lines&(1<<d) != 0 {
|
|
c.Rect(mathx.Rect{X: float32(int(r.X + float32(d)*th.Indent + th.Indent/2)), Y: r.Y, W: 1, H: r.H}, col)
|
|
}
|
|
}
|
|
gx := float32(int(r.X + float32(row.Depth-1)*th.Indent + th.Indent/2))
|
|
h := r.H
|
|
if row.Last {
|
|
h = mid - r.Y + 1
|
|
}
|
|
c.Rect(mathx.Rect{X: gx, Y: r.Y, W: 1, H: h}, col)
|
|
end := x + th.Indent
|
|
if row.HasChildren {
|
|
end = x + 2
|
|
}
|
|
c.Rect(mathx.Rect{X: gx + 1, Y: mid, W: max(0, end-gx-1), H: 1}, col)
|
|
}
|
|
|
|
type ScrollState struct {
|
|
grab float32
|
|
}
|
|
|
|
func ScrollBar(c *core.Context, id core.ID, track mathx.Rect, scroll *float32, content, view float32, st *ScrollState) {
|
|
th := &c.Theme
|
|
maxOff := max(0, content-view)
|
|
if maxOff <= 0 || track.H <= 0 {
|
|
*scroll = 0
|
|
return
|
|
}
|
|
c.RoundRect(track, track.W/2, th.PanelAlt)
|
|
thumbH := min(track.H, max(24, track.H*view/content))
|
|
span := track.H - thumbH
|
|
ty := track.Y + span*(*scroll/maxOff)
|
|
thumb := mathx.Rect{X: track.X + 1, Y: ty, W: track.W - 2, H: thumbH}
|
|
if c.Pressed(id, track) {
|
|
my := c.Mouse().Y
|
|
if my >= thumb.Y && my < thumb.Y+thumb.H {
|
|
st.grab = my - thumb.Y
|
|
} else {
|
|
st.grab = thumbH / 2
|
|
}
|
|
c.CaptureMouse(id)
|
|
}
|
|
if c.Active() == id {
|
|
if c.MouseDown(input.MouseLeft) {
|
|
if span > 0 {
|
|
*scroll = mathx.Clamp((c.Mouse().Y-track.Y-st.grab)/span*maxOff, 0, maxOff)
|
|
}
|
|
} else {
|
|
c.ReleaseMouse()
|
|
}
|
|
ty = track.Y + span*(*scroll/maxOff)
|
|
thumb.Y = ty
|
|
}
|
|
col := th.Widget
|
|
if c.Hot() == id || c.Active() == id {
|
|
col = th.WidgetHot
|
|
}
|
|
c.RoundRect(thumb, (track.W-2)/2, col)
|
|
}
|