232 lines
5.3 KiB
Go
232 lines
5.3 KiB
Go
package gel
|
|
|
|
import (
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/p9c/p9/pkg/opts/binary"
|
|
"github.com/p9c/p9/pkg/opts/meta"
|
|
|
|
"github.com/p9c/p9/pkg/gel/clipboard"
|
|
"github.com/p9c/p9/pkg/gel/fonts/p9fonts"
|
|
|
|
"github.com/p9c/p9/pkg/qu"
|
|
|
|
uberatomic "go.uber.org/atomic"
|
|
|
|
"github.com/p9c/p9/pkg/gel/gio/app"
|
|
"github.com/p9c/p9/pkg/gel/gio/io/event"
|
|
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
|
"github.com/p9c/p9/pkg/gel/gio/op"
|
|
"github.com/p9c/p9/pkg/gel/gio/unit"
|
|
)
|
|
|
|
type CallbackQueue chan func() error
|
|
|
|
func NewCallbackQueue(bufSize int) CallbackQueue {
|
|
return make(CallbackQueue, bufSize)
|
|
}
|
|
|
|
type scaledConfig struct {
|
|
Scale float32
|
|
}
|
|
|
|
func (s *scaledConfig) Now() time.Time {
|
|
return time.Now()
|
|
}
|
|
|
|
// Window is the main container for a gel application. It embeds Theme for styling
|
|
// and app.Window for platform integration. Window provides factory methods for
|
|
// creating widgets and handles the main event loop, clipboard operations, and
|
|
// overlay rendering.
|
|
type Window struct {
|
|
*Theme
|
|
*app.Window
|
|
opts []app.Option
|
|
scale *scaledConfig
|
|
Width *uberatomic.Int32 // stores the width at the beginning of render
|
|
Height *uberatomic.Int32
|
|
ops op.Ops
|
|
Runner CallbackQueue
|
|
overlay []*func(gtx l.Context)
|
|
ClipboardWriteReqs chan string
|
|
ClipboardReadReqs chan func(string)
|
|
ClipboardContent string
|
|
clipboardReadReady qu.C
|
|
clipboardReadResponse chan string
|
|
}
|
|
|
|
func (w *Window) PushOverlay(overlay *func(gtx l.Context)) {
|
|
w.overlay = append(w.overlay, overlay)
|
|
}
|
|
|
|
func (w *Window) PopOverlay(overlay *func(gtx l.Context)) {
|
|
if len(w.overlay) == 0 {
|
|
return
|
|
}
|
|
index := -1
|
|
for i := range w.overlay {
|
|
if overlay == w.overlay[i] {
|
|
index = i
|
|
break
|
|
}
|
|
}
|
|
if index != -1 {
|
|
if index == len(w.overlay)-1 {
|
|
w.overlay = w.overlay[:index]
|
|
} else if index == 0 {
|
|
w.overlay = w.overlay[1:]
|
|
} else {
|
|
w.overlay = append(w.overlay[:index], w.overlay[index+1:]...)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (w *Window) Overlay(gtx l.Context) {
|
|
for _, overlay := range w.overlay {
|
|
(*overlay)(gtx)
|
|
}
|
|
}
|
|
|
|
// NewWindowP9 creates a new window
|
|
func NewWindowP9(quit chan struct{}) (out *Window) {
|
|
out = &Window{
|
|
scale: &scaledConfig{1},
|
|
Runner: NewCallbackQueue(32),
|
|
Width: uberatomic.NewInt32(0),
|
|
Height: uberatomic.NewInt32(0),
|
|
ClipboardWriteReqs: make(chan string, 1),
|
|
ClipboardReadReqs: make(chan func(string), 32),
|
|
clipboardReadReady: qu.Ts(1),
|
|
clipboardReadResponse: make(chan string,1),
|
|
}
|
|
out.Theme = NewTheme(
|
|
binary.New(meta.Data{}, false, nil),
|
|
p9fonts.Collection(), quit,
|
|
)
|
|
out.Theme.WidgetPool = out.NewPool()
|
|
clipboard.Start()
|
|
return
|
|
}
|
|
|
|
// NewWindow creates a new window
|
|
func NewWindow(th *Theme) (out *Window) {
|
|
out = &Window{
|
|
Theme: th,
|
|
scale: &scaledConfig{1},
|
|
}
|
|
return
|
|
}
|
|
|
|
// Title sets the title of the window
|
|
func (w *Window) Title(title string) (out *Window) {
|
|
w.opts = append(w.opts, app.Title(title))
|
|
return w
|
|
}
|
|
|
|
// Size sets the dimensions of the window
|
|
func (w *Window) Size(width, height float32) (out *Window) {
|
|
w.opts = append(
|
|
w.opts,
|
|
app.Size(unit.Dp(float32(w.TextSize)*width), unit.Dp(float32(w.TextSize)*height)),
|
|
)
|
|
return w
|
|
}
|
|
|
|
// Scale sets the scale factor for rendering
|
|
func (w *Window) Scale(s float32) *Window {
|
|
w.scale = &scaledConfig{s}
|
|
return w
|
|
}
|
|
|
|
// Open sets the window options and initialise the node.window
|
|
func (w *Window) Open() (out *Window) {
|
|
if w.scale == nil {
|
|
w.Scale(1)
|
|
}
|
|
if w.opts != nil {
|
|
w.Window = new(app.Window)
|
|
w.Window.Option(w.opts...)
|
|
w.opts = nil
|
|
}
|
|
|
|
return w
|
|
}
|
|
|
|
func (w *Window) Run(frame func(ctx l.Context) l.Dimensions, destroy func(), quit qu.C,) (e error) {
|
|
// Start background tasks goroutine
|
|
go func() {
|
|
ticker := time.NewTicker(time.Second)
|
|
for {
|
|
select {
|
|
case content := <-w.ClipboardWriteReqs:
|
|
// Clipboard write is handled through commands in frame events
|
|
w.ClipboardContent = content
|
|
w.Invalidate()
|
|
case fn := <-w.ClipboardReadReqs:
|
|
go func() {
|
|
// Request clipboard read - response comes via clipboard event
|
|
w.Invalidate()
|
|
fn(<-w.clipboardReadResponse)
|
|
}()
|
|
case <-ticker.C:
|
|
w.TextSize = GetScaledTextSize(unit.Sp(16))
|
|
w.Invalidate()
|
|
case fn := <-w.Runner:
|
|
if e := fn(); E.Chk(e) {
|
|
return
|
|
}
|
|
case <-quit.Wait():
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Main event loop using blocking Event() method
|
|
runner := func() {
|
|
for {
|
|
ev := w.Window.Event()
|
|
if e = w.processEvents(ev, frame, destroy); E.Chk(e) {
|
|
return
|
|
}
|
|
// Check for quit signal
|
|
select {
|
|
case <-quit.Wait():
|
|
return
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
switch runtime.GOOS {
|
|
case "ios", "android":
|
|
go runner()
|
|
default:
|
|
runner()
|
|
}
|
|
return
|
|
}
|
|
|
|
// WriteClipboard writes content to the system clipboard
|
|
func (w *Window) WriteClipboard(content string) {
|
|
w.ClipboardWriteReqs <- content
|
|
}
|
|
|
|
func (w *Window) processEvents(e event.Event, frame func(ctx l.Context) l.Dimensions, destroy func()) error {
|
|
switch ev := e.(type) {
|
|
case app.DestroyEvent:
|
|
D.Ln("received destroy event", ev.Err)
|
|
destroy()
|
|
return ev.Err
|
|
case app.FrameEvent:
|
|
ops := op.Ops{}
|
|
c := app.NewContext(&ops, ev)
|
|
// update dimensions for responsive sizing widgets
|
|
w.Width.Store(int32(c.Constraints.Max.X))
|
|
w.Height.Store(int32(c.Constraints.Max.Y))
|
|
frame(c)
|
|
w.Overlay(c)
|
|
ev.Frame(c.Ops)
|
|
}
|
|
return nil
|
|
}
|