Files
aego/ui/dock/float.go
T
2026-10-19 20:27:40 +03:00

200 lines
4.8 KiB
Go

package dock
import (
"aego/core/mathx"
"aego/platform/input"
"aego/ui/core"
"aego/ui/draw"
"aego/ui/widgets"
)
const (
floatMinW = 220
floatMinH = 140
floatW = 560
floatH = 380
gripSize = 12
floatGrace = 80
)
var menuID = core.IDOf("dock.menu")
type Float struct {
Panel string
Rect mathx.Rect
}
type menuState struct {
panel string
floating bool
}
func (t *Tree) floatIndex(panel string) int {
for i := range t.floats {
if t.floats[i].Panel == panel {
return i
}
}
return -1
}
func (t *Tree) Floats() []Float {
return t.floats
}
func (t *Tree) floatContent(i int) mathx.Rect {
r := t.floats[i].Rect
return mathx.Rect{X: r.X, Y: r.Y + t.tabH, W: r.W, H: max(0, r.H-t.tabH)}
}
func (t *Tree) clampFloat(r mathx.Rect) mathx.Rect {
b := t.bounds
if b.W <= 0 || b.H <= 0 {
return r
}
r.W = mathx.Clamp(r.W, floatMinW, max(floatMinW, b.W))
r.H = mathx.Clamp(r.H, floatMinH, max(floatMinH, b.H))
r.X = mathx.Clamp(r.X, b.X-r.W+floatGrace, b.X+b.W-floatGrace)
r.Y = mathx.Clamp(r.Y, b.Y, max(b.Y, b.Y+b.H-t.tabH))
return r
}
func (t *Tree) floatAt(m mathx.Vec2) mathx.Rect {
return t.clampFloat(mathx.Rect{X: m.X - 60, Y: m.Y - 12, W: floatW, H: floatH})
}
func (t *Tree) Float(panel string, at mathx.Vec2) {
t.Undock(panel)
t.floats = append(t.floats, Float{Panel: panel, Rect: t.floatAt(at)})
t.changed = true
}
func (t *Tree) raise(i int) {
if i < 0 || i >= len(t.floats)-1 {
return
}
f := t.floats[i]
t.floats = append(t.floats[:i], t.floats[i+1:]...)
t.floats = append(t.floats, f)
t.changed = true
}
func (t *Tree) dockBack(panel string) {
t.DockPanel(panel, t.firstTabs(t.root), SideCenter)
}
func (t *Tree) close(panel string) {
if t.OnClose != nil && t.OnClose(panel) {
return
}
t.Undock(panel)
}
func (t *Tree) openMenu(ui *core.Context, panel string, floating bool) {
t.menu = menuState{panel: panel, floating: floating}
ui.CloseAllPopups()
ui.OpenPopup(menuID, ui.Mouse())
}
func (t *Tree) drawMenu(ui *core.Context) {
if !ui.BeginPopup(menuID, 180) {
return
}
p := t.menu.panel
label := "Maximize"
if t.Maximized() == p {
label = "Restore Layout"
}
if widgets.MenuItem(ui, label, "Shift+Space", true, false) {
t.ToggleMaximize(p)
}
switch {
case t.menu.floating:
if widgets.MenuItem(ui, "Dock", "", true, false) {
t.dockBack(p)
}
case t.Maximized() == "":
if widgets.MenuItem(ui, "Float", "Shift+drag", true, false) {
t.Float(p, ui.Mouse())
}
}
if widgets.MenuItem(ui, "Close", "", true, false) {
t.close(p)
}
ui.EndPopup()
}
func (t *Tree) drawFloats(ui *core.Context, fn func(string, mathx.Rect)) {
th := &ui.Theme
raise, closeAt, dockAt := "", "", ""
for i := range t.floats {
f := &t.floats[i]
f.Rect = t.clampFloat(f.Rect)
r := f.Rect
id := core.IDOf("dock.float/" + f.Panel)
ui.BeginOverlay(r, draw.LayerPopup)
ui.Rect(r, th.Panel)
bar := mathx.Rect{X: r.X, Y: r.Y, W: r.W, H: t.tabH}
ui.Rect(bar, th.Header)
cr := mathx.Rect{X: bar.X + bar.W - bar.H, Y: bar.Y, W: bar.H, H: bar.H}
dr := mathx.Rect{X: cr.X - bar.H, Y: bar.Y, W: bar.H, H: bar.H}
title := mathx.Rect{X: bar.X, Y: bar.Y, W: max(0, dr.X-bar.X), H: bar.H}
if ui.Clicked(core.IDSeq(id, 1), cr) {
closeAt = f.Panel
}
if ui.Clicked(core.IDSeq(id, 2), dr) {
dockAt = f.Panel
}
if d, ok := ui.Drag(id, title); ok && (d.X != 0 || d.Y != 0) {
f.Rect.X += d.X
f.Rect.Y += d.Y
t.changed = true
}
if ui.DoubleClicked(id) {
dockAt = f.Panel
}
if ui.RightClicked(id, title) {
t.openMenu(ui, f.Panel, true)
}
ui.Text(t.title(f.Panel), mathx.Vec2{X: bar.X + th.Padding + 4, Y: ui.TextY(bar)}, th.Text)
widgets.IconAt(ui, mathx.Rect{X: dr.X + 5, Y: dr.Y + 5, W: dr.W - 10, H: dr.H - 10}, "view.dock", th.TextDim)
widgets.IconAt(ui, mathx.Rect{X: cr.X + 5, Y: cr.Y + 5, W: cr.W - 10, H: cr.H - 10}, "ui.close", th.TextDim)
content := t.floatContent(i)
if content.W > 0 && content.H > 0 {
ui.PushClip(content)
ui.PushID(f.Panel)
fn(f.Panel, content)
ui.PopID()
ui.PopClip()
}
grip := mathx.Rect{X: r.X + r.W - gripSize, Y: r.Y + r.H - gripSize, W: gripSize, H: gripSize}
gid := core.IDSeq(id, 3)
if d, ok := ui.Drag(gid, grip); ok && (d.X != 0 || d.Y != 0) {
f.Rect.W = max(floatMinW, f.Rect.W+d.X)
f.Rect.H = max(floatMinH, f.Rect.H+d.Y)
t.changed = true
}
gc := th.Border
if ui.Hot() == gid || ui.Active() == gid {
gc = th.Accent
ui.SetCursorShape(core.CursorResizeNWSE)
}
for k := float32(0); k < 3; k++ {
ui.Rect(mathx.Rect{X: grip.X + 3 + k*3, Y: grip.Y + gripSize - 3 - k*3, W: 2, H: 2}, gc)
}
ui.Outline(r, th.Border)
ui.EndOverlay()
if ui.MousePressed(input.MouseLeft) && r.Contains(ui.Mouse()) {
raise = f.Panel
}
}
switch {
case closeAt != "":
t.close(closeAt)
case dockAt != "":
t.dockBack(dockAt)
case raise != "":
t.raise(t.floatIndex(raise))
}
}