232 lines
5.6 KiB
Go
232 lines
5.6 KiB
Go
package widgets
|
|
|
|
import (
|
|
"math/bits"
|
|
"strconv"
|
|
|
|
"aego/core/mathx"
|
|
"aego/platform/input"
|
|
"aego/ui/core"
|
|
"aego/ui/draw"
|
|
)
|
|
|
|
func glyph(c *core.Context, r rune, fallback string) string {
|
|
if f := c.Font(); f != nil && f.Has(r) {
|
|
return string(r)
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func Glyph(c *core.Context, r rune, fallback string) string {
|
|
return glyph(c, r, fallback)
|
|
}
|
|
|
|
func dropdownBox(c *core.Context, id core.ID, r mathx.Rect, label string) {
|
|
th := &c.Theme
|
|
c.Box(r, th.Radius.Field, c.WidgetColor(id), th.Border)
|
|
inner := mathx.Rect{X: r.X, Y: r.Y, W: r.W - r.H, H: r.H}
|
|
c.PushClip(inner)
|
|
c.TextIn(inner, label, th.Text, core.AlignLeft)
|
|
c.PopClip()
|
|
Chevron(c, mathx.Rect{X: r.X + r.W - r.H, Y: r.Y, W: r.H, H: r.H}, true, th.TextDim)
|
|
}
|
|
|
|
func togglePopup(c *core.Context, id core.ID, r mathx.Rect) {
|
|
if c.Pressed(id, r) {
|
|
if c.PopupOpen(id) {
|
|
c.ClosePopup(id)
|
|
} else {
|
|
c.OpenPopup(id, mathx.Vec2{X: r.X, Y: r.Y + r.H})
|
|
}
|
|
}
|
|
}
|
|
|
|
func Combo(c *core.Context, id core.ID, r mathx.Rect, cur *int, opts []string, mixed bool) bool {
|
|
togglePopup(c, id, r)
|
|
label := c.Theme.Mixed
|
|
if !mixed && *cur >= 0 && *cur < len(opts) {
|
|
label = opts[*cur]
|
|
}
|
|
dropdownBox(c, id, r, label)
|
|
changed := false
|
|
if c.BeginPopup(id, max(r.W, 140)) {
|
|
c.PushRawID(id)
|
|
for i, o := range opts {
|
|
if Selectable(c, c.IDInt(i), o, !mixed && i == *cur) {
|
|
*cur = i
|
|
changed = true
|
|
c.ClosePopup(id)
|
|
}
|
|
}
|
|
c.PopID()
|
|
c.EndPopup()
|
|
}
|
|
return changed
|
|
}
|
|
|
|
func allBits(n int) uint64 {
|
|
if n >= 64 {
|
|
return ^uint64(0)
|
|
}
|
|
return uint64(1)<<uint(n) - 1
|
|
}
|
|
|
|
func maskSummary(mask uint64, names []string, n int, mixed bool, th *core.Theme) string {
|
|
switch {
|
|
case mixed:
|
|
return th.Mixed
|
|
case mask == 0:
|
|
return "Nothing"
|
|
case mask == ^uint64(0) || mask == allBits(n):
|
|
return "Everything"
|
|
case bits.OnesCount64(mask) == 1:
|
|
i := bits.TrailingZeros64(mask)
|
|
if i < len(names) {
|
|
return names[i]
|
|
}
|
|
return "Layer " + strconv.Itoa(i)
|
|
}
|
|
if len(names) > 0 {
|
|
return strconv.Itoa(bits.OnesCount64(mask)) + " selected"
|
|
}
|
|
return strconv.Itoa(bits.OnesCount64(mask)) + " layers"
|
|
}
|
|
|
|
func CheckRow(c *core.Context, id core.ID, label string, on bool) bool {
|
|
th := &c.Theme
|
|
r := c.Cursor().Next(0)
|
|
clicked := c.Clicked(id, r)
|
|
if c.Hot() == id {
|
|
c.RoundRect(r, th.Radius.Menu, th.WidgetHot)
|
|
}
|
|
box := mathx.Rect{X: r.X + 2, Y: r.Y + 4, W: r.H - 8, H: r.H - 8}
|
|
if on {
|
|
c.RoundRect(box, th.Radius.Field-1, th.Accent)
|
|
CheckMark(c, box, th.Background)
|
|
} else {
|
|
c.Box(box, th.Radius.Field-1, th.Widget, th.Border)
|
|
}
|
|
c.Text(label, mathx.Vec2{X: box.X + box.W + 6, Y: c.TextY(r)}, th.Text)
|
|
return clicked
|
|
}
|
|
|
|
func MaskField(c *core.Context, id core.ID, r mathx.Rect, mask *uint64, names []string, mixed bool) bool {
|
|
n := len(names)
|
|
if n == 0 {
|
|
n = 32
|
|
}
|
|
togglePopup(c, id, r)
|
|
dropdownBox(c, id, r, maskSummary(*mask, names, n, mixed, &c.Theme))
|
|
changed := false
|
|
if c.BeginPopup(id, max(r.W, 180)) {
|
|
c.PushRawID(id)
|
|
if Selectable(c, c.ID("everything"), "Everything", !mixed && *mask == allBits(n)) {
|
|
*mask = allBits(n)
|
|
changed = true
|
|
}
|
|
if Selectable(c, c.ID("nothing"), "Nothing", !mixed && *mask == 0) {
|
|
*mask = 0
|
|
changed = true
|
|
}
|
|
Separator(c)
|
|
for i := 0; i < n; i++ {
|
|
name := "Layer " + strconv.Itoa(i)
|
|
if i < len(names) && names[i] != "" {
|
|
name = names[i]
|
|
}
|
|
bit := uint64(1) << uint(i)
|
|
if CheckRow(c, c.IDInt(i), name, *mask&bit != 0) {
|
|
*mask ^= bit
|
|
changed = true
|
|
}
|
|
}
|
|
c.PopID()
|
|
c.EndPopup()
|
|
}
|
|
return changed
|
|
}
|
|
|
|
func OpenContextMenu(c *core.Context, id core.ID) {
|
|
c.CloseAllPopups()
|
|
c.OpenPopup(id, c.Mouse())
|
|
}
|
|
|
|
func BeginContextMenu(c *core.Context, id core.ID, r mathx.Rect) bool {
|
|
if c.RightClicked(id, r) {
|
|
OpenContextMenu(c, id)
|
|
}
|
|
return c.BeginPopup(id, 220)
|
|
}
|
|
|
|
func ContextMenu(c *core.Context, id core.ID) bool {
|
|
return c.BeginPopup(id, 220)
|
|
}
|
|
|
|
func EndContextMenu(c *core.Context) {
|
|
c.EndPopup()
|
|
}
|
|
|
|
func Tooltip(c *core.Context, id core.ID, text string) {
|
|
TooltipAfter(c, id, text, 0.5)
|
|
}
|
|
|
|
func Confirm(c *core.Context, id core.ID, title, message string, buttons ...string) int {
|
|
th := &c.Theme
|
|
sz := c.Size()
|
|
w := min(float32(480), sz.X-40)
|
|
lines := int(c.TextSize(message).X/(w-4*th.Padding)) + 1
|
|
h := th.RowHeight*float32(3+lines) + th.Padding*4
|
|
r := mathx.Rect{X: (sz.X - w) / 2, Y: (sz.Y - h) / 3, W: w, H: h}
|
|
if !c.BeginModal(id, r) {
|
|
return -1
|
|
}
|
|
c.PushRawID(id)
|
|
result := -1
|
|
Label(c, title)
|
|
LabelWrapped(c, message, true)
|
|
row := mathx.Rect{X: r.X + th.Padding, Y: r.Y + r.H - th.RowHeight - th.Padding, W: r.W - 2*th.Padding, H: th.RowHeight}
|
|
x := row.X + row.W
|
|
for i := len(buttons) - 1; i >= 0; i-- {
|
|
bw := c.TextSize(buttons[i]).X + 28
|
|
x -= bw
|
|
if ButtonRect(c, c.IDInt(i), mathx.Rect{X: x, Y: row.Y, W: bw, H: row.H}, buttons[i]) {
|
|
result = i
|
|
}
|
|
x -= 6
|
|
}
|
|
if result < 0 && len(buttons) > 0 && c.Focus() == 0 {
|
|
if c.KeyPressed(input.KeyEnter) || c.KeyPressed(input.KeyKPEnter) {
|
|
result = 0
|
|
}
|
|
if c.KeyPressed(input.KeyEscape) {
|
|
result = len(buttons) - 1
|
|
}
|
|
}
|
|
c.PopID()
|
|
c.EndModal()
|
|
return result
|
|
}
|
|
|
|
func TooltipAfter(c *core.Context, id core.ID, text string, delay float64) {
|
|
if text == "" || c.HoverDuration(id) < delay {
|
|
return
|
|
}
|
|
th := &c.Theme
|
|
m := c.Mouse()
|
|
size := c.TextSize(text)
|
|
r := mathx.Rect{X: m.X + 14, Y: m.Y + 18, W: size.X + 2*th.Padding, H: size.Y + th.Padding}
|
|
sz := c.Size()
|
|
if r.X+r.W > sz.X {
|
|
r.X = max(0, sz.X-r.W)
|
|
}
|
|
if r.Y+r.H > sz.Y {
|
|
r.Y = max(0, m.Y-r.H-4)
|
|
}
|
|
l := c.List()
|
|
l.PushLayer(draw.LayerTop)
|
|
c.Shadow(r, th.Radius.Popup)
|
|
c.Box(r, th.Radius.Popup, th.PanelAlt, th.Border)
|
|
l.Text(text, mathx.Vec2{X: r.X + th.Padding, Y: r.Y + th.Padding/2}, th.Text)
|
|
l.PopLayer()
|
|
}
|