refactor gel for gio v0.9
This commit is contained in:
@@ -309,7 +309,7 @@ func (wg *WalletGUI) SideBarButton(title, page string, index int) func(gtx l.Con
|
||||
if title == " " {
|
||||
scale = gel.Scales["H6"] / 2
|
||||
}
|
||||
max := int(wg.MainApp.SideBarSize.V)
|
||||
max := int(wg.MainApp.SideBarSize)
|
||||
if max > 0 {
|
||||
gtx.Constraints.Max.X = max
|
||||
gtx.Constraints.Min.X = max
|
||||
|
||||
@@ -266,7 +266,7 @@ func (gm GroupsMap) Widget(ng *Config) l.Widget {
|
||||
out = append(
|
||||
out,
|
||||
ng.Fill(
|
||||
"Primary", l.Center, ng.TextSize.V*2, 0, ng.Flex().Flexed(
|
||||
"Primary", l.Center, float32(ng.TextSize)*2, 0, ng.Flex().Flexed(
|
||||
1,
|
||||
ng.Inset(
|
||||
0.75,
|
||||
@@ -296,7 +296,7 @@ func (gm GroupsMap) Widget(ng *Config) l.Widget {
|
||||
out, func(gtx l.Context) l.Dimensions {
|
||||
if k < len(gi.widget()) {
|
||||
return ng.Fill(
|
||||
"DocBg", l.Center, ng.TextSize.V, 0, ng.Flex().
|
||||
"DocBg", l.Center, float32(ng.TextSize), 0, ng.Flex().
|
||||
// Rigid(
|
||||
// ng.Inset(0.25, gel.EmptySpace(0, 0)).Fn,
|
||||
// ).
|
||||
@@ -322,7 +322,7 @@ func (gm GroupsMap) Widget(ng *Config) l.Widget {
|
||||
// Max: f32.Pt(float32(gtx.Constraints.Max.X), float32(gtx.Constraints.Max.Y)),
|
||||
// }, ng.TextSize.True/2).Add(gtx.Ops)
|
||||
return ng.Fill(
|
||||
"DocBg", l.Center, ng.TextSize.V, 0, ng.Inset(
|
||||
"DocBg", l.Center, float32(ng.TextSize), 0, ng.Inset(
|
||||
0.25,
|
||||
ng.lists["settings"].
|
||||
Vertical().
|
||||
@@ -566,7 +566,7 @@ func (c *Config) RenderRadio(item *Item) []l.Widget {
|
||||
c.Flex().
|
||||
Rigid(
|
||||
func(gtx l.Context) l.Dimensions {
|
||||
gtx.Constraints.Max.X = int(c.Theme.TextSize.Scale(10).V)
|
||||
gtx.Constraints.Max.X = int(float32(c.Theme.TextSize) * 10)
|
||||
return c.lists[item.slug].DisableScroll(true).Slice(gtx, options...)(gtx)
|
||||
// // return c.lists[item.slug].Length(len(options)).Vertical().ListElement(func(gtx l.Context, index int) l.Dimensions {
|
||||
// // return options[index](gtx)
|
||||
|
||||
@@ -275,7 +275,7 @@ func (c *Console) Fn(gtx l.Context) l.Dimensions {
|
||||
Flexed(
|
||||
0.1,
|
||||
c.Fill(
|
||||
"PanelBg", l.Center, c.TextSize.V, 0, func(gtx l.Context) l.Dimensions {
|
||||
"PanelBg", l.Center, float32(c.TextSize), 0, func(gtx l.Context) l.Dimensions {
|
||||
return c.Inset(
|
||||
0.25,
|
||||
c.outputList.
|
||||
@@ -295,7 +295,7 @@ func (c *Console) Fn(gtx l.Context) l.Dimensions {
|
||||
).
|
||||
Rigid(
|
||||
c.Fill(
|
||||
"DocBg", l.Center, c.TextSize.V, 0, c.Inset(
|
||||
"DocBg", l.Center, float32(c.TextSize), 0, c.Inset(
|
||||
0.25,
|
||||
c.Theme.Flex().
|
||||
Flexed(
|
||||
|
||||
@@ -18,7 +18,7 @@ func (wg *WalletGUI) HelpPage() func(gtx l.Context) l.Dimensions {
|
||||
).
|
||||
Rigid(
|
||||
wg.Fill(
|
||||
"DocBg", l.Center, wg.TextSize.V, 0, wg.Inset(
|
||||
"DocBg", l.Center, float32(wg.TextSize), 0, wg.Inset(
|
||||
0.5,
|
||||
wg.VFlex().
|
||||
AlignMiddle().
|
||||
|
||||
@@ -20,9 +20,9 @@ func (wg *WalletGUI) balanceCard() func(gtx l.Context) l.Dimensions {
|
||||
// gtx.Constraints.Min.X = int(wg.TextSize.True * sp.inputWidth)
|
||||
return func(gtx l.Context) l.Dimensions {
|
||||
gtx.Constraints.Min.X =
|
||||
int(wg.TextSize.V * 16)
|
||||
int(float32(wg.TextSize) * 16)
|
||||
gtx.Constraints.Max.X =
|
||||
int(wg.TextSize.V * 16)
|
||||
int(float32(wg.TextSize) * 16)
|
||||
return wg.VFlex().
|
||||
AlignStart().
|
||||
Rigid(
|
||||
@@ -572,7 +572,7 @@ func (wg *WalletGUI) recentTxCardDetail(txs *btcjson.ListTransactionsResult, cli
|
||||
return wg.VFlex().
|
||||
Rigid(
|
||||
wg.Fill(
|
||||
"Primary", l.Center, wg.TextSize.V, 0,
|
||||
"Primary", l.Center, float32(wg.TextSize), 0,
|
||||
wg.recentTxCardSummaryButton(txs, clickable, "Primary", false),
|
||||
).Fn,
|
||||
// ).
|
||||
@@ -655,7 +655,7 @@ func (wg *WalletGUI) txDetailEntry(name, detail string, bgColor string, small bo
|
||||
content = wg.Caption
|
||||
}
|
||||
return wg.Fill(
|
||||
bgColor, l.Center, wg.TextSize.V, 0,
|
||||
bgColor, l.Center, float32(wg.TextSize), 0,
|
||||
wg.Flex().AlignBaseline().
|
||||
Flexed(
|
||||
0.25,
|
||||
|
||||
@@ -101,8 +101,8 @@ func (rp *ReceivePage) MediumList(gtx l.Context) l.Dimensions {
|
||||
return wg.Flex().AlignStart().
|
||||
Rigid(
|
||||
func(gtx l.Context) l.Dimensions {
|
||||
gtx.Constraints.Max.X, gtx.Constraints.Min.X = int(wg.TextSize.V*rp.inputWidth),
|
||||
int(wg.TextSize.V*rp.inputWidth)
|
||||
gtx.Constraints.Max.X, gtx.Constraints.Min.X = int(float32(wg.TextSize)*rp.inputWidth),
|
||||
int(float32(wg.TextSize)*rp.inputWidth)
|
||||
return wg.VFlex().
|
||||
Rigid(
|
||||
wg.lists["receiveMedium"].
|
||||
@@ -275,9 +275,8 @@ func (rp *ReceivePage) MessageInput() l.Widget {
|
||||
func (rp *ReceivePage) RegenerateButton() l.Widget {
|
||||
return func(gtx l.Context) l.Dimensions {
|
||||
wg := rp.wg
|
||||
if wg.inputs["receiveAmount"].GetText() == "" || wg.inputs["receiveMessage"].GetText() == "" {
|
||||
gtx.Queue = nil
|
||||
}
|
||||
// Disable input when fields are empty - in new GIO, gtx.Source handles this
|
||||
// Fields validation should be done in the click handler instead
|
||||
// gtx.Constraints.Max.X, gtx.Constraints.Min.X = int(wg.TextSize.True*rp.inputWidth), int(wg.TextSize.True*rp.inputWidth)
|
||||
return wg.ButtonLayout(
|
||||
wg.currentReceiveRegenClickable.
|
||||
|
||||
@@ -135,7 +135,7 @@ func (sp *SendPage) MediumList(gtx l.Context) l.Dimensions {
|
||||
Rigid(
|
||||
func(gtx l.Context) l.Dimensions {
|
||||
gtx.Constraints.Max.X =
|
||||
int(wg.TextSize.V * sp.inputWidth)
|
||||
int(float32(wg.TextSize) * sp.inputWidth)
|
||||
// gtx.Constraints.Min.X = int(wg.TextSize.True * sp.inputWidth)
|
||||
|
||||
return wg.VFlex().AlignStart().
|
||||
@@ -187,10 +187,7 @@ func (sp *SendPage) MessageInput() l.Widget {
|
||||
func (sp *SendPage) SendButton() l.Widget {
|
||||
return func(gtx l.Context) l.Dimensions {
|
||||
wg := sp.wg
|
||||
if wg.inputs["sendAmount"].GetText() == "" || wg.inputs["sendMessage"].GetText() == "" ||
|
||||
wg.inputs["sendAddress"].GetText() == "" {
|
||||
gtx.Queue = nil
|
||||
}
|
||||
// Disable input when fields are empty - validation done in click handler
|
||||
return wg.ButtonLayout(
|
||||
wg.clickables["sendSend"].
|
||||
SetClick(
|
||||
@@ -302,10 +299,7 @@ func (sp *SendPage) saveForm(txid string) {
|
||||
func (sp *SendPage) SaveButton() l.Widget {
|
||||
return func(gtx l.Context) l.Dimensions {
|
||||
wg := sp.wg
|
||||
if wg.inputs["sendAmount"].GetText() == "" || wg.inputs["sendMessage"].GetText() == "" ||
|
||||
wg.inputs["sendAddress"].GetText() == "" {
|
||||
gtx.Queue = nil
|
||||
}
|
||||
// Disable input when fields are empty - validation done in click handler
|
||||
return wg.ButtonLayout(
|
||||
wg.clickables["sendSave"].
|
||||
SetClick(
|
||||
|
||||
@@ -144,7 +144,7 @@ func (wg *WalletGUI) getWalletUnlockAppWidget() (a *gel.App) {
|
||||
AlignMiddle().
|
||||
Rigid(
|
||||
wg.Fill(
|
||||
"DocBg", l.Center, wg.TextSize.V, 0,
|
||||
"DocBg", l.Center, float32(wg.TextSize), 0,
|
||||
wg.Inset(
|
||||
0.5,
|
||||
wg.Flex().
|
||||
@@ -160,7 +160,7 @@ func (wg *WalletGUI) getWalletUnlockAppWidget() (a *gel.App) {
|
||||
wg.Fill(
|
||||
"Fatal",
|
||||
l.Center,
|
||||
wg.TextSize.V/2,
|
||||
float32(wg.TextSize)/2,
|
||||
0,
|
||||
wg.Inset(
|
||||
0.5,
|
||||
|
||||
@@ -158,7 +158,7 @@ func (w *Widget) SideBarButton(title, page string, index int) func(gtx l.Context
|
||||
if title == " " {
|
||||
scale = gel.Scales["H6"] / 2
|
||||
}
|
||||
max := int(w.App.SideBarSize.V)
|
||||
max := int(w.App.SideBarSize)
|
||||
if max > 0 {
|
||||
gtx.Constraints.Max.X = max
|
||||
gtx.Constraints.Min.X = max
|
||||
|
||||
43
go.mod
43
go.mod
@@ -1,24 +1,22 @@
|
||||
module github.com/p9c/p9
|
||||
|
||||
go 1.16
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d
|
||||
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc
|
||||
github.com/VividCortex/ewma v1.2.0
|
||||
github.com/aead/siphash v1.0.1
|
||||
github.com/akavel/rsrc v0.10.2
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd
|
||||
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8
|
||||
github.com/btcsuite/goleveldb v1.0.0
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792
|
||||
github.com/chromedp/cdproto v0.0.0-20210429002609-5ec2b0624aec
|
||||
github.com/chromedp/chromedp v0.7.1
|
||||
github.com/conformal/fastsha256 v0.0.0-20160815193821-637e65642941
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815
|
||||
github.com/go-text/typesetting v0.3.0
|
||||
github.com/gookit/color v1.4.2
|
||||
github.com/hpcloud/tail v1.0.0 // indirect
|
||||
github.com/jackpal/gateway v1.0.7
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
github.com/kkdai/bstream v1.0.0
|
||||
@@ -30,16 +28,31 @@ require (
|
||||
github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3
|
||||
go.etcd.io/bbolt v1.3.5
|
||||
go.uber.org/atomic v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b
|
||||
golang.org/x/exp v0.0.0-20210503015746-b3083d562e1d
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210503073744-b6777538623b
|
||||
golang.org/x/text v0.3.6
|
||||
golang.org/x/tools v0.1.0
|
||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0
|
||||
golang.org/x/image v0.26.0
|
||||
golang.org/x/net v0.39.0
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/text v0.24.0
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
lukechampine.com/blake3 v1.1.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/btcsuite/snappy-go v1.0.0 // indirect
|
||||
github.com/emirpasic/gods v1.12.0 // indirect
|
||||
github.com/hpcloud/tail v1.0.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect
|
||||
github.com/klauspost/cpuid v1.3.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/sergi/go-diff v1.0.0 // indirect
|
||||
github.com/src-d/gcfg v1.4.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.2.1 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
129
go.sum
129
go.sum
@@ -1,14 +1,11 @@
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
||||
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
||||
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr2F7h1sriovOZ8BMhca2Rg85c2nk=
|
||||
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||
github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=
|
||||
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
@@ -27,15 +24,8 @@ github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJ
|
||||
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/chromedp/cdproto v0.0.0-20210429002609-5ec2b0624aec h1:dbdRUO+gH+jrbL7Q2ZFhhBe4I5JWXOXk5bBcGdWi2ts=
|
||||
github.com/chromedp/cdproto v0.0.0-20210429002609-5ec2b0624aec/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U=
|
||||
github.com/chromedp/chromedp v0.7.1 h1:OWS/1a82SDXccBBnKXtrud/adgQvaQYArCEwEs5l6Ws=
|
||||
github.com/chromedp/chromedp v0.7.1/go.mod h1:OOJJ9XkdOAphY+9ptamjez84TxBLBkAMVp9mZ/mkOfk=
|
||||
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||
github.com/conformal/fastsha256 v0.0.0-20160815193821-637e65642941 h1:rOVcN552l7af5e6si8Wdd574TTEaBP6xqHiF7T1ZWsU=
|
||||
github.com/conformal/fastsha256 v0.0.0-20160815193821-637e65642941/go.mod h1:L/DvjsI5Fhg+SLf++bxzYa06pZd1fwtOEm7CSFSmtjo=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -47,17 +37,13 @@ github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815/go.mod h1:wYFFK4LYXb
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.1.0-rc.5 h1:QOAag7FoBaBYYHRqzqkhhd8fq5RTubvI4v3Ft/gDVVQ=
|
||||
github.com/gobwas/ws v1.1.0-rc.5/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
|
||||
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
|
||||
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk=
|
||||
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
@@ -67,13 +53,10 @@ github.com/jackpal/gateway v1.0.7/go.mod h1:aRcO0UFKt+MgIZmRmvOmnejdDT4Y1DNiNOsS
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8=
|
||||
github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA=
|
||||
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
|
||||
@@ -84,8 +67,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/marusama/semaphore v0.0.0-20190110074507-6952cef993b2 h1:sq+a5mb8zHbmHhrIH06oqIMGsanjpbxNgxEgZVfgpvQ=
|
||||
github.com/marusama/semaphore v0.0.0-20190110074507-6952cef993b2/go.mod h1:TmeOqAKoDinfPfSohs14CO3VcEf7o+Bem6JiNe05yrQ=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
@@ -99,26 +80,19 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
||||
github.com/onsi/gomega v1.4.1 h1:PZSj/UFNaVp3KxrzHOcS7oyuWA7LoOY/77yCTEFu21U=
|
||||
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/programmer10110/gostreebog v0.0.0-20170704145444-a3e1d28291b2 h1:gb6u48DzkRwDpNtqaQ+SQYNJ8G3epwf9uJHxtKXKHec=
|
||||
github.com/programmer10110/gostreebog v0.0.0-20170704145444-a3e1d28291b2/go.mod h1:zSCZczSNxET3dzUjgsrViwmMCj8MRUw0bpEL+k7+IPE=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
|
||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tstranex/gozmq v0.0.0-20160831212417-0daa84a596ba h1:Xy5Ak7HEazhrOLJo3iQa8hG5REvye7hUkVvKyMASSZA=
|
||||
@@ -131,97 +105,49 @@ github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70
|
||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
golang.org/x/exp v0.0.0-20210503015746-b3083d562e1d h1:GVdk3fETn7OIrVsO9oikNOn5qWu8EcgkTtcDD9IGyQ4=
|
||||
golang.org/x/exp v0.0.0-20210503015746-b3083d562e1d/go.mod h1:MSdmUWF4ZWBPSUbgUX/gaau5kvnbkSs9pgtY6B9JXDE=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
|
||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 h1:a8jGStKg0XqKDlKqjLrXn0ioF5MH36pT7Z0BRTqLhbk=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210503073744-b6777538623b h1:JWC69xVWHab8bjJYC2FN1ueBMws7//XKzN4wy7XQoso=
|
||||
golang.org/x/sys v0.0.0-20210503073744-b6777538623b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
|
||||
@@ -234,12 +160,9 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
|
||||
lukechampine.com/blake3 v1.1.5 h1:hsACfxWvLdGmjYbWGrumQIphOvO+ZruZehWtgd2fxoM=
|
||||
lukechampine.com/blake3 v1.1.5/go.mod h1:hE8RpzdO8ttZ7446CXEwDP1eu2V4z7stv0Urj1El20g=
|
||||
|
||||
BIN
iconchooser
Executable file
BIN
iconchooser
Executable file
Binary file not shown.
427
pkg/gel/REFACTORING_PLAN.md
Normal file
427
pkg/gel/REFACTORING_PLAN.md
Normal file
@@ -0,0 +1,427 @@
|
||||
# GIO Refactoring Plan for pkg/gel
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The `pkg/gel` widget toolkit contains a vendored copy of the GIO library that is significantly outdated. This document outlines the breaking changes between the old vendored version and the latest GIO release, and provides a systematic plan for refactoring the gel toolkit to use the current GIO API.
|
||||
|
||||
## Breaking Changes Analysis
|
||||
|
||||
### 1. Unit Package (`unit`)
|
||||
|
||||
**Critical Change: Value type removed, replaced with typed units**
|
||||
|
||||
| Old API | New API |
|
||||
|---------|---------|
|
||||
| `unit.Value{V: float32, U: Unit}` | `unit.Dp` / `unit.Sp` (typed floats) |
|
||||
| `unit.Dp(v float32) Value` | `unit.Dp(v)` is now just `unit.Dp` type |
|
||||
| `unit.Sp(v float32) Value` | `unit.Sp(v)` is now just `unit.Sp` type |
|
||||
| `unit.Px(v float32) Value` | Removed - use raw int for pixels |
|
||||
| `metric.Px(unit.Value) int` | `metric.Dp(unit.Dp) int` / `metric.Sp(unit.Sp) int` |
|
||||
| `context.Px(unit.Value) int` | `context.Dp(unit.Dp) int` / `context.Sp(unit.Sp) int` |
|
||||
| `value.Scale(s float32) Value` | Manual multiplication: `unit.Dp(float32(dp) * s)` |
|
||||
|
||||
**Impact**: All widget sizing code using `unit.Value` must be refactored. This affects:
|
||||
- `pkg/gel/window.go`: `scaledConfig.Px()` method
|
||||
- `pkg/gel/button.go`: All sizing/inset code
|
||||
- `pkg/gel/label.go`: Text sizing
|
||||
- `pkg/gel/inset.go`: All inset dimensions
|
||||
- `pkg/gel/theme.go`: TextSize field
|
||||
- All widget files using dimensions
|
||||
|
||||
### 2. Layout Package (`layout`)
|
||||
|
||||
**Context changes:**
|
||||
|
||||
| Old API | New API |
|
||||
|---------|---------|
|
||||
| `Context.Queue event.Queue` | `Context.Source input.Source` (embedded) |
|
||||
| `Context.Events(tag) []Event` | `source.Event(filters...) (Event, bool)` |
|
||||
| `NewContext(ops, FrameEvent)` | Removed - construct Context directly |
|
||||
| `Inset{Top, Right, Bottom, Left unit.Value}` | `Inset{Top, Bottom, Left, Right unit.Dp}` |
|
||||
| `Spacer{Width, Height unit.Value}` | `Spacer{Width, Height unit.Dp}` |
|
||||
| `FRect(r image.Rectangle) f32.Rectangle` | Removed |
|
||||
|
||||
**Impact**:
|
||||
- `pkg/gel/window.go`: `l.NewContext()` usage
|
||||
- All widgets checking events via context
|
||||
|
||||
### 3. Op Package (`op`)
|
||||
|
||||
**Critical Change: State management overhauled**
|
||||
|
||||
| Old API | New API |
|
||||
|---------|---------|
|
||||
| `op.Save(ops) StateOp` | `op.Offset(pt).Push(ops) TransformStack` |
|
||||
| `stateOp.Load()` | `transformStack.Pop()` |
|
||||
| `op.Offset(f32.Point) TransformOp` | `op.Offset(image.Point) TransformOp` |
|
||||
| `transformOp.Add(ops)` | `transformOp.Push(ops)` for stack-based |
|
||||
| `op.InvalidateOp{}.Add(ops)` | `op.InvalidateCmd{}` (via Source.Execute) |
|
||||
| `ops.Write(n int) []byte` | Internal only via `ops.Write(&o.Internal, ...)` |
|
||||
| `ops.Write1/Write2` | Removed from public API |
|
||||
| `internal/opconst` | `internal/ops` |
|
||||
|
||||
**Impact**:
|
||||
- `pkg/gel/button.go`: `drawInk()` function uses `op.Save/Load`
|
||||
- All clipping/transform code
|
||||
- Every widget that manages transform state
|
||||
|
||||
### 4. App/Window Package (`app`)
|
||||
|
||||
**Critical Change: Event loop redesigned**
|
||||
|
||||
| Old API | New API |
|
||||
|---------|---------|
|
||||
| `app.NewWindow(options...) *Window` | `Window{}` zero value, lazy init |
|
||||
| `window.Events() <-chan event.Event` | `window.Event() event.Event` (blocking) |
|
||||
| `system.FrameEvent.Frame(ops)` | Still exists but context construction changed |
|
||||
| `system.StageEvent` | Removed |
|
||||
| `system.FrameEvent.Queue event.Queue` | Embedded in Context as Source |
|
||||
| `Option func(*wm.Options)` | `Option func(unit.Metric, *Config)` |
|
||||
| `app.Size(w, h unit.Value)` | `app.Size(w, h unit.Dp)` |
|
||||
|
||||
**Impact**:
|
||||
- `pkg/gel/window.go`: Complete rewrite of event loop
|
||||
- `pkg/gel/window.go`: `Window.Events()` channel-based pattern
|
||||
|
||||
### 5. IO/Event Package (`io/event`)
|
||||
|
||||
**Critical Change: Filter-based event system**
|
||||
|
||||
| Old API | New API |
|
||||
|---------|---------|
|
||||
| `event.Queue.Events(tag) []Event` | `source.Event(filters...) (Event, bool)` |
|
||||
| No Filter concept | `event.Filter` interface |
|
||||
| `event.Tag interface{}` | `event.Tag any` |
|
||||
|
||||
**Impact**: All event handling code needs filter-based pattern
|
||||
|
||||
### 6. IO/Router Package (`io/router`)
|
||||
|
||||
**Critical Change: Renamed and redesigned**
|
||||
|
||||
| Old API | New API |
|
||||
|---------|---------|
|
||||
| `io/router` package | `io/input` package |
|
||||
| `router.Router` | `input.Router` |
|
||||
| `router.TextInputState` | `input.TextInputState` |
|
||||
| `Queue(events)` returns bool | `Queue(events...)` void |
|
||||
|
||||
### 7. Gesture Package (`gesture`)
|
||||
|
||||
**Changes to click/scroll handling:**
|
||||
|
||||
| Old API | New API |
|
||||
|---------|---------|
|
||||
| `gesture.Click.Events(queue)` | `click.Update(source) ([]gesture.ClickEvent, bool)` |
|
||||
| `gesture.Scroll.Events(...)` | Filter-based via Source |
|
||||
|
||||
### 8. Clip Package (`op/clip`)
|
||||
|
||||
**Float to int conversion:**
|
||||
|
||||
| Old API | New API |
|
||||
|---------|---------|
|
||||
| `clip.RRect{Rect: f32.Rectangle, ...}` | `clip.RRect{Rect: image.Rectangle, ...}` |
|
||||
| `clip.Rect(f32.Rectangle)` | `clip.Rect(image.Rectangle)` |
|
||||
| Uses `f32.Point` for corners | Uses `int` for corner radii |
|
||||
|
||||
**Impact**: All clipping code in widgets
|
||||
|
||||
### 9. Pointer Package (`io/pointer`)
|
||||
|
||||
**Cursor type change:**
|
||||
|
||||
| Old API | New API |
|
||||
|---------|---------|
|
||||
| `pointer.CursorName string` | `pointer.Cursor` typed constant |
|
||||
|
||||
---
|
||||
|
||||
## Refactoring Plan
|
||||
|
||||
### Phase 1: Update Vendored GIO (Preparation)
|
||||
|
||||
1. **Backup existing code**
|
||||
- Create a backup branch of current state
|
||||
- Document any local modifications to vendored GIO
|
||||
|
||||
2. **Replace vendored GIO**
|
||||
- Remove `pkg/gel/gio/` entirely
|
||||
- Clone latest GIO into `pkg/gel/gio/`
|
||||
- Update import paths if module path differs
|
||||
|
||||
3. **Update go.mod**
|
||||
- Ensure Go version compatibility (1.21+ recommended)
|
||||
- Add any new dependencies GIO requires
|
||||
|
||||
### Phase 2: Core Type Adaptations
|
||||
|
||||
1. **Unit system migration** (`pkg/gel/`)
|
||||
- Create compatibility layer if needed:
|
||||
```go
|
||||
// Temporary bridge during migration
|
||||
func DpScale(base unit.Dp, scale float32) unit.Dp {
|
||||
return unit.Dp(float32(base) * scale)
|
||||
}
|
||||
```
|
||||
- Update `Theme.TextSize` from `unit.Value` to `unit.Sp`
|
||||
- Update all `unit.Value` usages to typed `unit.Dp`/`unit.Sp`
|
||||
|
||||
**Files to modify:**
|
||||
- `theme.go`
|
||||
- `window.go`
|
||||
- `button.go`
|
||||
- `label.go`
|
||||
- `inset.go`
|
||||
- `flex.go`
|
||||
- `list.go`
|
||||
- `editor.go`
|
||||
- `slider.go`
|
||||
- And all other widget files
|
||||
|
||||
2. **Float32 to image.Point conversions**
|
||||
- Update all `f32.Point` → `image.Point` where required
|
||||
- Update all `f32.Rectangle` → `image.Rectangle` in clips
|
||||
|
||||
**Files to modify:**
|
||||
- `button.go` (drawInk function)
|
||||
- `clickable.go`
|
||||
- All clipping operations
|
||||
|
||||
### Phase 3: Operation System Refactoring
|
||||
|
||||
1. **State save/load pattern**
|
||||
- Replace `op.Save(ops).Load()` pattern:
|
||||
```go
|
||||
// Old
|
||||
defer op.Save(c.Ops).Load()
|
||||
|
||||
// New
|
||||
defer op.Offset(image.Point{}).Push(c.Ops).Pop()
|
||||
// Or for transforms:
|
||||
stack := op.Offset(offset).Push(gtx.Ops)
|
||||
defer stack.Pop()
|
||||
```
|
||||
|
||||
2. **Transform operations**
|
||||
- Update `op.Offset(f32.Point)` → `op.Offset(image.Point)`
|
||||
- Use `.Push()` instead of `.Add()` for stack-based transforms
|
||||
|
||||
3. **InvalidateOp**
|
||||
- Replace `op.InvalidateOp{}.Add(ops)` with command pattern:
|
||||
```go
|
||||
gtx.Execute(op.InvalidateCmd{At: time.Time{}})
|
||||
```
|
||||
|
||||
### Phase 4: Event System Refactoring
|
||||
|
||||
1. **Context event access**
|
||||
- Replace `gtx.Events(tag)` with filter-based pattern:
|
||||
```go
|
||||
// Old
|
||||
for _, e := range gtx.Events(tag) { ... }
|
||||
|
||||
// New
|
||||
for {
|
||||
e, ok := gtx.Source.Event(pointer.Filter{Target: tag, Kinds: pointer.Press|pointer.Release})
|
||||
if !ok { break }
|
||||
// handle e
|
||||
}
|
||||
```
|
||||
|
||||
2. **Update gesture handling**
|
||||
- Refactor `Clickable` widget
|
||||
- Refactor `List` scroll handling
|
||||
- Refactor `Editor` input handling
|
||||
- Refactor `Slider` drag handling
|
||||
|
||||
### Phase 5: Window/App Refactoring
|
||||
|
||||
1. **Event loop redesign** (`pkg/gel/window.go`)
|
||||
```go
|
||||
// Old pattern
|
||||
for ev := range w.Window.Events() {
|
||||
switch e := ev.(type) { ... }
|
||||
}
|
||||
|
||||
// New pattern
|
||||
for {
|
||||
switch e := w.Window.Event().(type) {
|
||||
case app.DestroyEvent:
|
||||
return e.Err
|
||||
case app.FrameEvent:
|
||||
gtx := app.NewContext(&ops, e)
|
||||
// ... layout ...
|
||||
e.Frame(gtx.Ops)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Remove channel-based event reading**
|
||||
- The new GIO uses blocking `Event()` not channel
|
||||
|
||||
3. **Update window options**
|
||||
- `app.Size(unit.Value, unit.Value)` → `app.Size(unit.Dp, unit.Dp)`
|
||||
|
||||
### Phase 6: Widget-by-Widget Updates
|
||||
|
||||
Priority order (dependencies first):
|
||||
|
||||
1. **Foundation widgets:**
|
||||
- `theme.go` - Core theming
|
||||
- `colors.go` - Color system
|
||||
- `window.go` - Window management
|
||||
- `inset.go` - Inset layout
|
||||
|
||||
2. **Layout widgets:**
|
||||
- `flex.go` - Flex layout
|
||||
- `stack.go` - Stack layout
|
||||
- `list.go` - List widget
|
||||
|
||||
3. **Interactive widgets:**
|
||||
- `clickable.go` - Click handling
|
||||
- `button.go` - Buttons
|
||||
- `checkbox.go` - Checkboxes
|
||||
- `switch.go` - Toggle switches
|
||||
|
||||
4. **Text widgets:**
|
||||
- `label.go` - Labels
|
||||
- `editor.go` - Text editor
|
||||
- `input.go` - Text input
|
||||
- `password.go` - Password input
|
||||
|
||||
5. **Complex widgets:**
|
||||
- `slider.go` - Sliders
|
||||
- `table.go` - Tables
|
||||
- `app.go` - App framework
|
||||
|
||||
### Phase 7: Testing and Validation
|
||||
|
||||
1. **Create test harness**
|
||||
- Unit tests for each widget
|
||||
- Visual regression tests
|
||||
- Example applications from `pkg/gel/cmd/`
|
||||
|
||||
2. **Fix compilation errors**
|
||||
- Work through errors systematically
|
||||
- Document any behavioral changes
|
||||
|
||||
3. **Runtime testing**
|
||||
- Test on Linux, macOS, Windows
|
||||
- Test on mobile if applicable
|
||||
|
||||
---
|
||||
|
||||
## Detailed Code Migration Examples
|
||||
|
||||
### Example 1: Button sizing migration
|
||||
|
||||
```go
|
||||
// OLD
|
||||
cornerRadius: w.TextSize.Scale(0.25),
|
||||
inset: &l.Inset{
|
||||
Top: w.TextSize.Scale(0.5),
|
||||
Bottom: w.TextSize.Scale(0.5),
|
||||
Left: w.TextSize.Scale(0.5),
|
||||
Right: w.TextSize.Scale(0.5),
|
||||
},
|
||||
|
||||
// NEW
|
||||
cornerRadius: unit.Dp(float32(w.TextSize) * 0.25),
|
||||
inset: &l.Inset{
|
||||
Top: unit.Dp(float32(w.TextSize) * 0.5),
|
||||
Bottom: unit.Dp(float32(w.TextSize) * 0.5),
|
||||
Left: unit.Dp(float32(w.TextSize) * 0.5),
|
||||
Right: unit.Dp(float32(w.TextSize) * 0.5),
|
||||
},
|
||||
```
|
||||
|
||||
### Example 2: Transform state migration
|
||||
|
||||
```go
|
||||
// OLD (button.go drawInk)
|
||||
defer op.Save(c.Ops).Load()
|
||||
op.Offset(p.Position.Add(f32.Point{X: -rr, Y: -rr})).Add(c.Ops)
|
||||
|
||||
// NEW
|
||||
pos := image.Pt(int(p.Position.X-rr), int(p.Position.Y-rr))
|
||||
defer op.Offset(pos).Push(c.Ops).Pop()
|
||||
```
|
||||
|
||||
### Example 3: Clip migration
|
||||
|
||||
```go
|
||||
// OLD
|
||||
clip.RRect{
|
||||
Rect: f32.Rectangle{Max: f32.Point{X: size, Y: size}},
|
||||
NE: rr, NW: rr, SE: rr, SW: rr,
|
||||
}.Add(c.Ops)
|
||||
|
||||
// NEW
|
||||
clip.RRect{
|
||||
Rect: image.Rect(0, 0, int(size), int(size)),
|
||||
NE: int(rr), NW: int(rr), SE: int(rr), SW: int(rr),
|
||||
}.Push(c.Ops).Pop()
|
||||
```
|
||||
|
||||
### Example 4: Event handling migration
|
||||
|
||||
```go
|
||||
// OLD (clickable.go pattern)
|
||||
for _, e := range gtx.Events(c) {
|
||||
switch e := e.(type) {
|
||||
case pointer.Event:
|
||||
// handle
|
||||
}
|
||||
}
|
||||
|
||||
// NEW
|
||||
for {
|
||||
e, ok := gtx.Source.Event(
|
||||
pointer.Filter{Target: c, Kinds: pointer.Press | pointer.Release | pointer.Enter | pointer.Leave},
|
||||
)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
switch e := e.(type) {
|
||||
case pointer.Event:
|
||||
// handle
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
| Risk | Likelihood | Impact | Mitigation |
|
||||
|------|------------|--------|------------|
|
||||
| Subtle behavior changes | High | Medium | Comprehensive testing |
|
||||
| Missing API equivalents | Medium | High | Check GIO changelog/examples |
|
||||
| Performance regression | Low | Medium | Benchmark critical paths |
|
||||
| Platform-specific issues | Medium | Medium | Test on all target platforms |
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
| Phase | Effort Estimate |
|
||||
|-------|-----------------|
|
||||
| Phase 1: Preparation | 2-4 hours |
|
||||
| Phase 2: Core types | 4-8 hours |
|
||||
| Phase 3: Operations | 4-6 hours |
|
||||
| Phase 4: Events | 6-10 hours |
|
||||
| Phase 5: Window/App | 4-6 hours |
|
||||
| Phase 6: Widgets | 8-16 hours |
|
||||
| Phase 7: Testing | 8-12 hours |
|
||||
| **Total** | **36-62 hours** |
|
||||
|
||||
## Recommendations
|
||||
|
||||
1. **Consider a hybrid approach**: If timeline is tight, consider updating GIO incrementally while maintaining backward compatibility shims
|
||||
|
||||
2. **Check GIO examples**: The latest GIO repository has example code that demonstrates current patterns
|
||||
|
||||
3. **Join GIO community**: The GIO mailing list and IRC can help with migration questions
|
||||
|
||||
4. **Document local changes**: Any customizations to gel widgets should be documented to ensure they're preserved
|
||||
|
||||
5. **Incremental migration**: Consider migrating one widget at a time, testing each before moving to the next
|
||||
@@ -2,12 +2,11 @@ package gel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
|
||||
"go.uber.org/atomic"
|
||||
"golang.org/x/exp/shiny/materialdesign/icons"
|
||||
|
||||
|
||||
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/text"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
)
|
||||
|
||||
@@ -40,7 +39,7 @@ type App struct {
|
||||
sideBar []l.Widget
|
||||
sideBarBackground string
|
||||
sideBarColor string
|
||||
SideBarSize *unit.Value
|
||||
SideBarSize unit.Dp
|
||||
sideBarList *List
|
||||
Size *atomic.Int32
|
||||
statusBar []l.Widget
|
||||
@@ -93,7 +92,7 @@ func (w *Window) App(size *atomic.Int32, activePage *atomic.String, Break1 float
|
||||
LogoClickable: w.WidgetPool.GetClickable(),
|
||||
sideBarList: w.WidgetPool.GetList(),
|
||||
}
|
||||
a.SideBarSize = &unit.Value{}
|
||||
a.SideBarSize = unit.Dp(0)
|
||||
return a
|
||||
}
|
||||
|
||||
@@ -173,7 +172,7 @@ func (a *App) RenderHeader(gtx l.Context) l.Dimensions {
|
||||
).
|
||||
Flexed(
|
||||
1, If(
|
||||
float32(a.Width.Load()) >= a.TextSize.Scale(a.Break1).V,
|
||||
float32(a.Width.Load()) >= float32(a.TextSize)*a.Break1,
|
||||
a.Direction().W().Embed(a.LogoAndTitle).Fn,
|
||||
a.Direction().Center().Embed(a.LogoAndTitle).Fn,
|
||||
),
|
||||
@@ -352,14 +351,10 @@ func (a *App) RenderPage(gtx l.Context) l.Dimensions {
|
||||
1,
|
||||
a.VFlex().SpaceEvenly().
|
||||
Rigid(
|
||||
a.H1("404").
|
||||
Alignment(text.Middle).
|
||||
Fn,
|
||||
a.H1("404").Fn,
|
||||
).
|
||||
Rigid(
|
||||
a.Body1("page "+a.activePage.Load()+" not found").
|
||||
Alignment(text.Middle).
|
||||
Fn,
|
||||
a.Body1("page "+a.activePage.Load()+" not found").Fn,
|
||||
).
|
||||
Fn,
|
||||
).Fn(gtx)
|
||||
@@ -393,7 +388,7 @@ func (a *App) renderSideBar() l.Widget {
|
||||
max = i.Size.X
|
||||
}
|
||||
}
|
||||
a.SideBarSize.V = float32(max)
|
||||
a.SideBarSize = unit.Dp(max)
|
||||
gtx.Constraints.Max.X = max
|
||||
gtx.Constraints.Min.X = max
|
||||
out := a.VFlex().
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package gel
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/f32"
|
||||
|
||||
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op/clip"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op/paint"
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
type Border struct {
|
||||
*Window
|
||||
color color.NRGBA
|
||||
cornerRadius unit.Value
|
||||
width unit.Value
|
||||
cornerRadius unit.Dp
|
||||
width unit.Dp
|
||||
w l.Widget
|
||||
}
|
||||
|
||||
@@ -34,13 +34,13 @@ func (b *Border) Color(color string) *Border {
|
||||
|
||||
// CornerRadius sets the radius of the curve on the corners
|
||||
func (b *Border) CornerRadius(rad float32) *Border {
|
||||
b.cornerRadius = b.Theme.TextSize.Scale(rad)
|
||||
b.cornerRadius = unit.Dp(float32(b.Theme.TextSize) * rad)
|
||||
return b
|
||||
}
|
||||
|
||||
// Width sets the width of the border line
|
||||
func (b *Border) Width(width float32) *Border {
|
||||
b.width = b.Theme.TextSize.Scale(width)
|
||||
b.width = unit.Dp(float32(b.Theme.TextSize) * width)
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -52,23 +52,24 @@ func (b *Border) Embed(w l.Widget) *Border {
|
||||
// Fn renders the border
|
||||
func (b *Border) Fn(gtx l.Context) l.Dimensions {
|
||||
dims := b.w(gtx)
|
||||
sz := l.FPt(dims.Size)
|
||||
|
||||
rr := float32(gtx.Px(b.cornerRadius))
|
||||
width := float32(gtx.Px(b.width))
|
||||
sz.X -= width
|
||||
sz.Y -= width
|
||||
|
||||
r := f32.Rectangle{Max: sz}
|
||||
r = r.Add(f32.Point{X: width * 0.5, Y: width * 0.5})
|
||||
|
||||
|
||||
rr := gtx.Dp(b.cornerRadius)
|
||||
width := float32(gtx.Dp(b.width))
|
||||
widthInt := int(width)
|
||||
|
||||
// Create rectangle inset by half the stroke width
|
||||
r := image.Rectangle{
|
||||
Min: image.Pt(widthInt/2, widthInt/2),
|
||||
Max: image.Pt(dims.Size.X-widthInt/2, dims.Size.Y-widthInt/2),
|
||||
}
|
||||
|
||||
paint.FillShape(gtx.Ops,
|
||||
b.color,
|
||||
clip.Stroke{
|
||||
Path: clip.UniformRRect(r, rr).Path(gtx.Ops),
|
||||
Style: clip.StrokeStyle{Width: width},
|
||||
Width: width,
|
||||
}.Op(),
|
||||
)
|
||||
|
||||
|
||||
return dims
|
||||
}
|
||||
|
||||
@@ -121,7 +121,8 @@ func (e *editBuffer) Read(p []byte) (int, error) {
|
||||
e.pos += n
|
||||
}
|
||||
if e.pos > e.len() {
|
||||
panic("hey!")
|
||||
// Reset position to prevent further issues
|
||||
e.pos = e.len()
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
package gel
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/f32"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/font"
|
||||
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op/clip"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op/paint"
|
||||
"github.com/p9c/p9/pkg/gel/gio/text"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/f32color"
|
||||
)
|
||||
|
||||
@@ -21,35 +22,36 @@ type Button struct {
|
||||
*Window
|
||||
background color.NRGBA
|
||||
color color.NRGBA
|
||||
cornerRadius unit.Value
|
||||
font text.Font
|
||||
cornerRadius unit.Dp
|
||||
font font.Font
|
||||
inset *l.Inset
|
||||
text string
|
||||
textSize unit.Value
|
||||
textSize unit.Sp
|
||||
button *Clickable
|
||||
shaper text.Shaper
|
||||
shaper *text.Shaper
|
||||
}
|
||||
|
||||
// Button is a regular material text button where all the dimensions, colors, corners and font can be changed
|
||||
func (w *Window) Button(btn *Clickable) *Button {
|
||||
var font text.Font
|
||||
var fnt font.Font
|
||||
var e error
|
||||
if font, e = w.collection.Font("plan9"); E.Chk(e) {
|
||||
if fnt, e = w.collection.Font("plan9"); E.Chk(e) {
|
||||
}
|
||||
insetVal := unit.Dp(float32(w.TextSize) * 0.5)
|
||||
return &Button{
|
||||
Window: w,
|
||||
text: strings.ToUpper("text unset"),
|
||||
// default sets
|
||||
font: font,
|
||||
font: fnt,
|
||||
color: w.Colors.GetNRGBAFromName("DocBg"),
|
||||
cornerRadius: w.TextSize.Scale(0.25),
|
||||
cornerRadius: unit.Dp(float32(w.TextSize) * 0.25),
|
||||
background: w.Colors.GetNRGBAFromName("Primary"),
|
||||
textSize: w.TextSize,
|
||||
inset: &l.Inset{
|
||||
Top: w.TextSize.Scale(0.5),
|
||||
Bottom: w.TextSize.Scale(0.5),
|
||||
Left: w.TextSize.Scale(0.5),
|
||||
Right: w.TextSize.Scale(0.5),
|
||||
Top: insetVal,
|
||||
Bottom: insetVal,
|
||||
Left: insetVal,
|
||||
Right: insetVal,
|
||||
},
|
||||
button: btn,
|
||||
shaper: w.shaper,
|
||||
@@ -70,29 +72,28 @@ func (b *Button) Color(color string) *Button {
|
||||
|
||||
// CornerRadius sets the corner radius (all measurements are scaled from the base text size)
|
||||
func (b *Button) CornerRadius(cornerRadius float32) *Button {
|
||||
b.cornerRadius = b.TextSize.Scale(cornerRadius)
|
||||
b.cornerRadius = unit.Dp(float32(b.TextSize) * cornerRadius)
|
||||
return b
|
||||
}
|
||||
|
||||
// Font sets the font style
|
||||
func (b *Button) Font(font string) *Button {
|
||||
var fon text.Font
|
||||
var e error
|
||||
if fon, e = b.collection.Font(font); !E.Chk(e) {
|
||||
// Font sets the font style. If font not found, keeps current font.
|
||||
func (b *Button) Font(fontName string) *Button {
|
||||
if fon, e := b.collection.Font(fontName); e == nil {
|
||||
b.font = fon
|
||||
} else {
|
||||
panic(e)
|
||||
D.Ln("font not found:", fontName, "- keeping current font")
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Inset sets the inset between the button border and the text
|
||||
func (b *Button) Inset(scale float32) *Button {
|
||||
insetVal := unit.Dp(float32(b.TextSize) * scale)
|
||||
b.inset = &l.Inset{
|
||||
Top: b.TextSize.Scale(scale),
|
||||
Right: b.TextSize.Scale(scale),
|
||||
Bottom: b.TextSize.Scale(scale),
|
||||
Left: b.TextSize.Scale(scale),
|
||||
Top: insetVal,
|
||||
Right: insetVal,
|
||||
Bottom: insetVal,
|
||||
Left: insetVal,
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -105,7 +106,7 @@ func (b *Button) Text(text string) *Button {
|
||||
|
||||
// TextScale sets the dimensions of the text as a fraction of the base text size
|
||||
func (b *Button) TextScale(scale float32) *Button {
|
||||
b.textSize = b.Theme.TextSize.Scale(scale)
|
||||
b.textSize = unit.Sp(float32(b.Theme.TextSize) * scale)
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -130,6 +131,7 @@ func (b *Button) SetPress(fn func()) *Button {
|
||||
// Fn renders the button
|
||||
func (b *Button) Fn(gtx l.Context) l.Dimensions {
|
||||
bl := &ButtonLayout{
|
||||
Window: b.Window,
|
||||
background: b.background,
|
||||
cornerRadius: b.cornerRadius,
|
||||
button: b.button,
|
||||
@@ -138,7 +140,7 @@ func (b *Button) Fn(gtx l.Context) l.Dimensions {
|
||||
return b.inset.Layout(
|
||||
gtx, func(gtx l.Context) l.Dimensions {
|
||||
// paint.ColorOp{Color: b.color}.Add(gtx.Ops)
|
||||
return b.Flex().Rigid(b.Label().Text(b.text).TextScale(b.textSize.V / b.TextSize.V).Fn).Fn(gtx)
|
||||
return b.Flex().Rigid(b.Label().Text(b.text).TextScale(float32(b.textSize) / float32(b.TextSize)).Fn).Fn(gtx)
|
||||
// b.Window.Text().
|
||||
// Alignment(text.Middle).
|
||||
// Fn(gtx, b.shaper, b.font, b.textSize, b.text)
|
||||
@@ -198,10 +200,12 @@ func drawInk(c l.Context, p press) {
|
||||
sizet = endt
|
||||
}
|
||||
sizet /= expandDuration
|
||||
|
||||
// Animate only ended presses, and presses that are fading in.
|
||||
if !p.End.IsZero() || sizet <= 1.0 {
|
||||
op.InvalidateOp{}.Add(c.Ops)
|
||||
c.Execute(op.InvalidateCmd{})
|
||||
}
|
||||
|
||||
if sizet > 1.0 {
|
||||
sizet = 1.0
|
||||
}
|
||||
@@ -214,38 +218,24 @@ func drawInk(c l.Context, p press) {
|
||||
// Beziér ease-in curve.
|
||||
alphaBezier := t2 * t2 * (3.0 - 2.0*t2)
|
||||
sizeBezier := sizet * sizet * (3.0 - 2.0*sizet)
|
||||
size := float32(c.Constraints.Min.X)
|
||||
if h := float32(c.Constraints.Min.Y); h > size {
|
||||
size := c.Constraints.Min.X
|
||||
if h := c.Constraints.Min.Y; h > size {
|
||||
size = h
|
||||
}
|
||||
// Cover the entire constraints min rectangle.
|
||||
size *= 2 * float32(math.Sqrt(2))
|
||||
// Apply curve values to size and color.
|
||||
size *= sizeBezier
|
||||
// Cover the entire constraints min rectangle and
|
||||
// apply curve values to size and color.
|
||||
size = int(float32(size) * 2 * float32(math.Sqrt(2)) * sizeBezier)
|
||||
alpha := 0.7 * alphaBezier
|
||||
const col = 0.8
|
||||
ba, bc := byte(alpha*0xff), byte(col*0xff)
|
||||
defer op.Save(c.Ops).Load()
|
||||
rgba := f32color.MulAlpha(color.NRGBA{A: 0xff, R: bc, G: bc, B: bc}, ba)
|
||||
ink := paint.ColorOp{Color: rgba}
|
||||
ink.Add(c.Ops)
|
||||
rr := size * .5
|
||||
op.Offset(
|
||||
p.Position.Add(
|
||||
f32.Point{
|
||||
X: -rr,
|
||||
Y: -rr,
|
||||
},
|
||||
),
|
||||
).Add(c.Ops)
|
||||
clip.RRect{
|
||||
Rect: f32.Rectangle{
|
||||
Max: f32.Point{
|
||||
X: size,
|
||||
Y: size,
|
||||
},
|
||||
},
|
||||
NE: rr, NW: rr, SE: rr, SW: rr,
|
||||
}.Add(c.Ops)
|
||||
rr := size / 2
|
||||
defer op.Offset(p.Position.Add(image.Point{
|
||||
X: -rr,
|
||||
Y: -rr,
|
||||
})).Push(c.Ops).Pop()
|
||||
defer clip.UniformRRect(image.Rectangle{Max: image.Pt(size, size)}, rr).Push(c.Ops).Pop()
|
||||
paint.PaintOp{}.Add(c.Ops)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
package gel
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/f32"
|
||||
|
||||
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op/clip"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/f32color"
|
||||
)
|
||||
|
||||
type ButtonLayout struct {
|
||||
*Window
|
||||
background color.NRGBA
|
||||
cornerRadius unit.Value
|
||||
cornerRadius unit.Dp
|
||||
button *Clickable
|
||||
w l.Widget
|
||||
corners int
|
||||
@@ -26,7 +26,7 @@ func (w *Window) ButtonLayout(button *Clickable) *ButtonLayout {
|
||||
Window: w,
|
||||
button: button,
|
||||
background: w.Colors.GetNRGBAFromName("ButtonBg"),
|
||||
cornerRadius: w.TextSize.Scale(0.25),
|
||||
cornerRadius: unit.Dp(float32(w.TextSize) * 0.25),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func (b *ButtonLayout) Background(color string) *ButtonLayout {
|
||||
|
||||
// CornerRadius sets the radius of the corners of the button
|
||||
func (b *ButtonLayout) CornerRadius(radius float32) *ButtonLayout {
|
||||
b.cornerRadius = b.Theme.TextSize.Scale(radius)
|
||||
b.cornerRadius = unit.Dp(float32(b.Theme.TextSize) * radius)
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -75,19 +75,19 @@ func (b *ButtonLayout) Fn(gtx l.Context) l.Dimensions {
|
||||
return b.Stack().Alignment(l.Center).
|
||||
Expanded(
|
||||
func(gtx l.Context) l.Dimensions {
|
||||
rr := float32(gtx.Px(b.cornerRadius))
|
||||
clip.RRect{
|
||||
Rect: f32.Rectangle{Max: f32.Point{
|
||||
X: float32(gtx.Constraints.Min.X),
|
||||
Y: float32(gtx.Constraints.Min.Y),
|
||||
rr := gtx.Dp(b.cornerRadius)
|
||||
defer clip.RRect{
|
||||
Rect: image.Rectangle{Max: image.Point{
|
||||
X: gtx.Constraints.Min.X,
|
||||
Y: gtx.Constraints.Min.Y,
|
||||
}},
|
||||
NW: ifDir(rr, b.corners&NW),
|
||||
NE: ifDir(rr, b.corners&NE),
|
||||
SW: ifDir(rr, b.corners&SW),
|
||||
SE: ifDir(rr, b.corners&SE),
|
||||
}.Add(gtx.Ops)
|
||||
NW: ifDirInt(rr, b.corners&NW),
|
||||
NE: ifDirInt(rr, b.corners&NE),
|
||||
SW: ifDirInt(rr, b.corners&SW),
|
||||
SE: ifDirInt(rr, b.corners&SE),
|
||||
}.Push(gtx.Ops).Pop()
|
||||
background := b.background
|
||||
if gtx.Queue == nil {
|
||||
if !gtx.Source.Enabled() {
|
||||
background = f32color.MulAlpha(b.background, 150)
|
||||
}
|
||||
dims := Fill(gtx, background)
|
||||
|
||||
@@ -5,7 +5,7 @@ import l "github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
func (w *Window) Card(background string, embed l.Widget,
|
||||
) func(gtx l.Context) l.Dimensions {
|
||||
return w.Inset(0.0,
|
||||
w.Fill(background, l.Center, w.TextSize.V, 0, w.Inset(0.25,
|
||||
w.Fill(background, l.Center, float32(w.TextSize), 0, w.Inset(0.25,
|
||||
embed,
|
||||
).Fn).Fn,
|
||||
).Fn
|
||||
|
||||
@@ -3,8 +3,9 @@ package gel
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/pointer"
|
||||
"github.com/p9c/p9/pkg/gel/gio/font"
|
||||
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op/clip"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op/paint"
|
||||
"github.com/p9c/p9/pkg/gel/gio/text"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
@@ -15,37 +16,31 @@ type Checkable struct {
|
||||
*Window
|
||||
label string
|
||||
color string
|
||||
font text.Font
|
||||
textSize unit.Value
|
||||
font font.Font
|
||||
textSize unit.Sp
|
||||
iconColor string
|
||||
size unit.Value
|
||||
size unit.Sp
|
||||
checkedStateIcon *[]byte
|
||||
uncheckedStateIcon *[]byte
|
||||
shaper text.Shaper
|
||||
shaper *text.Shaper
|
||||
checked bool
|
||||
}
|
||||
|
||||
// Checkable creates a checkbox type widget
|
||||
func (w *Window) Checkable() *Checkable {
|
||||
font := "bariol regular"
|
||||
var f text.Font
|
||||
if fon, e := w.Theme.collection.Font(font); !E.Chk(e) {
|
||||
fontName := "bariol regular"
|
||||
var f font.Font
|
||||
if fon, e := w.collection.Font(fontName); !E.Chk(e) {
|
||||
f = fon
|
||||
}
|
||||
// for i := range w.collection {
|
||||
// if w.collection[i].Font.Typeface == text.Typeface(font) {
|
||||
// f = w.collection[i].Font
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
return &Checkable{
|
||||
Window: w,
|
||||
label: "checkable",
|
||||
color: "Primary",
|
||||
font: f,
|
||||
textSize: w.TextSize.Scale(14.0 / 16.0),
|
||||
textSize: unit.Sp(float32(w.TextSize) * 14.0 / 16.0),
|
||||
iconColor: "Primary",
|
||||
size: w.TextSize.Scale(1.5),
|
||||
size: unit.Sp(float32(w.TextSize) * 1.5),
|
||||
checkedStateIcon: &icons.ToggleCheckBox,
|
||||
uncheckedStateIcon: &icons.ToggleCheckBoxOutlineBlank,
|
||||
shaper: w.shaper,
|
||||
@@ -74,7 +69,7 @@ func (c *Checkable) Font(font string) *Checkable {
|
||||
|
||||
// TextScale sets the size of the font relative to the base text size
|
||||
func (c *Checkable) TextScale(scale float32) *Checkable {
|
||||
c.textSize = c.Theme.TextSize.Scale(scale)
|
||||
c.textSize = unit.Sp(float32(c.Theme.TextSize) * scale)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -86,7 +81,7 @@ func (c *Checkable) IconColor(color string) *Checkable {
|
||||
|
||||
// Scale sets the size of the checkbox icon relative to the base font size
|
||||
func (c *Checkable) Scale(size float32) *Checkable {
|
||||
c.size = c.Theme.TextSize.Scale(size)
|
||||
c.size = unit.Sp(float32(c.Theme.TextSize) * size)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -115,36 +110,24 @@ func (c *Checkable) Fn(gtx l.Context, checked bool) l.Dimensions {
|
||||
Src(c.uncheckedStateIcon)
|
||||
}
|
||||
icon.size = c.size
|
||||
// D.S(icon)
|
||||
dims :=
|
||||
c.Theme.Flex(). // AlignBaseline().
|
||||
c.Theme.Flex().
|
||||
Rigid(
|
||||
// c.Theme.ButtonInset(0.25,
|
||||
func(gtx l.Context) l.Dimensions {
|
||||
size := gtx.Px(c.size)
|
||||
// icon.color = c.iconColor
|
||||
// TODO: maybe make a special code for raw colors to do this kind of alpha
|
||||
// or add a parameter to apply it
|
||||
// if gtx.Queue == nil {
|
||||
// icon.color = f32color.MulAlpha(c.Theme.Colors.Get(icon.color), 150)
|
||||
// }
|
||||
size := gtx.Sp(c.size)
|
||||
icon.Fn(gtx)
|
||||
return l.Dimensions{
|
||||
Size: image.Point{X: size, Y: size},
|
||||
}
|
||||
},
|
||||
// ).Fn,
|
||||
).
|
||||
Rigid(
|
||||
// c.Theme.ButtonInset(0.25,
|
||||
func(gtx l.Context) l.Dimensions {
|
||||
paint.ColorOp{Color: c.Theme.Colors.GetNRGBAFromName(c.color)}.Add(gtx.Ops)
|
||||
return c.Caption(c.label).Color(c.color).Fn(gtx)
|
||||
// return widget.Label{}.Layout(gtx, c.shaper, c.font, c.textSize, c.label)
|
||||
},
|
||||
// ).Fn,
|
||||
).
|
||||
Fn(gtx)
|
||||
pointer.Rect(image.Rectangle{Max: dims.Size}).Add(gtx.Ops)
|
||||
defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop()
|
||||
return dims
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package gel
|
||||
|
||||
import (
|
||||
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
)
|
||||
|
||||
// CheckBox creates a checkbox with a text label
|
||||
@@ -44,7 +45,7 @@ func (c *Checkbox) TextColor(color string) *Checkbox {
|
||||
|
||||
// TextScale sets the scale relative to the base font size for the text label
|
||||
func (c *Checkbox) TextScale(scale float32) *Checkbox {
|
||||
c.textSize = c.TextSize.Scale(scale)
|
||||
c.textSize = unit.Sp(float32(c.TextSize) * scale)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -56,7 +57,7 @@ func (c *Checkbox) Text(label string) *Checkbox {
|
||||
|
||||
// IconScale sets the scaling of the check icon
|
||||
func (c *Checkbox) IconScale(scale float32) *Checkbox {
|
||||
c.size = c.TextSize.Scale(scale)
|
||||
c.size = unit.Sp(float32(c.TextSize) * scale)
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
@@ -3,20 +3,21 @@ package gel
|
||||
import (
|
||||
"image"
|
||||
"time"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/f32"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/gesture"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/key"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/pointer"
|
||||
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op/clip"
|
||||
)
|
||||
|
||||
// clickEvents holds callback functions for click interaction events.
|
||||
type clickEvents struct {
|
||||
Click, Cancel, Press func()
|
||||
}
|
||||
|
||||
// Clickable represents a clickable area.
|
||||
// Clickable represents a clickable area that handles pointer events.
|
||||
// It tracks click history for visual feedback (ink ripples) and provides
|
||||
// callbacks for click, cancel, and press events via the Events field.
|
||||
type Clickable struct {
|
||||
*Window
|
||||
click gesture.Click
|
||||
@@ -74,7 +75,7 @@ type click struct {
|
||||
// press represents a past pointer press.
|
||||
type press struct {
|
||||
// Position of the press.
|
||||
Position f32.Point
|
||||
Position image.Point
|
||||
// Start is when the press began.
|
||||
Start time.Time
|
||||
// End is when the press was ended by a release or Cancel. A zero End means it hasn't ended yet.
|
||||
@@ -113,10 +114,8 @@ func (c *Clickable) History() []press {
|
||||
|
||||
func (c *Clickable) Fn(gtx l.Context) l.Dimensions {
|
||||
c.update(gtx)
|
||||
stack := op.Save(gtx.Ops)
|
||||
pointer.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops)
|
||||
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop()
|
||||
c.click.Add(gtx.Ops)
|
||||
stack.Load()
|
||||
for len(c.history) > 0 {
|
||||
cc := c.history[0]
|
||||
if cc.End.IsZero() || gtx.Now.Sub(cc.End) < 1*time.Second {
|
||||
@@ -134,11 +133,14 @@ func (c *Clickable) update(gtx l.Context) {
|
||||
n := copy(c.clicks, c.clicks[c.prevClicks:])
|
||||
c.clicks = c.clicks[:n]
|
||||
c.prevClicks = n
|
||||
for _, ev := range c.click.Events(gtx) {
|
||||
switch ev.Type {
|
||||
case gesture.TypeClick:
|
||||
var clk click
|
||||
clk = click{
|
||||
for {
|
||||
ev, ok := c.click.Update(gtx.Source)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
switch ev.Kind {
|
||||
case gesture.KindClick:
|
||||
clk := click{
|
||||
Modifiers: ev.Modifiers,
|
||||
NumClicks: ev.NumClicks,
|
||||
}
|
||||
@@ -147,7 +149,7 @@ func (c *Clickable) update(gtx l.Context) {
|
||||
c.history[ll-1].End = gtx.Now
|
||||
}
|
||||
c.Window.Runner <- func() (e error) { c.Events.Click(); return nil }
|
||||
case gesture.TypeCancel:
|
||||
case gesture.KindCancel:
|
||||
for i := range c.history {
|
||||
c.history[i].Cancelled = true
|
||||
if c.history[i].End.IsZero() {
|
||||
@@ -155,17 +157,13 @@ func (c *Clickable) update(gtx l.Context) {
|
||||
}
|
||||
}
|
||||
c.Window.Runner <- func() (e error) { c.Events.Cancel(); return nil }
|
||||
case gesture.TypePress:
|
||||
case gesture.KindPress:
|
||||
c.history = append(c.history, press{
|
||||
Position: ev.Position,
|
||||
Start: gtx.Now,
|
||||
})
|
||||
c.
|
||||
Window.
|
||||
Runner <- func() (e error) {
|
||||
c.
|
||||
Events.
|
||||
Press()
|
||||
c.Window.Runner <- func() (e error) {
|
||||
c.Events.Press()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
165
pkg/gel/clickable_test.go
Normal file
165
pkg/gel/clickable_test.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package gel
|
||||
|
||||
import (
|
||||
"image"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClickableCreation(t *testing.T) {
|
||||
w := testWindow(t)
|
||||
click := w.Clickable()
|
||||
|
||||
if click == nil {
|
||||
t.Fatal("Clickable() returned nil")
|
||||
}
|
||||
if click.Window != w {
|
||||
t.Error("Clickable Window reference incorrect")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClickableEventCallbacks(t *testing.T) {
|
||||
w := testWindow(t)
|
||||
click := w.Clickable()
|
||||
|
||||
// Default callbacks should exist
|
||||
if click.Events.Click == nil {
|
||||
t.Error("Events.Click should not be nil")
|
||||
}
|
||||
if click.Events.Cancel == nil {
|
||||
t.Error("Events.Cancel should not be nil")
|
||||
}
|
||||
if click.Events.Press == nil {
|
||||
t.Error("Events.Press should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClickableSetClick(t *testing.T) {
|
||||
w := testWindow(t)
|
||||
click := w.Clickable()
|
||||
|
||||
called := false
|
||||
click.SetClick(func() {
|
||||
called = true
|
||||
})
|
||||
|
||||
// Invoke the callback
|
||||
click.Events.Click()
|
||||
if !called {
|
||||
t.Error("SetClick callback was not set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClickableSetCancel(t *testing.T) {
|
||||
w := testWindow(t)
|
||||
click := w.Clickable()
|
||||
|
||||
called := false
|
||||
click.SetCancel(func() {
|
||||
called = true
|
||||
})
|
||||
|
||||
click.Events.Cancel()
|
||||
if !called {
|
||||
t.Error("SetCancel callback was not set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClickableSetPress(t *testing.T) {
|
||||
w := testWindow(t)
|
||||
click := w.Clickable()
|
||||
|
||||
called := false
|
||||
click.SetPress(func() {
|
||||
called = true
|
||||
})
|
||||
|
||||
click.Events.Press()
|
||||
if !called {
|
||||
t.Error("SetPress callback was not set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClickableFn(t *testing.T) {
|
||||
w := testWindow(t)
|
||||
click := w.Clickable()
|
||||
gtx := testContext(t, image.Pt(100, 100))
|
||||
|
||||
// Fn should not panic and should return dimensions
|
||||
dims := click.Fn(gtx)
|
||||
|
||||
// Clickable by itself doesn't render anything visible, so zero dimensions are expected
|
||||
// but it should not panic
|
||||
_ = dims
|
||||
}
|
||||
|
||||
func TestClickableHistory(t *testing.T) {
|
||||
w := testWindow(t)
|
||||
click := w.Clickable()
|
||||
|
||||
// History should start empty
|
||||
history := click.History()
|
||||
if len(history) != 0 {
|
||||
t.Errorf("initial history length = %d, want 0", len(history))
|
||||
}
|
||||
}
|
||||
|
||||
func TestClickableClicked(t *testing.T) {
|
||||
w := testWindow(t)
|
||||
click := w.Clickable()
|
||||
|
||||
// Without any events, Clicked should return false
|
||||
if click.Clicked() {
|
||||
t.Error("Clicked() should return false without click events")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClickableClicks(t *testing.T) {
|
||||
w := testWindow(t)
|
||||
click := w.Clickable()
|
||||
|
||||
// Without any events, Clicks should return empty slice
|
||||
clicks := click.Clicks()
|
||||
if len(clicks) != 0 {
|
||||
t.Errorf("Clicks() returned %d clicks, want 0", len(clicks))
|
||||
}
|
||||
}
|
||||
|
||||
func TestClickableChainedMethods(t *testing.T) {
|
||||
w := testWindow(t)
|
||||
|
||||
clickCalled := false
|
||||
pressCalled := false
|
||||
cancelCalled := false
|
||||
|
||||
// Test fluent API
|
||||
click := w.Clickable().
|
||||
SetClick(func() { clickCalled = true }).
|
||||
SetPress(func() { pressCalled = true }).
|
||||
SetCancel(func() { cancelCalled = true })
|
||||
|
||||
// Verify all callbacks were set
|
||||
click.Events.Click()
|
||||
click.Events.Press()
|
||||
click.Events.Cancel()
|
||||
|
||||
if !clickCalled {
|
||||
t.Error("Click callback not set in chain")
|
||||
}
|
||||
if !pressCalled {
|
||||
t.Error("Press callback not set in chain")
|
||||
}
|
||||
if !cancelCalled {
|
||||
t.Error("Cancel callback not set in chain")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkClickableFn(b *testing.B) {
|
||||
w := testWindow(&testing.T{})
|
||||
click := w.Clickable()
|
||||
gtx := testContext(&testing.T{}, image.Pt(100, 100))
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
click.Fn(gtx)
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func (c *Column) List(gtx l.Context) (max int, out []l.Widget) {
|
||||
out = append(out, func(gtx l.Context) l.Dimensions {
|
||||
return c.Flex(). // AlignEnd().SpaceStart().
|
||||
Rigid(
|
||||
c.Fill("red", l.Center, c.TextSize.V, 0, EmptySpace(max-dims[i].Size.X, dims[i].Size.Y)).Fn,
|
||||
c.Fill("red", l.Center, float32(c.TextSize), 0, EmptySpace(max-dims[i].Size.X, dims[i].Size.Y)).Fn,
|
||||
).
|
||||
Rigid(
|
||||
c.Inset(0.5, func(gtx l.Context) l.Dimensions {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
package gel
|
||||
1606
pkg/gel/editor.go
1606
pkg/gel/editor.go
File diff suppressed because it is too large
Load Diff
@@ -4,9 +4,8 @@ import (
|
||||
"image"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/gesture"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/pointer"
|
||||
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op/clip"
|
||||
)
|
||||
|
||||
type Enum struct {
|
||||
@@ -53,8 +52,7 @@ func (e *Enum) Changed() bool {
|
||||
|
||||
// Fn adds the event handler for key.
|
||||
func (e *Enum) Fn(gtx l.Context, key string) l.Dimensions {
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
pointer.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Add(gtx.Ops)
|
||||
defer clip.Rect(image.Rectangle{Max: gtx.Constraints.Min}).Push(gtx.Ops).Pop()
|
||||
|
||||
if index(e.values, key) == -1 {
|
||||
e.values = append(e.values, key)
|
||||
@@ -63,9 +61,13 @@ func (e *Enum) Fn(gtx l.Context, key string) l.Dimensions {
|
||||
} else {
|
||||
idx := index(e.values, key)
|
||||
clk := &e.clicks[idx]
|
||||
for _, ev := range clk.Events(gtx) {
|
||||
switch ev.Type {
|
||||
case gesture.TypeClick:
|
||||
for {
|
||||
ev, ok := clk.Update(gtx.Source)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
switch ev.Kind {
|
||||
case gesture.KindClick:
|
||||
if niew := e.values[idx]; niew != e.value {
|
||||
e.value = niew
|
||||
e.changed = true
|
||||
|
||||
@@ -3,8 +3,7 @@ package gel
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/f32"
|
||||
|
||||
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op/clip"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op/paint"
|
||||
@@ -52,16 +51,24 @@ func ifDir(radius float32, dir int) float32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func ifDirInt(radius int, dir int) int {
|
||||
if dir != 0 {
|
||||
return radius
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func fill(gtx l.Context, col color.NRGBA, bounds image.Point, radius float32, cnrs int) {
|
||||
rect := f32.Rectangle{
|
||||
Max: f32.Pt(float32(bounds.X), float32(bounds.Y)),
|
||||
rr := int(radius)
|
||||
rect := image.Rectangle{
|
||||
Max: bounds,
|
||||
}
|
||||
clip.RRect{
|
||||
Rect: rect,
|
||||
NW: ifDir(radius, cnrs&NW),
|
||||
NE: ifDir(radius, cnrs&NE),
|
||||
SW: ifDir(radius, cnrs&SW),
|
||||
SE: ifDir(radius, cnrs&SE),
|
||||
}.Add(gtx.Ops)
|
||||
NW: ifDirInt(rr, cnrs&NW),
|
||||
NE: ifDirInt(rr, cnrs&NE),
|
||||
SW: ifDirInt(rr, cnrs&SW),
|
||||
SE: ifDirInt(rr, cnrs&SE),
|
||||
}.Push(gtx.Ops).Pop()
|
||||
paint.Fill(gtx.Ops, col)
|
||||
}
|
||||
|
||||
106
pkg/gel/fit.go
106
pkg/gel/fit.go
@@ -1,106 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package gel
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/f32"
|
||||
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op/clip"
|
||||
)
|
||||
|
||||
// Fit scales a widget to fit and clip to the constraints.
|
||||
type Fit uint8
|
||||
|
||||
const (
|
||||
// Unscaled does not alter the scale of a widget.
|
||||
Unscaled Fit = iota
|
||||
// Contain scales widget as large as possible without cropping
|
||||
// and it preserves aspect-ratio.
|
||||
Contain
|
||||
// Cover scales the widget to cover the constraint area and
|
||||
// preserves aspect-ratio.
|
||||
Cover
|
||||
// ScaleDown scales the widget smaller without cropping,
|
||||
// when it exceeds the constraint area.
|
||||
// It preserves aspect-ratio.
|
||||
ScaleDown
|
||||
// Stretch stretches the widget to the constraints and does not
|
||||
// preserve aspect-ratio.
|
||||
Stretch
|
||||
)
|
||||
|
||||
// scale adds clip and scale operations to fit dims to the constraints.
|
||||
// It positions the widget to the appropriate position.
|
||||
// It returns dimensions modified accordingly.
|
||||
func (fit Fit) scale(gtx l.Context, pos l.Direction, dims l.Dimensions) l.Dimensions {
|
||||
widgetSize := dims.Size
|
||||
|
||||
if fit == Unscaled || dims.Size.X == 0 || dims.Size.Y == 0 {
|
||||
dims.Size = gtx.Constraints.Constrain(dims.Size)
|
||||
clip.Rect{Max: dims.Size}.Add(gtx.Ops)
|
||||
|
||||
offset := pos.Position(widgetSize, dims.Size)
|
||||
op.Offset(l.FPt(offset)).Add(gtx.Ops)
|
||||
dims.Baseline += offset.Y
|
||||
return dims
|
||||
}
|
||||
|
||||
scale := f32.Point{
|
||||
X: float32(gtx.Constraints.Max.X) / float32(dims.Size.X),
|
||||
Y: float32(gtx.Constraints.Max.Y) / float32(dims.Size.Y),
|
||||
}
|
||||
|
||||
switch fit {
|
||||
case Contain:
|
||||
if scale.Y < scale.X {
|
||||
scale.X = scale.Y
|
||||
} else {
|
||||
scale.Y = scale.X
|
||||
}
|
||||
case Cover:
|
||||
if scale.Y > scale.X {
|
||||
scale.X = scale.Y
|
||||
} else {
|
||||
scale.Y = scale.X
|
||||
}
|
||||
case ScaleDown:
|
||||
if scale.Y < scale.X {
|
||||
scale.X = scale.Y
|
||||
} else {
|
||||
scale.Y = scale.X
|
||||
}
|
||||
|
||||
// The widget would need to be scaled up, no change needed.
|
||||
if scale.X >= 1 {
|
||||
dims.Size = gtx.Constraints.Constrain(dims.Size)
|
||||
clip.Rect{Max: dims.Size}.Add(gtx.Ops)
|
||||
|
||||
offset := pos.Position(widgetSize, dims.Size)
|
||||
op.Offset(l.FPt(offset)).Add(gtx.Ops)
|
||||
dims.Baseline += offset.Y
|
||||
return dims
|
||||
}
|
||||
case Stretch:
|
||||
}
|
||||
|
||||
var scaledSize image.Point
|
||||
scaledSize.X = int(float32(widgetSize.X) * scale.X)
|
||||
scaledSize.Y = int(float32(widgetSize.Y) * scale.Y)
|
||||
dims.Size = gtx.Constraints.Constrain(scaledSize)
|
||||
dims.Baseline = int(float32(dims.Baseline) * scale.Y)
|
||||
|
||||
clip.Rect{Max: dims.Size}.Add(gtx.Ops)
|
||||
|
||||
offset := pos.Position(scaledSize, dims.Size)
|
||||
op.Affine(f32.Affine2D{}.
|
||||
Scale(f32.Point{}, scale).
|
||||
Offset(l.FPt(offset)),
|
||||
).Add(gtx.Ops)
|
||||
|
||||
dims.Baseline += offset.Y
|
||||
|
||||
return dims
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/p9c/p9/pkg/gel/gio/gesture"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/pointer"
|
||||
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op/clip"
|
||||
)
|
||||
|
||||
// Float is for selecting a value in a range.
|
||||
@@ -43,11 +43,15 @@ func (f *Float) Fn(gtx l.Context, pointerMargin int, min, max float32) l.Dimensi
|
||||
size := gtx.Constraints.Min
|
||||
f.length = float32(size.X)
|
||||
var de *pointer.Event
|
||||
for _, ev := range f.drag.Events(gtx.Metric, gtx, gesture.Horizontal) {
|
||||
if ev.Type == pointer.Press || ev.Type == pointer.Drag {
|
||||
for {
|
||||
ev, ok := f.drag.Update(gtx.Metric, gtx.Source, gesture.Horizontal)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if ev.Kind == pointer.Press || ev.Kind == pointer.Drag {
|
||||
de = &ev
|
||||
}
|
||||
if ev.Type == pointer.Release {
|
||||
if ev.Kind == pointer.Release {
|
||||
f.changeHook(f.value)
|
||||
}
|
||||
}
|
||||
@@ -67,11 +71,10 @@ func (f *Float) Fn(gtx l.Context, pointerMargin int, min, max float32) l.Dimensi
|
||||
f.pos = 1
|
||||
}
|
||||
|
||||
defer op.Save(gtx.Ops).Load()
|
||||
rect := image.Rectangle{Max: size}
|
||||
rect.Min.X -= pointerMargin
|
||||
rect.Max.X += pointerMargin
|
||||
pointer.Rect(rect).Add(gtx.Ops)
|
||||
defer clip.Rect(rect).Push(gtx.Ops).Pop()
|
||||
f.drag.Add(gtx.Ops)
|
||||
|
||||
return l.Dimensions{Size: size}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/font"
|
||||
"github.com/p9c/p9/pkg/gel/gio/font/opentype"
|
||||
"github.com/p9c/p9/pkg/gel/gio/text"
|
||||
"golang.org/x/image/font/gofont/gomono"
|
||||
"golang.org/x/image/font/gofont/gomonobold"
|
||||
"golang.org/x/image/font/gofont/gomonobolditalic"
|
||||
@@ -22,23 +22,23 @@ import (
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
collection []text.FontFace
|
||||
Fonts = map[string]text.Font{
|
||||
collection []font.FontFace
|
||||
Fonts = map[string]font.Font{
|
||||
"plan9": {Typeface: "plan9"},
|
||||
"bariol regular": {Typeface: "bariol regular"},
|
||||
"bariol italic": {Typeface: "bariol italic", Style: text.Italic},
|
||||
"bariol bold": {Typeface: "bariol bold", Weight: text.Bold},
|
||||
"bariol bolditalic": {Typeface: "bariol bolditalic", Style: text.Italic, Weight: text.Bold},
|
||||
"bariol light": {Typeface: "bariol light", Weight: text.Medium},
|
||||
"bariol lightitalic": {Typeface: "bariol lightitalic", Weight: text.Medium, Style: text.Italic},
|
||||
"bariol italic": {Typeface: "bariol italic", Style: font.Italic},
|
||||
"bariol bold": {Typeface: "bariol bold", Weight: font.Bold},
|
||||
"bariol bolditalic": {Typeface: "bariol bolditalic", Style: font.Italic, Weight: font.Bold},
|
||||
"bariol light": {Typeface: "bariol light", Weight: font.Light},
|
||||
"bariol lightitalic": {Typeface: "bariol lightitalic", Weight: font.Light, Style: font.Italic},
|
||||
"go regular": {Typeface: "go regular"},
|
||||
"go bold": {Typeface: "go bold", Weight: text.Bold},
|
||||
"go bolditalic": {Typeface: "go bolditalic", Weight: text.Bold, Style: text.Italic},
|
||||
"go italic": {Typeface: "go italic", Style: text.Italic},
|
||||
"go bold": {Typeface: "go bold", Weight: font.Bold},
|
||||
"go bolditalic": {Typeface: "go bolditalic", Weight: font.Bold, Style: font.Italic},
|
||||
"go italic": {Typeface: "go italic", Style: font.Italic},
|
||||
}
|
||||
)
|
||||
|
||||
func Collection() []text.FontFace {
|
||||
func Collection() []font.FontFace {
|
||||
once.Do(func() {
|
||||
register(Fonts["plan9"], plan9.TTF)
|
||||
register(Fonts["bariol regular"], bariolregular.TTF)
|
||||
@@ -58,11 +58,10 @@ func Collection() []text.FontFace {
|
||||
return collection
|
||||
}
|
||||
|
||||
func register(fnt text.Font, ttf []byte) {
|
||||
func register(fnt font.Font, ttf []byte) {
|
||||
face, e := opentype.Parse(ttf)
|
||||
if e != nil {
|
||||
panic(fmt.Errorf("failed to parse font: %v", e))
|
||||
}
|
||||
// fnt.Typeface = "Go"
|
||||
collection = append(collection, text.FontFace{Font: fnt, Face: face})
|
||||
collection = append(collection, font.FontFace{Font: fnt, Face: face})
|
||||
}
|
||||
|
||||
@@ -8,21 +8,28 @@ packages:
|
||||
- libxml2-dev
|
||||
- libssl-dev
|
||||
- libz-dev
|
||||
- llvm-dev # for cctools
|
||||
- uuid-dev ## for cctools
|
||||
- llvm-dev # cctools
|
||||
- uuid-dev # cctools
|
||||
- ninja-build # cctools
|
||||
- systemtap-sdt-dev # cctools
|
||||
- libbsd-dev # cctools
|
||||
- linux-libc-dev # cctools
|
||||
- libplist-utils # for gogio
|
||||
- golang
|
||||
sources:
|
||||
- https://git.sr.ht/~eliasnaur/applesdks
|
||||
- https://git.sr.ht/~eliasnaur/gio
|
||||
- https://git.sr.ht/~eliasnaur/giouiorg
|
||||
- https://github.com/tpoechtrager/cctools-port.git
|
||||
- https://github.com/tpoechtrager/apple-libtapi.git
|
||||
- https://github.com/mackyle/xar.git
|
||||
- https://github.com/tpoechtrager/cctools-port
|
||||
- https://github.com/tpoechtrager/apple-libtapi
|
||||
- https://github.com/tpoechtrager/apple-libdispatch
|
||||
- https://github.com/mackyle/xar
|
||||
environment:
|
||||
APPLE_TOOLCHAIN_ROOT: /home/build/appletools
|
||||
PATH: /home/build/go/bin:/usr/bin
|
||||
PATH: /home/build/sdk/go/bin:/home/build/go/bin:/usr/bin
|
||||
tasks:
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl -s https://dl.google.com/go/go1.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
- prepare_toolchain: |
|
||||
mkdir -p $APPLE_TOOLCHAIN_ROOT
|
||||
cd $APPLE_TOOLCHAIN_ROOT
|
||||
@@ -40,6 +47,11 @@ tasks:
|
||||
- install_appletoolchain: |
|
||||
cd giouiorg
|
||||
go build -o $APPLE_TOOLCHAIN_ROOT/tools ./cmd/appletoolchain
|
||||
- build_libdispatch: |
|
||||
cd apple-libdispatch
|
||||
cmake -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_INSTALL_PREFIX=$APPLE_TOOLCHAIN_ROOT/libdispatch .
|
||||
ninja
|
||||
ninja install
|
||||
- build_xar: |
|
||||
cd xar/xar
|
||||
ac_cv_lib_crypto_OpenSSL_add_all_ciphers=yes CC=clang ./autogen.sh --prefix=/usr
|
||||
@@ -51,7 +63,7 @@ tasks:
|
||||
./install.sh
|
||||
- build_cctools: |
|
||||
cd cctools-port/cctools
|
||||
./configure --prefix $APPLE_TOOLCHAIN_ROOT/toolchain --with-libtapi=$APPLE_TOOLCHAIN_ROOT/libtapi --target=x86_64-apple-darwin19
|
||||
./configure --target=x86_64-apple-darwin19 --prefix $APPLE_TOOLCHAIN_ROOT/toolchain --with-libtapi=$APPLE_TOOLCHAIN_ROOT/libtapi --with-libdispatch=$APPLE_TOOLCHAIN_ROOT/libdispatch --with-libblocksruntime=$APPLE_TOOLCHAIN_ROOT/libdispatch
|
||||
make install
|
||||
- test_macos: |
|
||||
cd gio
|
||||
@@ -59,14 +71,4 @@ tasks:
|
||||
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-macos GOOS=darwin CGO_ENABLED=1 go build ./...
|
||||
- test_ios: |
|
||||
cd gio
|
||||
CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./...
|
||||
- install_gogio: |
|
||||
cd gio/cmd
|
||||
go install ./gogio
|
||||
- test_ios_gogio: |
|
||||
mkdir tmp
|
||||
cd tmp
|
||||
go mod init example.com
|
||||
go get -d github.com/p9c/p9/pkg/gel/gio/example/kitchen
|
||||
export PATH=/home/build/appletools/bin:$PATH
|
||||
gogio -target ios -o app.app github.com/p9c/p9/pkg/gel/gio/example/kitchen
|
||||
CGO_CFLAGS=-Wno-deprecated-module-dot-map CC=$APPLE_TOOLCHAIN_ROOT/tools/clang-ios GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -tags ios ./...
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# SPDX-License-Identifier: Unlicense OR MIT
|
||||
image: freebsd/11.x
|
||||
image: freebsd/latest
|
||||
packages:
|
||||
- libX11
|
||||
- libxkbcommon
|
||||
- libXcursor
|
||||
- libXfixes
|
||||
- vulkan-headers
|
||||
- wayland
|
||||
- mesa-libs
|
||||
- xorg-vfbserver
|
||||
@@ -13,13 +14,9 @@ sources:
|
||||
environment:
|
||||
PATH: /home/build/sdk/go/bin:/bin:/usr/local/bin:/usr/bin
|
||||
tasks:
|
||||
- install_go1_14: |
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl https://dl.google.com/go/go1.14.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
curl https://dl.google.com/go/go1.24.2.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
- test_gio: |
|
||||
export EGL_PLATFORM=surfaceless # for headless tests
|
||||
cd gio
|
||||
go test ./...
|
||||
- test_cmd: |
|
||||
cd gio/cmd
|
||||
go test ./...
|
||||
|
||||
@@ -3,6 +3,7 @@ image: debian/testing
|
||||
packages:
|
||||
- curl
|
||||
- pkg-config
|
||||
- gcc-multilib
|
||||
- libwayland-dev
|
||||
- libx11-dev
|
||||
- libx11-xcb-dev
|
||||
@@ -11,23 +12,24 @@ packages:
|
||||
- libgles2-mesa-dev
|
||||
- libegl1-mesa-dev
|
||||
- libffi-dev
|
||||
- libvulkan-dev
|
||||
- libxcursor-dev
|
||||
- libxrandr-dev
|
||||
- libxinerama-dev
|
||||
- libxi-dev
|
||||
- libxxf86vm-dev
|
||||
- mesa-vulkan-drivers
|
||||
- wine
|
||||
- xvfb
|
||||
- xdotool
|
||||
- scrot
|
||||
- sway
|
||||
- grim
|
||||
- wine
|
||||
- unzip
|
||||
sources:
|
||||
- https://git.sr.ht/~eliasnaur/gio
|
||||
environment:
|
||||
GOFLAGS: -mod=readonly
|
||||
PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig/:/usr/lib/i386-linux-gnu/pkgconfig/
|
||||
PATH: /home/build/sdk/go/bin:/usr/bin:/home/build/go/bin:/home/build/android/tools/bin
|
||||
ANDROID_SDK_ROOT: /home/build/android
|
||||
android_sdk_tools_zip: sdk-tools-linux-3859397.zip
|
||||
@@ -36,53 +38,9 @@ environment:
|
||||
secrets:
|
||||
- 75d8a1eb-5fc5-4074-8a36-db6015d6ed5a
|
||||
tasks:
|
||||
- install_go1_14: |
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl -s https://dl.google.com/go/go1.14.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
- test_gio: |
|
||||
cd gio
|
||||
export EGL_PLATFORM=surfaceless # for headless tests
|
||||
go test -race ./...
|
||||
GOOS=windows go test -exec=wine ./...
|
||||
GOOS=js GOARCH=wasm go build -o /dev/null ./...
|
||||
- install_chrome: |
|
||||
curl -s https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
|
||||
sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get -qq install -y google-chrome-stable
|
||||
- test_cmd: |
|
||||
cd gio/cmd
|
||||
go test ./...
|
||||
go test -race ./...
|
||||
cd gogio # since we need -modfile to point at the parent directory
|
||||
GOFLAGS=-modfile=../go.local.mod go test
|
||||
- install_jdk8: |
|
||||
curl -so jdk.deb "https://cdn.azul.com/zulu/bin/zulu8.42.0.21-ca-jdk8.0.232-linux_amd64.deb"
|
||||
sudo apt-get -qq install -y -f ./jdk.deb
|
||||
- install_android: |
|
||||
mkdir android
|
||||
cd android
|
||||
curl -so sdk-tools.zip https://dl.google.com/android/repository/$android_sdk_tools_zip
|
||||
unzip -q sdk-tools.zip
|
||||
rm sdk-tools.zip
|
||||
curl -so ndk.zip https://dl.google.com/android/repository/$android_ndk_zip
|
||||
unzip -q ndk.zip
|
||||
rm ndk.zip
|
||||
mv android-ndk-* ndk-bundle
|
||||
yes|sdkmanager --licenses
|
||||
sdkmanager "platforms;android-29" "build-tools;29.0.2"
|
||||
- test_android: |
|
||||
cd gio
|
||||
CC=$ANDROID_SDK_ROOT/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang GOOS=android GOARCH=arm64 CGO_ENABLED=1 go build ./...
|
||||
- install_gogio: |
|
||||
cd gio/cmd
|
||||
go install ./gogio
|
||||
- test_android_gogio: |
|
||||
mkdir tmp
|
||||
cd tmp
|
||||
go mod init example.com
|
||||
go get -d github.com/p9c/p9/pkg/gel/gio/example/kitchen
|
||||
gogio -target android github.com/p9c/p9/pkg/gel/gio/example/kitchen
|
||||
curl -s https://dl.google.com/go/go1.24.2.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
- check_gofmt: |
|
||||
cd gio
|
||||
test -z "$(gofmt -s -l .)"
|
||||
@@ -99,3 +57,34 @@ tasks:
|
||||
- mirror: |
|
||||
# mirror to github
|
||||
ssh-keyscan github.com > "$HOME"/.ssh/known_hosts && cd gio && git push --mirror "$github_mirror" || echo "failed mirroring"
|
||||
- add_32bit_arch: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y "libwayland-dev:i386" "libx11-dev:i386" "libx11-xcb-dev:i386" "libxkbcommon-dev:i386" "libxkbcommon-x11-dev:i386" "libgles2-mesa-dev:i386" "libegl1-mesa-dev:i386" "libffi-dev:i386" "libvulkan-dev:i386" "libxcursor-dev:i386"
|
||||
- test_gio: |
|
||||
cd gio
|
||||
go test -race ./...
|
||||
CGO_ENABLED=1 GOARCH=386 go test ./...
|
||||
GOOS=windows go test -exec=wine ./...
|
||||
GOOS=js GOARCH=wasm go build -o /dev/null ./...
|
||||
- install_jdk8: |
|
||||
curl -so jdk.deb "https://cdn.azul.com/zulu/bin/zulu8.42.0.21-ca-jdk8.0.232-linux_amd64.deb"
|
||||
sudo apt-get -qq install -y -f ./jdk.deb
|
||||
- install_android: |
|
||||
mkdir android
|
||||
cd android
|
||||
curl -so sdk-tools.zip https://dl.google.com/android/repository/$android_sdk_tools_zip
|
||||
unzip -q sdk-tools.zip
|
||||
rm sdk-tools.zip
|
||||
curl -so ndk.zip https://dl.google.com/android/repository/$android_ndk_zip
|
||||
unzip -q ndk.zip
|
||||
rm ndk.zip
|
||||
mv android-ndk-* ndk-bundle
|
||||
# sdkmanager needs lots of file descriptors
|
||||
ulimit -n 10000
|
||||
yes|sdkmanager --licenses
|
||||
sdkmanager "platforms;android-31" "build-tools;32.0.0"
|
||||
- test_android: |
|
||||
cd gio
|
||||
CC=$ANDROID_SDK_ROOT/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang GOOS=android GOARCH=arm64 CGO_ENABLED=1 go build ./...
|
||||
CC=$ANDROID_SDK_ROOT/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi29-clang GOOS=android GOARCH=arm CGO_ENABLED=1 go build ./...
|
||||
|
||||
@@ -8,14 +8,11 @@ sources:
|
||||
environment:
|
||||
PATH: /home/build/sdk/go/bin:/bin:/usr/local/bin:/usr/bin
|
||||
tasks:
|
||||
- install_go1_14: |
|
||||
- install_go: |
|
||||
mkdir -p /home/build/sdk
|
||||
curl https://dl.google.com/go/go1.14.src.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
curl https://dl.google.com/go/go1.24.2.src.tar.gz | tar -C /home/build/sdk -xzf -
|
||||
cd /home/build/sdk/go/src
|
||||
./make.bash
|
||||
- test_gio: |
|
||||
cd gio
|
||||
go test ./...
|
||||
- test_cmd: |
|
||||
cd gio/cmd
|
||||
go test ./...
|
||||
|
||||
3
pkg/gel/gio/.gitattributes
vendored
Normal file
3
pkg/gel/gio/.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Treat all files as binary, with no git magic updating
|
||||
# line endings.
|
||||
* -text
|
||||
50
pkg/gel/gio/LOCAL_MODIFICATIONS.md
Normal file
50
pkg/gel/gio/LOCAL_MODIFICATIONS.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Local Modifications to Vendored GIO
|
||||
|
||||
## Import Path Changes
|
||||
|
||||
The original GIO library uses import paths starting with `gioui.org/`.
|
||||
This vendored copy has been modified to use `github.com/p9c/p9/pkg/gel/gio/`.
|
||||
|
||||
### Examples of modified imports:
|
||||
|
||||
| Original GIO Path | Vendored Path |
|
||||
|-------------------|---------------|
|
||||
| `gioui.org/app` | `github.com/p9c/p9/pkg/gel/gio/app` |
|
||||
| `gioui.org/layout` | `github.com/p9c/p9/pkg/gel/gio/layout` |
|
||||
| `gioui.org/op` | `github.com/p9c/p9/pkg/gel/gio/op` |
|
||||
| `gioui.org/unit` | `github.com/p9c/p9/pkg/gel/gio/unit` |
|
||||
| `gioui.org/widget` | `github.com/p9c/p9/pkg/gel/gio/widget` |
|
||||
| `gioui.org/shader` | `github.com/p9c/p9/pkg/gel/gio/shader` |
|
||||
| etc. | etc. |
|
||||
|
||||
## Version Information
|
||||
|
||||
**Updated: November 2024**
|
||||
|
||||
This vendored copy is now from the latest GIO (as of November 2024):
|
||||
- Go 1.23+ required
|
||||
- New API patterns (unit.Dp/unit.Sp typed units, input.Source, etc.)
|
||||
- Package structure includes io/input (renamed from io/router)
|
||||
- Shader module included from gioui.org/shader v1.0.8
|
||||
|
||||
## External Dependencies
|
||||
|
||||
The following external dependencies are still required (not vendored):
|
||||
- `eliasnaur.com/font` - Font collection
|
||||
- `github.com/go-text/typesetting` - Text layout and shaping
|
||||
|
||||
## Removed Components
|
||||
|
||||
The following were removed from the vendor:
|
||||
- `shader/piet/` - Compute shader programs (require gioui.org/cpu)
|
||||
- `shader/cmd/` - Shader compilation tools
|
||||
|
||||
## When Updating
|
||||
|
||||
After replacing with a newer GIO, all imports need to be rewritten:
|
||||
- `gioui.org/...` → `github.com/p9c/p9/pkg/gel/gio/...`
|
||||
|
||||
This can be done with:
|
||||
```bash
|
||||
find pkg/gel/gio -name "*.go" -type f -exec sed -i 's|"gioui.org/|"github.com/p9c/p9/pkg/gel/gio/|g' {} \;
|
||||
```
|
||||
@@ -1,11 +1,11 @@
|
||||
# Gio - https://github.com/p9c/p9/pkg/gel/gio
|
||||
# Gio - https://gioui.org
|
||||
|
||||
Immediate mode GUI programs in Go for Android, iOS, macOS, Linux,
|
||||
FreeBSD, OpenBSD, Windows, and WebAssembly (experimental).
|
||||
|
||||
# Installation, examples, documentation
|
||||
|
||||
Go to [github.com/p9c/p9/pkg/gel/gio](https://github.com/p9c/p9/pkg/gel/gio).
|
||||
Go to [gioui.org](https://gioui.org).
|
||||
|
||||
[](https://builds.sr.ht/~eliasnaur/gio)
|
||||
|
||||
@@ -21,6 +21,15 @@ Post discussion to the [mailing list](https://lists.sr.ht/~eliasnaur/gio) and pa
|
||||
[gio-patches](https://lists.sr.ht/~eliasnaur/gio-patches). No Sourcehut
|
||||
account is required and you can post without being subscribed.
|
||||
|
||||
See the [contribution guide](https://github.com/p9c/p9/pkg/gel/gio/doc/contribute) for more details.
|
||||
See the [contribution guide](https://gioui.org/doc/contribute) for more details.
|
||||
|
||||
An [official GitHub mirror](https://github.com/gioui/gio) is available.
|
||||
|
||||
## Tags
|
||||
|
||||
Pre-1.0 tags are provided for reference only, and do not designate releases with ongoing support. Bugfixes will not be backported to older tags.
|
||||
|
||||
Tags follow semantic versioning. In particular, as the major version is zero:
|
||||
|
||||
- breaking API or behavior changes will increment the *minor* version component.
|
||||
- non-breaking changes will increment the *patch* version component.
|
||||
|
||||
@@ -3,27 +3,32 @@
|
||||
package org.gioui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.content.res.Configuration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public final class GioActivity extends Activity {
|
||||
private GioView view;
|
||||
public FrameLayout layer;
|
||||
|
||||
@Override public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
super.onCreate(state);
|
||||
|
||||
Window w = getWindow();
|
||||
layer = new FrameLayout(this);
|
||||
view = new GioView(this);
|
||||
|
||||
this.view = new GioView(this);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
this.view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
}
|
||||
this.view.setLayoutParams(new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT));
|
||||
setContentView(view);
|
||||
view.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
));
|
||||
view.setFocusable(true);
|
||||
view.setFocusableInTouchMode(true);
|
||||
|
||||
layer.addView(view);
|
||||
setContentView(layer);
|
||||
}
|
||||
|
||||
@Override public void onDestroy() {
|
||||
@@ -48,7 +53,7 @@ public final class GioActivity extends Activity {
|
||||
|
||||
@Override public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
view.lowMemory();
|
||||
GioView.onLowMemory();
|
||||
}
|
||||
|
||||
@Override public void onBackPressed() {
|
||||
842
pkg/gel/gio/app/GioView.java
Normal file
842
pkg/gel/gio/app/GioView.java
Normal file
@@ -0,0 +1,842 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package org.gioui;
|
||||
|
||||
import java.lang.Class;
|
||||
import java.lang.IllegalAccessException;
|
||||
import java.lang.InstantiationException;
|
||||
import java.lang.ExceptionInInitializerError;
|
||||
import java.lang.SecurityException;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
import android.text.Selection;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Choreographer;
|
||||
import android.view.Display;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.PointerIcon;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.CorrectionInfo;
|
||||
import android.view.inputmethod.CompletionInfo;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
import android.view.inputmethod.ExtractedTextRequest;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.InputContentInfo;
|
||||
import android.view.inputmethod.SurroundingText;
|
||||
import android.view.accessibility.AccessibilityNodeProvider;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public final class GioView extends SurfaceView implements Choreographer.FrameCallback {
|
||||
private static boolean jniLoaded;
|
||||
|
||||
private final SurfaceHolder.Callback surfCallbacks;
|
||||
private final View.OnFocusChangeListener focusCallback;
|
||||
private final InputMethodManager imm;
|
||||
private final float scrollXScale;
|
||||
private final float scrollYScale;
|
||||
private final AccessibilityManager accessManager;
|
||||
private int keyboardHint;
|
||||
|
||||
private long nhandle;
|
||||
|
||||
public GioView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public GioView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
}
|
||||
setLayoutParams(new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT));
|
||||
|
||||
// Late initialization of the Go runtime to wait for a valid context.
|
||||
Gio.init(context.getApplicationContext());
|
||||
|
||||
// Set background color to transparent to avoid a flickering
|
||||
// issue on ChromeOS.
|
||||
setBackgroundColor(Color.argb(0, 0, 0, 0));
|
||||
|
||||
ViewConfiguration conf = ViewConfiguration.get(context);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
scrollXScale = conf.getScaledHorizontalScrollFactor();
|
||||
scrollYScale = conf.getScaledVerticalScrollFactor();
|
||||
|
||||
// The platform focus highlight is not aware of Gio's widgets.
|
||||
setDefaultFocusHighlightEnabled(false);
|
||||
} else {
|
||||
float listItemHeight = 48; // dp
|
||||
float px = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
listItemHeight,
|
||||
getResources().getDisplayMetrics()
|
||||
);
|
||||
scrollXScale = px;
|
||||
scrollYScale = px;
|
||||
}
|
||||
|
||||
setHighRefreshRate();
|
||||
|
||||
accessManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
nhandle = onCreateView(this);
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
focusCallback = new View.OnFocusChangeListener() {
|
||||
@Override public void onFocusChange(View v, boolean focus) {
|
||||
GioView.this.onFocusChange(nhandle, focus);
|
||||
}
|
||||
};
|
||||
setOnFocusChangeListener(focusCallback);
|
||||
surfCallbacks = new SurfaceHolder.Callback() {
|
||||
@Override public void surfaceCreated(SurfaceHolder holder) {
|
||||
// Ignore; surfaceChanged is guaranteed to be called immediately after this.
|
||||
}
|
||||
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
onSurfaceChanged(nhandle, getHolder().getSurface());
|
||||
}
|
||||
@Override public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
onSurfaceDestroyed(nhandle);
|
||||
}
|
||||
};
|
||||
getHolder().addCallback(surfCallbacks);
|
||||
}
|
||||
|
||||
@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (nhandle != 0) {
|
||||
onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), true, event.getEventTime());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (nhandle != 0) {
|
||||
onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), false, event.getEventTime());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
dispatchMotionEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean onTouchEvent(MotionEvent event) {
|
||||
// Ask for unbuffered events. Flutter and Chrome do it
|
||||
// so assume it's good for us as well.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
requestUnbufferedDispatch(event);
|
||||
}
|
||||
|
||||
dispatchMotionEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setCursor(int id) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
return;
|
||||
}
|
||||
PointerIcon pointerIcon = PointerIcon.getSystemIcon(getContext(), id);
|
||||
setPointerIcon(pointerIcon);
|
||||
}
|
||||
|
||||
private void setOrientation(int id, int fallback) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
id = fallback;
|
||||
}
|
||||
((Activity) this.getContext()).setRequestedOrientation(id);
|
||||
}
|
||||
|
||||
private void setFullscreen(boolean enabled) {
|
||||
int flags = this.getSystemUiVisibility();
|
||||
if (enabled) {
|
||||
flags |= SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
flags |= SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
||||
flags |= SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
flags |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||
} else {
|
||||
flags &= ~SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
flags &= ~SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
||||
flags &= ~SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
flags &= ~SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||
}
|
||||
this.setSystemUiVisibility(flags);
|
||||
}
|
||||
|
||||
private enum Bar {
|
||||
NAVIGATION,
|
||||
STATUS,
|
||||
}
|
||||
|
||||
private void setBarColor(Bar t, int color, int luminance) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return;
|
||||
}
|
||||
|
||||
Window window = ((Activity) this.getContext()).getWindow();
|
||||
|
||||
int insetsMask;
|
||||
int viewMask;
|
||||
|
||||
switch (t) {
|
||||
case STATUS:
|
||||
insetsMask = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
|
||||
viewMask = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
||||
window.setStatusBarColor(color);
|
||||
break;
|
||||
case NAVIGATION:
|
||||
insetsMask = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
|
||||
viewMask = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
||||
window.setNavigationBarColor(color);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("invalid bar type");
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
int flags = this.getSystemUiVisibility();
|
||||
if (luminance > 128) {
|
||||
flags |= viewMask;
|
||||
} else {
|
||||
flags &= ~viewMask;
|
||||
}
|
||||
this.setSystemUiVisibility(flags);
|
||||
return;
|
||||
}
|
||||
|
||||
WindowInsetsController insetsController = window.getInsetsController();
|
||||
if (insetsController == null) {
|
||||
return;
|
||||
}
|
||||
if (luminance > 128) {
|
||||
insetsController.setSystemBarsAppearance(insetsMask, insetsMask);
|
||||
} else {
|
||||
insetsController.setSystemBarsAppearance(0, insetsMask);
|
||||
}
|
||||
}
|
||||
|
||||
private void setStatusColor(int color, int luminance) {
|
||||
this.setBarColor(Bar.STATUS, color, luminance);
|
||||
}
|
||||
|
||||
private void setNavigationColor(int color, int luminance) {
|
||||
this.setBarColor(Bar.NAVIGATION, color, luminance);
|
||||
}
|
||||
|
||||
private void setHighRefreshRate() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = getContext();
|
||||
Display display = context.getDisplay();
|
||||
Display.Mode[] supportedModes = display.getSupportedModes();
|
||||
if (supportedModes.length <= 1) {
|
||||
// Nothing to set
|
||||
return;
|
||||
}
|
||||
|
||||
Display.Mode currentMode = display.getMode();
|
||||
int currentWidth = currentMode.getPhysicalWidth();
|
||||
int currentHeight = currentMode.getPhysicalHeight();
|
||||
|
||||
float minRefreshRate = -1;
|
||||
float maxRefreshRate = -1;
|
||||
float bestRefreshRate = -1;
|
||||
int bestModeId = -1;
|
||||
for (Display.Mode mode : supportedModes) {
|
||||
float refreshRate = mode.getRefreshRate();
|
||||
float width = mode.getPhysicalWidth();
|
||||
float height = mode.getPhysicalHeight();
|
||||
|
||||
if (minRefreshRate == -1 || refreshRate < minRefreshRate) {
|
||||
minRefreshRate = refreshRate;
|
||||
}
|
||||
if (maxRefreshRate == -1 || refreshRate > maxRefreshRate) {
|
||||
maxRefreshRate = refreshRate;
|
||||
}
|
||||
|
||||
boolean refreshRateIsBetter = bestRefreshRate == -1 || refreshRate > bestRefreshRate;
|
||||
if (width == currentWidth && height == currentHeight && refreshRateIsBetter) {
|
||||
int modeId = mode.getModeId();
|
||||
bestRefreshRate = refreshRate;
|
||||
bestModeId = modeId;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestModeId == -1) {
|
||||
// Not expecting this but just in case
|
||||
return;
|
||||
}
|
||||
|
||||
if (minRefreshRate == maxRefreshRate) {
|
||||
// Can't improve the refresh rate
|
||||
return;
|
||||
}
|
||||
|
||||
Window window = ((Activity) context).getWindow();
|
||||
WindowManager.LayoutParams layoutParams = window.getAttributes();
|
||||
layoutParams.preferredDisplayModeId = bestModeId;
|
||||
window.setAttributes(layoutParams);
|
||||
}
|
||||
|
||||
@Override protected boolean dispatchHoverEvent(MotionEvent event) {
|
||||
if (!accessManager.isTouchExplorationEnabled()) {
|
||||
return super.dispatchHoverEvent(event);
|
||||
}
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_HOVER_ENTER:
|
||||
// Fall through.
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
onTouchExploration(nhandle, event.getX(), event.getY());
|
||||
break;
|
||||
case MotionEvent.ACTION_HOVER_EXIT:
|
||||
onExitTouchExploration(nhandle);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void sendA11yEvent(int eventType, int viewId) {
|
||||
if (!accessManager.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
AccessibilityEvent event = obtainA11yEvent(eventType, viewId);
|
||||
getParent().requestSendAccessibilityEvent(this, event);
|
||||
}
|
||||
|
||||
AccessibilityEvent obtainA11yEvent(int eventType, int viewId) {
|
||||
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
|
||||
event.setPackageName(getContext().getPackageName());
|
||||
event.setSource(this, viewId);
|
||||
return event;
|
||||
}
|
||||
|
||||
boolean isA11yActive() {
|
||||
return accessManager.isEnabled();
|
||||
}
|
||||
|
||||
void sendA11yChange(int viewId) {
|
||||
if (!accessManager.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
AccessibilityEvent event = obtainA11yEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED, viewId);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
|
||||
}
|
||||
getParent().requestSendAccessibilityEvent(this, event);
|
||||
}
|
||||
|
||||
private void dispatchMotionEvent(MotionEvent event) {
|
||||
if (nhandle == 0) {
|
||||
return;
|
||||
}
|
||||
for (int j = 0; j < event.getHistorySize(); j++) {
|
||||
long time = event.getHistoricalEventTime(j);
|
||||
for (int i = 0; i < event.getPointerCount(); i++) {
|
||||
onTouchEvent(
|
||||
nhandle,
|
||||
event.ACTION_MOVE,
|
||||
event.getPointerId(i),
|
||||
event.getToolType(i),
|
||||
event.getHistoricalX(i, j),
|
||||
event.getHistoricalY(i, j),
|
||||
scrollXScale*event.getHistoricalAxisValue(MotionEvent.AXIS_HSCROLL, i, j),
|
||||
scrollYScale*event.getHistoricalAxisValue(MotionEvent.AXIS_VSCROLL, i, j),
|
||||
event.getButtonState(),
|
||||
time);
|
||||
}
|
||||
}
|
||||
int act = event.getActionMasked();
|
||||
int idx = event.getActionIndex();
|
||||
for (int i = 0; i < event.getPointerCount(); i++) {
|
||||
int pact = event.ACTION_MOVE;
|
||||
if (i == idx) {
|
||||
pact = act;
|
||||
}
|
||||
onTouchEvent(
|
||||
nhandle,
|
||||
pact,
|
||||
event.getPointerId(i),
|
||||
event.getToolType(i),
|
||||
event.getX(i), event.getY(i),
|
||||
scrollXScale*event.getAxisValue(MotionEvent.AXIS_HSCROLL, i),
|
||||
scrollYScale*event.getAxisValue(MotionEvent.AXIS_VSCROLL, i),
|
||||
event.getButtonState(),
|
||||
event.getEventTime());
|
||||
}
|
||||
}
|
||||
|
||||
@Override public InputConnection onCreateInputConnection(EditorInfo editor) {
|
||||
Snippet snip = getSnippet();
|
||||
editor.inputType = this.keyboardHint;
|
||||
editor.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
|
||||
editor.initialSelStart = imeToUTF16(nhandle, imeSelectionStart(nhandle));
|
||||
editor.initialSelEnd = imeToUTF16(nhandle, imeSelectionEnd(nhandle));
|
||||
int selStart = editor.initialSelStart - snip.offset;
|
||||
editor.initialCapsMode = TextUtils.getCapsMode(snip.snippet, selStart, this.keyboardHint);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
editor.setInitialSurroundingSubText(snip.snippet, imeToUTF16(nhandle, snip.offset));
|
||||
}
|
||||
imeSetComposingRegion(nhandle, -1, -1);
|
||||
return new GioInputConnection();
|
||||
}
|
||||
|
||||
void setInputHint(int hint) {
|
||||
if (hint == this.keyboardHint) {
|
||||
return;
|
||||
}
|
||||
this.keyboardHint = hint;
|
||||
restartInput();
|
||||
}
|
||||
|
||||
void showTextInput() {
|
||||
GioView.this.requestFocus();
|
||||
imm.showSoftInput(GioView.this, 0);
|
||||
}
|
||||
|
||||
void hideTextInput() {
|
||||
imm.hideSoftInputFromWindow(getWindowToken(), 0);
|
||||
}
|
||||
|
||||
@Override protected boolean fitSystemWindows(Rect insets) {
|
||||
if (nhandle != 0) {
|
||||
onWindowInsets(nhandle, insets.top, insets.right, insets.bottom, insets.left);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void postFrameCallback() {
|
||||
Choreographer.getInstance().removeFrameCallback(this);
|
||||
Choreographer.getInstance().postFrameCallback(this);
|
||||
}
|
||||
|
||||
@Override public void doFrame(long nanos) {
|
||||
if (nhandle != 0) {
|
||||
onFrameCallback(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
int getDensity() {
|
||||
return getResources().getDisplayMetrics().densityDpi;
|
||||
}
|
||||
|
||||
float getFontScale() {
|
||||
return getResources().getConfiguration().fontScale;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (nhandle != 0) {
|
||||
onStartView(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (nhandle != 0) {
|
||||
onStopView(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (nhandle != 0) {
|
||||
onDestroyView(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
protected void unregister() {
|
||||
setOnFocusChangeListener(null);
|
||||
getHolder().removeCallback(surfCallbacks);
|
||||
nhandle = 0;
|
||||
}
|
||||
|
||||
public void configurationChanged() {
|
||||
if (nhandle != 0) {
|
||||
onConfigurationChanged(nhandle);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean backPressed() {
|
||||
if (nhandle == 0) {
|
||||
return false;
|
||||
}
|
||||
return onBack(nhandle);
|
||||
}
|
||||
|
||||
void restartInput() {
|
||||
imm.restartInput(this);
|
||||
}
|
||||
|
||||
void updateSelection() {
|
||||
int selStart = imeToUTF16(nhandle, imeSelectionStart(nhandle));
|
||||
int selEnd = imeToUTF16(nhandle, imeSelectionEnd(nhandle));
|
||||
int compStart = imeToUTF16(nhandle, imeComposingStart(nhandle));
|
||||
int compEnd = imeToUTF16(nhandle, imeComposingEnd(nhandle));
|
||||
imm.updateSelection(this, selStart, selEnd, compStart, compEnd);
|
||||
}
|
||||
|
||||
void updateCaret(float m00, float m01, float m02, float m10, float m11, float m12, float caretX, float caretTop, float caretBase, float caretBottom) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return;
|
||||
}
|
||||
Matrix m = new Matrix();
|
||||
m.setValues(new float[]{m00, m01, m02, m10, m11, m12, 0.0f, 0.0f, 1.0f});
|
||||
m.setConcat(getMatrix(), m);
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
int compStart = imeComposingStart(nhandle);
|
||||
int compEnd = imeComposingEnd(nhandle);
|
||||
Snippet snip = getSnippet();
|
||||
String composing = "";
|
||||
if (compStart != -1) {
|
||||
composing = snip.substringRunes(compStart, compEnd);
|
||||
}
|
||||
CursorAnchorInfo inf = new CursorAnchorInfo.Builder()
|
||||
.setMatrix(m)
|
||||
.setComposingText(imeToUTF16(nhandle, compStart), composing)
|
||||
.setSelectionRange(imeToUTF16(nhandle, selStart), imeToUTF16(nhandle, selEnd))
|
||||
.setInsertionMarkerLocation(caretX, caretTop, caretBase, caretBottom, 0)
|
||||
.build();
|
||||
imm.updateCursorAnchorInfo(this, inf);
|
||||
}
|
||||
|
||||
static private native long onCreateView(GioView view);
|
||||
static private native void onDestroyView(long handle);
|
||||
static private native void onStartView(long handle);
|
||||
static private native void onStopView(long handle);
|
||||
static private native void onSurfaceDestroyed(long handle);
|
||||
static private native void onSurfaceChanged(long handle, Surface surface);
|
||||
static private native void onConfigurationChanged(long handle);
|
||||
static private native void onWindowInsets(long handle, int top, int right, int bottom, int left);
|
||||
static public native void onLowMemory();
|
||||
static private native void onTouchEvent(long handle, int action, int pointerID, int tool, float x, float y, float scrollX, float scrollY, int buttons, long time);
|
||||
static private native void onKeyEvent(long handle, int code, int character, boolean pressed, long time);
|
||||
static private native void onFrameCallback(long handle);
|
||||
static private native boolean onBack(long handle);
|
||||
static private native void onFocusChange(long handle, boolean focus);
|
||||
static private native AccessibilityNodeInfo initializeAccessibilityNodeInfo(long handle, int viewId, int screenX, int screenY, AccessibilityNodeInfo info);
|
||||
static private native void onTouchExploration(long handle, float x, float y);
|
||||
static private native void onExitTouchExploration(long handle);
|
||||
static private native void onA11yFocus(long handle, int viewId);
|
||||
static private native void onClearA11yFocus(long handle, int viewId);
|
||||
static private native void imeSetSnippet(long handle, int start, int end);
|
||||
static private native String imeSnippet(long handle);
|
||||
static private native int imeSnippetStart(long handle);
|
||||
static private native int imeSelectionStart(long handle);
|
||||
static private native int imeSelectionEnd(long handle);
|
||||
static private native int imeComposingStart(long handle);
|
||||
static private native int imeComposingEnd(long handle);
|
||||
static private native int imeReplace(long handle, int start, int end, String text);
|
||||
static private native int imeSetSelection(long handle, int start, int end);
|
||||
static private native int imeSetComposingRegion(long handle, int start, int end);
|
||||
// imeToRunes converts the Java character index into runes (Java code points).
|
||||
static private native int imeToRunes(long handle, int chars);
|
||||
// imeToUTF16 converts the rune index into Java characters.
|
||||
static private native int imeToUTF16(long handle, int runes);
|
||||
|
||||
private class GioInputConnection implements InputConnection {
|
||||
private int batchDepth;
|
||||
|
||||
@Override public boolean beginBatchEdit() {
|
||||
batchDepth++;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean endBatchEdit() {
|
||||
batchDepth--;
|
||||
return batchDepth > 0;
|
||||
}
|
||||
|
||||
@Override public boolean clearMetaKeyStates(int states) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean commitCompletion(CompletionInfo text) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean commitCorrection(CorrectionInfo info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean commitText(CharSequence text, int cursor) {
|
||||
setComposingText(text, cursor);
|
||||
return finishComposingText();
|
||||
}
|
||||
|
||||
@Override public boolean deleteSurroundingText(int beforeChars, int afterChars) {
|
||||
// translate before and after to runes.
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
int before = selStart - imeToRunes(nhandle, imeToUTF16(nhandle, selStart) - beforeChars);
|
||||
int after = selEnd - imeToRunes(nhandle, imeToUTF16(nhandle, selEnd) - afterChars);
|
||||
return deleteSurroundingTextInCodePoints(before, after);
|
||||
}
|
||||
|
||||
@Override public boolean finishComposingText() {
|
||||
imeSetComposingRegion(nhandle, -1, -1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public int getCursorCapsMode(int reqModes) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int off = imeToUTF16(nhandle, selStart - snip.offset);
|
||||
if (off < 0 || off > snip.snippet.length()) {
|
||||
return 0;
|
||||
}
|
||||
return TextUtils.getCapsMode(snip.snippet, off, reqModes);
|
||||
}
|
||||
|
||||
@Override public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public CharSequence getSelectedText(int flags) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
String sub = snip.substringRunes(selStart, selEnd);
|
||||
return sub;
|
||||
}
|
||||
|
||||
@Override public CharSequence getTextAfterCursor(int n, int flags) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
// n are in Java characters, but in worst case we'll just ask for more runes
|
||||
// than wanted.
|
||||
imeSetSnippet(nhandle, selStart - n, selEnd + n);
|
||||
int start = selEnd;
|
||||
int end = imeToRunes(nhandle, imeToUTF16(nhandle, selEnd) + n);
|
||||
String ret = snip.substringRunes(start, end);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override public CharSequence getTextBeforeCursor(int n, int flags) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
// n are in Java characters, but in worst case we'll just ask for more runes
|
||||
// than wanted.
|
||||
imeSetSnippet(nhandle, selStart - n, selEnd + n);
|
||||
int start = imeToRunes(nhandle, imeToUTF16(nhandle, selStart) - n);
|
||||
int end = selStart;
|
||||
String ret = snip.substringRunes(start, end);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override public boolean performContextMenuAction(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean performEditorAction(int editorAction) {
|
||||
long eventTime = SystemClock.uptimeMillis();
|
||||
// Translate to enter key.
|
||||
onKeyEvent(nhandle, KeyEvent.KEYCODE_ENTER, '\n', true, eventTime);
|
||||
onKeyEvent(nhandle, KeyEvent.KEYCODE_ENTER, '\n', false, eventTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean performPrivateCommand(String action, Bundle data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean reportFullscreenMode(boolean enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean sendKeyEvent(KeyEvent event) {
|
||||
boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
|
||||
onKeyEvent(nhandle, event.getKeyCode(), event.getUnicodeChar(), pressed, event.getEventTime());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean setComposingRegion(int startChars, int endChars) {
|
||||
int compStart = imeToRunes(nhandle, startChars);
|
||||
int compEnd = imeToRunes(nhandle, endChars);
|
||||
imeSetComposingRegion(nhandle, compStart, compEnd);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean setComposingText(CharSequence text, int relCursor) {
|
||||
int start = imeComposingStart(nhandle);
|
||||
int end = imeComposingEnd(nhandle);
|
||||
if (start == -1 || end == -1) {
|
||||
start = imeSelectionStart(nhandle);
|
||||
end = imeSelectionEnd(nhandle);
|
||||
}
|
||||
String str = text.toString();
|
||||
imeReplace(nhandle, start, end, str);
|
||||
int cursor = start;
|
||||
int runes = str.codePointCount(0, str.length());
|
||||
if (relCursor > 0) {
|
||||
cursor += runes;
|
||||
relCursor--;
|
||||
}
|
||||
imeSetComposingRegion(nhandle, start, start + runes);
|
||||
|
||||
// Move cursor.
|
||||
Snippet snip = getSnippet();
|
||||
cursor = imeToRunes(nhandle, imeToUTF16(nhandle, cursor) + relCursor);
|
||||
imeSetSelection(nhandle, cursor, cursor);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean setSelection(int startChars, int endChars) {
|
||||
int start = imeToRunes(nhandle, startChars);
|
||||
int end = imeToRunes(nhandle, endChars);
|
||||
imeSetSelection(nhandle, start, end);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*@Override*/ public boolean requestCursorUpdates(int cursorUpdateMode) {
|
||||
// We always provide cursor updates.
|
||||
return true;
|
||||
}
|
||||
|
||||
/*@Override*/ public void closeConnection() {
|
||||
}
|
||||
|
||||
/*@Override*/ public Handler getHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*@Override*/ public boolean commitContent(InputContentInfo info, int flags, Bundle opts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*@Override*/ public boolean deleteSurroundingTextInCodePoints(int before, int after) {
|
||||
if (after > 0) {
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
imeReplace(nhandle, selEnd, selEnd + after, "");
|
||||
}
|
||||
if (before > 0) {
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
imeReplace(nhandle, selStart - before, selStart, "");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*@Override*/ public SurroundingText getSurroundingText(int beforeChars, int afterChars, int flags) {
|
||||
Snippet snip = getSnippet();
|
||||
int selStart = imeSelectionStart(nhandle);
|
||||
int selEnd = imeSelectionEnd(nhandle);
|
||||
// Expanding in Java characters is ok.
|
||||
imeSetSnippet(nhandle, selStart - beforeChars, selEnd + afterChars);
|
||||
return new SurroundingText(snip.snippet, imeToUTF16(nhandle, selStart), imeToUTF16(nhandle, selEnd), imeToUTF16(nhandle, snip.offset));
|
||||
}
|
||||
}
|
||||
|
||||
private Snippet getSnippet() {
|
||||
Snippet snip = new Snippet();
|
||||
snip.snippet = imeSnippet(nhandle);
|
||||
snip.offset = imeSnippetStart(nhandle);
|
||||
return snip;
|
||||
}
|
||||
|
||||
// Snippet is like android.view.inputmethod.SurroundingText but available for Android < 31.
|
||||
private static class Snippet {
|
||||
String snippet;
|
||||
// offset of snippet into the entire editor content. It is in runes because we won't require
|
||||
// Gio editors to keep track of UTF-16 offsets. The distinction won't matter in practice because IMEs only
|
||||
// ever see snippets.
|
||||
int offset;
|
||||
|
||||
// substringRunes returns the substring from start to end in runes. The resuls is
|
||||
// truncated to the snippet.
|
||||
String substringRunes(int start, int end) {
|
||||
start -= this.offset;
|
||||
end -= this.offset;
|
||||
int runes = snippet.codePointCount(0, snippet.length());
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
}
|
||||
if (end < 0) {
|
||||
end = 0;
|
||||
}
|
||||
if (start > runes) {
|
||||
start = runes;
|
||||
}
|
||||
if (end > runes) {
|
||||
end = runes;
|
||||
}
|
||||
return snippet.substring(
|
||||
snippet.offsetByCodePoints(0, start),
|
||||
snippet.offsetByCodePoints(0, end)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public AccessibilityNodeProvider getAccessibilityNodeProvider() {
|
||||
return new AccessibilityNodeProvider() {
|
||||
private final int[] screenOff = new int[2];
|
||||
|
||||
@Override public AccessibilityNodeInfo createAccessibilityNodeInfo(int viewId) {
|
||||
AccessibilityNodeInfo info = null;
|
||||
if (viewId == View.NO_ID) {
|
||||
info = AccessibilityNodeInfo.obtain(GioView.this);
|
||||
GioView.this.onInitializeAccessibilityNodeInfo(info);
|
||||
} else {
|
||||
info = AccessibilityNodeInfo.obtain(GioView.this, viewId);
|
||||
info.setPackageName(getContext().getPackageName());
|
||||
info.setVisibleToUser(true);
|
||||
}
|
||||
GioView.this.getLocationOnScreen(screenOff);
|
||||
info = GioView.this.initializeAccessibilityNodeInfo(nhandle, viewId, screenOff[0], screenOff[1], info);
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override public boolean performAction(int viewId, int action, Bundle arguments) {
|
||||
if (viewId == View.NO_ID) {
|
||||
return GioView.this.performAccessibilityAction(action, arguments);
|
||||
}
|
||||
switch (action) {
|
||||
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
|
||||
GioView.this.onA11yFocus(nhandle, viewId);
|
||||
GioView.this.sendA11yEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, viewId);
|
||||
return true;
|
||||
case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
|
||||
GioView.this.onClearA11yFocus(nhandle, viewId);
|
||||
GioView.this.sendA11yEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, viewId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,16 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"image"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/app/internal/wm"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/input"
|
||||
"github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
)
|
||||
|
||||
// extraArgs contains extra arguments to append to
|
||||
@@ -16,10 +22,93 @@ import (
|
||||
// Set with the go linker flag -X.
|
||||
var extraArgs string
|
||||
|
||||
func init() {
|
||||
if extraArgs != "" {
|
||||
args := strings.Split(extraArgs, "|")
|
||||
os.Args = append(os.Args, args...)
|
||||
// ID is the app id exposed to the platform.
|
||||
//
|
||||
// On Android ID is the package property of AndroidManifest.xml,
|
||||
// on iOS ID is the CFBundleIdentifier of the app Info.plist,
|
||||
// on Wayland it is the toplevel app_id,
|
||||
// on X11 it is the X11 XClassHint.
|
||||
//
|
||||
// ID is set by the [gioui.org/cmd/gogio] tool or manually with the -X linker flag. For example,
|
||||
//
|
||||
// go build -ldflags="-X 'gioui.org/app.ID=org.gioui.example.Kitchen'" .
|
||||
//
|
||||
// Note that ID is treated as a constant, and that changing it at runtime
|
||||
// is not supported. The default value of ID is filepath.Base(os.Args[0]).
|
||||
var ID = ""
|
||||
|
||||
// A FrameEvent requests a new frame in the form of a list of
|
||||
// operations that describes the window content.
|
||||
type FrameEvent struct {
|
||||
// Now is the current animation. Use Now instead of time.Now to
|
||||
// synchronize animation and to avoid the time.Now call overhead.
|
||||
Now time.Time
|
||||
// Metric converts device independent dp and sp to device pixels.
|
||||
Metric unit.Metric
|
||||
// Size is the dimensions of the window.
|
||||
Size image.Point
|
||||
// Insets represent the space occupied by system decorations and controls.
|
||||
Insets Insets
|
||||
// Frame completes the FrameEvent by drawing the graphical operations
|
||||
// from ops into the window.
|
||||
Frame func(frame *op.Ops)
|
||||
// Source is the interface between the window and widgets.
|
||||
Source input.Source
|
||||
}
|
||||
|
||||
// ViewEvent provides handles to the underlying window objects for the
|
||||
// current display protocol.
|
||||
type ViewEvent interface {
|
||||
implementsViewEvent()
|
||||
ImplementsEvent()
|
||||
// Valid will return true when the ViewEvent does contains valid handles.
|
||||
// If a window receives an invalid ViewEvent, it should deinitialize any
|
||||
// state referring to handles from a previous ViewEvent.
|
||||
Valid() bool
|
||||
}
|
||||
|
||||
// Insets is the space taken up by
|
||||
// system decoration such as translucent
|
||||
// system bars and software keyboards.
|
||||
type Insets struct {
|
||||
// Values are in pixels.
|
||||
Top, Bottom, Left, Right unit.Dp
|
||||
}
|
||||
|
||||
// NewContext is shorthand for
|
||||
//
|
||||
// layout.Context{
|
||||
// Ops: ops,
|
||||
// Now: e.Now,
|
||||
// Source: e.Source,
|
||||
// Metric: e.Metric,
|
||||
// Constraints: layout.Exact(e.Size),
|
||||
// }
|
||||
//
|
||||
// NewContext calls ops.Reset and adjusts ops for e.Insets.
|
||||
func NewContext(ops *op.Ops, e FrameEvent) layout.Context {
|
||||
ops.Reset()
|
||||
|
||||
size := e.Size
|
||||
|
||||
if e.Insets != (Insets{}) {
|
||||
left := e.Metric.Dp(e.Insets.Left)
|
||||
top := e.Metric.Dp(e.Insets.Top)
|
||||
op.Offset(image.Point{
|
||||
X: left,
|
||||
Y: top,
|
||||
}).Add(ops)
|
||||
|
||||
size.X -= left + e.Metric.Dp(e.Insets.Right)
|
||||
size.Y -= top + e.Metric.Dp(e.Insets.Bottom)
|
||||
}
|
||||
|
||||
return layout.Context{
|
||||
Ops: ops,
|
||||
Now: e.Now,
|
||||
Source: e.Source,
|
||||
Metric: e.Metric,
|
||||
Constraints: layout.Exact(size),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,5 +133,17 @@ func DataDir() (string, error) {
|
||||
// require control of the main thread of the program for
|
||||
// running windows.
|
||||
func Main() {
|
||||
wm.Main()
|
||||
osMain()
|
||||
}
|
||||
|
||||
func (FrameEvent) ImplementsEvent() {}
|
||||
|
||||
func init() {
|
||||
if extraArgs != "" {
|
||||
args := strings.Split(extraArgs, "|")
|
||||
os.Args = append(os.Args, args...)
|
||||
}
|
||||
if ID == "" {
|
||||
ID = filepath.Base(os.Args[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/p9c/p9/pkg/gel/gio/app/internal/wm"
|
||||
)
|
||||
|
||||
type ViewEvent = wm.ViewEvent
|
||||
|
||||
// JavaVM returns the global JNI JavaVM.
|
||||
func JavaVM() uintptr {
|
||||
return wm.JavaVM()
|
||||
}
|
||||
|
||||
// AppContext returns the global Application context as a JNI
|
||||
// jobject.
|
||||
func AppContext() uintptr {
|
||||
return wm.AppContext()
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -17,19 +17,18 @@ type d3d11Context struct {
|
||||
|
||||
swchain *d3d11.IDXGISwapChain
|
||||
renderTarget *d3d11.RenderTargetView
|
||||
depthView *d3d11.DepthStencilView
|
||||
width, height int
|
||||
}
|
||||
|
||||
const debug = false
|
||||
const debugDirectX = false
|
||||
|
||||
func init() {
|
||||
drivers = append(drivers, gpuAPI{
|
||||
priority: 1,
|
||||
initializer: func(w *window) (Context, error) {
|
||||
initializer: func(w *window) (context, error) {
|
||||
hwnd, _, _ := w.HWND()
|
||||
var flags uint32
|
||||
if debug {
|
||||
if debugDirectX {
|
||||
flags |= d3d11.CREATE_DEVICE_DEBUG
|
||||
}
|
||||
dev, ctx, _, err := d3d11.CreateDevice(
|
||||
@@ -54,40 +53,42 @@ func (c *d3d11Context) API() gpu.API {
|
||||
return gpu.Direct3D11{Device: unsafe.Pointer(c.dev)}
|
||||
}
|
||||
|
||||
func (c *d3d11Context) RenderTarget() (gpu.RenderTarget, error) {
|
||||
return gpu.Direct3D11RenderTarget{
|
||||
RenderTarget: unsafe.Pointer(c.renderTarget),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Present() error {
|
||||
err := c.swchain.Present(1, 0)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return wrapErr(c.swchain.Present(1, 0))
|
||||
}
|
||||
|
||||
func wrapErr(err error) error {
|
||||
if err, ok := err.(d3d11.ErrorCode); ok {
|
||||
switch err.Code {
|
||||
case d3d11.DXGI_STATUS_OCCLUDED:
|
||||
// Ignore
|
||||
return nil
|
||||
case d3d11.DXGI_ERROR_DEVICE_RESET, d3d11.DXGI_ERROR_DEVICE_REMOVED, d3d11.D3DDDIERR_DEVICEREMOVED:
|
||||
return ErrDeviceLost
|
||||
return gpu.ErrDeviceLost
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *d3d11Context) MakeCurrent() error {
|
||||
_, width, height := c.win.HWND()
|
||||
func (c *d3d11Context) Refresh() error {
|
||||
var width, height int
|
||||
_, width, height = c.win.HWND()
|
||||
if c.renderTarget != nil && width == c.width && height == c.height {
|
||||
c.ctx.OMSetRenderTargets(c.renderTarget, c.depthView)
|
||||
return nil
|
||||
}
|
||||
c.releaseFBO()
|
||||
if err := c.swchain.ResizeBuffers(0, 0, 0, d3d11.DXGI_FORMAT_UNKNOWN, 0); err != nil {
|
||||
return err
|
||||
return wrapErr(err)
|
||||
}
|
||||
c.width = width
|
||||
c.height = height
|
||||
|
||||
desc, err := c.swchain.GetDesc()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backBuffer, err := c.swchain.GetBuffer(0, &d3d11.IID_Texture2D)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -98,19 +99,14 @@ func (c *d3d11Context) MakeCurrent() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
depthView, err := d3d11.CreateDepthView(c.dev, int(desc.BufferDesc.Width), int(desc.BufferDesc.Height), 24)
|
||||
if err != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(renderTarget), renderTarget.Vtbl.Release)
|
||||
return err
|
||||
}
|
||||
c.renderTarget = renderTarget
|
||||
c.depthView = depthView
|
||||
|
||||
c.ctx.OMSetRenderTargets(c.renderTarget, c.depthView)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Lock() {}
|
||||
func (c *d3d11Context) Lock() error {
|
||||
c.ctx.OMSetRenderTargets(c.renderTarget, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *d3d11Context) Unlock() {}
|
||||
|
||||
@@ -126,13 +122,12 @@ func (c *d3d11Context) Release() {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(c.dev), c.dev.Vtbl.Release)
|
||||
}
|
||||
*c = d3d11Context{}
|
||||
if debugDirectX {
|
||||
d3d11.ReportLiveObjects()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *d3d11Context) releaseFBO() {
|
||||
if c.depthView != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(c.depthView), c.depthView.Vtbl.Release)
|
||||
c.depthView = nil
|
||||
}
|
||||
if c.renderTarget != nil {
|
||||
d3d11.IUnknownRelease(unsafe.Pointer(c.renderTarget), c.renderTarget.Vtbl.Release)
|
||||
c.renderTarget = nil
|
||||
@@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !android
|
||||
// +build !android
|
||||
|
||||
package app
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build android
|
||||
|
||||
package app
|
||||
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/app/internal/wm"
|
||||
)
|
||||
|
||||
var (
|
||||
dataDirOnce sync.Once
|
||||
dataPath string
|
||||
)
|
||||
|
||||
func dataDir() (string, error) {
|
||||
dataDirOnce.Do(func() {
|
||||
dataPath = wm.GetDataDir()
|
||||
// Set XDG_CACHE_HOME to make os.UserCacheDir work.
|
||||
if _, exists := os.LookupEnv("XDG_CACHE_HOME"); !exists {
|
||||
cachePath := filepath.Join(dataPath, "cache")
|
||||
os.Setenv("XDG_CACHE_HOME", cachePath)
|
||||
}
|
||||
// Set XDG_CONFIG_HOME to make os.UserConfigDir work.
|
||||
if _, exists := os.LookupEnv("XDG_CONFIG_HOME"); !exists {
|
||||
cfgPath := filepath.Join(dataPath, "config")
|
||||
os.Setenv("XDG_CONFIG_HOME", cfgPath)
|
||||
}
|
||||
// Set HOME to make os.UserHomeDir work.
|
||||
if _, exists := os.LookupEnv("HOME"); !exists {
|
||||
os.Setenv("HOME", dataPath)
|
||||
}
|
||||
})
|
||||
return dataPath, nil
|
||||
}
|
||||
@@ -4,25 +4,24 @@
|
||||
Package app provides a platform-independent interface to operating system
|
||||
functionality for running graphical user interfaces.
|
||||
|
||||
See https://github.com/p9c/p9/pkg/gel/gio for instructions to set up and run Gio programs.
|
||||
See https://gioui.org for instructions to set up and run Gio programs.
|
||||
|
||||
Windows
|
||||
# Windows
|
||||
|
||||
Create a new Window by calling NewWindow. On mobile platforms or when Gio
|
||||
is embedded in another project, NewWindow merely connects with a previously
|
||||
created window.
|
||||
A Window is run by calling its Event method in a loop. The first time a
|
||||
method on Window is called, a new GUI window is created and shown. On mobile
|
||||
platforms or when Gio is embedded in another project, Window merely connects
|
||||
with a previously created GUI window.
|
||||
|
||||
A Window is run by receiving events from its Events channel. The most
|
||||
important event is FrameEvent that prompts an update of the window
|
||||
contents and state.
|
||||
The most important event is [FrameEvent] that prompts an update of the window
|
||||
contents.
|
||||
|
||||
For example:
|
||||
|
||||
import "github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
|
||||
w := app.NewWindow()
|
||||
for e := range w.Events() {
|
||||
if e, ok := e.(system.FrameEvent); ok {
|
||||
w := new(app.Window)
|
||||
for {
|
||||
e := w.Event()
|
||||
if e, ok := e.(app.FrameEvent); ok {
|
||||
ops.Reset()
|
||||
// Add operations to ops.
|
||||
...
|
||||
@@ -32,9 +31,9 @@ For example:
|
||||
}
|
||||
|
||||
A program must keep receiving events from the event channel until
|
||||
DestroyEvent is received.
|
||||
[DestroyEvent] is received.
|
||||
|
||||
Main
|
||||
# Main
|
||||
|
||||
The Main function must be called from a program's main function, to hand over
|
||||
control of the main thread to operating systems that need it.
|
||||
@@ -50,24 +49,18 @@ For example, to display a blank but otherwise functional window:
|
||||
func main() {
|
||||
go func() {
|
||||
w := app.NewWindow()
|
||||
for range w.Events() {
|
||||
for {
|
||||
w.Event()
|
||||
}
|
||||
}()
|
||||
app.Main()
|
||||
}
|
||||
|
||||
# Permissions
|
||||
|
||||
Event queue
|
||||
|
||||
A FrameEvent's Queue method returns an event.Queue implementation that distributes
|
||||
incoming events to the event handlers declared in the last frame.
|
||||
See the github.com/p9c/p9/pkg/gel/gio/io/event package for more information about event handlers.
|
||||
|
||||
Permissions
|
||||
|
||||
The packages under github.com/p9c/p9/pkg/gel/gio/app/permission should be imported
|
||||
The packages under gioui.org/app/permission should be imported
|
||||
by a Gio program or by one of its dependencies to indicate that specific
|
||||
operating-system permissions are required. Please see documentation for
|
||||
package github.com/p9c/p9/pkg/gel/gio/app/permission for more information.
|
||||
package gioui.org/app/permission for more information.
|
||||
*/
|
||||
package app
|
||||
|
||||
66
pkg/gel/gio/app/egl_android.go
Normal file
66
pkg/gel/gio/app/egl_android.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !noopengl
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#include <android/native_window_jni.h>
|
||||
#include <EGL/egl.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/egl"
|
||||
)
|
||||
|
||||
type androidContext struct {
|
||||
win *window
|
||||
eglSurf egl.NativeWindowType
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func init() {
|
||||
newAndroidGLESContext = func(w *window) (context, error) {
|
||||
ctx, err := egl.NewContext(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &androidContext{win: w, Context: ctx}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *androidContext) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *androidContext) Refresh() error {
|
||||
c.Context.ReleaseSurface()
|
||||
if err := c.win.setVisual(c.Context.VisualID()); err != nil {
|
||||
return err
|
||||
}
|
||||
win, _, _ := c.win.nativeWindow()
|
||||
c.eglSurf = egl.NativeWindowType(unsafe.Pointer(win))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *androidContext) Lock() error {
|
||||
// The Android emulator creates a broken surface if it is not
|
||||
// created on the same thread as the context is made current.
|
||||
if c.eglSurf != nil {
|
||||
if err := c.Context.CreateSurface(c.eglSurf); err != nil {
|
||||
return err
|
||||
}
|
||||
c.eglSurf = nil
|
||||
}
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *androidContext) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
88
pkg/gel/gio/app/egl_wayland.go
Normal file
88
pkg/gel/gio/app/egl_wayland.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland && !noopengl
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
// +build !noopengl
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/egl"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: egl wayland-egl
|
||||
#cgo freebsd openbsd LDFLAGS: -lwayland-egl
|
||||
#cgo CFLAGS: -DEGL_NO_X11
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-egl.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type wlContext struct {
|
||||
win *window
|
||||
*egl.Context
|
||||
eglWin *C.struct_wl_egl_window
|
||||
}
|
||||
|
||||
func init() {
|
||||
newWaylandEGLContext = func(w *window) (context, error) {
|
||||
disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
surf, width, height := w.surface()
|
||||
if surf == nil {
|
||||
return nil, errors.New("wayland: no surface")
|
||||
}
|
||||
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
||||
if eglWin == nil {
|
||||
return nil, errors.New("wayland: wl_egl_window_create failed")
|
||||
}
|
||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We're in charge of the frame callbacks, don't let eglSwapBuffers
|
||||
// wait for callbacks that may never arrive.
|
||||
ctx.EnableVSync(false)
|
||||
|
||||
return &wlContext{Context: ctx, win: w, eglWin: eglWin}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *wlContext) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
if c.eglWin != nil {
|
||||
C.wl_egl_window_destroy(c.eglWin)
|
||||
c.eglWin = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *wlContext) Refresh() error {
|
||||
surf, width, height := c.win.surface()
|
||||
if surf == nil {
|
||||
return errors.New("wayland: no surface")
|
||||
}
|
||||
C.wl_egl_window_resize(c.eglWin, C.int(width), C.int(height), 0, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *wlContext) Lock() error {
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *wlContext) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
59
pkg/gel/gio/app/egl_windows.go
Normal file
59
pkg/gel/gio/app/egl_windows.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !noopengl
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/egl"
|
||||
)
|
||||
|
||||
type glContext struct {
|
||||
win *window
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers = append(drivers, gpuAPI{
|
||||
priority: 2,
|
||||
initializer: func(w *window) (context, error) {
|
||||
disp := egl.NativeDisplayType(w.HDC())
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
win, _, _ := w.HWND()
|
||||
eglSurf := egl.NativeWindowType(win)
|
||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
if err := ctx.MakeCurrent(); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
defer ctx.ReleaseCurrent()
|
||||
ctx.EnableVSync(true)
|
||||
return &glContext{win: w, Context: ctx}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (c *glContext) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *glContext) Refresh() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Lock() error {
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *glContext) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
61
pkg/gel/gio/app/egl_x11.go
Normal file
61
pkg/gel/gio/app/egl_x11.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build ((linux && !android) || freebsd || openbsd) && !nox11 && !noopengl
|
||||
// +build linux,!android freebsd openbsd
|
||||
// +build !nox11
|
||||
// +build !noopengl
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/egl"
|
||||
)
|
||||
|
||||
type x11Context struct {
|
||||
win *x11Window
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func init() {
|
||||
newX11EGLContext = func(w *x11Window) (context, error) {
|
||||
disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
win, _, _ := w.window()
|
||||
eglSurf := egl.NativeWindowType(uintptr(win))
|
||||
if err := ctx.CreateSurface(eglSurf); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
if err := ctx.MakeCurrent(); err != nil {
|
||||
ctx.Release()
|
||||
return nil, err
|
||||
}
|
||||
defer ctx.ReleaseCurrent()
|
||||
ctx.EnableVSync(true)
|
||||
return &x11Context{win: w, Context: ctx}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *x11Context) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *x11Context) Refresh() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *x11Context) Lock() error {
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *x11Context) Unlock() {
|
||||
c.Context.ReleaseCurrent()
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,ios
|
||||
//go:build darwin && ios && nometal
|
||||
// +build darwin,ios,nometal
|
||||
|
||||
package wm
|
||||
package app
|
||||
|
||||
/*
|
||||
@import UIKit;
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <OpenGLES/ES2/gl.h>
|
||||
#include <OpenGLES/ES2/glext.h>
|
||||
@@ -14,31 +17,34 @@ __attribute__ ((visibility ("hidden"))) int gio_presentRenderbuffer(CFTypeRef ct
|
||||
__attribute__ ((visibility ("hidden"))) int gio_makeCurrent(CFTypeRef ctx);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createContext(void);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLLayer(void);
|
||||
|
||||
static CFTypeRef getViewLayer(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
return CFBridgingRetain(view.layer);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/gpu"
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/gl"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
owner *window
|
||||
c *gl.Functions
|
||||
ctx C.CFTypeRef
|
||||
layer C.CFTypeRef
|
||||
init bool
|
||||
frameBuffer gl.Framebuffer
|
||||
colorBuffer, depthBuffer gl.Renderbuffer
|
||||
}
|
||||
|
||||
func init() {
|
||||
layerFactory = func() uintptr {
|
||||
return uintptr(C.gio_createGLLayer())
|
||||
}
|
||||
owner *window
|
||||
c *gl.Functions
|
||||
ctx C.CFTypeRef
|
||||
layer C.CFTypeRef
|
||||
init bool
|
||||
frameBuffer gl.Framebuffer
|
||||
colorBuffer gl.Renderbuffer
|
||||
}
|
||||
|
||||
func newContext(w *window) (*context, error) {
|
||||
@@ -46,19 +52,32 @@ func newContext(w *window) (*context, error) {
|
||||
if ctx == 0 {
|
||||
return nil, fmt.Errorf("failed to create EAGLContext")
|
||||
}
|
||||
api := contextAPI()
|
||||
f, err := gl.NewFunctions(api.Context, api.ES)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &context{
|
||||
ctx: ctx,
|
||||
owner: w,
|
||||
layer: C.CFTypeRef(w.contextLayer()),
|
||||
c: new(gl.Functions),
|
||||
layer: C.getViewLayer(w.contextView()),
|
||||
c: f,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *context) API() gpu.API {
|
||||
func contextAPI() gpu.OpenGL {
|
||||
return gpu.OpenGL{}
|
||||
}
|
||||
|
||||
func (c *context) RenderTarget() gpu.RenderTarget {
|
||||
return gpu.OpenGLRenderTarget(c.frameBuffer)
|
||||
}
|
||||
|
||||
func (c *context) API() gpu.API {
|
||||
return contextAPI()
|
||||
}
|
||||
|
||||
func (c *context) Release() {
|
||||
if c.ctx == 0 {
|
||||
return
|
||||
@@ -66,7 +85,6 @@ func (c *context) Release() {
|
||||
C.gio_renderbufferStorage(c.ctx, 0, C.GLenum(gl.RENDERBUFFER))
|
||||
c.c.DeleteFramebuffer(c.frameBuffer)
|
||||
c.c.DeleteRenderbuffer(c.colorBuffer)
|
||||
c.c.DeleteRenderbuffer(c.depthBuffer)
|
||||
C.gio_makeCurrent(0)
|
||||
C.CFRelease(c.ctx)
|
||||
c.ctx = 0
|
||||
@@ -76,10 +94,6 @@ func (c *context) Present() error {
|
||||
if c.layer == 0 {
|
||||
panic("context is not active")
|
||||
}
|
||||
// Discard depth buffer as recommended in
|
||||
// https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/WorkingwithEAGLContexts/WorkingwithEAGLContexts.html
|
||||
c.c.BindFramebuffer(gl.FRAMEBUFFER, c.frameBuffer)
|
||||
c.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT)
|
||||
c.c.BindRenderbuffer(gl.RENDERBUFFER, c.colorBuffer)
|
||||
if C.gio_presentRenderbuffer(c.ctx, C.GLenum(gl.RENDERBUFFER)) == 0 {
|
||||
return errors.New("presentRenderBuffer failed")
|
||||
@@ -87,23 +101,30 @@ func (c *context) Present() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Lock() {}
|
||||
func (c *context) Lock() error {
|
||||
// OpenGL contexts are implicit and thread-local. Lock the OS thread.
|
||||
runtime.LockOSThread()
|
||||
|
||||
func (c *context) Unlock() {}
|
||||
|
||||
func (c *context) MakeCurrent() error {
|
||||
if C.gio_makeCurrent(c.ctx) == 0 {
|
||||
C.CFRelease(c.ctx)
|
||||
c.ctx = 0
|
||||
return errors.New("[EAGLContext setCurrentContext] failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Unlock() {
|
||||
C.gio_makeCurrent(0)
|
||||
}
|
||||
|
||||
func (c *context) Refresh() error {
|
||||
if C.gio_makeCurrent(c.ctx) == 0 {
|
||||
return errors.New("[EAGLContext setCurrentContext] failed")
|
||||
}
|
||||
if !c.init {
|
||||
c.init = true
|
||||
c.frameBuffer = c.c.CreateFramebuffer()
|
||||
c.colorBuffer = c.c.CreateRenderbuffer()
|
||||
c.depthBuffer = c.c.CreateRenderbuffer()
|
||||
}
|
||||
if !c.owner.isVisible() {
|
||||
if !c.owner.visible {
|
||||
// Make sure any in-flight GL commands are complete.
|
||||
c.c.Finish()
|
||||
return nil
|
||||
@@ -113,14 +134,9 @@ func (c *context) MakeCurrent() error {
|
||||
if C.gio_renderbufferStorage(c.ctx, c.layer, C.GLenum(gl.RENDERBUFFER)) == 0 {
|
||||
return errors.New("renderbufferStorage failed")
|
||||
}
|
||||
w := c.c.GetRenderbufferParameteri(gl.RENDERBUFFER, gl.RENDERBUFFER_WIDTH)
|
||||
h := c.c.GetRenderbufferParameteri(gl.RENDERBUFFER, gl.RENDERBUFFER_HEIGHT)
|
||||
c.c.BindRenderbuffer(gl.RENDERBUFFER, c.depthBuffer)
|
||||
c.c.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h)
|
||||
c.c.BindRenderbuffer(gl.RENDERBUFFER, currentRB)
|
||||
c.c.BindFramebuffer(gl.FRAMEBUFFER, c.frameBuffer)
|
||||
c.c.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, c.colorBuffer)
|
||||
c.c.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, c.depthBuffer)
|
||||
if st := c.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
|
||||
return fmt.Errorf("framebuffer incomplete, status: %#x\n", st)
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,ios
|
||||
// +build darwin,ios,nometal
|
||||
|
||||
@import UIKit;
|
||||
@import OpenGLES;
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
Class gio_layerClass(void) {
|
||||
return [CAEAGLLayer class];
|
||||
}
|
||||
|
||||
int gio_renderbufferStorage(CFTypeRef ctxRef, CFTypeRef layerRef, GLenum buffer) {
|
||||
EAGLContext *ctx = (__bridge EAGLContext *)ctxRef;
|
||||
CAEAGLLayer *layer = (__bridge CAEAGLLayer *)layerRef;
|
||||
79
pkg/gel/gio/app/gl_js.go
Normal file
79
pkg/gel/gio/app/gl_js.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall/js"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/gpu"
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/gl"
|
||||
)
|
||||
|
||||
type glContext struct {
|
||||
ctx js.Value
|
||||
cnv js.Value
|
||||
w *window
|
||||
}
|
||||
|
||||
func newContext(w *window) (*glContext, error) {
|
||||
args := map[string]interface{}{
|
||||
// Enable low latency rendering.
|
||||
// See https://developers.google.com/web/updates/2019/05/desynchronized.
|
||||
"desynchronized": true,
|
||||
"preserveDrawingBuffer": true,
|
||||
}
|
||||
ctx := w.cnv.Call("getContext", "webgl2", args)
|
||||
if ctx.IsNull() {
|
||||
ctx = w.cnv.Call("getContext", "webgl", args)
|
||||
}
|
||||
if ctx.IsNull() {
|
||||
return nil, errors.New("app: webgl is not supported")
|
||||
}
|
||||
c := &glContext{
|
||||
ctx: ctx,
|
||||
cnv: w.cnv,
|
||||
w: w,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *glContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
if c.w.contextStatus != contextStatusOkay {
|
||||
return nil, gpu.ErrDeviceLost
|
||||
}
|
||||
return gpu.OpenGLRenderTarget{}, nil
|
||||
}
|
||||
|
||||
func (c *glContext) API() gpu.API {
|
||||
return gpu.OpenGL{Context: gl.Context(c.ctx)}
|
||||
}
|
||||
|
||||
func (c *glContext) Release() {
|
||||
}
|
||||
|
||||
func (c *glContext) Present() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Unlock() {}
|
||||
|
||||
func (c *glContext) Refresh() error {
|
||||
switch c.w.contextStatus {
|
||||
case contextStatusLost:
|
||||
return errOutOfDate
|
||||
case contextStatusRestored:
|
||||
c.w.contextStatus = contextStatusOkay
|
||||
return gpu.ErrDeviceLost
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (context, error) {
|
||||
return newContext(w)
|
||||
}
|
||||
123
pkg/gel/gio/app/gl_macos.go
Normal file
123
pkg/gel/gio/app/gl_macos.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && !ios && nometal
|
||||
// +build darwin,!ios,nometal
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/gpu"
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/gl"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -xobjective-c -fobjc-arc
|
||||
#cgo LDFLAGS: -framework OpenGL
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <AppKit/AppKit.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLContext(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setContextView(CFTypeRef ctx, CFTypeRef view);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_makeCurrentContext(CFTypeRef ctx);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_updateContext(CFTypeRef ctx);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_flushContextBuffer(CFTypeRef ctx);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_clearCurrentContext(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_lockContext(CFTypeRef ctxRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_unlockContext(CFTypeRef ctxRef);
|
||||
|
||||
typedef void (*PFN_glFlush)(void);
|
||||
|
||||
static void glFlush(PFN_glFlush f) {
|
||||
f();
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type glContext struct {
|
||||
c *gl.Functions
|
||||
ctx C.CFTypeRef
|
||||
view C.CFTypeRef
|
||||
|
||||
glFlush C.PFN_glFlush
|
||||
}
|
||||
|
||||
func newContext(w *window) (*glContext, error) {
|
||||
clib := C.CString("/System/Library/Frameworks/OpenGL.framework/OpenGL")
|
||||
defer C.free(unsafe.Pointer(clib))
|
||||
lib, err := C.dlopen(clib, C.RTLD_NOW|C.RTLD_LOCAL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csym := C.CString("glFlush")
|
||||
defer C.free(unsafe.Pointer(csym))
|
||||
glFlush := C.PFN_glFlush(C.dlsym(lib, csym))
|
||||
if glFlush == nil {
|
||||
return nil, errors.New("gl: missing symbol glFlush in the OpenGL framework")
|
||||
}
|
||||
view := w.contextView()
|
||||
ctx := C.gio_createGLContext()
|
||||
if ctx == 0 {
|
||||
return nil, errors.New("gl: failed to create NSOpenGLContext")
|
||||
}
|
||||
C.gio_setContextView(ctx, view)
|
||||
c := &glContext{
|
||||
ctx: ctx,
|
||||
view: view,
|
||||
glFlush: glFlush,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *glContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
return gpu.OpenGLRenderTarget{}, nil
|
||||
}
|
||||
|
||||
func (c *glContext) API() gpu.API {
|
||||
return gpu.OpenGL{}
|
||||
}
|
||||
|
||||
func (c *glContext) Release() {
|
||||
if c.ctx != 0 {
|
||||
C.gio_clearCurrentContext()
|
||||
C.CFRelease(c.ctx)
|
||||
c.ctx = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (c *glContext) Present() error {
|
||||
// Assume the caller already locked the context.
|
||||
C.glFlush(c.glFlush)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Lock() error {
|
||||
// OpenGL contexts are implicit and thread-local. Lock the OS thread.
|
||||
runtime.LockOSThread()
|
||||
|
||||
C.gio_lockContext(c.ctx)
|
||||
C.gio_makeCurrentContext(c.ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Unlock() {
|
||||
C.gio_clearCurrentContext()
|
||||
C.gio_unlockContext(c.ctx)
|
||||
}
|
||||
|
||||
func (c *glContext) Refresh() error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
C.gio_updateContext(c.ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (context, error) {
|
||||
return newContext(w)
|
||||
}
|
||||
73
pkg/gel/gio/app/gl_macos.m
Normal file
73
pkg/gel/gio/app/gl_macos.m
Normal file
@@ -0,0 +1,73 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios,nometal
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
return [CALayer layer];
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createGLContext(void) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLPixelFormatAttribute attr[] = {
|
||||
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
|
||||
NSOpenGLPFAColorSize, 24,
|
||||
NSOpenGLPFAAccelerated,
|
||||
// Opt-in to automatic GPU switching. CGL-only property.
|
||||
kCGLPFASupportsAutomaticGraphicsSwitching,
|
||||
NSOpenGLPFAAllowOfflineRenderers,
|
||||
0
|
||||
};
|
||||
NSOpenGLPixelFormat *pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
|
||||
|
||||
NSOpenGLContext *ctx = [[NSOpenGLContext alloc] initWithFormat:pixFormat shareContext: nil];
|
||||
return CFBridgingRetain(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void gio_setContextView(CFTypeRef ctxRef, CFTypeRef viewRef) {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[view setWantsBestResolutionOpenGLSurface:YES];
|
||||
[ctx setView:view];
|
||||
}
|
||||
|
||||
void gio_clearCurrentContext(void) {
|
||||
@autoreleasepool {
|
||||
[NSOpenGLContext clearCurrentContext];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_updateContext(CFTypeRef ctxRef) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
[ctx update];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_makeCurrentContext(CFTypeRef ctxRef) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
[ctx makeCurrentContext];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_lockContext(CFTypeRef ctxRef) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
CGLLockContext([ctx CGLContextObj]);
|
||||
}
|
||||
}
|
||||
|
||||
void gio_unlockContext(CFTypeRef ctxRef) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
CGLUnlockContext([ctx CGLContextObj]);
|
||||
}
|
||||
}
|
||||
145
pkg/gel/gio/app/ime.go
Normal file
145
pkg/gel/gio/app/ime.go
Normal file
@@ -0,0 +1,145 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/input"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/key"
|
||||
)
|
||||
|
||||
type editorState struct {
|
||||
input.EditorState
|
||||
compose key.Range
|
||||
}
|
||||
|
||||
func (e *editorState) Replace(r key.Range, text string) {
|
||||
if r.Start > r.End {
|
||||
r.Start, r.End = r.End, r.Start
|
||||
}
|
||||
runes := []rune(text)
|
||||
newEnd := r.Start + len(runes)
|
||||
adjust := func(pos int) int {
|
||||
switch {
|
||||
case newEnd < pos && pos <= r.End:
|
||||
return newEnd
|
||||
case r.End < pos:
|
||||
diff := newEnd - r.End
|
||||
return pos + diff
|
||||
}
|
||||
return pos
|
||||
}
|
||||
e.Selection.Start = adjust(e.Selection.Start)
|
||||
e.Selection.End = adjust(e.Selection.End)
|
||||
if e.compose.Start != -1 {
|
||||
e.compose.Start = adjust(e.compose.Start)
|
||||
e.compose.End = adjust(e.compose.End)
|
||||
}
|
||||
s := e.Snippet
|
||||
if r.End < s.Start || r.Start > s.End {
|
||||
// Discard snippet if it doesn't overlap with replacement.
|
||||
s = key.Snippet{
|
||||
Range: key.Range{
|
||||
Start: r.Start,
|
||||
End: r.Start,
|
||||
},
|
||||
}
|
||||
}
|
||||
var newSnippet []rune
|
||||
snippet := []rune(s.Text)
|
||||
// Append first part of existing snippet.
|
||||
if end := r.Start - s.Start; end > 0 {
|
||||
newSnippet = append(newSnippet, snippet[:end]...)
|
||||
}
|
||||
// Append replacement.
|
||||
newSnippet = append(newSnippet, runes...)
|
||||
// Append last part of existing snippet.
|
||||
if start := r.End; start < s.End {
|
||||
newSnippet = append(newSnippet, snippet[start-s.Start:]...)
|
||||
}
|
||||
// Adjust snippet range to include replacement.
|
||||
if r.Start < s.Start {
|
||||
s.Start = r.Start
|
||||
}
|
||||
s.End = s.Start + len(newSnippet)
|
||||
s.Text = string(newSnippet)
|
||||
e.Snippet = s
|
||||
}
|
||||
|
||||
// UTF16Index converts the given index in runes into an index in utf16 characters.
|
||||
func (e *editorState) UTF16Index(runes int) int {
|
||||
if runes == -1 {
|
||||
return -1
|
||||
}
|
||||
if runes < e.Snippet.Start {
|
||||
// Assume runes before sippet are one UTF-16 character each.
|
||||
return runes
|
||||
}
|
||||
chars := e.Snippet.Start
|
||||
runes -= e.Snippet.Start
|
||||
for _, r := range e.Snippet.Text {
|
||||
if runes == 0 {
|
||||
break
|
||||
}
|
||||
runes--
|
||||
chars++
|
||||
if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar {
|
||||
chars++
|
||||
}
|
||||
}
|
||||
// Assume runes after snippets are one UTF-16 character each.
|
||||
return chars + runes
|
||||
}
|
||||
|
||||
// RunesIndex converts the given index in utf16 characters to an index in runes.
|
||||
func (e *editorState) RunesIndex(chars int) int {
|
||||
if chars == -1 {
|
||||
return -1
|
||||
}
|
||||
if chars < e.Snippet.Start {
|
||||
// Assume runes before offset are one UTF-16 character each.
|
||||
return chars
|
||||
}
|
||||
runes := e.Snippet.Start
|
||||
chars -= e.Snippet.Start
|
||||
for _, r := range e.Snippet.Text {
|
||||
if chars == 0 {
|
||||
break
|
||||
}
|
||||
chars--
|
||||
runes++
|
||||
if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar {
|
||||
chars--
|
||||
}
|
||||
}
|
||||
// Assume runes after snippets are one UTF-16 character each.
|
||||
return runes + chars
|
||||
}
|
||||
|
||||
// areSnippetsConsistent reports whether the content of the old snippet is
|
||||
// consistent with the content of the new.
|
||||
func areSnippetsConsistent(old, new key.Snippet) bool {
|
||||
// Compute the overlapping range.
|
||||
r := old.Range
|
||||
r.Start = max(r.Start, new.Start)
|
||||
r.End = max(r.End, r.Start)
|
||||
r.End = min(r.End, new.End)
|
||||
return snippetSubstring(old, r) == snippetSubstring(new, r)
|
||||
}
|
||||
|
||||
func snippetSubstring(s key.Snippet, r key.Range) string {
|
||||
for r.Start > s.Start && r.Start < s.End {
|
||||
_, n := utf8.DecodeRuneInString(s.Text)
|
||||
s.Text = s.Text[n:]
|
||||
s.Start++
|
||||
}
|
||||
for r.End < s.End && r.End > s.Start {
|
||||
_, n := utf8.DecodeLastRuneInString(s.Text)
|
||||
s.Text = s.Text[:len(s.Text)-n]
|
||||
s.End--
|
||||
}
|
||||
return s.Text
|
||||
}
|
||||
166
pkg/gel/gio/app/ime_test.go
Normal file
166
pkg/gel/gio/app/ime_test.go
Normal file
@@ -0,0 +1,166 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/p9c/p9/pkg/gel/gio/f32"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/font"
|
||||
"github.com/p9c/p9/pkg/gel/gio/font/gofont"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/input"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/key"
|
||||
"github.com/p9c/p9/pkg/gel/gio/layout"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op"
|
||||
"github.com/p9c/p9/pkg/gel/gio/text"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
"github.com/p9c/p9/pkg/gel/gio/widget"
|
||||
)
|
||||
|
||||
func FuzzIME(f *testing.F) {
|
||||
runes := []rune("Hello, 世界! 🤬 علي،الحسنب北查爾斯頓工廠的安全漏洞已")
|
||||
f.Add([]byte("20\x0010"))
|
||||
f.Add([]byte("80000"))
|
||||
f.Add([]byte("2008\"80\r00"))
|
||||
f.Add([]byte("20007900002\x02000"))
|
||||
f.Add([]byte("20007800002\x02000"))
|
||||
f.Add([]byte("200A02000990\x19002\x17\x0200"))
|
||||
f.Fuzz(func(t *testing.T, cmds []byte) {
|
||||
cache := text.NewShaper(text.WithCollection(gofont.Collection()))
|
||||
e := new(widget.Editor)
|
||||
|
||||
var r input.Router
|
||||
gtx := layout.Context{Ops: new(op.Ops), Source: r.Source()}
|
||||
gtx.Execute(key.FocusCmd{Tag: e})
|
||||
// Layout once to register focus.
|
||||
e.Layout(gtx, cache, font.Font{}, unit.Sp(10), op.CallOp{}, op.CallOp{})
|
||||
r.Frame(gtx.Ops)
|
||||
|
||||
var state editorState
|
||||
state.Selection.Transform = f32.AffineId()
|
||||
const (
|
||||
cmdReplace = iota
|
||||
cmdSelect
|
||||
cmdSnip
|
||||
maxCmd
|
||||
)
|
||||
const cmdLen = 5
|
||||
for len(cmds) >= cmdLen {
|
||||
n := e.Len()
|
||||
rng := key.Range{
|
||||
Start: int(cmds[1]) % (n + 1),
|
||||
End: int(cmds[2]) % (n + 1),
|
||||
}
|
||||
switch cmds[0] % cmdLen {
|
||||
case cmdReplace:
|
||||
rstart := int(cmds[3]) % len(runes)
|
||||
rend := int(cmds[4]) % len(runes)
|
||||
if rstart > rend {
|
||||
rstart, rend = rend, rstart
|
||||
}
|
||||
replacement := string(runes[rstart:rend])
|
||||
state.Replace(rng, replacement)
|
||||
r.Queue(key.EditEvent{Range: rng, Text: replacement})
|
||||
r.Queue(key.SnippetEvent(state.Snippet.Range))
|
||||
case cmdSelect:
|
||||
r.Queue(key.SelectionEvent(rng))
|
||||
runes := []rune(e.Text())
|
||||
if rng.Start < 0 {
|
||||
rng.Start = 0
|
||||
}
|
||||
if rng.End < 0 {
|
||||
rng.End = 0
|
||||
}
|
||||
if rng.Start > len(runes) {
|
||||
rng.Start = len(runes)
|
||||
}
|
||||
if rng.End > len(runes) {
|
||||
rng.End = len(runes)
|
||||
}
|
||||
state.Selection.Range = rng
|
||||
case cmdSnip:
|
||||
r.Queue(key.SnippetEvent(rng))
|
||||
runes := []rune(e.Text())
|
||||
if rng.Start > rng.End {
|
||||
rng.Start, rng.End = rng.End, rng.Start
|
||||
}
|
||||
if rng.Start < 0 {
|
||||
rng.Start = 0
|
||||
}
|
||||
if rng.End < 0 {
|
||||
rng.End = 0
|
||||
}
|
||||
if rng.Start > len(runes) {
|
||||
rng.Start = len(runes)
|
||||
}
|
||||
if rng.End > len(runes) {
|
||||
rng.End = len(runes)
|
||||
}
|
||||
state.Snippet = key.Snippet{
|
||||
Range: rng,
|
||||
Text: string(runes[rng.Start:rng.End]),
|
||||
}
|
||||
}
|
||||
cmds = cmds[cmdLen:]
|
||||
e.Layout(gtx, cache, font.Font{}, unit.Sp(10), op.CallOp{}, op.CallOp{})
|
||||
r.Frame(gtx.Ops)
|
||||
newState := r.EditorState()
|
||||
// We don't track caret position.
|
||||
state.Selection.Caret = newState.Selection.Caret
|
||||
// Expanded snippets are ok.
|
||||
their, our := newState.Snippet, state.EditorState.Snippet
|
||||
beforeLen := 0
|
||||
for before := our.Start - their.Start; before > 0; before-- {
|
||||
_, n := utf8.DecodeRuneInString(their.Text[beforeLen:])
|
||||
beforeLen += n
|
||||
}
|
||||
afterLen := 0
|
||||
for after := their.End - our.End; after > 0; after-- {
|
||||
_, n := utf8.DecodeLastRuneInString(their.Text[:len(their.Text)-afterLen])
|
||||
afterLen += n
|
||||
}
|
||||
if beforeLen > 0 {
|
||||
our.Text = their.Text[:beforeLen] + our.Text
|
||||
our.Start = their.Start
|
||||
}
|
||||
if afterLen > 0 {
|
||||
our.Text = our.Text + their.Text[len(their.Text)-afterLen:]
|
||||
our.End = their.End
|
||||
}
|
||||
state.EditorState.Snippet = our
|
||||
if newState != state.EditorState {
|
||||
t.Errorf("IME state: %+v\neditor state: %+v", state.EditorState, newState)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEditorIndices(t *testing.T) {
|
||||
var s editorState
|
||||
s.Selection.Transform = f32.AffineId()
|
||||
const str = "Hello, 😀"
|
||||
s.Snippet = key.Snippet{
|
||||
Text: str,
|
||||
Range: key.Range{
|
||||
Start: 10,
|
||||
End: utf8.RuneCountInString(str),
|
||||
},
|
||||
}
|
||||
utf16Indices := [...]struct {
|
||||
Runes, UTF16 int
|
||||
}{
|
||||
{0, 0}, {10, 10}, {17, 17}, {18, 19}, {30, 31},
|
||||
}
|
||||
for _, p := range utf16Indices {
|
||||
if want, got := p.UTF16, s.UTF16Index(p.Runes); want != got {
|
||||
t.Errorf("UTF16Index(%d) = %d, wanted %d", p.Runes, got, want)
|
||||
}
|
||||
if want, got := p.Runes, s.RunesIndex(p.UTF16); want != got {
|
||||
t.Errorf("RunesIndex(%d) = %d, wanted %d", p.UTF16, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package points standard output, standard error and the standard
|
||||
// library package log to the platform logger.
|
||||
package log
|
||||
|
||||
var appID = "gio"
|
||||
@@ -1,11 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,ios
|
||||
|
||||
@import Foundation;
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
void nslog(char *str) {
|
||||
NSLog(@"%@", @(str));
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
@@ -8,11 +9,25 @@ import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type CompositionForm struct {
|
||||
dwStyle uint32
|
||||
ptCurrentPos Point
|
||||
rcArea Rect
|
||||
}
|
||||
|
||||
type CandidateForm struct {
|
||||
dwIndex uint32
|
||||
dwStyle uint32
|
||||
ptCurrentPos Point
|
||||
rcArea Rect
|
||||
}
|
||||
|
||||
type Rect struct {
|
||||
Left, Top, Right, Bottom int32
|
||||
}
|
||||
@@ -32,6 +47,13 @@ type WndClassEx struct {
|
||||
HIconSm syscall.Handle
|
||||
}
|
||||
|
||||
type Margins struct {
|
||||
CxLeftWidth int32
|
||||
CxRightWidth int32
|
||||
CyTopHeight int32
|
||||
CyBottomHeight int32
|
||||
}
|
||||
|
||||
type Msg struct {
|
||||
Hwnd syscall.Handle
|
||||
Message uint32
|
||||
@@ -54,6 +76,21 @@ type MinMaxInfo struct {
|
||||
PtMaxTrackSize Point
|
||||
}
|
||||
|
||||
type NCCalcSizeParams struct {
|
||||
Rgrc [3]Rect
|
||||
LpPos *WindowPos
|
||||
}
|
||||
|
||||
type WindowPos struct {
|
||||
HWND syscall.Handle
|
||||
HWNDInsertAfter syscall.Handle
|
||||
x int32
|
||||
y int32
|
||||
cx int32
|
||||
cy int32
|
||||
flags uint32
|
||||
}
|
||||
|
||||
type WindowPlacement struct {
|
||||
length uint32
|
||||
flags uint32
|
||||
@@ -71,27 +108,126 @@ type MonitorInfo struct {
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
type POINTER_INPUT_TYPE int32
|
||||
|
||||
const (
|
||||
PT_POINTER POINTER_INPUT_TYPE = 1
|
||||
PT_TOUCH POINTER_INPUT_TYPE = 2
|
||||
PT_PEN POINTER_INPUT_TYPE = 3
|
||||
PT_MOUSE POINTER_INPUT_TYPE = 4
|
||||
PT_TOUCHPAD POINTER_INPUT_TYPE = 5
|
||||
)
|
||||
|
||||
type POINTER_INFO_POINTER_FLAGS int32
|
||||
|
||||
const (
|
||||
POINTER_FLAG_NEW POINTER_INFO_POINTER_FLAGS = 0x00000001
|
||||
POINTER_FLAG_INRANGE POINTER_INFO_POINTER_FLAGS = 0x00000002
|
||||
POINTER_FLAG_INCONTACT POINTER_INFO_POINTER_FLAGS = 0x00000004
|
||||
POINTER_FLAG_FIRSTBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000010
|
||||
POINTER_FLAG_SECONDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000020
|
||||
POINTER_FLAG_THIRDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000040
|
||||
POINTER_FLAG_FOURTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000080
|
||||
POINTER_FLAG_FIFTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000100
|
||||
POINTER_FLAG_PRIMARY POINTER_INFO_POINTER_FLAGS = 0x00002000
|
||||
POINTER_FLAG_CONFIDENCE POINTER_INFO_POINTER_FLAGS = 0x00004000
|
||||
POINTER_FLAG_CANCELED POINTER_INFO_POINTER_FLAGS = 0x00008000
|
||||
POINTER_FLAG_DOWN POINTER_INFO_POINTER_FLAGS = 0x00010000
|
||||
POINTER_FLAG_UPDATE POINTER_INFO_POINTER_FLAGS = 0x00020000
|
||||
POINTER_FLAG_UP POINTER_INFO_POINTER_FLAGS = 0x00040000
|
||||
POINTER_FLAG_WHEEL POINTER_INFO_POINTER_FLAGS = 0x00080000
|
||||
POINTER_FLAG_HWHEEL POINTER_INFO_POINTER_FLAGS = 0x00100000
|
||||
POINTER_FLAG_CAPTURECHANGED POINTER_INFO_POINTER_FLAGS = 0x00200000
|
||||
POINTER_FLAG_HASTRANSFORM POINTER_INFO_POINTER_FLAGS = 0x00400000
|
||||
)
|
||||
|
||||
type POINTER_BUTTON_CHANGE_TYPE int32
|
||||
|
||||
const (
|
||||
POINTER_CHANGE_NONE POINTER_BUTTON_CHANGE_TYPE = 0
|
||||
POINTER_CHANGE_FIRSTBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 1
|
||||
POINTER_CHANGE_FIRSTBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 2
|
||||
POINTER_CHANGE_SECONDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 3
|
||||
POINTER_CHANGE_SECONDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 4
|
||||
POINTER_CHANGE_THIRDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 5
|
||||
POINTER_CHANGE_THIRDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 6
|
||||
POINTER_CHANGE_FOURTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 7
|
||||
POINTER_CHANGE_FOURTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 8
|
||||
POINTER_CHANGE_FIFTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 9
|
||||
POINTER_CHANGE_FIFTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 10
|
||||
)
|
||||
|
||||
type PointerInfo struct {
|
||||
PointerType POINTER_INPUT_TYPE
|
||||
PointerId uint32
|
||||
FrameId uint32
|
||||
PointerFlags POINTER_INFO_POINTER_FLAGS
|
||||
SourceDevice syscall.Handle
|
||||
HwndTarget syscall.Handle
|
||||
PtPixelLocation Point
|
||||
PtHimetricLocation Point
|
||||
PtPixelLocationRaw Point
|
||||
PtHimetricLocationRaw Point
|
||||
DwTime uint32
|
||||
HistoryCount uint32
|
||||
InputData int32
|
||||
DwKeyStates uint32
|
||||
PerformanceCount uint64
|
||||
ButtonChangeType POINTER_BUTTON_CHANGE_TYPE
|
||||
}
|
||||
|
||||
const (
|
||||
TRUE = 1
|
||||
|
||||
CS_HREDRAW = 0x0002
|
||||
CS_VREDRAW = 0x0001
|
||||
CS_OWNDC = 0x0020
|
||||
CPS_CANCEL = 0x0004
|
||||
|
||||
CS_HREDRAW = 0x0002
|
||||
CS_INSERTCHAR = 0x2000
|
||||
CS_NOMOVECARET = 0x4000
|
||||
CS_VREDRAW = 0x0001
|
||||
CS_OWNDC = 0x0020
|
||||
|
||||
CW_USEDEFAULT = -2147483648
|
||||
|
||||
GWL_STYLE = ^(uint32(16) - 1) // -16
|
||||
HWND_TOPMOST = ^(uint32(1) - 1) // -1
|
||||
GWL_STYLE = ^(uintptr(16) - 1) // -16
|
||||
|
||||
HTCLIENT = 1
|
||||
GCS_COMPSTR = 0x0008
|
||||
GCS_COMPREADSTR = 0x0001
|
||||
GCS_CURSORPOS = 0x0080
|
||||
GCS_DELTASTART = 0x0100
|
||||
GCS_RESULTREADSTR = 0x0200
|
||||
GCS_RESULTSTR = 0x0800
|
||||
|
||||
IDC_ARROW = 32512
|
||||
IDC_IBEAM = 32513
|
||||
IDC_HAND = 32649
|
||||
IDC_CROSS = 32515
|
||||
IDC_SIZENS = 32645
|
||||
IDC_SIZEWE = 32644
|
||||
IDC_SIZEALL = 32646
|
||||
CFS_POINT = 0x0002
|
||||
CFS_CANDIDATEPOS = 0x0040
|
||||
|
||||
HWND_TOPMOST = ^(uint32(1) - 1) // -1
|
||||
|
||||
HTCAPTION = 2
|
||||
HTCLIENT = 1
|
||||
HTLEFT = 10
|
||||
HTRIGHT = 11
|
||||
HTTOP = 12
|
||||
HTTOPLEFT = 13
|
||||
HTTOPRIGHT = 14
|
||||
HTBOTTOM = 15
|
||||
HTBOTTOMLEFT = 16
|
||||
HTBOTTOMRIGHT = 17
|
||||
|
||||
IDC_APPSTARTING = 32650 // Standard arrow and small hourglass
|
||||
IDC_ARROW = 32512 // Standard arrow
|
||||
IDC_CROSS = 32515 // Crosshair
|
||||
IDC_HAND = 32649 // Hand
|
||||
IDC_HELP = 32651 // Arrow and question mark
|
||||
IDC_IBEAM = 32513 // I-beam
|
||||
IDC_NO = 32648 // Slashed circle
|
||||
IDC_SIZEALL = 32646 // Four-pointed arrow pointing north, south, east, and west
|
||||
IDC_SIZENESW = 32643 // Double-pointed arrow pointing northeast and southwest
|
||||
IDC_SIZENS = 32645 // Double-pointed arrow pointing north and south
|
||||
IDC_SIZENWSE = 32642 // Double-pointed arrow pointing northwest and southeast
|
||||
IDC_SIZEWE = 32644 // Double-pointed arrow pointing west and east
|
||||
IDC_UPARROW = 32516 // Vertical arrow
|
||||
IDC_WAIT = 32514 // Hour
|
||||
|
||||
INFINITE = 0xFFFFFFFF
|
||||
|
||||
@@ -101,17 +237,29 @@ const (
|
||||
|
||||
MONITOR_DEFAULTTOPRIMARY = 1
|
||||
|
||||
NI_COMPOSITIONSTR = 0x0015
|
||||
|
||||
SIZE_MAXIMIZED = 2
|
||||
SIZE_MINIMIZED = 1
|
||||
SIZE_RESTORED = 0
|
||||
|
||||
SW_SHOWDEFAULT = 10
|
||||
SCS_SETSTR = GCS_COMPREADSTR | GCS_COMPSTR
|
||||
|
||||
SM_CXSIZEFRAME = 32
|
||||
SM_CYSIZEFRAME = 33
|
||||
|
||||
SW_SHOWDEFAULT = 10
|
||||
SW_SHOWMINIMIZED = 2
|
||||
SW_SHOWMAXIMIZED = 3
|
||||
SW_SHOWNORMAL = 1
|
||||
SW_SHOW = 5
|
||||
|
||||
SWP_FRAMECHANGED = 0x0020
|
||||
SWP_NOMOVE = 0x0002
|
||||
SWP_NOOWNERZORDER = 0x0200
|
||||
SWP_NOSIZE = 0x0001
|
||||
SWP_NOZORDER = 0x0004
|
||||
SWP_SHOWWINDOW = 0x0040
|
||||
|
||||
USER_TIMER_MINIMUM = 0x0000000A
|
||||
|
||||
@@ -164,40 +312,56 @@ const (
|
||||
|
||||
UNICODE_NOCHAR = 65535
|
||||
|
||||
WM_CANCELMODE = 0x001F
|
||||
WM_CHAR = 0x0102
|
||||
WM_CREATE = 0x0001
|
||||
WM_DPICHANGED = 0x02E0
|
||||
WM_DESTROY = 0x0002
|
||||
WM_ERASEBKGND = 0x0014
|
||||
WM_KEYDOWN = 0x0100
|
||||
WM_KEYUP = 0x0101
|
||||
WM_LBUTTONDOWN = 0x0201
|
||||
WM_LBUTTONUP = 0x0202
|
||||
WM_MBUTTONDOWN = 0x0207
|
||||
WM_MBUTTONUP = 0x0208
|
||||
WM_MOUSEMOVE = 0x0200
|
||||
WM_MOUSEWHEEL = 0x020A
|
||||
WM_MOUSEHWHEEL = 0x020E
|
||||
WM_PAINT = 0x000F
|
||||
WM_CLOSE = 0x0010
|
||||
WM_QUIT = 0x0012
|
||||
WM_SETCURSOR = 0x0020
|
||||
WM_SETFOCUS = 0x0007
|
||||
WM_KILLFOCUS = 0x0008
|
||||
WM_SHOWWINDOW = 0x0018
|
||||
WM_SIZE = 0x0005
|
||||
WM_SYSKEYDOWN = 0x0104
|
||||
WM_SYSKEYUP = 0x0105
|
||||
WM_RBUTTONDOWN = 0x0204
|
||||
WM_RBUTTONUP = 0x0205
|
||||
WM_TIMER = 0x0113
|
||||
WM_UNICHAR = 0x0109
|
||||
WM_USER = 0x0400
|
||||
WM_GETMINMAXINFO = 0x0024
|
||||
WM_CANCELMODE = 0x001F
|
||||
WM_CHAR = 0x0102
|
||||
WM_CLOSE = 0x0010
|
||||
WM_CREATE = 0x0001
|
||||
WM_DPICHANGED = 0x02E0
|
||||
WM_DESTROY = 0x0002
|
||||
WM_ERASEBKGND = 0x0014
|
||||
WM_GETMINMAXINFO = 0x0024
|
||||
WM_IME_COMPOSITION = 0x010F
|
||||
WM_IME_ENDCOMPOSITION = 0x010E
|
||||
WM_IME_STARTCOMPOSITION = 0x010D
|
||||
WM_KEYDOWN = 0x0100
|
||||
WM_KEYUP = 0x0101
|
||||
WM_KILLFOCUS = 0x0008
|
||||
WM_LBUTTONDOWN = 0x0201
|
||||
WM_LBUTTONUP = 0x0202
|
||||
WM_MBUTTONDOWN = 0x0207
|
||||
WM_MBUTTONUP = 0x0208
|
||||
WM_MOUSEMOVE = 0x0200
|
||||
WM_MOUSEWHEEL = 0x020A
|
||||
WM_MOUSEHWHEEL = 0x020E
|
||||
WM_NCACTIVATE = 0x0086
|
||||
WM_NCHITTEST = 0x0084
|
||||
WM_NCCALCSIZE = 0x0083
|
||||
WM_PAINT = 0x000F
|
||||
WM_POINTERCAPTURECHANGED = 0x024C
|
||||
WM_POINTERDOWN = 0x0246
|
||||
WM_POINTERUP = 0x0247
|
||||
WM_POINTERUPDATE = 0x0245
|
||||
WM_POINTERWHEEL = 0x024E
|
||||
WM_POINTERHWHEEL = 0x024F
|
||||
WM_QUIT = 0x0012
|
||||
WM_RBUTTONDOWN = 0x0204
|
||||
WM_RBUTTONUP = 0x0205
|
||||
WM_SETCURSOR = 0x0020
|
||||
WM_SETFOCUS = 0x0007
|
||||
WM_SHOWWINDOW = 0x0018
|
||||
WM_SIZE = 0x0005
|
||||
WM_STYLECHANGED = 0x007D
|
||||
WM_SYSKEYDOWN = 0x0104
|
||||
WM_SYSKEYUP = 0x0105
|
||||
WM_TIMER = 0x0113
|
||||
WM_UNICHAR = 0x0109
|
||||
WM_USER = 0x0400
|
||||
WM_WINDOWPOSCHANGED = 0x0047
|
||||
|
||||
WS_CLIPCHILDREN = 0x00010000
|
||||
WS_CLIPCHILDREN = 0x02000000
|
||||
WS_CLIPSIBLINGS = 0x04000000
|
||||
WS_MAXIMIZE = 0x01000000
|
||||
WS_ICONIC = 0x20000000
|
||||
WS_VISIBLE = 0x10000000
|
||||
WS_OVERLAPPED = 0x00000000
|
||||
WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME |
|
||||
@@ -256,6 +420,8 @@ var (
|
||||
_DestroyWindow = user32.NewProc("DestroyWindow")
|
||||
_DispatchMessage = user32.NewProc("DispatchMessageW")
|
||||
_EmptyClipboard = user32.NewProc("EmptyClipboard")
|
||||
_EnableMouseInPointer = user32.NewProc("EnableMouseInPointer")
|
||||
_GetWindowRect = user32.NewProc("GetWindowRect")
|
||||
_GetClientRect = user32.NewProc("GetClientRect")
|
||||
_GetClipboardData = user32.NewProc("GetClipboardData")
|
||||
_GetDC = user32.NewProc("GetDC")
|
||||
@@ -264,7 +430,10 @@ var (
|
||||
_GetMessage = user32.NewProc("GetMessageW")
|
||||
_GetMessageTime = user32.NewProc("GetMessageTime")
|
||||
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
|
||||
_GetPointerInfo = user32.NewProc("GetPointerInfo")
|
||||
_GetSystemMetrics = user32.NewProc("GetSystemMetrics")
|
||||
_GetWindowLong = user32.NewProc("GetWindowLongPtrW")
|
||||
_GetWindowLong32 = user32.NewProc("GetWindowLongW")
|
||||
_GetWindowPlacement = user32.NewProc("GetWindowPlacement")
|
||||
_KillTimer = user32.NewProc("KillTimer")
|
||||
_LoadCursor = user32.NewProc("LoadCursorW")
|
||||
@@ -279,6 +448,7 @@ var (
|
||||
_PostQuitMessage = user32.NewProc("PostQuitMessage")
|
||||
_ReleaseCapture = user32.NewProc("ReleaseCapture")
|
||||
_RegisterClassExW = user32.NewProc("RegisterClassExW")
|
||||
_RegisterTouchWindow = user32.NewProc("RegisterTouchWindow")
|
||||
_ReleaseDC = user32.NewProc("ReleaseDC")
|
||||
_ScreenToClient = user32.NewProc("ScreenToClient")
|
||||
_ShowWindow = user32.NewProc("ShowWindow")
|
||||
@@ -290,6 +460,7 @@ var (
|
||||
_SetProcessDPIAware = user32.NewProc("SetProcessDPIAware")
|
||||
_SetTimer = user32.NewProc("SetTimer")
|
||||
_SetWindowLong = user32.NewProc("SetWindowLongPtrW")
|
||||
_SetWindowLong32 = user32.NewProc("SetWindowLongW")
|
||||
_SetWindowPlacement = user32.NewProc("SetWindowPlacement")
|
||||
_SetWindowPos = user32.NewProc("SetWindowPos")
|
||||
_SetWindowText = user32.NewProc("SetWindowTextW")
|
||||
@@ -302,16 +473,25 @@ var (
|
||||
|
||||
gdi32 = syscall.NewLazySystemDLL("gdi32")
|
||||
_GetDeviceCaps = gdi32.NewProc("GetDeviceCaps")
|
||||
|
||||
imm32 = syscall.NewLazySystemDLL("imm32")
|
||||
_ImmGetContext = imm32.NewProc("ImmGetContext")
|
||||
_ImmGetCompositionString = imm32.NewProc("ImmGetCompositionStringW")
|
||||
_ImmNotifyIME = imm32.NewProc("ImmNotifyIME")
|
||||
_ImmReleaseContext = imm32.NewProc("ImmReleaseContext")
|
||||
_ImmSetCandidateWindow = imm32.NewProc("ImmSetCandidateWindow")
|
||||
_ImmSetCompositionWindow = imm32.NewProc("ImmSetCompositionWindow")
|
||||
|
||||
dwmapi = syscall.NewLazySystemDLL("dwmapi")
|
||||
_DwmExtendFrameIntoClientArea = dwmapi.NewProc("DwmExtendFrameIntoClientArea")
|
||||
)
|
||||
|
||||
func AdjustWindowRectEx(r *Rect, dwStyle uint32, bMenu int, dwExStyle uint32) {
|
||||
_AdjustWindowRectEx.Call(uintptr(unsafe.Pointer(r)), uintptr(dwStyle), uintptr(bMenu), uintptr(dwExStyle))
|
||||
issue34474KeepAlive(r)
|
||||
}
|
||||
|
||||
func CallMsgFilter(m *Msg, nCode uintptr) bool {
|
||||
r, _, _ := _CallMsgFilter.Call(uintptr(unsafe.Pointer(m)), nCode)
|
||||
issue34474KeepAlive(m)
|
||||
return r != 0
|
||||
}
|
||||
|
||||
@@ -336,13 +516,37 @@ func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, d
|
||||
uintptr(hMenu),
|
||||
uintptr(hInstance),
|
||||
uintptr(lpParam))
|
||||
issue34474KeepAlive(wname)
|
||||
if hwnd == 0 {
|
||||
return 0, fmt.Errorf("CreateWindowEx failed: %v", err)
|
||||
}
|
||||
return syscall.Handle(hwnd), nil
|
||||
}
|
||||
|
||||
func GetPointerInfo(pointerId uint32) (PointerInfo, error) {
|
||||
var info PointerInfo
|
||||
r1, _, err := _GetPointerInfo.Call(uintptr(pointerId), uintptr(unsafe.Pointer(&info)))
|
||||
if r1 == 0 {
|
||||
return PointerInfo{}, fmt.Errorf("GetPointerInfo failed: %v", err)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func RegisterTouchWindow(hwnd syscall.Handle, flags uint32) error {
|
||||
r1, _, err := _RegisterTouchWindow.Call(uintptr(hwnd), uintptr(flags))
|
||||
if r1 == 0 {
|
||||
return fmt.Errorf("RegisterTouchWindow failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnableMouseInPointer(enable uint) error {
|
||||
r1, _, err := _EnableMouseInPointer.Call(uintptr(enable))
|
||||
if r1 == 0 {
|
||||
return fmt.Errorf("EnableMouseInPointer failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
|
||||
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
|
||||
return r
|
||||
@@ -354,7 +558,14 @@ func DestroyWindow(hwnd syscall.Handle) {
|
||||
|
||||
func DispatchMessage(m *Msg) {
|
||||
_DispatchMessage.Call(uintptr(unsafe.Pointer(m)))
|
||||
issue34474KeepAlive(m)
|
||||
}
|
||||
|
||||
func DwmExtendFrameIntoClientArea(hwnd syscall.Handle, margins Margins) error {
|
||||
r, _, _ := _DwmExtendFrameIntoClientArea.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&margins)))
|
||||
if r != 0 {
|
||||
return fmt.Errorf("DwmExtendFrameIntoClientArea: %#x", r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EmptyClipboard() error {
|
||||
@@ -365,9 +576,16 @@ func EmptyClipboard() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetClientRect(hwnd syscall.Handle, r *Rect) {
|
||||
_GetClientRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(r)))
|
||||
issue34474KeepAlive(r)
|
||||
func GetWindowRect(hwnd syscall.Handle) Rect {
|
||||
var r Rect
|
||||
_GetWindowRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
|
||||
return r
|
||||
}
|
||||
|
||||
func GetClientRect(hwnd syscall.Handle) Rect {
|
||||
var r Rect
|
||||
_GetClientRect.Call(uintptr(hwnd), uintptr(unsafe.Pointer(&r)))
|
||||
return r
|
||||
}
|
||||
|
||||
func GetClipboardData(format uint32) (syscall.Handle, error) {
|
||||
@@ -432,7 +650,6 @@ func GetMessage(m *Msg, hwnd syscall.Handle, wMsgFilterMin, wMsgFilterMax uint32
|
||||
uintptr(hwnd),
|
||||
uintptr(wMsgFilterMin),
|
||||
uintptr(wMsgFilterMax))
|
||||
issue34474KeepAlive(m)
|
||||
return int32(r)
|
||||
}
|
||||
|
||||
@@ -441,6 +658,11 @@ func GetMessageTime() time.Duration {
|
||||
return time.Duration(r) * time.Millisecond
|
||||
}
|
||||
|
||||
func GetSystemMetrics(nIndex int) int {
|
||||
r, _, _ := _GetSystemMetrics.Call(uintptr(nIndex))
|
||||
return int(r)
|
||||
}
|
||||
|
||||
// GetWindowDPI returns the effective DPI of the window.
|
||||
func GetWindowDPI(hwnd syscall.Handle) int {
|
||||
// Check for GetDpiForWindow, introduced in Windows 10.
|
||||
@@ -467,13 +689,69 @@ func GetMonitorInfo(hwnd syscall.Handle) MonitorInfo {
|
||||
return mi
|
||||
}
|
||||
|
||||
func GetWindowLong(hwnd syscall.Handle) (style uintptr) {
|
||||
style, _, _ = _GetWindowLong.Call(uintptr(hwnd), uintptr(GWL_STYLE))
|
||||
func GetWindowLong(hwnd syscall.Handle, index uintptr) (val uintptr) {
|
||||
if runtime.GOARCH == "386" {
|
||||
val, _, _ = _GetWindowLong32.Call(uintptr(hwnd), index)
|
||||
} else {
|
||||
val, _, _ = _GetWindowLong.Call(uintptr(hwnd), index)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SetWindowLong(hwnd syscall.Handle, idx uint32, style uintptr) {
|
||||
_SetWindowLong.Call(uintptr(hwnd), uintptr(idx), style)
|
||||
func ImmGetContext(hwnd syscall.Handle) syscall.Handle {
|
||||
h, _, _ := _ImmGetContext.Call(uintptr(hwnd))
|
||||
return syscall.Handle(h)
|
||||
}
|
||||
|
||||
func ImmReleaseContext(hwnd, imc syscall.Handle) {
|
||||
_ImmReleaseContext.Call(uintptr(hwnd), uintptr(imc))
|
||||
}
|
||||
|
||||
func ImmNotifyIME(imc syscall.Handle, action, index, value int) {
|
||||
_ImmNotifyIME.Call(uintptr(imc), uintptr(action), uintptr(index), uintptr(value))
|
||||
}
|
||||
|
||||
func ImmGetCompositionString(imc syscall.Handle, key int) string {
|
||||
size, _, _ := _ImmGetCompositionString.Call(uintptr(imc), uintptr(key), 0, 0)
|
||||
if int32(size) <= 0 {
|
||||
return ""
|
||||
}
|
||||
u16 := make([]uint16, size/unsafe.Sizeof(uint16(0)))
|
||||
_ImmGetCompositionString.Call(uintptr(imc), uintptr(key), uintptr(unsafe.Pointer(&u16[0])), size)
|
||||
return string(utf16.Decode(u16))
|
||||
}
|
||||
|
||||
func ImmGetCompositionValue(imc syscall.Handle, key int) int {
|
||||
val, _, _ := _ImmGetCompositionString.Call(uintptr(imc), uintptr(key), 0, 0)
|
||||
return int(int32(val))
|
||||
}
|
||||
|
||||
func ImmSetCompositionWindow(imc syscall.Handle, x, y int) {
|
||||
f := CompositionForm{
|
||||
dwStyle: CFS_POINT,
|
||||
ptCurrentPos: Point{
|
||||
X: int32(x), Y: int32(y),
|
||||
},
|
||||
}
|
||||
_ImmSetCompositionWindow.Call(uintptr(imc), uintptr(unsafe.Pointer(&f)))
|
||||
}
|
||||
|
||||
func ImmSetCandidateWindow(imc syscall.Handle, x, y int) {
|
||||
f := CandidateForm{
|
||||
dwStyle: CFS_CANDIDATEPOS,
|
||||
ptCurrentPos: Point{
|
||||
X: int32(x), Y: int32(y),
|
||||
},
|
||||
}
|
||||
_ImmSetCandidateWindow.Call(uintptr(imc), uintptr(unsafe.Pointer(&f)))
|
||||
}
|
||||
|
||||
func SetWindowLong(hwnd syscall.Handle, idx uintptr, style uintptr) {
|
||||
if runtime.GOARCH == "386" {
|
||||
_SetWindowLong32.Call(uintptr(hwnd), idx, style)
|
||||
} else {
|
||||
_SetWindowLong.Call(uintptr(hwnd), idx, style)
|
||||
}
|
||||
}
|
||||
|
||||
func SetWindowPlacement(hwnd syscall.Handle, wp *WindowPlacement) {
|
||||
@@ -505,12 +783,12 @@ func GlobalFree(h syscall.Handle) {
|
||||
_GlobalFree.Call(uintptr(h))
|
||||
}
|
||||
|
||||
func GlobalLock(h syscall.Handle) (uintptr, error) {
|
||||
func GlobalLock(h syscall.Handle) (unsafe.Pointer, error) {
|
||||
r, _, err := _GlobalLock.Call(uintptr(h))
|
||||
if r == 0 {
|
||||
return 0, fmt.Errorf("GlobalLock: %v", err)
|
||||
return nil, fmt.Errorf("GlobalLock: %v", err)
|
||||
}
|
||||
return r, nil
|
||||
return unsafe.Pointer(r), nil
|
||||
}
|
||||
|
||||
func GlobalUnlock(h syscall.Handle) {
|
||||
@@ -573,7 +851,6 @@ func OpenClipboard(hwnd syscall.Handle) error {
|
||||
|
||||
func PeekMessage(m *Msg, hwnd syscall.Handle, wMsgFilterMin, wMsgFilterMax, wRemoveMsg uint32) bool {
|
||||
r, _, _ := _PeekMessage.Call(uintptr(unsafe.Pointer(m)), uintptr(hwnd), uintptr(wMsgFilterMin), uintptr(wMsgFilterMax), uintptr(wRemoveMsg))
|
||||
issue34474KeepAlive(m)
|
||||
return r != 0
|
||||
}
|
||||
|
||||
@@ -596,7 +873,6 @@ func ReleaseCapture() bool {
|
||||
|
||||
func RegisterClassEx(cls *WndClassEx) (uint16, error) {
|
||||
a, _, err := _RegisterClassExW.Call(uintptr(unsafe.Pointer(cls)))
|
||||
issue34474KeepAlive(cls)
|
||||
if a == 0 {
|
||||
return 0, fmt.Errorf("RegisterClassExW failed: %v", err)
|
||||
}
|
||||
@@ -646,7 +922,6 @@ func SetTimer(hwnd syscall.Handle, nIDEvent uintptr, uElapse uint32, timerProc u
|
||||
|
||||
func ScreenToClient(hwnd syscall.Handle, p *Point) {
|
||||
_ScreenToClient.Call(uintptr(hwnd), uintptr(unsafe.Pointer(p)))
|
||||
issue34474KeepAlive(p)
|
||||
}
|
||||
|
||||
func ShowWindow(hwnd syscall.Handle, nCmdShow int32) {
|
||||
@@ -655,7 +930,6 @@ func ShowWindow(hwnd syscall.Handle, nCmdShow int32) {
|
||||
|
||||
func TranslateMessage(m *Msg) {
|
||||
_TranslateMessage.Call(uintptr(unsafe.Pointer(m)))
|
||||
issue34474KeepAlive(m)
|
||||
}
|
||||
|
||||
func UnregisterClass(cls uint16, hInst syscall.Handle) {
|
||||
@@ -666,8 +940,21 @@ func UpdateWindow(hwnd syscall.Handle) {
|
||||
_UpdateWindow.Call(uintptr(hwnd))
|
||||
}
|
||||
|
||||
// issue34474KeepAlive calls runtime.KeepAlive as a
|
||||
// workaround for golang.org/issue/34474.
|
||||
func issue34474KeepAlive(v interface{}) {
|
||||
runtime.KeepAlive(v)
|
||||
func (p WindowPlacement) Rect() Rect {
|
||||
return p.rcNormalPosition
|
||||
}
|
||||
|
||||
func (p WindowPlacement) IsMinimized() bool {
|
||||
return p.showCmd == SW_SHOWMINIMIZED
|
||||
}
|
||||
|
||||
func (p WindowPlacement) IsMaximized() bool {
|
||||
return p.showCmd == SW_SHOWMAXIMIZED
|
||||
}
|
||||
|
||||
func (p *WindowPlacement) Set(Left, Top, Right, Bottom int) {
|
||||
p.rcNormalPosition.Left = int32(Left)
|
||||
p.rcNormalPosition.Top = int32(Top)
|
||||
p.rcNormalPosition.Right = int32(Right)
|
||||
p.rcNormalPosition.Bottom = int32(Bottom)
|
||||
}
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package org.gioui;
|
||||
|
||||
import java.lang.Class;
|
||||
import java.lang.IllegalAccessException;
|
||||
import java.lang.InstantiationException;
|
||||
import java.lang.ExceptionInInitializerError;
|
||||
import java.lang.SecurityException;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.text.Editable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Choreographer;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.PointerIcon;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.inputmethod.BaseInputConnection;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public final class GioView extends SurfaceView implements Choreographer.FrameCallback {
|
||||
private static boolean jniLoaded;
|
||||
|
||||
private final SurfaceHolder.Callback surfCallbacks;
|
||||
private final View.OnFocusChangeListener focusCallback;
|
||||
private final InputMethodManager imm;
|
||||
private final float scrollXScale;
|
||||
private final float scrollYScale;
|
||||
|
||||
private long nhandle;
|
||||
|
||||
public GioView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public GioView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
// Late initialization of the Go runtime to wait for a valid context.
|
||||
Gio.init(context.getApplicationContext());
|
||||
|
||||
// Set background color to transparent to avoid a flickering
|
||||
// issue on ChromeOS.
|
||||
setBackgroundColor(Color.argb(0, 0, 0, 0));
|
||||
|
||||
ViewConfiguration conf = ViewConfiguration.get(context);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
scrollXScale = conf.getScaledHorizontalScrollFactor();
|
||||
scrollYScale = conf.getScaledVerticalScrollFactor();
|
||||
|
||||
// The platform focus highlight is not aware of Gio's widgets.
|
||||
setDefaultFocusHighlightEnabled(false);
|
||||
} else {
|
||||
float listItemHeight = 48; // dp
|
||||
float px = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
listItemHeight,
|
||||
getResources().getDisplayMetrics()
|
||||
);
|
||||
scrollXScale = px;
|
||||
scrollYScale = px;
|
||||
}
|
||||
|
||||
nhandle = onCreateView(this);
|
||||
imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
focusCallback = new View.OnFocusChangeListener() {
|
||||
@Override public void onFocusChange(View v, boolean focus) {
|
||||
GioView.this.onFocusChange(nhandle, focus);
|
||||
}
|
||||
};
|
||||
setOnFocusChangeListener(focusCallback);
|
||||
surfCallbacks = new SurfaceHolder.Callback() {
|
||||
@Override public void surfaceCreated(SurfaceHolder holder) {
|
||||
// Ignore; surfaceChanged is guaranteed to be called immediately after this.
|
||||
}
|
||||
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
onSurfaceChanged(nhandle, getHolder().getSurface());
|
||||
}
|
||||
@Override public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
onSurfaceDestroyed(nhandle);
|
||||
}
|
||||
};
|
||||
getHolder().addCallback(surfCallbacks);
|
||||
}
|
||||
|
||||
@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), event.getEventTime());
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
dispatchMotionEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean onTouchEvent(MotionEvent event) {
|
||||
// Ask for unbuffered events. Flutter and Chrome do it
|
||||
// so assume it's good for us as well.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
requestUnbufferedDispatch(event);
|
||||
}
|
||||
|
||||
dispatchMotionEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setCursor(int id) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
return;
|
||||
}
|
||||
PointerIcon pointerIcon = PointerIcon.getSystemIcon(getContext(), id);
|
||||
setPointerIcon(pointerIcon);
|
||||
}
|
||||
|
||||
private void dispatchMotionEvent(MotionEvent event) {
|
||||
for (int j = 0; j < event.getHistorySize(); j++) {
|
||||
long time = event.getHistoricalEventTime(j);
|
||||
for (int i = 0; i < event.getPointerCount(); i++) {
|
||||
onTouchEvent(
|
||||
nhandle,
|
||||
event.ACTION_MOVE,
|
||||
event.getPointerId(i),
|
||||
event.getToolType(i),
|
||||
event.getHistoricalX(i, j),
|
||||
event.getHistoricalY(i, j),
|
||||
scrollXScale*event.getHistoricalAxisValue(MotionEvent.AXIS_HSCROLL, i, j),
|
||||
scrollYScale*event.getHistoricalAxisValue(MotionEvent.AXIS_VSCROLL, i, j),
|
||||
event.getButtonState(),
|
||||
time);
|
||||
}
|
||||
}
|
||||
int act = event.getActionMasked();
|
||||
int idx = event.getActionIndex();
|
||||
for (int i = 0; i < event.getPointerCount(); i++) {
|
||||
int pact = event.ACTION_MOVE;
|
||||
if (i == idx) {
|
||||
pact = act;
|
||||
}
|
||||
onTouchEvent(
|
||||
nhandle,
|
||||
pact,
|
||||
event.getPointerId(i),
|
||||
event.getToolType(i),
|
||||
event.getX(i), event.getY(i),
|
||||
scrollXScale*event.getAxisValue(MotionEvent.AXIS_HSCROLL, i),
|
||||
scrollYScale*event.getAxisValue(MotionEvent.AXIS_VSCROLL, i),
|
||||
event.getButtonState(),
|
||||
event.getEventTime());
|
||||
}
|
||||
}
|
||||
|
||||
@Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
|
||||
return new InputConnection(this);
|
||||
}
|
||||
|
||||
void showTextInput() {
|
||||
GioView.this.requestFocus();
|
||||
imm.showSoftInput(GioView.this, 0);
|
||||
}
|
||||
|
||||
void hideTextInput() {
|
||||
imm.hideSoftInputFromWindow(getWindowToken(), 0);
|
||||
}
|
||||
|
||||
@Override protected boolean fitSystemWindows(Rect insets) {
|
||||
onWindowInsets(nhandle, insets.top, insets.right, insets.bottom, insets.left);
|
||||
return true;
|
||||
}
|
||||
|
||||
void postFrameCallback() {
|
||||
Choreographer.getInstance().removeFrameCallback(this);
|
||||
Choreographer.getInstance().postFrameCallback(this);
|
||||
}
|
||||
|
||||
@Override public void doFrame(long nanos) {
|
||||
onFrameCallback(nhandle, nanos);
|
||||
}
|
||||
|
||||
int getDensity() {
|
||||
return getResources().getDisplayMetrics().densityDpi;
|
||||
}
|
||||
|
||||
float getFontScale() {
|
||||
return getResources().getConfiguration().fontScale;
|
||||
}
|
||||
|
||||
void start() {
|
||||
onStartView(nhandle);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
onStopView(nhandle);
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
setOnFocusChangeListener(null);
|
||||
getHolder().removeCallback(surfCallbacks);
|
||||
onDestroyView(nhandle);
|
||||
nhandle = 0;
|
||||
}
|
||||
|
||||
void configurationChanged() {
|
||||
onConfigurationChanged(nhandle);
|
||||
}
|
||||
|
||||
void lowMemory() {
|
||||
onLowMemory();
|
||||
}
|
||||
|
||||
boolean backPressed() {
|
||||
return onBack(nhandle);
|
||||
}
|
||||
|
||||
static private native long onCreateView(GioView view);
|
||||
static private native void onDestroyView(long handle);
|
||||
static private native void onStartView(long handle);
|
||||
static private native void onStopView(long handle);
|
||||
static private native void onSurfaceDestroyed(long handle);
|
||||
static private native void onSurfaceChanged(long handle, Surface surface);
|
||||
static private native void onConfigurationChanged(long handle);
|
||||
static private native void onWindowInsets(long handle, int top, int right, int bottom, int left);
|
||||
static private native void onLowMemory();
|
||||
static private native void onTouchEvent(long handle, int action, int pointerID, int tool, float x, float y, float scrollX, float scrollY, int buttons, long time);
|
||||
static private native void onKeyEvent(long handle, int code, int character, long time);
|
||||
static private native void onFrameCallback(long handle, long nanos);
|
||||
static private native boolean onBack(long handle);
|
||||
static private native void onFocusChange(long handle, boolean focus);
|
||||
|
||||
private static class InputConnection extends BaseInputConnection {
|
||||
private final Editable editable;
|
||||
|
||||
InputConnection(View view) {
|
||||
// Passing false enables "dummy mode", where the BaseInputConnection
|
||||
// attempts to convert IME operations to key events.
|
||||
super(view, false);
|
||||
editable = Editable.Factory.getInstance().newEditable("");
|
||||
}
|
||||
|
||||
@Override public Editable getEditable() {
|
||||
return editable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
|
||||
/*
|
||||
#include <EGL/egl.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/egl"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
win *window
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
ctx, err := egl.NewContext(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &context{win: w, Context: ctx}, nil
|
||||
}
|
||||
|
||||
func (c *context) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) MakeCurrent() error {
|
||||
c.Context.ReleaseSurface()
|
||||
win, width, height := c.win.nativeWindow(c.Context.VisualID())
|
||||
if win == nil {
|
||||
return nil
|
||||
}
|
||||
eglSurf := egl.NativeWindowType(unsafe.Pointer(win))
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Context.MakeCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Lock() {}
|
||||
|
||||
func (c *context) Unlock() {}
|
||||
@@ -1,75 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build linux,!android,!nowayland freebsd
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/egl"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: egl wayland-egl
|
||||
#cgo freebsd openbsd LDFLAGS: -lwayland-egl
|
||||
#cgo CFLAGS: -DEGL_NO_X11
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-egl.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type context struct {
|
||||
win *window
|
||||
*egl.Context
|
||||
eglWin *C.struct_wl_egl_window
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &context{Context: ctx, win: w}, nil
|
||||
}
|
||||
|
||||
func (c *context) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
if c.eglWin != nil {
|
||||
C.wl_egl_window_destroy(c.eglWin)
|
||||
c.eglWin = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) MakeCurrent() error {
|
||||
c.Context.ReleaseSurface()
|
||||
if c.eglWin != nil {
|
||||
C.wl_egl_window_destroy(c.eglWin)
|
||||
c.eglWin = nil
|
||||
}
|
||||
surf, width, height := c.win.surface()
|
||||
if surf == nil {
|
||||
return errors.New("wayland: no surface")
|
||||
}
|
||||
eglWin := C.wl_egl_window_create(surf, C.int(width), C.int(height))
|
||||
if eglWin == nil {
|
||||
return errors.New("wayland: wl_egl_window_create failed")
|
||||
}
|
||||
c.eglWin = eglWin
|
||||
eglSurf := egl.NativeWindowType(uintptr(unsafe.Pointer(eglWin)))
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Context.MakeCurrent()
|
||||
}
|
||||
|
||||
func (c *context) Lock() {}
|
||||
|
||||
func (c *context) Unlock() {}
|
||||
@@ -1,51 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/egl"
|
||||
)
|
||||
|
||||
type glContext struct {
|
||||
win *window
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers = append(drivers, gpuAPI{
|
||||
priority: 2,
|
||||
initializer: func(w *window) (Context, error) {
|
||||
disp := egl.NativeDisplayType(w.HDC())
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &glContext{win: w, Context: ctx}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (c *glContext) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *glContext) MakeCurrent() error {
|
||||
c.Context.ReleaseSurface()
|
||||
win, width, height := c.win.HWND()
|
||||
eglSurf := egl.NativeWindowType(win)
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Context.MakeCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Context.EnableVSync(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *glContext) Lock() {}
|
||||
|
||||
func (c *glContext) Unlock() {}
|
||||
@@ -1,50 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build linux,!android,!nox11 freebsd openbsd
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/egl"
|
||||
)
|
||||
|
||||
type x11Context struct {
|
||||
win *x11Window
|
||||
*egl.Context
|
||||
}
|
||||
|
||||
func (w *x11Window) NewContext() (Context, error) {
|
||||
disp := egl.NativeDisplayType(unsafe.Pointer(w.display()))
|
||||
ctx, err := egl.NewContext(disp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &x11Context{win: w, Context: ctx}, nil
|
||||
}
|
||||
|
||||
func (c *x11Context) Release() {
|
||||
if c.Context != nil {
|
||||
c.Context.Release()
|
||||
c.Context = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *x11Context) MakeCurrent() error {
|
||||
c.Context.ReleaseSurface()
|
||||
win, width, height := c.win.window()
|
||||
eglSurf := egl.NativeWindowType(uintptr(win))
|
||||
if err := c.Context.CreateSurface(eglSurf, width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Context.MakeCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Context.EnableVSync(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *x11Context) Lock() {}
|
||||
|
||||
func (c *x11Context) Unlock() {}
|
||||
@@ -1,89 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall/js"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/gpu"
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/gl"
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/srgb"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
ctx js.Value
|
||||
cnv js.Value
|
||||
srgbFBO *srgb.FBO
|
||||
}
|
||||
|
||||
func newContext(w *window) (*context, error) {
|
||||
args := map[string]interface{}{
|
||||
// Enable low latency rendering.
|
||||
// See https://developers.google.com/web/updates/2019/05/desynchronized.
|
||||
"desynchronized": true,
|
||||
"preserveDrawingBuffer": true,
|
||||
}
|
||||
ctx := w.cnv.Call("getContext", "webgl2", args)
|
||||
if ctx.IsNull() {
|
||||
ctx = w.cnv.Call("getContext", "webgl", args)
|
||||
}
|
||||
if ctx.IsNull() {
|
||||
return nil, errors.New("app: webgl is not supported")
|
||||
}
|
||||
c := &context{
|
||||
ctx: ctx,
|
||||
cnv: w.cnv,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *context) API() gpu.API {
|
||||
return gpu.OpenGL{Context: gl.Context(c.ctx)}
|
||||
}
|
||||
|
||||
func (c *context) Release() {
|
||||
if c.srgbFBO != nil {
|
||||
c.srgbFBO.Release()
|
||||
c.srgbFBO = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) Present() error {
|
||||
if c.srgbFBO != nil {
|
||||
c.srgbFBO.Blit()
|
||||
}
|
||||
if c.srgbFBO != nil {
|
||||
c.srgbFBO.AfterPresent()
|
||||
}
|
||||
if c.ctx.Call("isContextLost").Bool() {
|
||||
return errors.New("context lost")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Lock() {}
|
||||
|
||||
func (c *context) Unlock() {}
|
||||
|
||||
func (c *context) MakeCurrent() error {
|
||||
if c.srgbFBO == nil {
|
||||
var err error
|
||||
c.srgbFBO, err = srgb.New(gl.Context(c.ctx))
|
||||
if err != nil {
|
||||
c.Release()
|
||||
c.srgbFBO = nil
|
||||
return err
|
||||
}
|
||||
}
|
||||
w, h := c.cnv.Get("width").Int(), c.cnv.Get("height").Int()
|
||||
if err := c.srgbFBO.Refresh(w, h); err != nil {
|
||||
c.Release()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
return newContext(w)
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"github.com/p9c/p9/pkg/gel/gio/gpu"
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/gl"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <AppKit/AppKit.h>
|
||||
#include <OpenGL/gl3.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createGLView(void);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_contextForView(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_makeCurrentContext(CFTypeRef ctx);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_flushContextBuffer(CFTypeRef ctx);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_clearCurrentContext(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_lockContext(CFTypeRef ctxRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_unlockContext(CFTypeRef ctxRef);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type context struct {
|
||||
c *gl.Functions
|
||||
ctx C.CFTypeRef
|
||||
view C.CFTypeRef
|
||||
}
|
||||
|
||||
func init() {
|
||||
viewFactory = func() C.CFTypeRef {
|
||||
return C.gio_createGLView()
|
||||
}
|
||||
}
|
||||
|
||||
func newContext(w *window) (*context, error) {
|
||||
view := w.contextView()
|
||||
ctx := C.gio_contextForView(view)
|
||||
c := &context{
|
||||
ctx: ctx,
|
||||
view: view,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *context) API() gpu.API {
|
||||
return gpu.OpenGL{}
|
||||
}
|
||||
|
||||
func (c *context) Release() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
C.gio_clearCurrentContext()
|
||||
// We could release the context with [view clearGLContext]
|
||||
// and rely on [view openGLContext] auto-creating a new context.
|
||||
// However that second context is not properly set up by
|
||||
// OpenGLContextView, so we'll stay on the safe side and keep
|
||||
// the first context around.
|
||||
}
|
||||
|
||||
func (c *context) Present() error {
|
||||
// Assume the caller already locked the context.
|
||||
C.glFlush()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Lock() {
|
||||
C.gio_lockContext(c.ctx)
|
||||
}
|
||||
|
||||
func (c *context) Unlock() {
|
||||
C.gio_unlockContext(c.ctx)
|
||||
}
|
||||
|
||||
func (c *context) MakeCurrent() error {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
C.gio_makeCurrentContext(c.ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
return newContext(w)
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios
|
||||
|
||||
@import AppKit;
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#include <OpenGL/gl3.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
|
||||
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
|
||||
if (!event.hasPreciseScrollingDeltas) {
|
||||
// dx and dy are in rows and columns.
|
||||
dx *= 10;
|
||||
dy *= 10;
|
||||
}
|
||||
gio_onMouse((__bridge CFTypeRef)view, typ, [NSEvent pressedMouseButtons], p.x, p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
||||
}
|
||||
|
||||
@interface GioView : NSOpenGLView
|
||||
@end
|
||||
|
||||
@implementation GioView
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect
|
||||
pixelFormat:(NSOpenGLPixelFormat *)format {
|
||||
return [super initWithFrame:frameRect pixelFormat:format];
|
||||
}
|
||||
- (void)prepareOpenGL {
|
||||
[super prepareOpenGL];
|
||||
// Bind a default VBA to emulate OpenGL ES 2.
|
||||
GLuint defVBA;
|
||||
glGenVertexArrays(1, &defVBA);
|
||||
glBindVertexArray(defVBA);
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
}
|
||||
- (BOOL)isFlipped {
|
||||
return YES;
|
||||
}
|
||||
- (void)update {
|
||||
[super update];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
- (void)drawRect:(NSRect)r {
|
||||
gio_onDraw((__bridge CFTypeRef)self);
|
||||
}
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)mouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)middleMouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)middletMouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)rightMouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)rightMouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)mouseMoved:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)mouseDragged:(NSEvent *)event {
|
||||
handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)scrollWheel:(NSEvent *)event {
|
||||
CGFloat dx = -event.scrollingDeltaX;
|
||||
CGFloat dy = -event.scrollingDeltaY;
|
||||
handleMouse(self, event, GIO_MOUSE_SCROLL, dx, dy);
|
||||
}
|
||||
- (void)keyDown:(NSEvent *)event {
|
||||
NSString *keys = [event charactersIgnoringModifiers];
|
||||
gio_onKeys((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags], true);
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
}
|
||||
- (void)keyUp:(NSEvent *)event {
|
||||
NSString *keys = [event charactersIgnoringModifiers];
|
||||
gio_onKeys((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags], false);
|
||||
}
|
||||
- (void)insertText:(id)string {
|
||||
const char *utf8 = [string UTF8String];
|
||||
gio_onText((__bridge CFTypeRef)self, (char *)utf8);
|
||||
}
|
||||
- (void)doCommandBySelector:(SEL)sel {
|
||||
// Don't pass commands up the responder chain.
|
||||
// They will end up in a beep.
|
||||
}
|
||||
@end
|
||||
|
||||
CFTypeRef gio_createGLView(void) {
|
||||
@autoreleasepool {
|
||||
NSOpenGLPixelFormatAttribute attr[] = {
|
||||
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
|
||||
NSOpenGLPFAColorSize, 24,
|
||||
NSOpenGLPFADepthSize, 16,
|
||||
NSOpenGLPFAAccelerated,
|
||||
// Opt-in to automatic GPU switching. CGL-only property.
|
||||
kCGLPFASupportsAutomaticGraphicsSwitching,
|
||||
NSOpenGLPFAAllowOfflineRenderers,
|
||||
0
|
||||
};
|
||||
id pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
|
||||
|
||||
NSRect frame = NSMakeRect(0, 0, 0, 0);
|
||||
GioView* view = [[GioView alloc] initWithFrame:frame pixelFormat:pixFormat];
|
||||
|
||||
[view setWantsBestResolutionOpenGLSurface:YES];
|
||||
[view setWantsLayer:YES]; // The default in Mojave.
|
||||
|
||||
return CFBridgingRetain(view);
|
||||
}
|
||||
}
|
||||
|
||||
void gio_setNeedsDisplay(CFTypeRef viewRef) {
|
||||
NSOpenGLView *view = (__bridge NSOpenGLView *)viewRef;
|
||||
[view setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
CFTypeRef gio_contextForView(CFTypeRef viewRef) {
|
||||
NSOpenGLView *view = (__bridge NSOpenGLView *)viewRef;
|
||||
return (__bridge CFTypeRef)view.openGLContext;
|
||||
}
|
||||
|
||||
void gio_clearCurrentContext(void) {
|
||||
[NSOpenGLContext clearCurrentContext];
|
||||
}
|
||||
|
||||
void gio_makeCurrentContext(CFTypeRef ctxRef) {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
[ctx makeCurrentContext];
|
||||
}
|
||||
|
||||
void gio_lockContext(CFTypeRef ctxRef) {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
CGLLockContext([ctx CGLContextObj]);
|
||||
}
|
||||
|
||||
void gio_unlockContext(CFTypeRef ctxRef) {
|
||||
NSOpenGLContext *ctx = (__bridge NSOpenGLContext *)ctxRef;
|
||||
CGLUnlockContext([ctx CGLContextObj]);
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
#include <jni.h>
|
||||
#include "_cgo_export.h"
|
||||
|
||||
jint gio_jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version) {
|
||||
return (*vm)->GetEnv(vm, (void **)env, version);
|
||||
}
|
||||
|
||||
jint gio_jni_GetJavaVM(JNIEnv *env, JavaVM **jvm) {
|
||||
return (*env)->GetJavaVM(env, jvm);
|
||||
}
|
||||
|
||||
jint gio_jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) {
|
||||
return (*vm)->AttachCurrentThread(vm, p_env, thr_args);
|
||||
}
|
||||
|
||||
jint gio_jni_DetachCurrentThread(JavaVM *vm) {
|
||||
return (*vm)->DetachCurrentThread(vm);
|
||||
}
|
||||
|
||||
jobject gio_jni_NewGlobalRef(JNIEnv *env, jobject obj) {
|
||||
return (*env)->NewGlobalRef(env, obj);
|
||||
}
|
||||
|
||||
void gio_jni_DeleteGlobalRef(JNIEnv *env, jobject obj) {
|
||||
(*env)->DeleteGlobalRef(env, obj);
|
||||
}
|
||||
|
||||
jclass gio_jni_GetObjectClass(JNIEnv *env, jobject obj) {
|
||||
return (*env)->GetObjectClass(env, obj);
|
||||
}
|
||||
|
||||
jmethodID gio_jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
|
||||
return (*env)->GetMethodID(env, clazz, name, sig);
|
||||
}
|
||||
|
||||
jmethodID gio_jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
|
||||
return (*env)->GetStaticMethodID(env, clazz, name, sig);
|
||||
}
|
||||
|
||||
jfloat gio_jni_CallFloatMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
|
||||
return (*env)->CallFloatMethod(env, obj, methodID);
|
||||
}
|
||||
|
||||
jint gio_jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
|
||||
return (*env)->CallIntMethod(env, obj, methodID);
|
||||
}
|
||||
|
||||
void gio_jni_CallStaticVoidMethodA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args) {
|
||||
(*env)->CallStaticVoidMethodA(env, cls, methodID, args);
|
||||
}
|
||||
|
||||
void gio_jni_CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args) {
|
||||
(*env)->CallVoidMethodA(env, obj, methodID, args);
|
||||
}
|
||||
|
||||
jbyte *gio_jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr) {
|
||||
return (*env)->GetByteArrayElements(env, arr, NULL);
|
||||
}
|
||||
|
||||
void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes) {
|
||||
(*env)->ReleaseByteArrayElements(env, arr, bytes, JNI_ABORT);
|
||||
}
|
||||
|
||||
jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr) {
|
||||
return (*env)->GetArrayLength(env, arr);
|
||||
}
|
||||
|
||||
jstring gio_jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len) {
|
||||
return (*env)->NewString(env, unicodeChars, len);
|
||||
}
|
||||
|
||||
jsize gio_jni_GetStringLength(JNIEnv *env, jstring str) {
|
||||
return (*env)->GetStringLength(env, str);
|
||||
}
|
||||
|
||||
const jchar *gio_jni_GetStringChars(JNIEnv *env, jstring str) {
|
||||
return (*env)->GetStringChars(env, str, NULL);
|
||||
}
|
||||
|
||||
jthrowable gio_jni_ExceptionOccurred(JNIEnv *env) {
|
||||
return (*env)->ExceptionOccurred(env);
|
||||
}
|
||||
|
||||
void gio_jni_ExceptionClear(JNIEnv *env) {
|
||||
(*env)->ExceptionClear(env);
|
||||
}
|
||||
|
||||
jobject gio_jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) {
|
||||
return (*env)->CallObjectMethodA(env, obj, method, args);
|
||||
}
|
||||
|
||||
jobject gio_jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args) {
|
||||
return (*env)->CallStaticObjectMethodA(env, cls, method, args);
|
||||
}
|
||||
@@ -1,756 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror
|
||||
#cgo LDFLAGS: -landroid
|
||||
|
||||
#include <android/native_window_jni.h>
|
||||
#include <android/configuration.h>
|
||||
#include <android/keycodes.h>
|
||||
#include <android/input.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) jint gio_jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version);
|
||||
__attribute__ ((visibility ("hidden"))) jint gio_jni_GetJavaVM(JNIEnv *env, JavaVM **jvm);
|
||||
__attribute__ ((visibility ("hidden"))) jint gio_jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args);
|
||||
__attribute__ ((visibility ("hidden"))) jint gio_jni_DetachCurrentThread(JavaVM *vm);
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) jobject gio_jni_NewGlobalRef(JNIEnv *env, jobject obj);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_jni_DeleteGlobalRef(JNIEnv *env, jobject obj);
|
||||
__attribute__ ((visibility ("hidden"))) jclass gio_jni_GetObjectClass(JNIEnv *env, jobject obj);
|
||||
__attribute__ ((visibility ("hidden"))) jmethodID gio_jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
|
||||
__attribute__ ((visibility ("hidden"))) jmethodID gio_jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
|
||||
__attribute__ ((visibility ("hidden"))) jfloat gio_jni_CallFloatMethod(JNIEnv *env, jobject obj, jmethodID methodID);
|
||||
__attribute__ ((visibility ("hidden"))) jint gio_jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_jni_CallStaticVoidMethodA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_jni_CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
|
||||
__attribute__ ((visibility ("hidden"))) jbyte *gio_jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes);
|
||||
__attribute__ ((visibility ("hidden"))) jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr);
|
||||
__attribute__ ((visibility ("hidden"))) jstring gio_jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len);
|
||||
__attribute__ ((visibility ("hidden"))) jsize gio_jni_GetStringLength(JNIEnv *env, jstring str);
|
||||
__attribute__ ((visibility ("hidden"))) const jchar *gio_jni_GetStringChars(JNIEnv *env, jstring str);
|
||||
__attribute__ ((visibility ("hidden"))) jthrowable gio_jni_ExceptionOccurred(JNIEnv *env);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_jni_ExceptionClear(JNIEnv *env);
|
||||
__attribute__ ((visibility ("hidden"))) jobject gio_jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args);
|
||||
__attribute__ ((visibility ("hidden"))) jobject gio_jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/f32"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/clipboard"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/key"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/pointer"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/system"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
)
|
||||
|
||||
type window struct {
|
||||
callbacks Callbacks
|
||||
|
||||
view C.jobject
|
||||
|
||||
dpi int
|
||||
fontScale float32
|
||||
insets system.Insets
|
||||
|
||||
stage system.Stage
|
||||
started bool
|
||||
|
||||
state, newState windowState
|
||||
|
||||
// mu protects the fields following it.
|
||||
mu sync.Mutex
|
||||
win *C.ANativeWindow
|
||||
animating bool
|
||||
}
|
||||
|
||||
// windowState tracks the View or Activity specific state lost when Android
|
||||
// re-creates our Activity.
|
||||
type windowState struct {
|
||||
cursor *pointer.CursorName
|
||||
}
|
||||
|
||||
// gioView hold cached JNI methods for GioView.
|
||||
var gioView struct {
|
||||
once sync.Once
|
||||
getDensity C.jmethodID
|
||||
getFontScale C.jmethodID
|
||||
showTextInput C.jmethodID
|
||||
hideTextInput C.jmethodID
|
||||
postFrameCallback C.jmethodID
|
||||
setCursor C.jmethodID
|
||||
}
|
||||
|
||||
// ViewEvent is sent whenever the Window's underlying Android view
|
||||
// changes.
|
||||
type ViewEvent struct {
|
||||
// View is a JNI global reference to the android.view.View
|
||||
// instance backing the Window. The reference is valid until
|
||||
// the next ViewEvent is received.
|
||||
// A zero View means that there is currently no view attached.
|
||||
View uintptr
|
||||
}
|
||||
|
||||
type jvalue uint64 // The largest JNI type fits in 64 bits.
|
||||
|
||||
var dataDirChan = make(chan string, 1)
|
||||
|
||||
var android struct {
|
||||
// mu protects all fields of this structure. However, once a
|
||||
// non-nil jvm is returned from javaVM, all the other fields may
|
||||
// be accessed unlocked.
|
||||
mu sync.Mutex
|
||||
jvm *C.JavaVM
|
||||
|
||||
// appCtx is the global Android App context.
|
||||
appCtx C.jobject
|
||||
// gioCls is the class of the Gio class.
|
||||
gioCls C.jclass
|
||||
|
||||
mwriteClipboard C.jmethodID
|
||||
mreadClipboard C.jmethodID
|
||||
mwakeupMainThread C.jmethodID
|
||||
}
|
||||
|
||||
// view maps from GioView JNI refenreces to windows.
|
||||
var views = make(map[C.jlong]*window)
|
||||
|
||||
// windows maps from Callbacks to windows
|
||||
var windows = make(map[Callbacks]*window)
|
||||
|
||||
var mainWindow = newWindowRendezvous()
|
||||
|
||||
var mainFuncs = make(chan func(env *C.JNIEnv), 1)
|
||||
|
||||
func getMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
|
||||
m := C.CString(method)
|
||||
defer C.free(unsafe.Pointer(m))
|
||||
s := C.CString(sig)
|
||||
defer C.free(unsafe.Pointer(s))
|
||||
jm := C.gio_jni_GetMethodID(env, class, m, s)
|
||||
if err := exception(env); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return jm
|
||||
}
|
||||
|
||||
func getStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
|
||||
m := C.CString(method)
|
||||
defer C.free(unsafe.Pointer(m))
|
||||
s := C.CString(sig)
|
||||
defer C.free(unsafe.Pointer(s))
|
||||
jm := C.gio_jni_GetStaticMethodID(env, class, m, s)
|
||||
if err := exception(env); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return jm
|
||||
}
|
||||
|
||||
//export Java_org_gioui_Gio_runGoMain
|
||||
func Java_org_gioui_Gio_runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray, context C.jobject) {
|
||||
initJVM(env, class, context)
|
||||
dirBytes := C.gio_jni_GetByteArrayElements(env, jdataDir)
|
||||
if dirBytes == nil {
|
||||
panic("runGoMain: GetByteArrayElements failed")
|
||||
}
|
||||
n := C.gio_jni_GetArrayLength(env, jdataDir)
|
||||
dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n)
|
||||
dataDirChan <- dataDir
|
||||
C.gio_jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
|
||||
|
||||
runMain()
|
||||
}
|
||||
|
||||
func initJVM(env *C.JNIEnv, gio C.jclass, ctx C.jobject) {
|
||||
android.mu.Lock()
|
||||
defer android.mu.Unlock()
|
||||
if res := C.gio_jni_GetJavaVM(env, &android.jvm); res != 0 {
|
||||
panic("gio: GetJavaVM failed")
|
||||
}
|
||||
android.appCtx = C.gio_jni_NewGlobalRef(env, ctx)
|
||||
android.gioCls = C.jclass(C.gio_jni_NewGlobalRef(env, C.jobject(gio)))
|
||||
android.mwriteClipboard = getStaticMethodID(env, gio, "writeClipboard", "(Landroid/content/Context;Ljava/lang/String;)V")
|
||||
android.mreadClipboard = getStaticMethodID(env, gio, "readClipboard", "(Landroid/content/Context;)Ljava/lang/String;")
|
||||
android.mwakeupMainThread = getStaticMethodID(env, gio, "wakeupMainThread", "()V")
|
||||
}
|
||||
|
||||
func JavaVM() uintptr {
|
||||
jvm := javaVM()
|
||||
return uintptr(unsafe.Pointer(jvm))
|
||||
}
|
||||
|
||||
func javaVM() *C.JavaVM {
|
||||
android.mu.Lock()
|
||||
defer android.mu.Unlock()
|
||||
return android.jvm
|
||||
}
|
||||
|
||||
func AppContext() uintptr {
|
||||
android.mu.Lock()
|
||||
defer android.mu.Unlock()
|
||||
return uintptr(android.appCtx)
|
||||
}
|
||||
|
||||
func GetDataDir() string {
|
||||
return <-dataDirChan
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onCreateView
|
||||
func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
|
||||
gioView.once.Do(func() {
|
||||
m := &gioView
|
||||
m.getDensity = getMethodID(env, class, "getDensity", "()I")
|
||||
m.getFontScale = getMethodID(env, class, "getFontScale", "()F")
|
||||
m.showTextInput = getMethodID(env, class, "showTextInput", "()V")
|
||||
m.hideTextInput = getMethodID(env, class, "hideTextInput", "()V")
|
||||
m.postFrameCallback = getMethodID(env, class, "postFrameCallback", "()V")
|
||||
m.setCursor = getMethodID(env, class, "setCursor", "(I)V")
|
||||
})
|
||||
view = C.gio_jni_NewGlobalRef(env, view)
|
||||
wopts := <-mainWindow.out
|
||||
w, ok := windows[wopts.window]
|
||||
if !ok {
|
||||
w = &window{
|
||||
callbacks: wopts.window,
|
||||
}
|
||||
windows[wopts.window] = w
|
||||
}
|
||||
w.callbacks.SetDriver(w)
|
||||
w.view = view
|
||||
handle := C.jlong(view)
|
||||
views[handle] = w
|
||||
w.loadConfig(env, class)
|
||||
applyStateDiff(env, view, windowState{}, w.state)
|
||||
w.setStage(system.StagePaused)
|
||||
w.callbacks.Event(ViewEvent{View: uintptr(view)})
|
||||
return handle
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onDestroyView
|
||||
func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := views[handle]
|
||||
w.callbacks.Event(ViewEvent{View: 0})
|
||||
w.callbacks.SetDriver(nil)
|
||||
delete(views, handle)
|
||||
C.gio_jni_DeleteGlobalRef(env, w.view)
|
||||
w.view = 0
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onStopView
|
||||
func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := views[handle]
|
||||
w.started = false
|
||||
w.setStage(system.StagePaused)
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onStartView
|
||||
func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := views[handle]
|
||||
w.started = true
|
||||
if w.aNativeWindow() != nil {
|
||||
w.setVisible()
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onSurfaceDestroyed
|
||||
func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
|
||||
w := views[handle]
|
||||
w.mu.Lock()
|
||||
w.win = nil
|
||||
w.mu.Unlock()
|
||||
w.setStage(system.StagePaused)
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onSurfaceChanged
|
||||
func Java_org_gioui_GioView_onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) {
|
||||
w := views[handle]
|
||||
w.mu.Lock()
|
||||
w.win = C.ANativeWindow_fromSurface(env, surf)
|
||||
w.mu.Unlock()
|
||||
if w.started {
|
||||
w.setVisible()
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onLowMemory
|
||||
func Java_org_gioui_GioView_onLowMemory() {
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onConfigurationChanged
|
||||
func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
|
||||
w := views[view]
|
||||
w.loadConfig(env, class)
|
||||
if w.stage >= system.StageRunning {
|
||||
w.draw(true)
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onFrameCallback
|
||||
func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong, nanos C.jlong) {
|
||||
w, exist := views[view]
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
if w.stage < system.StageRunning {
|
||||
return
|
||||
}
|
||||
w.mu.Lock()
|
||||
anim := w.animating
|
||||
w.mu.Unlock()
|
||||
if anim {
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
||||
})
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onBack
|
||||
func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
|
||||
w := views[view]
|
||||
ev := &system.CommandEvent{Type: system.CommandBack}
|
||||
w.callbacks.Event(ev)
|
||||
if ev.Cancel {
|
||||
return C.JNI_TRUE
|
||||
}
|
||||
return C.JNI_FALSE
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onFocusChange
|
||||
func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
|
||||
w := views[view]
|
||||
w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onWindowInsets
|
||||
func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) {
|
||||
w := views[view]
|
||||
w.insets = system.Insets{
|
||||
Top: unit.Px(float32(top)),
|
||||
Right: unit.Px(float32(right)),
|
||||
Bottom: unit.Px(float32(bottom)),
|
||||
Left: unit.Px(float32(left)),
|
||||
}
|
||||
if w.stage >= system.StageRunning {
|
||||
w.draw(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) setVisible() {
|
||||
win := w.aNativeWindow()
|
||||
width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
|
||||
if width == 0 || height == 0 {
|
||||
return
|
||||
}
|
||||
w.setStage(system.StageRunning)
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func (w *window) setStage(stage system.Stage) {
|
||||
if stage == w.stage {
|
||||
return
|
||||
}
|
||||
w.stage = stage
|
||||
w.callbacks.Event(system.StageEvent{stage})
|
||||
}
|
||||
|
||||
func (w *window) nativeWindow(visID int) (*C.ANativeWindow, int, int) {
|
||||
win := w.aNativeWindow()
|
||||
var width, height int
|
||||
if win != nil {
|
||||
if C.ANativeWindow_setBuffersGeometry(win, 0, 0, C.int32_t(visID)) != 0 {
|
||||
panic(errors.New("ANativeWindow_setBuffersGeometry failed"))
|
||||
}
|
||||
w, h := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
|
||||
width, height = int(w), int(h)
|
||||
}
|
||||
return win, width, height
|
||||
}
|
||||
|
||||
func (w *window) aNativeWindow() *C.ANativeWindow {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.win
|
||||
}
|
||||
|
||||
func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) {
|
||||
dpi := int(C.gio_jni_CallIntMethod(env, w.view, gioView.getDensity))
|
||||
w.fontScale = float32(C.gio_jni_CallFloatMethod(env, w.view, gioView.getFontScale))
|
||||
switch dpi {
|
||||
case C.ACONFIGURATION_DENSITY_NONE,
|
||||
C.ACONFIGURATION_DENSITY_DEFAULT,
|
||||
C.ACONFIGURATION_DENSITY_ANY:
|
||||
// Assume standard density.
|
||||
w.dpi = C.ACONFIGURATION_DENSITY_MEDIUM
|
||||
default:
|
||||
w.dpi = int(dpi)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.mu.Lock()
|
||||
w.animating = anim
|
||||
w.mu.Unlock()
|
||||
if anim {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
if w.view == 0 {
|
||||
// View was destroyed while switching to main thread.
|
||||
return
|
||||
}
|
||||
callVoidMethod(env, w.view, gioView.postFrameCallback)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
win := w.aNativeWindow()
|
||||
width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
|
||||
if width == 0 || height == 0 {
|
||||
return
|
||||
}
|
||||
const inchPrDp = 1.0 / 160
|
||||
ppdp := float32(w.dpi) * inchPrDp
|
||||
w.callbacks.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: int(width),
|
||||
Y: int(height),
|
||||
},
|
||||
Insets: w.insets,
|
||||
Metric: unit.Metric{
|
||||
PxPerDp: ppdp,
|
||||
PxPerSp: w.fontScale * ppdp,
|
||||
},
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
}
|
||||
|
||||
type keyMapper func(devId, keyCode C.int32_t) rune
|
||||
|
||||
func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
|
||||
if jvm == nil {
|
||||
panic("nil JVM")
|
||||
}
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
var env *C.JNIEnv
|
||||
if res := C.gio_jni_GetEnv(jvm, &env, C.JNI_VERSION_1_6); res != C.JNI_OK {
|
||||
if res != C.JNI_EDETACHED {
|
||||
panic(fmt.Errorf("JNI GetEnv failed with error %d", res))
|
||||
}
|
||||
if C.gio_jni_AttachCurrentThread(jvm, &env, nil) != C.JNI_OK {
|
||||
panic(errors.New("runInJVM: AttachCurrentThread failed"))
|
||||
}
|
||||
defer C.gio_jni_DetachCurrentThread(jvm)
|
||||
}
|
||||
|
||||
f(env)
|
||||
}
|
||||
|
||||
func convertKeyCode(code C.jint) (string, bool) {
|
||||
var n string
|
||||
switch code {
|
||||
case C.AKEYCODE_DPAD_UP:
|
||||
n = key.NameUpArrow
|
||||
case C.AKEYCODE_DPAD_DOWN:
|
||||
n = key.NameDownArrow
|
||||
case C.AKEYCODE_DPAD_LEFT:
|
||||
n = key.NameLeftArrow
|
||||
case C.AKEYCODE_DPAD_RIGHT:
|
||||
n = key.NameRightArrow
|
||||
case C.AKEYCODE_FORWARD_DEL:
|
||||
n = key.NameDeleteForward
|
||||
case C.AKEYCODE_DEL:
|
||||
n = key.NameDeleteBackward
|
||||
case C.AKEYCODE_NUMPAD_ENTER:
|
||||
n = key.NameEnter
|
||||
case C.AKEYCODE_ENTER:
|
||||
n = key.NameEnter
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onKeyEvent
|
||||
func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, t C.jlong) {
|
||||
w := views[handle]
|
||||
if n, ok := convertKeyCode(keyCode); ok {
|
||||
w.callbacks.Event(key.Event{Name: n})
|
||||
}
|
||||
if r != 0 {
|
||||
w.callbacks.Event(key.EditEvent{Text: string(rune(r))})
|
||||
}
|
||||
}
|
||||
|
||||
//export Java_org_gioui_GioView_onTouchEvent
|
||||
func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y, scrollX, scrollY C.jfloat, jbtns C.jint, t C.jlong) {
|
||||
w := views[handle]
|
||||
var typ pointer.Type
|
||||
switch action {
|
||||
case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
|
||||
typ = pointer.Press
|
||||
case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
|
||||
typ = pointer.Release
|
||||
case C.AMOTION_EVENT_ACTION_CANCEL:
|
||||
typ = pointer.Cancel
|
||||
case C.AMOTION_EVENT_ACTION_MOVE:
|
||||
typ = pointer.Move
|
||||
case C.AMOTION_EVENT_ACTION_SCROLL:
|
||||
typ = pointer.Scroll
|
||||
default:
|
||||
return
|
||||
}
|
||||
var src pointer.Source
|
||||
var btns pointer.Buttons
|
||||
if jbtns&C.AMOTION_EVENT_BUTTON_PRIMARY != 0 {
|
||||
btns |= pointer.ButtonPrimary
|
||||
}
|
||||
if jbtns&C.AMOTION_EVENT_BUTTON_SECONDARY != 0 {
|
||||
btns |= pointer.ButtonSecondary
|
||||
}
|
||||
if jbtns&C.AMOTION_EVENT_BUTTON_TERTIARY != 0 {
|
||||
btns |= pointer.ButtonTertiary
|
||||
}
|
||||
switch tool {
|
||||
case C.AMOTION_EVENT_TOOL_TYPE_FINGER:
|
||||
src = pointer.Touch
|
||||
case C.AMOTION_EVENT_TOOL_TYPE_MOUSE:
|
||||
src = pointer.Mouse
|
||||
case C.AMOTION_EVENT_TOOL_TYPE_UNKNOWN:
|
||||
// For example, triggered via 'adb shell input tap'.
|
||||
// Instead of discarding it, treat it as a touch event.
|
||||
src = pointer.Touch
|
||||
default:
|
||||
return
|
||||
}
|
||||
w.callbacks.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: src,
|
||||
Buttons: btns,
|
||||
PointerID: pointer.ID(pointerID),
|
||||
Time: time.Duration(t) * time.Millisecond,
|
||||
Position: f32.Point{X: float32(x), Y: float32(y)},
|
||||
Scroll: f32.Pt(float32(scrollX), float32(scrollY)),
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
if w.view == 0 {
|
||||
return
|
||||
}
|
||||
if show {
|
||||
callVoidMethod(env, w.view, gioView.showTextInput)
|
||||
} else {
|
||||
callVoidMethod(env, w.view, gioView.hideTextInput)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func javaString(env *C.JNIEnv, str string) C.jstring {
|
||||
if str == "" {
|
||||
return 0
|
||||
}
|
||||
utf16Chars := utf16.Encode([]rune(str))
|
||||
return C.gio_jni_NewString(env, (*C.jchar)(unsafe.Pointer(&utf16Chars[0])), C.int(len(utf16Chars)))
|
||||
}
|
||||
|
||||
func varArgs(args []jvalue) *C.jvalue {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
return (*C.jvalue)(unsafe.Pointer(&args[0]))
|
||||
}
|
||||
|
||||
func callStaticVoidMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) error {
|
||||
C.gio_jni_CallStaticVoidMethodA(env, cls, method, varArgs(args))
|
||||
return exception(env)
|
||||
}
|
||||
|
||||
func callStaticObjectMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) {
|
||||
res := C.gio_jni_CallStaticObjectMethodA(env, cls, method, varArgs(args))
|
||||
return res, exception(env)
|
||||
}
|
||||
|
||||
func callVoidMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) error {
|
||||
C.gio_jni_CallVoidMethodA(env, obj, method, varArgs(args))
|
||||
return exception(env)
|
||||
}
|
||||
|
||||
func callObjectMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (C.jobject, error) {
|
||||
res := C.gio_jni_CallObjectMethodA(env, obj, method, varArgs(args))
|
||||
return res, exception(env)
|
||||
}
|
||||
|
||||
// exception returns an error corresponding to the pending
|
||||
// exception, or nil if no exception is pending. The pending
|
||||
// exception is cleared.
|
||||
func exception(env *C.JNIEnv) error {
|
||||
thr := C.gio_jni_ExceptionOccurred(env)
|
||||
if thr == 0 {
|
||||
return nil
|
||||
}
|
||||
C.gio_jni_ExceptionClear(env)
|
||||
cls := getObjectClass(env, C.jobject(thr))
|
||||
toString := getMethodID(env, cls, "toString", "()Ljava/lang/String;")
|
||||
msg, err := callObjectMethod(env, C.jobject(thr), toString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New(goString(env, C.jstring(msg)))
|
||||
}
|
||||
|
||||
func getObjectClass(env *C.JNIEnv, obj C.jobject) C.jclass {
|
||||
if obj == 0 {
|
||||
panic("null object")
|
||||
}
|
||||
cls := C.gio_jni_GetObjectClass(env, C.jobject(obj))
|
||||
if err := exception(env); err != nil {
|
||||
// GetObjectClass should never fail.
|
||||
panic(err)
|
||||
}
|
||||
return cls
|
||||
}
|
||||
|
||||
// goString converts the JVM jstring to a Go string.
|
||||
func goString(env *C.JNIEnv, str C.jstring) string {
|
||||
if str == 0 {
|
||||
return ""
|
||||
}
|
||||
strlen := C.gio_jni_GetStringLength(env, C.jstring(str))
|
||||
chars := C.gio_jni_GetStringChars(env, C.jstring(str))
|
||||
var utf16Chars []uint16
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&utf16Chars))
|
||||
hdr.Data = uintptr(unsafe.Pointer(chars))
|
||||
hdr.Cap = int(strlen)
|
||||
hdr.Len = int(strlen)
|
||||
utf8 := utf16.Decode(utf16Chars)
|
||||
return string(utf8)
|
||||
}
|
||||
|
||||
func Main() {
|
||||
}
|
||||
|
||||
func NewWindow(window Callbacks, opts *Options) error {
|
||||
mainWindow.in <- windowAndOptions{window, opts}
|
||||
return <-mainWindow.errs
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
jstr := javaString(env, s)
|
||||
callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
|
||||
jvalue(android.appCtx), jvalue(jstr))
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
c, err := callStaticObjectMethod(env, android.gioCls, android.mreadClipboard,
|
||||
jvalue(android.appCtx))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
content := goString(env, C.jstring(c))
|
||||
w.callbacks.Event(clipboard.Event{Text: content})
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) Option(opts *Options) {}
|
||||
|
||||
func (w *window) SetCursor(name pointer.CursorName) {
|
||||
w.setState(func(state *windowState) {
|
||||
state.cursor = &name
|
||||
})
|
||||
}
|
||||
|
||||
// setState adjust the window state on the main thread.
|
||||
func (w *window) setState(f func(state *windowState)) {
|
||||
runOnMain(func(env *C.JNIEnv) {
|
||||
f(&w.newState)
|
||||
if w.view == 0 {
|
||||
// No View attached. The state will be applied at next onCreateView.
|
||||
return
|
||||
}
|
||||
old := w.state
|
||||
state := w.newState
|
||||
applyStateDiff(env, w.view, old, state)
|
||||
w.state = state
|
||||
})
|
||||
}
|
||||
|
||||
func applyStateDiff(env *C.JNIEnv, view C.jobject, old, state windowState) {
|
||||
if state.cursor != nil && old.cursor != state.cursor {
|
||||
setCursor(env, view, *state.cursor)
|
||||
}
|
||||
}
|
||||
|
||||
func setCursor(env *C.JNIEnv, view C.jobject, name pointer.CursorName) {
|
||||
var curID int
|
||||
switch name {
|
||||
default:
|
||||
fallthrough
|
||||
case pointer.CursorDefault:
|
||||
curID = 1000 // TYPE_ARROW
|
||||
case pointer.CursorText:
|
||||
curID = 1008 // TYPE_TEXT
|
||||
case pointer.CursorPointer:
|
||||
curID = 1002 // TYPE_HAND
|
||||
case pointer.CursorCrossHair:
|
||||
curID = 1007 // TYPE_CROSSHAIR
|
||||
case pointer.CursorColResize:
|
||||
curID = 1014 // TYPE_HORIZONTAL_DOUBLE_ARROW
|
||||
case pointer.CursorRowResize:
|
||||
curID = 1015 // TYPE_VERTICAL_DOUBLE_ARROW
|
||||
case pointer.CursorNone:
|
||||
curID = 0 // TYPE_NULL
|
||||
}
|
||||
callVoidMethod(env, view, gioView.setCursor, jvalue(curID))
|
||||
}
|
||||
|
||||
// Close the window. Not implemented for Android.
|
||||
func (w *window) Close() {}
|
||||
|
||||
// runOnMain runs a function on the Java main thread.
|
||||
func runOnMain(f func(env *C.JNIEnv)) {
|
||||
go func() {
|
||||
mainFuncs <- f
|
||||
runInJVM(javaVM(), func(env *C.JNIEnv) {
|
||||
callStaticVoidMethod(env, android.gioCls, android.mwakeupMainThread)
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
//export Java_org_gioui_Gio_scheduleMainFuncs
|
||||
func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
|
||||
for {
|
||||
select {
|
||||
case f := <-mainFuncs:
|
||||
f(env)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (_ ViewEvent) ImplementsEvent() {}
|
||||
@@ -1,26 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
@import Dispatch;
|
||||
@import Foundation;
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
void gio_wakeupMainThread(void) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
gio_dispatchMainFuncs();
|
||||
});
|
||||
}
|
||||
|
||||
bool gio_isMainThread() {
|
||||
return [NSThread isMainThread];
|
||||
}
|
||||
|
||||
NSUInteger gio_nsstringLength(CFTypeRef cstr) {
|
||||
NSString *str = (__bridge NSString *)cstr;
|
||||
return [str length];
|
||||
}
|
||||
|
||||
void gio_nsstringGetCharacters(CFTypeRef cstr, unichar *chars, NSUInteger loc, NSUInteger length) {
|
||||
NSString *str = (__bridge NSString *)cstr;
|
||||
[str getCharacters:chars range:NSMakeRange(loc, length)];
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,ios
|
||||
|
||||
package wm
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -DGLES_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c
|
||||
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <UIKit/UIKit.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct drawParams {
|
||||
CGFloat dpi, sdpi;
|
||||
CGFloat width, height;
|
||||
CGFloat top, right, bottom, left;
|
||||
};
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_showTextInput(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_hideTextInput(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_addLayerToView(CFTypeRef viewRef, CFTypeRef layerRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_updateView(CFTypeRef viewRef, CFTypeRef layerRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_removeLayer(CFTypeRef layerRef);
|
||||
__attribute__ ((visibility ("hidden"))) struct drawParams gio_viewDrawParams(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_readClipboard(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_writeClipboard(unichar *chars, NSUInteger length);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"image"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/f32"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/clipboard"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/key"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/pointer"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/system"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
)
|
||||
|
||||
type window struct {
|
||||
view C.CFTypeRef
|
||||
w Callbacks
|
||||
displayLink *displayLink
|
||||
|
||||
layer C.CFTypeRef
|
||||
visible atomic.Value
|
||||
cursor pointer.CursorName
|
||||
|
||||
pointerMap []C.CFTypeRef
|
||||
}
|
||||
|
||||
var mainWindow = newWindowRendezvous()
|
||||
|
||||
var layerFactory func() uintptr
|
||||
|
||||
var views = make(map[C.CFTypeRef]*window)
|
||||
|
||||
func init() {
|
||||
// Darwin requires UI operations happen on the main thread only.
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
//export onCreate
|
||||
func onCreate(view C.CFTypeRef) {
|
||||
w := &window{
|
||||
view: view,
|
||||
}
|
||||
dl, err := NewDisplayLink(func() {
|
||||
w.draw(false)
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.displayLink = dl
|
||||
wopts := <-mainWindow.out
|
||||
w.w = wopts.window
|
||||
w.w.SetDriver(w)
|
||||
w.visible.Store(false)
|
||||
w.layer = C.CFTypeRef(layerFactory())
|
||||
C.gio_addLayerToView(view, w.layer)
|
||||
views[view] = w
|
||||
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
||||
}
|
||||
|
||||
//export gio_onDraw
|
||||
func gio_onDraw(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
params := C.gio_viewDrawParams(w.view)
|
||||
if params.width == 0 || params.height == 0 {
|
||||
return
|
||||
}
|
||||
wasVisible := w.isVisible()
|
||||
w.visible.Store(true)
|
||||
C.gio_updateView(w.view, w.layer)
|
||||
if !wasVisible {
|
||||
w.w.Event(system.StageEvent{Stage: system.StageRunning})
|
||||
}
|
||||
const inchPrDp = 1.0 / 163
|
||||
w.w.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: int(params.width + .5),
|
||||
Y: int(params.height + .5),
|
||||
},
|
||||
Insets: system.Insets{
|
||||
Top: unit.Px(float32(params.top)),
|
||||
Right: unit.Px(float32(params.right)),
|
||||
Bottom: unit.Px(float32(params.bottom)),
|
||||
Left: unit.Px(float32(params.left)),
|
||||
},
|
||||
Metric: unit.Metric{
|
||||
PxPerDp: float32(params.dpi) * inchPrDp,
|
||||
PxPerSp: float32(params.sdpi) * inchPrDp,
|
||||
},
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
}
|
||||
|
||||
//export onStop
|
||||
func onStop(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
w.visible.Store(false)
|
||||
w.w.Event(system.StageEvent{Stage: system.StagePaused})
|
||||
}
|
||||
|
||||
//export onDestroy
|
||||
func onDestroy(view C.CFTypeRef) {
|
||||
w := views[view]
|
||||
delete(views, view)
|
||||
w.w.Event(system.DestroyEvent{})
|
||||
w.displayLink.Close()
|
||||
C.gio_removeLayer(w.layer)
|
||||
C.CFRelease(w.layer)
|
||||
w.layer = 0
|
||||
w.view = 0
|
||||
}
|
||||
|
||||
//export onFocus
|
||||
func onFocus(view C.CFTypeRef, focus int) {
|
||||
w := views[view]
|
||||
w.w.Event(key.FocusEvent{Focus: focus != 0})
|
||||
}
|
||||
|
||||
//export onLowMemory
|
||||
func onLowMemory() {
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
}
|
||||
|
||||
//export onUpArrow
|
||||
func onUpArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameUpArrow)
|
||||
}
|
||||
|
||||
//export onDownArrow
|
||||
func onDownArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameDownArrow)
|
||||
}
|
||||
|
||||
//export onLeftArrow
|
||||
func onLeftArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameLeftArrow)
|
||||
}
|
||||
|
||||
//export onRightArrow
|
||||
func onRightArrow(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameRightArrow)
|
||||
}
|
||||
|
||||
//export onDeleteBackward
|
||||
func onDeleteBackward(view C.CFTypeRef) {
|
||||
views[view].onKeyCommand(key.NameDeleteBackward)
|
||||
}
|
||||
|
||||
//export onText
|
||||
func onText(view C.CFTypeRef, str *C.char) {
|
||||
w := views[view]
|
||||
w.w.Event(key.EditEvent{
|
||||
Text: C.GoString(str),
|
||||
})
|
||||
}
|
||||
|
||||
//export onTouch
|
||||
func onTouch(last C.int, view, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
||||
var typ pointer.Type
|
||||
switch phase {
|
||||
case C.UITouchPhaseBegan:
|
||||
typ = pointer.Press
|
||||
case C.UITouchPhaseMoved:
|
||||
typ = pointer.Move
|
||||
case C.UITouchPhaseEnded:
|
||||
typ = pointer.Release
|
||||
case C.UITouchPhaseCancelled:
|
||||
typ = pointer.Cancel
|
||||
default:
|
||||
return
|
||||
}
|
||||
w := views[view]
|
||||
t := time.Duration(float64(ti) * float64(time.Second))
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: pointer.Touch,
|
||||
PointerID: w.lookupTouch(last != 0, touchRef),
|
||||
Position: p,
|
||||
Time: t,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
runOnMain(func() {
|
||||
content := nsstringToString(C.gio_readClipboard())
|
||||
w.w.Event(clipboard.Event{Text: content})
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
u16 := utf16.Encode([]rune(s))
|
||||
runOnMain(func() {
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
}
|
||||
C.gio_writeClipboard(chars, C.NSUInteger(len(u16)))
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) Option(opts *Options) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
v := w.view
|
||||
if v == 0 {
|
||||
return
|
||||
}
|
||||
if anim {
|
||||
w.displayLink.Start()
|
||||
} else {
|
||||
w.displayLink.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetCursor(name pointer.CursorName) {
|
||||
w.cursor = windowSetCursor(w.cursor, name)
|
||||
}
|
||||
|
||||
func (w *window) onKeyCommand(name string) {
|
||||
w.w.Event(key.Event{
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
|
||||
// lookupTouch maps an UITouch pointer value to an index. If
|
||||
// last is set, the map is cleared.
|
||||
func (w *window) lookupTouch(last bool, touch C.CFTypeRef) pointer.ID {
|
||||
id := -1
|
||||
for i, ref := range w.pointerMap {
|
||||
if ref == touch {
|
||||
id = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if id == -1 {
|
||||
id = len(w.pointerMap)
|
||||
w.pointerMap = append(w.pointerMap, touch)
|
||||
}
|
||||
if last {
|
||||
w.pointerMap = w.pointerMap[:0]
|
||||
}
|
||||
return pointer.ID(id)
|
||||
}
|
||||
|
||||
func (w *window) contextLayer() uintptr {
|
||||
return uintptr(w.layer)
|
||||
}
|
||||
|
||||
func (w *window) isVisible() bool {
|
||||
return w.visible.Load().(bool)
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
v := w.view
|
||||
if v == 0 {
|
||||
return
|
||||
}
|
||||
C.CFRetain(v)
|
||||
runOnMain(func() {
|
||||
defer C.CFRelease(v)
|
||||
if show {
|
||||
C.gio_showTextInput(w.view)
|
||||
} else {
|
||||
C.gio_hideTextInput(w.view)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Close the window. Not implemented for iOS.
|
||||
func (w *window) Close() {}
|
||||
|
||||
func NewWindow(win Callbacks, opts *Options) error {
|
||||
mainWindow.in <- windowAndOptions{win, opts}
|
||||
return <-mainWindow.errs
|
||||
}
|
||||
|
||||
func Main() {
|
||||
}
|
||||
|
||||
//export gio_runMain
|
||||
func gio_runMain() {
|
||||
runMain()
|
||||
}
|
||||
@@ -1,513 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"runtime"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/f32"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/clipboard"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/key"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/pointer"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/system"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
|
||||
_ "github.com/p9c/p9/pkg/gel/gio/internal/cocoainit"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c
|
||||
|
||||
#include <AppKit/AppKit.h>
|
||||
|
||||
#define GIO_MOUSE_MOVE 1
|
||||
#define GIO_MOUSE_UP 2
|
||||
#define GIO_MOUSE_DOWN 3
|
||||
#define GIO_MOUSE_SCROLL 4
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_main(void);
|
||||
__attribute__ ((visibility ("hidden"))) CGFloat gio_viewWidth(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) CGFloat gio_viewHeight(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) CGFloat gio_getViewBackingScale(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) CGFloat gio_getScreenBackingScale(void);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_readClipboard(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_writeClipboard(unichar *chars, NSUInteger length);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setNeedsDisplay(CFTypeRef viewRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_toggleFullScreen(CFTypeRef windowRef);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_makeKeyAndOrderFront(CFTypeRef windowRef);
|
||||
__attribute__ ((visibility ("hidden"))) NSPoint gio_cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_close(CFTypeRef windowRef);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setSize(CFTypeRef windowRef, CGFloat width, CGFloat height);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setTitle(CFTypeRef windowRef, const char *title);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func init() {
|
||||
// Darwin requires that UI operations happen on the main thread only.
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
type window struct {
|
||||
view C.CFTypeRef
|
||||
window C.CFTypeRef
|
||||
w Callbacks
|
||||
stage system.Stage
|
||||
displayLink *displayLink
|
||||
cursor pointer.CursorName
|
||||
|
||||
scale float32
|
||||
mode WindowMode
|
||||
}
|
||||
|
||||
// viewMap is the mapping from Cocoa NSViews to Go windows.
|
||||
var viewMap = make(map[C.CFTypeRef]*window)
|
||||
|
||||
var viewFactory func() C.CFTypeRef
|
||||
|
||||
// launched is closed when applicationDidFinishLaunching is called.
|
||||
var launched = make(chan struct{})
|
||||
|
||||
// nextTopLeft is the offset to use for the next window's call to
|
||||
// cascadeTopLeftFromPoint.
|
||||
var nextTopLeft C.NSPoint
|
||||
|
||||
// mustView is like lookupView, except that it panics
|
||||
// if the view isn't mapped.
|
||||
func mustView(view C.CFTypeRef) *window {
|
||||
w, ok := lookupView(view)
|
||||
if !ok {
|
||||
panic("no window for view")
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func lookupView(view C.CFTypeRef) (*window, bool) {
|
||||
w, exists := viewMap[view]
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
return w, true
|
||||
}
|
||||
|
||||
func deleteView(view C.CFTypeRef) {
|
||||
delete(viewMap, view)
|
||||
}
|
||||
|
||||
func insertView(view C.CFTypeRef, w *window) {
|
||||
viewMap[view] = w
|
||||
}
|
||||
|
||||
func (w *window) contextView() C.CFTypeRef {
|
||||
return w.view
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
runOnMain(func() {
|
||||
content := nsstringToString(C.gio_readClipboard())
|
||||
w.w.Event(clipboard.Event{Text: content})
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
u16 := utf16.Encode([]rune(s))
|
||||
runOnMain(func() {
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
}
|
||||
C.gio_writeClipboard(chars, C.NSUInteger(len(u16)))
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) Option(opts *Options) {
|
||||
w.runOnMain(func() {
|
||||
screenScale := float32(C.gio_getScreenBackingScale())
|
||||
cfg := configFor(screenScale)
|
||||
val := func(v unit.Value) float32 {
|
||||
return float32(cfg.Px(v)) / screenScale
|
||||
}
|
||||
if o := opts.Size; o != nil {
|
||||
width := val(o.Width)
|
||||
height := val(o.Height)
|
||||
if width > 0 || height > 0 {
|
||||
C.gio_setSize(w.window, C.CGFloat(width), C.CGFloat(height))
|
||||
}
|
||||
}
|
||||
if o := opts.MinSize; o != nil {
|
||||
width := val(o.Width)
|
||||
height := val(o.Height)
|
||||
if width > 0 || height > 0 {
|
||||
C.gio_setMinSize(w.window, C.CGFloat(width), C.CGFloat(height))
|
||||
}
|
||||
}
|
||||
if o := opts.MaxSize; o != nil {
|
||||
width := val(o.Width)
|
||||
height := val(o.Height)
|
||||
if width > 0 || height > 0 {
|
||||
C.gio_setMaxSize(w.window, C.CGFloat(width), C.CGFloat(height))
|
||||
}
|
||||
}
|
||||
if o := opts.Title; o != nil {
|
||||
title := C.CString(*o)
|
||||
defer C.free(unsafe.Pointer(title))
|
||||
C.gio_setTitle(w.window, title)
|
||||
}
|
||||
if o := opts.WindowMode; o != nil {
|
||||
w.SetWindowMode(*o)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) SetWindowMode(mode WindowMode) {
|
||||
switch mode {
|
||||
case w.mode:
|
||||
case Windowed, Fullscreen:
|
||||
C.gio_toggleFullScreen(w.window)
|
||||
w.mode = mode
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetCursor(name pointer.CursorName) {
|
||||
w.cursor = windowSetCursor(w.cursor, name)
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
if anim {
|
||||
w.displayLink.Start()
|
||||
} else {
|
||||
w.displayLink.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) runOnMain(f func()) {
|
||||
runOnMain(func() {
|
||||
// Make sure the view is still valid. The window might've been closed
|
||||
// during the switch to the main thread.
|
||||
if w.view != 0 {
|
||||
f()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) Close() {
|
||||
w.runOnMain(func() {
|
||||
C.gio_close(w.window)
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) setStage(stage system.Stage) {
|
||||
if stage == w.stage {
|
||||
return
|
||||
}
|
||||
w.stage = stage
|
||||
w.w.Event(system.StageEvent{Stage: stage})
|
||||
}
|
||||
|
||||
//export gio_onKeys
|
||||
func gio_onKeys(view C.CFTypeRef, cstr *C.char, ti C.double, mods C.NSUInteger, keyDown C.bool) {
|
||||
str := C.GoString(cstr)
|
||||
kmods := convertMods(mods)
|
||||
ks := key.Release
|
||||
if keyDown {
|
||||
ks = key.Press
|
||||
}
|
||||
w := mustView(view)
|
||||
for _, k := range str {
|
||||
if n, ok := convertKey(k); ok {
|
||||
w.w.Event(key.Event{
|
||||
Name: n,
|
||||
Modifiers: kmods,
|
||||
State: ks,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onText
|
||||
func gio_onText(view C.CFTypeRef, cstr *C.char) {
|
||||
str := C.GoString(cstr)
|
||||
w := mustView(view)
|
||||
w.w.Event(key.EditEvent{Text: str})
|
||||
}
|
||||
|
||||
//export gio_onMouse
|
||||
func gio_onMouse(view C.CFTypeRef, cdir C.int, cbtns C.NSUInteger, x, y, dx, dy C.CGFloat, ti C.double, mods C.NSUInteger) {
|
||||
var typ pointer.Type
|
||||
switch cdir {
|
||||
case C.GIO_MOUSE_MOVE:
|
||||
typ = pointer.Move
|
||||
case C.GIO_MOUSE_UP:
|
||||
typ = pointer.Release
|
||||
case C.GIO_MOUSE_DOWN:
|
||||
typ = pointer.Press
|
||||
case C.GIO_MOUSE_SCROLL:
|
||||
typ = pointer.Scroll
|
||||
default:
|
||||
panic("invalid direction")
|
||||
}
|
||||
var btns pointer.Buttons
|
||||
if cbtns&(1<<0) != 0 {
|
||||
btns |= pointer.ButtonPrimary
|
||||
}
|
||||
if cbtns&(1<<1) != 0 {
|
||||
btns |= pointer.ButtonSecondary
|
||||
}
|
||||
if cbtns&(1<<2) != 0 {
|
||||
btns |= pointer.ButtonTertiary
|
||||
}
|
||||
t := time.Duration(float64(ti)*float64(time.Second) + .5)
|
||||
w := mustView(view)
|
||||
xf, yf := float32(x)*w.scale, float32(y)*w.scale
|
||||
dxf, dyf := float32(dx)*w.scale, float32(dy)*w.scale
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: pointer.Mouse,
|
||||
Time: t,
|
||||
Buttons: btns,
|
||||
Position: f32.Point{X: xf, Y: yf},
|
||||
Scroll: f32.Point{X: dxf, Y: dyf},
|
||||
Modifiers: convertMods(mods),
|
||||
})
|
||||
}
|
||||
|
||||
//export gio_onDraw
|
||||
func gio_onDraw(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.draw()
|
||||
}
|
||||
|
||||
//export gio_onFocus
|
||||
func gio_onFocus(view C.CFTypeRef, focus C.int) {
|
||||
w := mustView(view)
|
||||
w.w.Event(key.FocusEvent{Focus: focus == 1})
|
||||
w.SetCursor(w.cursor)
|
||||
}
|
||||
|
||||
//export gio_onChangeScreen
|
||||
func gio_onChangeScreen(view C.CFTypeRef, did uint64) {
|
||||
w := mustView(view)
|
||||
w.displayLink.SetDisplayID(did)
|
||||
}
|
||||
|
||||
func (w *window) draw() {
|
||||
w.scale = float32(C.gio_getViewBackingScale(w.view))
|
||||
wf, hf := float32(C.gio_viewWidth(w.view)), float32(C.gio_viewHeight(w.view))
|
||||
if wf == 0 || hf == 0 {
|
||||
return
|
||||
}
|
||||
width := int(wf*w.scale + .5)
|
||||
height := int(hf*w.scale + .5)
|
||||
cfg := configFor(w.scale)
|
||||
w.setStage(system.StageRunning)
|
||||
w.w.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: width,
|
||||
Y: height,
|
||||
},
|
||||
Metric: cfg,
|
||||
},
|
||||
Sync: true,
|
||||
})
|
||||
}
|
||||
|
||||
func configFor(scale float32) unit.Metric {
|
||||
return unit.Metric{
|
||||
PxPerDp: scale,
|
||||
PxPerSp: scale,
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onClose
|
||||
func gio_onClose(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.displayLink.Close()
|
||||
deleteView(view)
|
||||
w.w.Event(system.DestroyEvent{})
|
||||
C.CFRelease(w.view)
|
||||
w.view = 0
|
||||
C.CFRelease(w.window)
|
||||
w.window = 0
|
||||
}
|
||||
|
||||
//export gio_onHide
|
||||
func gio_onHide(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.setStage(system.StagePaused)
|
||||
}
|
||||
|
||||
//export gio_onShow
|
||||
func gio_onShow(view C.CFTypeRef) {
|
||||
w := mustView(view)
|
||||
w.setStage(system.StageRunning)
|
||||
}
|
||||
|
||||
//export gio_onAppHide
|
||||
func gio_onAppHide() {
|
||||
for _, w := range viewMap {
|
||||
w.setStage(system.StagePaused)
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onAppShow
|
||||
func gio_onAppShow() {
|
||||
for _, w := range viewMap {
|
||||
w.setStage(system.StageRunning)
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_onFinishLaunching
|
||||
func gio_onFinishLaunching() {
|
||||
close(launched)
|
||||
}
|
||||
|
||||
func NewWindow(win Callbacks, opts *Options) error {
|
||||
<-launched
|
||||
errch := make(chan error)
|
||||
runOnMain(func() {
|
||||
w, err := newWindow(opts)
|
||||
if err != nil {
|
||||
errch <- err
|
||||
return
|
||||
}
|
||||
errch <- nil
|
||||
win.SetDriver(w)
|
||||
w.w = win
|
||||
w.window = C.gio_createWindow(w.view, nil, 0, 0, 0, 0, 0, 0)
|
||||
w.Option(opts)
|
||||
if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
|
||||
// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
|
||||
// and just returns the offset we need for the first window.
|
||||
nextTopLeft = C.gio_cascadeTopLeftFromPoint(w.window, nextTopLeft)
|
||||
}
|
||||
nextTopLeft = C.gio_cascadeTopLeftFromPoint(w.window, nextTopLeft)
|
||||
C.gio_makeKeyAndOrderFront(w.window)
|
||||
})
|
||||
return <-errch
|
||||
}
|
||||
|
||||
func newWindow(opts *Options) (*window, error) {
|
||||
view := viewFactory()
|
||||
if view == 0 {
|
||||
return nil, errors.New("CreateWindow: failed to create view")
|
||||
}
|
||||
scale := float32(C.gio_getViewBackingScale(view))
|
||||
w := &window{
|
||||
view: view,
|
||||
scale: scale,
|
||||
}
|
||||
dl, err := NewDisplayLink(func() {
|
||||
w.runOnMain(func() {
|
||||
C.gio_setNeedsDisplay(w.view)
|
||||
})
|
||||
})
|
||||
w.displayLink = dl
|
||||
if err != nil {
|
||||
C.CFRelease(view)
|
||||
return nil, err
|
||||
}
|
||||
insertView(view, w)
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func Main() {
|
||||
C.gio_main()
|
||||
}
|
||||
|
||||
func convertKey(k rune) (string, bool) {
|
||||
var n string
|
||||
switch k {
|
||||
case 0x1b:
|
||||
n = key.NameEscape
|
||||
case C.NSLeftArrowFunctionKey:
|
||||
n = key.NameLeftArrow
|
||||
case C.NSRightArrowFunctionKey:
|
||||
n = key.NameRightArrow
|
||||
case C.NSUpArrowFunctionKey:
|
||||
n = key.NameUpArrow
|
||||
case C.NSDownArrowFunctionKey:
|
||||
n = key.NameDownArrow
|
||||
case 0xd:
|
||||
n = key.NameReturn
|
||||
case 0x3:
|
||||
n = key.NameEnter
|
||||
case C.NSHomeFunctionKey:
|
||||
n = key.NameHome
|
||||
case C.NSEndFunctionKey:
|
||||
n = key.NameEnd
|
||||
case 0x7f:
|
||||
n = key.NameDeleteBackward
|
||||
case C.NSDeleteFunctionKey:
|
||||
n = key.NameDeleteForward
|
||||
case C.NSPageUpFunctionKey:
|
||||
n = key.NamePageUp
|
||||
case C.NSPageDownFunctionKey:
|
||||
n = key.NamePageDown
|
||||
case C.NSF1FunctionKey:
|
||||
n = "F1"
|
||||
case C.NSF2FunctionKey:
|
||||
n = "F2"
|
||||
case C.NSF3FunctionKey:
|
||||
n = "F3"
|
||||
case C.NSF4FunctionKey:
|
||||
n = "F4"
|
||||
case C.NSF5FunctionKey:
|
||||
n = "F5"
|
||||
case C.NSF6FunctionKey:
|
||||
n = "F6"
|
||||
case C.NSF7FunctionKey:
|
||||
n = "F7"
|
||||
case C.NSF8FunctionKey:
|
||||
n = "F8"
|
||||
case C.NSF9FunctionKey:
|
||||
n = "F9"
|
||||
case C.NSF10FunctionKey:
|
||||
n = "F10"
|
||||
case C.NSF11FunctionKey:
|
||||
n = "F11"
|
||||
case C.NSF12FunctionKey:
|
||||
n = "F12"
|
||||
case 0x09, 0x19:
|
||||
n = key.NameTab
|
||||
case 0x20:
|
||||
n = key.NameSpace
|
||||
default:
|
||||
k = unicode.ToUpper(k)
|
||||
if !unicode.IsPrint(k) {
|
||||
return "", false
|
||||
}
|
||||
n = string(k)
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
func convertMods(mods C.NSUInteger) key.Modifiers {
|
||||
var kmods key.Modifiers
|
||||
if mods&C.NSAlternateKeyMask != 0 {
|
||||
kmods |= key.ModAlt
|
||||
}
|
||||
if mods&C.NSControlKeyMask != 0 {
|
||||
kmods |= key.ModCtrl
|
||||
}
|
||||
if mods&C.NSCommandKeyMask != 0 {
|
||||
kmods |= key.ModCommand
|
||||
}
|
||||
if mods&C.NSShiftKeyMask != 0 {
|
||||
kmods |= key.ModShift
|
||||
}
|
||||
return kmods
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios
|
||||
|
||||
@import AppKit;
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
||||
@end
|
||||
|
||||
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
||||
@end
|
||||
|
||||
@implementation GioWindowDelegate
|
||||
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onHide((__bridge CFTypeRef)window.contentView);
|
||||
}
|
||||
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onShow((__bridge CFTypeRef)window.contentView);
|
||||
}
|
||||
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
||||
CFTypeRef view = (__bridge CFTypeRef)window.contentView;
|
||||
gio_onChangeScreen(view, dispID);
|
||||
}
|
||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onFocus((__bridge CFTypeRef)window.contentView, 1);
|
||||
}
|
||||
- (void)windowDidResignKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
gio_onFocus((__bridge CFTypeRef)window.contentView, 0);
|
||||
}
|
||||
- (void)windowWillClose:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
window.delegate = nil;
|
||||
gio_onClose((__bridge CFTypeRef)window.contentView);
|
||||
}
|
||||
@end
|
||||
|
||||
// Delegates are weakly referenced from their peers. Nothing
|
||||
// else holds a strong reference to our window delegate, so
|
||||
// keep a single global reference instead.
|
||||
static GioWindowDelegate *globalWindowDel;
|
||||
|
||||
void gio_writeClipboard(unichar *chars, NSUInteger length) {
|
||||
@autoreleasepool {
|
||||
NSString *s = [NSString string];
|
||||
if (length > 0) {
|
||||
s = [NSString stringWithCharacters:chars length:length];
|
||||
}
|
||||
NSPasteboard *p = NSPasteboard.generalPasteboard;
|
||||
[p declareTypes:@[NSPasteboardTypeString] owner:nil];
|
||||
[p setString:s forType:NSPasteboardTypeString];
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_readClipboard(void) {
|
||||
@autoreleasepool {
|
||||
NSPasteboard *p = NSPasteboard.generalPasteboard;
|
||||
NSString *content = [p stringForType:NSPasteboardTypeString];
|
||||
return (__bridge_retained CFTypeRef)content;
|
||||
}
|
||||
}
|
||||
|
||||
CGFloat gio_viewHeight(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view bounds].size.height;
|
||||
}
|
||||
|
||||
CGFloat gio_viewWidth(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view bounds].size.width;
|
||||
}
|
||||
|
||||
CGFloat gio_getScreenBackingScale(void) {
|
||||
return [NSScreen.mainScreen backingScaleFactor];
|
||||
}
|
||||
|
||||
CGFloat gio_getViewBackingScale(CFTypeRef viewRef) {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return [view.window backingScaleFactor];
|
||||
}
|
||||
|
||||
void gio_hideCursor() {
|
||||
@autoreleasepool {
|
||||
[NSCursor hide];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_showCursor() {
|
||||
@autoreleasepool {
|
||||
[NSCursor unhide];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_setCursor(NSUInteger curID) {
|
||||
@autoreleasepool {
|
||||
switch (curID) {
|
||||
case 1:
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
case 2:
|
||||
[NSCursor.IBeamCursor set];
|
||||
break;
|
||||
case 3:
|
||||
[NSCursor.pointingHandCursor set];
|
||||
break;
|
||||
case 4:
|
||||
[NSCursor.crosshairCursor set];
|
||||
break;
|
||||
case 5:
|
||||
[NSCursor.resizeLeftRightCursor set];
|
||||
break;
|
||||
case 6:
|
||||
[NSCursor.resizeUpDownCursor set];
|
||||
break;
|
||||
case 7:
|
||||
[NSCursor.openHandCursor set];
|
||||
break;
|
||||
default:
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static CVReturn displayLinkCallback(CVDisplayLinkRef dl, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) {
|
||||
gio_onFrameCallback(dl);
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
|
||||
CFTypeRef gio_createDisplayLink(void) {
|
||||
CVDisplayLinkRef dl;
|
||||
CVDisplayLinkCreateWithActiveCGDisplays(&dl);
|
||||
CVDisplayLinkSetOutputCallback(dl, displayLinkCallback, nil);
|
||||
return dl;
|
||||
}
|
||||
|
||||
int gio_startDisplayLink(CFTypeRef dl) {
|
||||
return CVDisplayLinkStart((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
int gio_stopDisplayLink(CFTypeRef dl) {
|
||||
return CVDisplayLinkStop((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
void gio_releaseDisplayLink(CFTypeRef dl) {
|
||||
CVDisplayLinkRelease((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) {
|
||||
CVDisplayLinkSetCurrentCGDisplay((CVDisplayLinkRef)dl, (CGDirectDisplayID)did);
|
||||
}
|
||||
|
||||
NSPoint gio_cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
return [window cascadeTopLeftFromPoint:topLeft];
|
||||
}
|
||||
|
||||
void gio_makeKeyAndOrderFront(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
void gio_toggleFullScreen(CFTypeRef windowRef) {
|
||||
NSWindow *window = (__bridge NSWindow *)windowRef;
|
||||
[window toggleFullScreen:nil];
|
||||
}
|
||||
|
||||
CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) {
|
||||
@autoreleasepool {
|
||||
NSRect rect = NSMakeRect(0, 0, width, height);
|
||||
NSUInteger styleMask = NSTitledWindowMask |
|
||||
NSResizableWindowMask |
|
||||
NSMiniaturizableWindowMask |
|
||||
NSClosableWindowMask;
|
||||
|
||||
NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
if (minWidth > 0 || minHeight > 0) {
|
||||
window.contentMinSize = NSMakeSize(minWidth, minHeight);
|
||||
}
|
||||
if (maxWidth > 0 || maxHeight > 0) {
|
||||
window.contentMaxSize = NSMakeSize(maxWidth, maxHeight);
|
||||
}
|
||||
[window setAcceptsMouseMovedEvents:YES];
|
||||
if (title != nil) {
|
||||
window.title = [NSString stringWithUTF8String: title];
|
||||
}
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[window setContentView:view];
|
||||
[window makeFirstResponder:view];
|
||||
window.releasedWhenClosed = NO;
|
||||
window.delegate = globalWindowDel;
|
||||
return (__bridge_retained CFTypeRef)window;
|
||||
}
|
||||
}
|
||||
|
||||
void gio_close(CFTypeRef windowRef) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
[window performClose:nil];
|
||||
}
|
||||
|
||||
void gio_setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
NSSize size = NSMakeSize(width, height);
|
||||
[window setContentSize:size];
|
||||
}
|
||||
|
||||
void gio_setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
window.contentMinSize = NSMakeSize(width, height);
|
||||
}
|
||||
|
||||
void gio_setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
window.contentMaxSize = NSMakeSize(width, height);
|
||||
}
|
||||
|
||||
void gio_setTitle(CFTypeRef windowRef, const char *title) {
|
||||
NSWindow* window = (__bridge NSWindow *)windowRef;
|
||||
window.title = [NSString stringWithUTF8String: title];
|
||||
}
|
||||
|
||||
@implementation GioAppDelegate
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)];
|
||||
gio_onFinishLaunching();
|
||||
}
|
||||
- (void)applicationDidHide:(NSNotification *)aNotification {
|
||||
gio_onAppHide();
|
||||
}
|
||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||
gio_onAppShow();
|
||||
}
|
||||
@end
|
||||
|
||||
void gio_main() {
|
||||
@autoreleasepool {
|
||||
[NSApplication sharedApplication];
|
||||
GioAppDelegate *del = [[GioAppDelegate alloc] init];
|
||||
[NSApp setDelegate:del];
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
|
||||
NSMenuItem *mainMenu = [NSMenuItem new];
|
||||
|
||||
NSMenu *menu = [NSMenu new];
|
||||
NSMenuItem *hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide"
|
||||
action:@selector(hide:)
|
||||
keyEquivalent:@"h"];
|
||||
[menu addItem:hideMenuItem];
|
||||
NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
|
||||
action:@selector(terminate:)
|
||||
keyEquivalent:@"q"];
|
||||
[menu addItem:quitMenuItem];
|
||||
[mainMenu setSubmenu:menu];
|
||||
NSMenu *menuBar = [NSMenu new];
|
||||
[menuBar addItem:mainMenu];
|
||||
[NSApp setMainMenu:menuBar];
|
||||
|
||||
globalWindowDel = [[GioWindowDelegate alloc] init];
|
||||
|
||||
[NSApp run];
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build linux,!android freebsd openbsd
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
func Main() {
|
||||
select {}
|
||||
}
|
||||
|
||||
type windowDriver func(Callbacks, *Options) error
|
||||
|
||||
// Instead of creating files with build tags for each combination of wayland +/- x11
|
||||
// let each driver initialize these variables with their own version of createWindow.
|
||||
var wlDriver, x11Driver windowDriver
|
||||
|
||||
func NewWindow(window Callbacks, opts *Options) error {
|
||||
var errFirst error
|
||||
for _, d := range []windowDriver{x11Driver, wlDriver} {
|
||||
if d == nil {
|
||||
continue
|
||||
}
|
||||
err := d(window, opts)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if errFirst == nil {
|
||||
errFirst = err
|
||||
}
|
||||
}
|
||||
if errFirst != nil {
|
||||
return errFirst
|
||||
}
|
||||
return errors.New("app: no window driver available")
|
||||
}
|
||||
@@ -1,798 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/app/internal/windows"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
gowindows "golang.org/x/sys/windows"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/f32"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/clipboard"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/key"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/pointer"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/system"
|
||||
)
|
||||
|
||||
type winConstraints struct {
|
||||
minWidth, minHeight int32
|
||||
maxWidth, maxHeight int32
|
||||
}
|
||||
|
||||
type winDeltas struct {
|
||||
width int32
|
||||
height int32
|
||||
}
|
||||
|
||||
type window struct {
|
||||
hwnd syscall.Handle
|
||||
hdc syscall.Handle
|
||||
w Callbacks
|
||||
width int
|
||||
height int
|
||||
stage system.Stage
|
||||
pointerBtns pointer.Buttons
|
||||
|
||||
// cursorIn tracks whether the cursor was inside the window according
|
||||
// to the most recent WM_SETCURSOR.
|
||||
cursorIn bool
|
||||
cursor syscall.Handle
|
||||
|
||||
// placement saves the previous window position when in full screen mode.
|
||||
placement *windows.WindowPlacement
|
||||
|
||||
mu sync.Mutex
|
||||
animating bool
|
||||
|
||||
minmax winConstraints
|
||||
deltas winDeltas
|
||||
opts *Options
|
||||
}
|
||||
|
||||
const (
|
||||
_WM_REDRAW = windows.WM_USER + iota
|
||||
_WM_CURSOR
|
||||
_WM_OPTION
|
||||
)
|
||||
|
||||
type gpuAPI struct {
|
||||
priority int
|
||||
initializer func(w *window) (Context, error)
|
||||
}
|
||||
|
||||
// drivers is the list of potential Context implementations.
|
||||
var drivers []gpuAPI
|
||||
|
||||
// winMap maps win32 HWNDs to *windows.
|
||||
var winMap sync.Map
|
||||
|
||||
// iconID is the ID of the icon in the resource file.
|
||||
const iconID = 1
|
||||
|
||||
var resources struct {
|
||||
once sync.Once
|
||||
// handle is the module handle from GetModuleHandle.
|
||||
handle syscall.Handle
|
||||
// class is the Gio window class from RegisterClassEx.
|
||||
class uint16
|
||||
// cursor is the arrow cursor resource.
|
||||
cursor syscall.Handle
|
||||
}
|
||||
|
||||
func Main() {
|
||||
select {}
|
||||
}
|
||||
|
||||
func NewWindow(window Callbacks, opts *Options) error {
|
||||
cerr := make(chan error)
|
||||
go func() {
|
||||
// GetMessage and PeekMessage can filter on a window HWND, but
|
||||
// then thread-specific messages such as WM_QUIT are ignored.
|
||||
// Instead lock the thread so window messages arrive through
|
||||
// unfiltered GetMessage calls.
|
||||
runtime.LockOSThread()
|
||||
w, err := createNativeWindow(opts)
|
||||
if err != nil {
|
||||
cerr <- err
|
||||
return
|
||||
}
|
||||
defer w.destroy()
|
||||
cerr <- nil
|
||||
winMap.Store(w.hwnd, w)
|
||||
defer winMap.Delete(w.hwnd)
|
||||
w.w = window
|
||||
w.w.SetDriver(w)
|
||||
defer w.w.Event(system.DestroyEvent{})
|
||||
w.Option(opts)
|
||||
windows.ShowWindow(w.hwnd, windows.SW_SHOWDEFAULT)
|
||||
windows.SetForegroundWindow(w.hwnd)
|
||||
windows.SetFocus(w.hwnd)
|
||||
// Since the window class for the cursor is null,
|
||||
// set it here to show the cursor.
|
||||
w.SetCursor(pointer.CursorDefault)
|
||||
if err := w.loop(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
return <-cerr
|
||||
}
|
||||
|
||||
// initResources initializes the resources global.
|
||||
func initResources() error {
|
||||
windows.SetProcessDPIAware()
|
||||
hInst, err := windows.GetModuleHandle()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources.handle = hInst
|
||||
c, err := windows.LoadCursor(windows.IDC_ARROW)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources.cursor = c
|
||||
icon, _ := windows.LoadImage(hInst, iconID, windows.IMAGE_ICON, 0, 0, windows.LR_DEFAULTSIZE|windows.LR_SHARED)
|
||||
wcls := windows.WndClassEx{
|
||||
CbSize: uint32(unsafe.Sizeof(windows.WndClassEx{})),
|
||||
Style: windows.CS_HREDRAW | windows.CS_VREDRAW | windows.CS_OWNDC,
|
||||
LpfnWndProc: syscall.NewCallback(windowProc),
|
||||
HInstance: hInst,
|
||||
HIcon: icon,
|
||||
LpszClassName: syscall.StringToUTF16Ptr("GioWindow"),
|
||||
}
|
||||
cls, err := windows.RegisterClassEx(&wcls)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resources.class = cls
|
||||
return nil
|
||||
}
|
||||
|
||||
func getWindowConstraints(cfg unit.Metric, opts *Options) winConstraints {
|
||||
var minmax winConstraints
|
||||
if o := opts.MinSize; o != nil {
|
||||
minmax.minWidth = int32(cfg.Px(o.Width))
|
||||
minmax.minHeight = int32(cfg.Px(o.Height))
|
||||
}
|
||||
if o := opts.MaxSize; o != nil {
|
||||
minmax.maxWidth = int32(cfg.Px(o.Width))
|
||||
minmax.maxHeight = int32(cfg.Px(o.Height))
|
||||
}
|
||||
return minmax
|
||||
}
|
||||
|
||||
func createNativeWindow(opts *Options) (*window, error) {
|
||||
var resErr error
|
||||
resources.once.Do(func() {
|
||||
resErr = initResources()
|
||||
})
|
||||
if resErr != nil {
|
||||
return nil, resErr
|
||||
}
|
||||
dpi := windows.GetSystemDPI()
|
||||
cfg := configForDPI(dpi)
|
||||
dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW)
|
||||
dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE)
|
||||
|
||||
hwnd, err := windows.CreateWindowEx(dwExStyle,
|
||||
resources.class,
|
||||
"",
|
||||
dwStyle|windows.WS_CLIPSIBLINGS|windows.WS_CLIPCHILDREN,
|
||||
windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
|
||||
windows.CW_USEDEFAULT, windows.CW_USEDEFAULT,
|
||||
0,
|
||||
0,
|
||||
resources.handle,
|
||||
0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w := &window{
|
||||
hwnd: hwnd,
|
||||
minmax: getWindowConstraints(cfg, opts),
|
||||
opts: opts,
|
||||
}
|
||||
w.hdc, err = windows.GetDC(hwnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
|
||||
win, exists := winMap.Load(hwnd)
|
||||
if !exists {
|
||||
return windows.DefWindowProc(hwnd, msg, wParam, lParam)
|
||||
}
|
||||
|
||||
w := win.(*window)
|
||||
|
||||
switch msg {
|
||||
case windows.WM_UNICHAR:
|
||||
if wParam == windows.UNICODE_NOCHAR {
|
||||
// Tell the system that we accept WM_UNICHAR messages.
|
||||
return windows.TRUE
|
||||
}
|
||||
fallthrough
|
||||
case windows.WM_CHAR:
|
||||
if r := rune(wParam); unicode.IsPrint(r) {
|
||||
w.w.Event(key.EditEvent{Text: string(r)})
|
||||
}
|
||||
// The message is processed.
|
||||
return windows.TRUE
|
||||
case windows.WM_DPICHANGED:
|
||||
// Let Windows know we're prepared for runtime DPI changes.
|
||||
return windows.TRUE
|
||||
case windows.WM_ERASEBKGND:
|
||||
// Avoid flickering between GPU content and background color.
|
||||
return windows.TRUE
|
||||
case windows.WM_KEYDOWN, windows.WM_KEYUP, windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP:
|
||||
if n, ok := convertKeyCode(wParam); ok {
|
||||
e := key.Event{
|
||||
Name: n,
|
||||
Modifiers: getModifiers(),
|
||||
State: key.Press,
|
||||
}
|
||||
if msg == windows.WM_KEYUP || msg == windows.WM_SYSKEYUP {
|
||||
e.State = key.Release
|
||||
}
|
||||
|
||||
w.w.Event(e)
|
||||
|
||||
if (wParam == windows.VK_F10) && (msg == windows.WM_SYSKEYDOWN || msg == windows.WM_SYSKEYUP) {
|
||||
// Reserve F10 for ourselves, and don't let it open the system menu. Other Windows programs
|
||||
// such as cmd.exe and graphical debuggers also reserve F10.
|
||||
return 0
|
||||
}
|
||||
}
|
||||
case windows.WM_LBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers())
|
||||
case windows.WM_LBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers())
|
||||
case windows.WM_RBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers())
|
||||
case windows.WM_RBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers())
|
||||
case windows.WM_MBUTTONDOWN:
|
||||
w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers())
|
||||
case windows.WM_MBUTTONUP:
|
||||
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers())
|
||||
case windows.WM_CANCELMODE:
|
||||
w.w.Event(pointer.Event{
|
||||
Type: pointer.Cancel,
|
||||
})
|
||||
case windows.WM_SETFOCUS:
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
case windows.WM_KILLFOCUS:
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
case windows.WM_MOUSEMOVE:
|
||||
x, y := coordsFromlParam(lParam)
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: pointer.Move,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
Buttons: w.pointerBtns,
|
||||
Time: windows.GetMessageTime(),
|
||||
})
|
||||
case windows.WM_MOUSEWHEEL:
|
||||
w.scrollEvent(wParam, lParam, false)
|
||||
case windows.WM_MOUSEHWHEEL:
|
||||
w.scrollEvent(wParam, lParam, true)
|
||||
case windows.WM_DESTROY:
|
||||
windows.PostQuitMessage(0)
|
||||
case windows.WM_PAINT:
|
||||
w.draw(true)
|
||||
case windows.WM_SIZE:
|
||||
switch wParam {
|
||||
case windows.SIZE_MINIMIZED:
|
||||
w.setStage(system.StagePaused)
|
||||
case windows.SIZE_MAXIMIZED, windows.SIZE_RESTORED:
|
||||
w.setStage(system.StageRunning)
|
||||
}
|
||||
case windows.WM_GETMINMAXINFO:
|
||||
mm := (*windows.MinMaxInfo)(unsafe.Pointer(uintptr(lParam)))
|
||||
if w.minmax.minWidth > 0 || w.minmax.minHeight > 0 {
|
||||
mm.PtMinTrackSize = windows.Point{
|
||||
X: w.minmax.minWidth + w.deltas.width,
|
||||
Y: w.minmax.minHeight + w.deltas.height,
|
||||
}
|
||||
}
|
||||
if w.minmax.maxWidth > 0 || w.minmax.maxHeight > 0 {
|
||||
mm.PtMaxTrackSize = windows.Point{
|
||||
X: w.minmax.maxWidth + w.deltas.width,
|
||||
Y: w.minmax.maxHeight + w.deltas.height,
|
||||
}
|
||||
}
|
||||
case windows.WM_SETCURSOR:
|
||||
w.cursorIn = (lParam & 0xffff) == windows.HTCLIENT
|
||||
fallthrough
|
||||
case _WM_CURSOR:
|
||||
if w.cursorIn {
|
||||
windows.SetCursor(w.cursor)
|
||||
return windows.TRUE
|
||||
}
|
||||
case _WM_OPTION:
|
||||
w.setOptions()
|
||||
}
|
||||
|
||||
return windows.DefWindowProc(hwnd, msg, wParam, lParam)
|
||||
}
|
||||
|
||||
func getModifiers() key.Modifiers {
|
||||
var kmods key.Modifiers
|
||||
if windows.GetKeyState(windows.VK_LWIN)&0x1000 != 0 || windows.GetKeyState(windows.VK_RWIN)&0x1000 != 0 {
|
||||
kmods |= key.ModSuper
|
||||
}
|
||||
if windows.GetKeyState(windows.VK_MENU)&0x1000 != 0 {
|
||||
kmods |= key.ModAlt
|
||||
}
|
||||
if windows.GetKeyState(windows.VK_CONTROL)&0x1000 != 0 {
|
||||
kmods |= key.ModCtrl
|
||||
}
|
||||
if windows.GetKeyState(windows.VK_SHIFT)&0x1000 != 0 {
|
||||
kmods |= key.ModShift
|
||||
}
|
||||
return kmods
|
||||
}
|
||||
|
||||
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) {
|
||||
var typ pointer.Type
|
||||
if press {
|
||||
typ = pointer.Press
|
||||
if w.pointerBtns == 0 {
|
||||
windows.SetCapture(w.hwnd)
|
||||
}
|
||||
w.pointerBtns |= btn
|
||||
} else {
|
||||
typ = pointer.Release
|
||||
w.pointerBtns &^= btn
|
||||
if w.pointerBtns == 0 {
|
||||
windows.ReleaseCapture()
|
||||
}
|
||||
}
|
||||
x, y := coordsFromlParam(lParam)
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
Buttons: w.pointerBtns,
|
||||
Time: windows.GetMessageTime(),
|
||||
Modifiers: kmods,
|
||||
})
|
||||
}
|
||||
|
||||
func coordsFromlParam(lParam uintptr) (int, int) {
|
||||
x := int(int16(lParam & 0xffff))
|
||||
y := int(int16((lParam >> 16) & 0xffff))
|
||||
return x, y
|
||||
}
|
||||
|
||||
func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool) {
|
||||
x, y := coordsFromlParam(lParam)
|
||||
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
|
||||
// to other mouse events.
|
||||
np := windows.Point{X: int32(x), Y: int32(y)}
|
||||
windows.ScreenToClient(w.hwnd, &np)
|
||||
p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
|
||||
dist := float32(int16(wParam >> 16))
|
||||
var sp f32.Point
|
||||
if horizontal {
|
||||
sp.X = dist
|
||||
} else {
|
||||
sp.Y = -dist
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: pointer.Scroll,
|
||||
Source: pointer.Mouse,
|
||||
Position: p,
|
||||
Buttons: w.pointerBtns,
|
||||
Scroll: sp,
|
||||
Time: windows.GetMessageTime(),
|
||||
})
|
||||
}
|
||||
|
||||
// Adapted from https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513/
|
||||
func (w *window) loop() error {
|
||||
msg := new(windows.Msg)
|
||||
loop:
|
||||
for {
|
||||
w.mu.Lock()
|
||||
anim := w.animating
|
||||
w.mu.Unlock()
|
||||
if anim && !windows.PeekMessage(msg, 0, 0, 0, windows.PM_NOREMOVE) {
|
||||
w.draw(false)
|
||||
continue
|
||||
}
|
||||
switch ret := windows.GetMessage(msg, 0, 0, 0); ret {
|
||||
case -1:
|
||||
return errors.New("GetMessage failed")
|
||||
case 0:
|
||||
// WM_QUIT received.
|
||||
break loop
|
||||
}
|
||||
windows.TranslateMessage(msg)
|
||||
windows.DispatchMessage(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.mu.Lock()
|
||||
w.animating = anim
|
||||
w.mu.Unlock()
|
||||
if anim {
|
||||
w.postRedraw()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) postRedraw() {
|
||||
if err := windows.PostMessage(w.hwnd, _WM_REDRAW, 0, 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) setStage(s system.Stage) {
|
||||
w.stage = s
|
||||
w.w.Event(system.StageEvent{Stage: s})
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
var r windows.Rect
|
||||
windows.GetClientRect(w.hwnd, &r)
|
||||
w.width = int(r.Right - r.Left)
|
||||
w.height = int(r.Bottom - r.Top)
|
||||
if w.width == 0 || w.height == 0 {
|
||||
return
|
||||
}
|
||||
dpi := windows.GetWindowDPI(w.hwnd)
|
||||
cfg := configForDPI(dpi)
|
||||
w.minmax = getWindowConstraints(cfg, w.opts)
|
||||
w.w.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: w.width,
|
||||
Y: w.height,
|
||||
},
|
||||
Metric: cfg,
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) destroy() {
|
||||
if w.hdc != 0 {
|
||||
windows.ReleaseDC(w.hdc)
|
||||
w.hdc = 0
|
||||
}
|
||||
if w.hwnd != 0 {
|
||||
windows.DestroyWindow(w.hwnd)
|
||||
w.hwnd = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (Context, error) {
|
||||
sort.Slice(drivers, func(i, j int) bool {
|
||||
return drivers[i].priority < drivers[j].priority
|
||||
})
|
||||
var errs []string
|
||||
for _, b := range drivers {
|
||||
ctx, err := b.initializer(w)
|
||||
if err == nil {
|
||||
return ctx, nil
|
||||
}
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return nil, fmt.Errorf("NewContext: failed to create a GPU device, tried: %s", strings.Join(errs, ", "))
|
||||
}
|
||||
return nil, errors.New("NewContext: no available GPU drivers")
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
w.readClipboard()
|
||||
}
|
||||
|
||||
func (w *window) readClipboard() error {
|
||||
if err := windows.OpenClipboard(w.hwnd); err != nil {
|
||||
return err
|
||||
}
|
||||
defer windows.CloseClipboard()
|
||||
mem, err := windows.GetClipboardData(windows.CF_UNICODETEXT)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ptr, err := windows.GlobalLock(mem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer windows.GlobalUnlock(mem)
|
||||
content := gowindows.UTF16PtrToString((*uint16)(unsafe.Pointer(ptr)))
|
||||
go func() {
|
||||
w.w.Event(clipboard.Event{Text: content})
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) Option(opts *Options) {
|
||||
w.mu.Lock()
|
||||
w.opts = opts
|
||||
w.mu.Unlock()
|
||||
if err := windows.PostMessage(w.hwnd, _WM_OPTION, 0, 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) setOptions() {
|
||||
w.mu.Lock()
|
||||
opts := w.opts
|
||||
w.mu.Unlock()
|
||||
if o := opts.Size; o != nil {
|
||||
dpi := windows.GetSystemDPI()
|
||||
cfg := configForDPI(dpi)
|
||||
width := int32(cfg.Px(o.Width))
|
||||
height := int32(cfg.Px(o.Height))
|
||||
|
||||
// Include the window decorations.
|
||||
wr := windows.Rect{
|
||||
Right: width,
|
||||
Bottom: height,
|
||||
}
|
||||
dwStyle := uint32(windows.WS_OVERLAPPEDWINDOW)
|
||||
dwExStyle := uint32(windows.WS_EX_APPWINDOW | windows.WS_EX_WINDOWEDGE)
|
||||
windows.AdjustWindowRectEx(&wr, dwStyle, 0, dwExStyle)
|
||||
|
||||
dw, dh := width, height
|
||||
width = wr.Right - wr.Left
|
||||
height = wr.Bottom - wr.Top
|
||||
w.deltas.width = width - dw
|
||||
w.deltas.height = height - dh
|
||||
|
||||
w.opts.Size = o
|
||||
windows.MoveWindow(w.hwnd, 0, 0, width, height, true)
|
||||
}
|
||||
if o := opts.MinSize; o != nil {
|
||||
w.opts.MinSize = o
|
||||
}
|
||||
if o := opts.MaxSize; o != nil {
|
||||
w.opts.MaxSize = o
|
||||
}
|
||||
if o := opts.Title; o != nil {
|
||||
windows.SetWindowText(w.hwnd, *opts.Title)
|
||||
}
|
||||
if o := opts.WindowMode; o != nil {
|
||||
w.SetWindowMode(*o)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetWindowMode(mode WindowMode) {
|
||||
// https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353
|
||||
switch mode {
|
||||
case Windowed:
|
||||
if w.placement == nil {
|
||||
return
|
||||
}
|
||||
windows.SetWindowPlacement(w.hwnd, w.placement)
|
||||
w.placement = nil
|
||||
style := windows.GetWindowLong(w.hwnd)
|
||||
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style|windows.WS_OVERLAPPEDWINDOW)
|
||||
windows.SetWindowPos(w.hwnd, windows.HWND_TOPMOST,
|
||||
0, 0, 0, 0,
|
||||
windows.SWP_NOOWNERZORDER|windows.SWP_FRAMECHANGED,
|
||||
)
|
||||
case Fullscreen:
|
||||
if w.placement != nil {
|
||||
return
|
||||
}
|
||||
w.placement = windows.GetWindowPlacement(w.hwnd)
|
||||
style := windows.GetWindowLong(w.hwnd)
|
||||
windows.SetWindowLong(w.hwnd, windows.GWL_STYLE, style&^windows.WS_OVERLAPPEDWINDOW)
|
||||
mi := windows.GetMonitorInfo(w.hwnd)
|
||||
windows.SetWindowPos(w.hwnd, 0,
|
||||
mi.Monitor.Left, mi.Monitor.Top,
|
||||
mi.Monitor.Right-mi.Monitor.Left,
|
||||
mi.Monitor.Bottom-mi.Monitor.Top,
|
||||
windows.SWP_NOOWNERZORDER|windows.SWP_FRAMECHANGED,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
w.writeClipboard(s)
|
||||
}
|
||||
|
||||
func (w *window) writeClipboard(s string) error {
|
||||
if err := windows.OpenClipboard(w.hwnd); err != nil {
|
||||
return err
|
||||
}
|
||||
defer windows.CloseClipboard()
|
||||
if err := windows.EmptyClipboard(); err != nil {
|
||||
return err
|
||||
}
|
||||
u16, err := gowindows.UTF16FromString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n := len(u16) * int(unsafe.Sizeof(u16[0]))
|
||||
mem, err := windows.GlobalAlloc(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ptr, err := windows.GlobalLock(mem)
|
||||
if err != nil {
|
||||
windows.GlobalFree(mem)
|
||||
return err
|
||||
}
|
||||
var u16v []uint16
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&u16v))
|
||||
hdr.Data = ptr
|
||||
hdr.Cap = len(u16)
|
||||
hdr.Len = len(u16)
|
||||
copy(u16v, u16)
|
||||
windows.GlobalUnlock(mem)
|
||||
if err := windows.SetClipboardData(windows.CF_UNICODETEXT, mem); err != nil {
|
||||
windows.GlobalFree(mem)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) SetCursor(name pointer.CursorName) {
|
||||
c, err := loadCursor(name)
|
||||
if err != nil {
|
||||
c = resources.cursor
|
||||
}
|
||||
w.cursor = c
|
||||
if err := windows.PostMessage(w.hwnd, _WM_CURSOR, 0, 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func loadCursor(name pointer.CursorName) (syscall.Handle, error) {
|
||||
var curID uint16
|
||||
switch name {
|
||||
default:
|
||||
fallthrough
|
||||
case pointer.CursorDefault:
|
||||
return resources.cursor, nil
|
||||
case pointer.CursorText:
|
||||
curID = windows.IDC_IBEAM
|
||||
case pointer.CursorPointer:
|
||||
curID = windows.IDC_HAND
|
||||
case pointer.CursorCrossHair:
|
||||
curID = windows.IDC_CROSS
|
||||
case pointer.CursorColResize:
|
||||
curID = windows.IDC_SIZEWE
|
||||
case pointer.CursorRowResize:
|
||||
curID = windows.IDC_SIZENS
|
||||
case pointer.CursorGrab:
|
||||
curID = windows.IDC_SIZEALL
|
||||
case pointer.CursorNone:
|
||||
return 0, nil
|
||||
}
|
||||
return windows.LoadCursor(curID)
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {}
|
||||
|
||||
func (w *window) HDC() syscall.Handle {
|
||||
return w.hdc
|
||||
}
|
||||
|
||||
func (w *window) HWND() (syscall.Handle, int, int) {
|
||||
return w.hwnd, w.width, w.height
|
||||
}
|
||||
|
||||
func (w *window) Close() {
|
||||
windows.PostMessage(w.hwnd, windows.WM_CLOSE, 0, 0)
|
||||
}
|
||||
|
||||
func convertKeyCode(code uintptr) (string, bool) {
|
||||
if '0' <= code && code <= '9' || 'A' <= code && code <= 'Z' {
|
||||
return string(rune(code)), true
|
||||
}
|
||||
var r string
|
||||
switch code {
|
||||
case windows.VK_ESCAPE:
|
||||
r = key.NameEscape
|
||||
case windows.VK_LEFT:
|
||||
r = key.NameLeftArrow
|
||||
case windows.VK_RIGHT:
|
||||
r = key.NameRightArrow
|
||||
case windows.VK_RETURN:
|
||||
r = key.NameReturn
|
||||
case windows.VK_UP:
|
||||
r = key.NameUpArrow
|
||||
case windows.VK_DOWN:
|
||||
r = key.NameDownArrow
|
||||
case windows.VK_HOME:
|
||||
r = key.NameHome
|
||||
case windows.VK_END:
|
||||
r = key.NameEnd
|
||||
case windows.VK_BACK:
|
||||
r = key.NameDeleteBackward
|
||||
case windows.VK_DELETE:
|
||||
r = key.NameDeleteForward
|
||||
case windows.VK_PRIOR:
|
||||
r = key.NamePageUp
|
||||
case windows.VK_NEXT:
|
||||
r = key.NamePageDown
|
||||
case windows.VK_F1:
|
||||
r = "F1"
|
||||
case windows.VK_F2:
|
||||
r = "F2"
|
||||
case windows.VK_F3:
|
||||
r = "F3"
|
||||
case windows.VK_F4:
|
||||
r = "F4"
|
||||
case windows.VK_F5:
|
||||
r = "F5"
|
||||
case windows.VK_F6:
|
||||
r = "F6"
|
||||
case windows.VK_F7:
|
||||
r = "F7"
|
||||
case windows.VK_F8:
|
||||
r = "F8"
|
||||
case windows.VK_F9:
|
||||
r = "F9"
|
||||
case windows.VK_F10:
|
||||
r = "F10"
|
||||
case windows.VK_F11:
|
||||
r = "F11"
|
||||
case windows.VK_F12:
|
||||
r = "F12"
|
||||
case windows.VK_TAB:
|
||||
r = key.NameTab
|
||||
case windows.VK_SPACE:
|
||||
r = key.NameSpace
|
||||
case windows.VK_OEM_1:
|
||||
r = ";"
|
||||
case windows.VK_OEM_PLUS:
|
||||
r = "+"
|
||||
case windows.VK_OEM_COMMA:
|
||||
r = ","
|
||||
case windows.VK_OEM_MINUS:
|
||||
r = "-"
|
||||
case windows.VK_OEM_PERIOD:
|
||||
r = "."
|
||||
case windows.VK_OEM_2:
|
||||
r = "/"
|
||||
case windows.VK_OEM_3:
|
||||
r = "`"
|
||||
case windows.VK_OEM_4:
|
||||
r = "["
|
||||
case windows.VK_OEM_5, windows.VK_OEM_102:
|
||||
r = "\\"
|
||||
case windows.VK_OEM_6:
|
||||
r = "]"
|
||||
case windows.VK_OEM_7:
|
||||
r = "'"
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
return r, true
|
||||
}
|
||||
|
||||
func configForDPI(dpi int) unit.Metric {
|
||||
const inchPrDp = 1.0 / 96.0
|
||||
ppdp := float32(dpi) * inchPrDp
|
||||
return unit.Metric{
|
||||
PxPerDp: ppdp,
|
||||
PxPerSp: ppdp,
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// package wm implements platform specific windows
|
||||
// and GPU contexts.
|
||||
package wm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/gpu"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/event"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/pointer"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/system"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
)
|
||||
|
||||
type Size struct {
|
||||
Width unit.Value
|
||||
Height unit.Value
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Size *Size
|
||||
MinSize *Size
|
||||
MaxSize *Size
|
||||
Title *string
|
||||
WindowMode *WindowMode
|
||||
}
|
||||
|
||||
type WindowMode uint8
|
||||
|
||||
const (
|
||||
Windowed WindowMode = iota
|
||||
Fullscreen
|
||||
)
|
||||
|
||||
type FrameEvent struct {
|
||||
system.FrameEvent
|
||||
|
||||
Sync bool
|
||||
}
|
||||
|
||||
type Callbacks interface {
|
||||
SetDriver(d Driver)
|
||||
Event(e event.Event)
|
||||
}
|
||||
|
||||
type Context interface {
|
||||
API() gpu.API
|
||||
Present() error
|
||||
MakeCurrent() error
|
||||
Release()
|
||||
Lock()
|
||||
Unlock()
|
||||
}
|
||||
|
||||
// ErrDeviceLost is returned from Context.Present when
|
||||
// the underlying GPU device is gone and should be
|
||||
// recreated.
|
||||
var ErrDeviceLost = errors.New("GPU device lost")
|
||||
|
||||
// Driver is the interface for the platform implementation
|
||||
// of a window.
|
||||
type Driver interface {
|
||||
// SetAnimating sets the animation flag. When the window is animating,
|
||||
// FrameEvents are delivered as fast as the display can handle them.
|
||||
SetAnimating(anim bool)
|
||||
// ShowTextInput updates the virtual keyboard state.
|
||||
ShowTextInput(show bool)
|
||||
NewContext() (Context, error)
|
||||
|
||||
// ReadClipboard requests the clipboard content.
|
||||
ReadClipboard()
|
||||
// WriteClipboard requests a clipboard write.
|
||||
WriteClipboard(s string)
|
||||
|
||||
// Option processes option changes.
|
||||
Option(opts *Options)
|
||||
|
||||
// SetCursor updates the current cursor to name.
|
||||
SetCursor(name pointer.CursorName)
|
||||
|
||||
// Close the window.
|
||||
Close()
|
||||
}
|
||||
|
||||
type windowRendezvous struct {
|
||||
in chan windowAndOptions
|
||||
out chan windowAndOptions
|
||||
errs chan error
|
||||
}
|
||||
|
||||
type windowAndOptions struct {
|
||||
window Callbacks
|
||||
opts *Options
|
||||
}
|
||||
|
||||
func newWindowRendezvous() *windowRendezvous {
|
||||
wr := &windowRendezvous{
|
||||
in: make(chan windowAndOptions),
|
||||
out: make(chan windowAndOptions),
|
||||
errs: make(chan error),
|
||||
}
|
||||
go func() {
|
||||
var main windowAndOptions
|
||||
var out chan windowAndOptions
|
||||
for {
|
||||
select {
|
||||
case w := <-wr.in:
|
||||
var err error
|
||||
if main.window != nil {
|
||||
err = errors.New("multiple windows are not supported")
|
||||
}
|
||||
wr.errs <- err
|
||||
main = w
|
||||
out = wr.out
|
||||
case out <- main:
|
||||
}
|
||||
}
|
||||
}()
|
||||
return wr
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build (linux && !android) || freebsd || openbsd
|
||||
// +build linux,!android freebsd openbsd
|
||||
|
||||
// Package xkb implements a Go interface for the X Keyboard Extension library.
|
||||
@@ -135,6 +136,10 @@ func (x *Context) LoadKeymap(format int, fd int, size int) error {
|
||||
|
||||
func (x *Context) Modifiers() key.Modifiers {
|
||||
var mods key.Modifiers
|
||||
if x.state == nil {
|
||||
return mods
|
||||
}
|
||||
|
||||
if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_CTRL[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 {
|
||||
mods |= key.ModCtrl
|
||||
}
|
||||
@@ -218,6 +223,9 @@ func (x *Context) charsForKeycode(keyCode C.xkb_keycode_t) []byte {
|
||||
}
|
||||
|
||||
func (x *Context) IsRepeatKey(keyCode uint32) bool {
|
||||
if x.state == nil {
|
||||
return false
|
||||
}
|
||||
kc := C.xkb_keycode_t(keyCode)
|
||||
return C.xkb_keymap_key_repeats(x.keyMap, kc) == 1
|
||||
}
|
||||
@@ -230,14 +238,17 @@ func (x *Context) UpdateMask(depressed, latched, locked, depressedGroup, latched
|
||||
C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup))
|
||||
}
|
||||
|
||||
func convertKeysym(s C.xkb_keysym_t) (string, bool) {
|
||||
func convertKeysym(s C.xkb_keysym_t) (key.Name, bool) {
|
||||
if 'a' <= s && s <= 'z' {
|
||||
return string(rune(s - 'a' + 'A')), true
|
||||
return key.Name(rune(s - 'a' + 'A')), true
|
||||
}
|
||||
if C.XKB_KEY_KP_0 <= s && s <= C.XKB_KEY_KP_9 {
|
||||
return key.Name(rune(s - C.XKB_KEY_KP_0 + '0')), true
|
||||
}
|
||||
if ' ' < s && s <= '~' {
|
||||
return string(rune(s)), true
|
||||
return key.Name(rune(s)), true
|
||||
}
|
||||
var n string
|
||||
var n key.Name
|
||||
switch s {
|
||||
case C.XKB_KEY_Escape:
|
||||
n = key.NameEscape
|
||||
@@ -247,8 +258,6 @@ func convertKeysym(s C.xkb_keysym_t) (string, bool) {
|
||||
n = key.NameRightArrow
|
||||
case C.XKB_KEY_Return:
|
||||
n = key.NameReturn
|
||||
case C.XKB_KEY_KP_Enter:
|
||||
n = key.NameEnter
|
||||
case C.XKB_KEY_Up:
|
||||
n = key.NameUpArrow
|
||||
case C.XKB_KEY_Down:
|
||||
@@ -266,33 +275,99 @@ func convertKeysym(s C.xkb_keysym_t) (string, bool) {
|
||||
case C.XKB_KEY_Page_Down:
|
||||
n = key.NamePageDown
|
||||
case C.XKB_KEY_F1:
|
||||
n = "F1"
|
||||
n = key.NameF1
|
||||
case C.XKB_KEY_F2:
|
||||
n = "F2"
|
||||
n = key.NameF2
|
||||
case C.XKB_KEY_F3:
|
||||
n = "F3"
|
||||
n = key.NameF3
|
||||
case C.XKB_KEY_F4:
|
||||
n = "F4"
|
||||
n = key.NameF4
|
||||
case C.XKB_KEY_F5:
|
||||
n = "F5"
|
||||
n = key.NameF5
|
||||
case C.XKB_KEY_F6:
|
||||
n = "F6"
|
||||
n = key.NameF6
|
||||
case C.XKB_KEY_F7:
|
||||
n = "F7"
|
||||
n = key.NameF7
|
||||
case C.XKB_KEY_F8:
|
||||
n = "F8"
|
||||
n = key.NameF8
|
||||
case C.XKB_KEY_F9:
|
||||
n = "F9"
|
||||
n = key.NameF9
|
||||
case C.XKB_KEY_F10:
|
||||
n = "F10"
|
||||
n = key.NameF10
|
||||
case C.XKB_KEY_F11:
|
||||
n = "F11"
|
||||
n = key.NameF11
|
||||
case C.XKB_KEY_F12:
|
||||
n = "F12"
|
||||
case C.XKB_KEY_Tab, C.XKB_KEY_KP_Tab, C.XKB_KEY_ISO_Left_Tab:
|
||||
n = key.NameF12
|
||||
case C.XKB_KEY_Tab, C.XKB_KEY_ISO_Left_Tab:
|
||||
n = key.NameTab
|
||||
case 0x20, C.XKB_KEY_KP_Space:
|
||||
case 0x20:
|
||||
n = key.NameSpace
|
||||
case C.XKB_KEY_Control_L, C.XKB_KEY_Control_R:
|
||||
n = key.NameCtrl
|
||||
case C.XKB_KEY_Shift_L, C.XKB_KEY_Shift_R:
|
||||
n = key.NameShift
|
||||
case C.XKB_KEY_Alt_L, C.XKB_KEY_Alt_R:
|
||||
n = key.NameAlt
|
||||
case C.XKB_KEY_Super_L, C.XKB_KEY_Super_R:
|
||||
n = key.NameSuper
|
||||
|
||||
case C.XKB_KEY_KP_Space:
|
||||
n = key.NameSpace
|
||||
case C.XKB_KEY_KP_Tab:
|
||||
n = key.NameTab
|
||||
case C.XKB_KEY_KP_Enter:
|
||||
n = key.NameEnter
|
||||
case C.XKB_KEY_KP_F1:
|
||||
n = key.NameF1
|
||||
case C.XKB_KEY_KP_F2:
|
||||
n = key.NameF2
|
||||
case C.XKB_KEY_KP_F3:
|
||||
n = key.NameF3
|
||||
case C.XKB_KEY_KP_F4:
|
||||
n = key.NameF4
|
||||
case C.XKB_KEY_KP_Home:
|
||||
n = key.NameHome
|
||||
case C.XKB_KEY_KP_Left:
|
||||
n = key.NameLeftArrow
|
||||
case C.XKB_KEY_KP_Up:
|
||||
n = key.NameUpArrow
|
||||
case C.XKB_KEY_KP_Right:
|
||||
n = key.NameRightArrow
|
||||
case C.XKB_KEY_KP_Down:
|
||||
n = key.NameDownArrow
|
||||
case C.XKB_KEY_KP_Prior:
|
||||
// not supported
|
||||
return "", false
|
||||
case C.XKB_KEY_KP_Next:
|
||||
// not supported
|
||||
return "", false
|
||||
case C.XKB_KEY_KP_End:
|
||||
n = key.NameEnd
|
||||
case C.XKB_KEY_KP_Begin:
|
||||
n = key.NameHome
|
||||
case C.XKB_KEY_KP_Insert:
|
||||
// not supported
|
||||
return "", false
|
||||
case C.XKB_KEY_KP_Delete:
|
||||
n = key.NameDeleteForward
|
||||
case C.XKB_KEY_KP_Multiply:
|
||||
n = "*"
|
||||
case C.XKB_KEY_KP_Add:
|
||||
n = "+"
|
||||
case C.XKB_KEY_KP_Separator:
|
||||
// not supported
|
||||
return "", false
|
||||
case C.XKB_KEY_KP_Subtract:
|
||||
n = "-"
|
||||
case C.XKB_KEY_KP_Decimal:
|
||||
// TODO(dh): does a German keyboard layout also translate the numpad key to XKB_KEY_KP_DECIMAL? Because in
|
||||
// German, the decimal is a comma, not a period.
|
||||
n = "."
|
||||
case C.XKB_KEY_KP_Divide:
|
||||
n = "/"
|
||||
case C.XKB_KEY_KP_Equal:
|
||||
n = "="
|
||||
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package log
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -llog
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
// 1024 is the truncation limit from android/log.h, plus a \n.
|
||||
const logLineLimit = 1024
|
||||
|
||||
var logTag = C.CString(appID)
|
||||
var logTag = C.CString(ID)
|
||||
|
||||
func init() {
|
||||
// Android's logcat already includes timestamps.
|
||||
@@ -1,13 +1,18 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && ios
|
||||
// +build darwin,ios
|
||||
|
||||
package log
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -fmodules -fobjc-arc -x objective-c
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void nslog(char *str);
|
||||
@import Foundation;
|
||||
|
||||
static void nslog(char *str) {
|
||||
NSLog(@"%@", @(str));
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package log
|
||||
package app
|
||||
|
||||
import (
|
||||
"log"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
syscall "golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type logger struct{}
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
||||
kernel32 = syscall.NewLazySystemDLL("kernel32")
|
||||
outputDebugStringW = kernel32.NewProc("OutputDebugStringW")
|
||||
debugView *logger
|
||||
)
|
||||
@@ -1,161 +0,0 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"runtime"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/app/internal/wm"
|
||||
"github.com/p9c/p9/pkg/gel/gio/gpu"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op"
|
||||
)
|
||||
|
||||
type renderLoop struct {
|
||||
summary string
|
||||
drawing bool
|
||||
err error
|
||||
|
||||
frames chan frame
|
||||
results chan frameResult
|
||||
refresh chan struct{}
|
||||
refreshErr chan error
|
||||
ack chan struct{}
|
||||
stop chan struct{}
|
||||
stopped chan struct{}
|
||||
}
|
||||
|
||||
type frame struct {
|
||||
viewport image.Point
|
||||
ops *op.Ops
|
||||
}
|
||||
|
||||
type frameResult struct {
|
||||
profile string
|
||||
err error
|
||||
}
|
||||
|
||||
func newLoop(ctx wm.Context) (*renderLoop, error) {
|
||||
l := &renderLoop{
|
||||
frames: make(chan frame),
|
||||
results: make(chan frameResult),
|
||||
refresh: make(chan struct{}),
|
||||
refreshErr: make(chan error),
|
||||
// Ack is buffered so GPU commands can be issued after
|
||||
// ack'ing the frame.
|
||||
ack: make(chan struct{}, 1),
|
||||
stop: make(chan struct{}),
|
||||
stopped: make(chan struct{}),
|
||||
}
|
||||
if err := l.renderLoop(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *renderLoop) renderLoop(ctx wm.Context) error {
|
||||
// GL Operations must happen on a single OS thread, so
|
||||
// pass initialization result through a channel.
|
||||
initErr := make(chan error)
|
||||
go func() {
|
||||
defer close(l.stopped)
|
||||
runtime.LockOSThread()
|
||||
// Don't UnlockOSThread to avoid reuse by the Go runtime.
|
||||
|
||||
if err := ctx.MakeCurrent(); err != nil {
|
||||
initErr <- err
|
||||
return
|
||||
}
|
||||
g, err := gpu.New(ctx.API())
|
||||
if err != nil {
|
||||
initErr <- err
|
||||
return
|
||||
}
|
||||
defer g.Release()
|
||||
initErr <- nil
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-l.refresh:
|
||||
l.refreshErr <- ctx.MakeCurrent()
|
||||
case frame := <-l.frames:
|
||||
ctx.Lock()
|
||||
if runtime.GOOS == "js" {
|
||||
// Use transparent black when Gio is embedded, to allow mixing of Gio and
|
||||
// foreign content below.
|
||||
g.Clear(color.NRGBA{A: 0x00, R: 0x00, G: 0x00, B: 0x00})
|
||||
} else {
|
||||
g.Clear(color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff})
|
||||
}
|
||||
g.Collect(frame.viewport, frame.ops)
|
||||
// Signal that we're done with the frame ops.
|
||||
l.ack <- struct{}{}
|
||||
var res frameResult
|
||||
res.err = g.Frame()
|
||||
if res.err == nil {
|
||||
res.err = ctx.Present()
|
||||
}
|
||||
res.profile = g.Profile()
|
||||
ctx.Unlock()
|
||||
l.results <- res
|
||||
case <-l.stop:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}()
|
||||
return <-initErr
|
||||
}
|
||||
|
||||
func (l *renderLoop) Release() {
|
||||
// Flush error.
|
||||
l.Flush()
|
||||
close(l.stop)
|
||||
<-l.stopped
|
||||
l.stop = nil
|
||||
}
|
||||
|
||||
func (l *renderLoop) Flush() error {
|
||||
if l.drawing {
|
||||
st := <-l.results
|
||||
l.setErr(st.err)
|
||||
if st.profile != "" {
|
||||
l.summary = st.profile
|
||||
}
|
||||
l.drawing = false
|
||||
}
|
||||
return l.err
|
||||
}
|
||||
|
||||
func (l *renderLoop) Summary() string {
|
||||
return l.summary
|
||||
}
|
||||
|
||||
func (l *renderLoop) Refresh() {
|
||||
if l.err != nil {
|
||||
return
|
||||
}
|
||||
// Make sure any pending frame is complete.
|
||||
l.Flush()
|
||||
l.refresh <- struct{}{}
|
||||
l.setErr(<-l.refreshErr)
|
||||
}
|
||||
|
||||
// Draw initiates a draw of a frame. It returns a channel
|
||||
// than signals when the frame is no longer being accessed.
|
||||
func (l *renderLoop) Draw(viewport image.Point, frameOps *op.Ops) <-chan struct{} {
|
||||
if l.err != nil {
|
||||
l.ack <- struct{}{}
|
||||
return l.ack
|
||||
}
|
||||
l.Flush()
|
||||
l.frames <- frame{viewport, frameOps}
|
||||
l.drawing = true
|
||||
return l.ack
|
||||
}
|
||||
|
||||
func (l *renderLoop) setErr(err error) {
|
||||
if l.err == nil {
|
||||
l.err = err
|
||||
}
|
||||
}
|
||||
174
pkg/gel/gio/app/metal_darwin.go
Normal file
174
pkg/gel/gio/app/metal_darwin.go
Normal file
@@ -0,0 +1,174 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !nometal
|
||||
// +build !nometal
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/gpu"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -xobjective-c -fobjc-arc
|
||||
#cgo LDFLAGS: -framework QuartzCore -framework Metal
|
||||
|
||||
#import <Metal/Metal.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
static CFTypeRef createMetalDevice(void) {
|
||||
@autoreleasepool {
|
||||
id<MTLDevice> dev = MTLCreateSystemDefaultDevice();
|
||||
return CFBridgingRetain(dev);
|
||||
}
|
||||
}
|
||||
|
||||
static void setupLayer(CFTypeRef layerRef, CFTypeRef devRef) {
|
||||
@autoreleasepool {
|
||||
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
|
||||
id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
|
||||
layer.device = dev;
|
||||
// Package gpu assumes an sRGB-encoded framebuffer.
|
||||
layer.pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
// Never let nextDrawable time out and return nil.
|
||||
layer.allowsNextDrawableTimeout = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef nextDrawable(CFTypeRef layerRef) {
|
||||
@autoreleasepool {
|
||||
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
|
||||
return CFBridgingRetain([layer nextDrawable]);
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef drawableTexture(CFTypeRef drawableRef) {
|
||||
@autoreleasepool {
|
||||
id<CAMetalDrawable> drawable = (__bridge id<CAMetalDrawable>)drawableRef;
|
||||
return CFBridgingRetain(drawable.texture);
|
||||
}
|
||||
}
|
||||
|
||||
static void presentDrawable(CFTypeRef queueRef, CFTypeRef drawableRef) {
|
||||
@autoreleasepool {
|
||||
id<MTLDrawable> drawable = (__bridge id<MTLDrawable>)drawableRef;
|
||||
id<MTLCommandQueue> queue = (__bridge id<MTLCommandQueue>)queueRef;
|
||||
id<MTLCommandBuffer> cmdBuffer = [queue commandBuffer];
|
||||
[cmdBuffer commit];
|
||||
[cmdBuffer waitUntilScheduled];
|
||||
[drawable present];
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef newCommandQueue(CFTypeRef devRef) {
|
||||
@autoreleasepool {
|
||||
id<MTLDevice> dev = (__bridge id<MTLDevice>)devRef;
|
||||
return CFBridgingRetain([dev newCommandQueue]);
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type mtlContext struct {
|
||||
dev C.CFTypeRef
|
||||
view C.CFTypeRef
|
||||
layer C.CFTypeRef
|
||||
queue C.CFTypeRef
|
||||
drawable C.CFTypeRef
|
||||
texture C.CFTypeRef
|
||||
}
|
||||
|
||||
func newMtlContext(w *window) (*mtlContext, error) {
|
||||
dev := C.createMetalDevice()
|
||||
if dev == 0 {
|
||||
return nil, errors.New("metal: MTLCreateSystemDefaultDevice failed")
|
||||
}
|
||||
view := w.contextView()
|
||||
layer := getMetalLayer(view)
|
||||
if layer == 0 {
|
||||
C.CFRelease(dev)
|
||||
return nil, errors.New("metal: CAMetalLayer construction failed")
|
||||
}
|
||||
queue := C.newCommandQueue(dev)
|
||||
if queue == 0 {
|
||||
C.CFRelease(dev)
|
||||
C.CFRelease(layer)
|
||||
return nil, errors.New("metal: [MTLDevice newCommandQueue] failed")
|
||||
}
|
||||
C.setupLayer(layer, dev)
|
||||
c := &mtlContext{
|
||||
dev: dev,
|
||||
view: view,
|
||||
layer: layer,
|
||||
queue: queue,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *mtlContext) RenderTarget() (gpu.RenderTarget, error) {
|
||||
if c.drawable != 0 || c.texture != 0 {
|
||||
return nil, errors.New("metal:a previous RenderTarget wasn't Presented")
|
||||
}
|
||||
c.drawable = C.nextDrawable(c.layer)
|
||||
if c.drawable == 0 {
|
||||
return nil, errors.New("metal: [CAMetalLayer nextDrawable] failed")
|
||||
}
|
||||
c.texture = C.drawableTexture(c.drawable)
|
||||
if c.texture == 0 {
|
||||
return nil, errors.New("metal: CADrawable.texture is nil")
|
||||
}
|
||||
return gpu.MetalRenderTarget{
|
||||
Texture: uintptr(c.texture),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *mtlContext) API() gpu.API {
|
||||
return gpu.Metal{
|
||||
Device: uintptr(c.dev),
|
||||
Queue: uintptr(c.queue),
|
||||
PixelFormat: int(C.MTLPixelFormatBGRA8Unorm_sRGB),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *mtlContext) Release() {
|
||||
C.CFRelease(c.queue)
|
||||
C.CFRelease(c.dev)
|
||||
C.CFRelease(c.layer)
|
||||
if c.drawable != 0 {
|
||||
C.CFRelease(c.drawable)
|
||||
}
|
||||
if c.texture != 0 {
|
||||
C.CFRelease(c.texture)
|
||||
}
|
||||
*c = mtlContext{}
|
||||
}
|
||||
|
||||
func (c *mtlContext) Present() error {
|
||||
C.CFRelease(c.texture)
|
||||
c.texture = 0
|
||||
C.presentDrawable(c.queue, c.drawable)
|
||||
C.CFRelease(c.drawable)
|
||||
c.drawable = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mtlContext) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mtlContext) Unlock() {}
|
||||
|
||||
func (c *mtlContext) Refresh() error {
|
||||
resizeDrawable(c.view, c.layer)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *window) NewContext() (context, error) {
|
||||
return newMtlContext(w)
|
||||
}
|
||||
51
pkg/gel/gio/app/metal_ios.go
Normal file
51
pkg/gel/gio/app/metal_ios.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build !nometal
|
||||
// +build !nometal
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -xobjective-c -fmodules -fobjc-arc
|
||||
|
||||
@import UIKit;
|
||||
|
||||
@import QuartzCore.CAMetalLayer;
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
Class gio_layerClass(void) {
|
||||
return [CAMetalLayer class];
|
||||
}
|
||||
|
||||
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
CAMetalLayer *l = (CAMetalLayer *)view.layer;
|
||||
l.needsDisplayOnBoundsChange = YES;
|
||||
l.presentsWithTransaction = YES;
|
||||
return CFBridgingRetain(l);
|
||||
}
|
||||
}
|
||||
|
||||
static void resizeDrawable(CFTypeRef viewRef, CFTypeRef layerRef) {
|
||||
@autoreleasepool {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
|
||||
layer.contentsScale = view.contentScaleFactor;
|
||||
CGSize size = layer.bounds.size;
|
||||
size.width *= layer.contentsScale;
|
||||
size.height *= layer.contentsScale;
|
||||
layer.drawableSize = size;
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func getMetalLayer(view C.CFTypeRef) C.CFTypeRef {
|
||||
return C.getMetalLayer(view)
|
||||
}
|
||||
|
||||
func resizeDrawable(view, layer C.CFTypeRef) {
|
||||
C.resizeDrawable(view, layer)
|
||||
}
|
||||
51
pkg/gel/gio/app/metal_macos.go
Normal file
51
pkg/gel/gio/app/metal_macos.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && !ios && !nometal
|
||||
// +build darwin,!ios,!nometal
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Werror -xobjective-c -fobjc-arc
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
CALayer *gio_layerFactory(BOOL presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
CAMetalLayer *l = [CAMetalLayer layer];
|
||||
l.autoresizingMask = kCALayerHeightSizable|kCALayerWidthSizable;
|
||||
l.needsDisplayOnBoundsChange = YES;
|
||||
l.presentsWithTransaction = presentWithTrans;
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
static CFTypeRef getMetalLayer(CFTypeRef viewRef) {
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
return CFBridgingRetain(view.layer);
|
||||
}
|
||||
}
|
||||
|
||||
static void resizeDrawable(CFTypeRef viewRef, CFTypeRef layerRef) {
|
||||
@autoreleasepool {
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
CAMetalLayer *layer = (__bridge CAMetalLayer *)layerRef;
|
||||
CGSize size = layer.bounds.size;
|
||||
size.width *= layer.contentsScale;
|
||||
size.height *= layer.contentsScale;
|
||||
layer.drawableSize = size;
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func getMetalLayer(view C.CFTypeRef) C.CFTypeRef {
|
||||
return C.getMetalLayer(view)
|
||||
}
|
||||
|
||||
func resizeDrawable(view, layer C.CFTypeRef) {
|
||||
C.resizeDrawable(view, layer)
|
||||
}
|
||||
365
pkg/gel/gio/app/os.go
Normal file
365
pkg/gel/gio/app/os.go
Normal file
@@ -0,0 +1,365 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/event"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/key"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/gpu"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/pointer"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/system"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
)
|
||||
|
||||
// errOutOfDate is reported when the GPU surface dimensions or properties no
|
||||
// longer match the window.
|
||||
var errOutOfDate = errors.New("app: GPU surface out of date")
|
||||
|
||||
// Config describes a Window configuration.
|
||||
type Config struct {
|
||||
// Size is the window dimensions (Width, Height).
|
||||
Size image.Point
|
||||
// MaxSize is the window maximum allowed dimensions.
|
||||
MaxSize image.Point
|
||||
// MinSize is the window minimum allowed dimensions.
|
||||
MinSize image.Point
|
||||
// Title is the window title displayed in its decoration bar.
|
||||
Title string
|
||||
// WindowMode is the window mode.
|
||||
Mode WindowMode
|
||||
// StatusColor is the color of the Android status bar.
|
||||
StatusColor color.NRGBA
|
||||
// NavigationColor is the color of the navigation bar
|
||||
// on Android, or the address bar in browsers.
|
||||
NavigationColor color.NRGBA
|
||||
// Orientation is the current window orientation.
|
||||
Orientation Orientation
|
||||
// CustomRenderer is true when the window content is rendered by the
|
||||
// client.
|
||||
CustomRenderer bool
|
||||
// Decorated reports whether window decorations are provided automatically.
|
||||
Decorated bool
|
||||
// Focused reports whether has the keyboard focus.
|
||||
Focused bool
|
||||
// decoHeight is the height of the fallback decoration for platforms such
|
||||
// as Wayland that may need fallback client-side decorations.
|
||||
decoHeight unit.Dp
|
||||
}
|
||||
|
||||
// ConfigEvent is sent whenever the configuration of a Window changes.
|
||||
type ConfigEvent struct {
|
||||
Config Config
|
||||
}
|
||||
|
||||
func (c *Config) apply(m unit.Metric, options []Option) {
|
||||
for _, o := range options {
|
||||
o(m, c)
|
||||
}
|
||||
}
|
||||
|
||||
type wakeupEvent struct{}
|
||||
|
||||
// WindowMode is the window mode (WindowMode.Option sets it).
|
||||
// Note that mode can be changed programatically as well as by the user
|
||||
// clicking on the minimize/maximize buttons on the window's title bar.
|
||||
type WindowMode uint8
|
||||
|
||||
const (
|
||||
// Windowed is the normal window mode with OS specific window decorations.
|
||||
Windowed WindowMode = iota
|
||||
// Fullscreen is the full screen window mode.
|
||||
Fullscreen
|
||||
// Minimized is for systems where the window can be minimized to an icon.
|
||||
Minimized
|
||||
// Maximized is for systems where the window can be made to fill the available monitor area.
|
||||
Maximized
|
||||
)
|
||||
|
||||
// Option changes the mode of a Window.
|
||||
func (m WindowMode) Option() Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.Mode = m
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the mode name.
|
||||
func (m WindowMode) String() string {
|
||||
switch m {
|
||||
case Windowed:
|
||||
return "windowed"
|
||||
case Fullscreen:
|
||||
return "fullscreen"
|
||||
case Minimized:
|
||||
return "minimized"
|
||||
case Maximized:
|
||||
return "maximized"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Orientation is the orientation of the app (Orientation.Option sets it).
|
||||
//
|
||||
// Supported platforms are Android and JS.
|
||||
type Orientation uint8
|
||||
|
||||
const (
|
||||
// AnyOrientation allows the window to be freely orientated.
|
||||
AnyOrientation Orientation = iota
|
||||
// LandscapeOrientation constrains the window to landscape orientations.
|
||||
LandscapeOrientation
|
||||
// PortraitOrientation constrains the window to portrait orientations.
|
||||
PortraitOrientation
|
||||
)
|
||||
|
||||
func (o Orientation) Option() Option {
|
||||
return func(_ unit.Metric, cnf *Config) {
|
||||
cnf.Orientation = o
|
||||
}
|
||||
}
|
||||
|
||||
func (o Orientation) String() string {
|
||||
switch o {
|
||||
case AnyOrientation:
|
||||
return "any"
|
||||
case LandscapeOrientation:
|
||||
return "landscape"
|
||||
case PortraitOrientation:
|
||||
return "portrait"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// eventLoop implements the functionality required for drivers where
|
||||
// window event loops must run on a separate thread.
|
||||
type eventLoop struct {
|
||||
win *callbacks
|
||||
// wakeup is the callback to wake up the event loop.
|
||||
wakeup func()
|
||||
// driverFuncs is a channel of functions to run the next
|
||||
// time the window loop waits for events.
|
||||
driverFuncs chan func()
|
||||
// invalidates is notified when an invalidate is requested by the client.
|
||||
invalidates chan struct{}
|
||||
// immediateInvalidates is an optimistic invalidates that doesn't require a wakeup.
|
||||
immediateInvalidates chan struct{}
|
||||
// events is where the platform backend delivers events bound for the
|
||||
// user program.
|
||||
events chan event.Event
|
||||
frames chan *op.Ops
|
||||
frameAck chan struct{}
|
||||
// delivering avoids re-entrant event delivery.
|
||||
delivering bool
|
||||
}
|
||||
|
||||
type frameEvent struct {
|
||||
FrameEvent
|
||||
|
||||
Sync bool
|
||||
}
|
||||
|
||||
type context interface {
|
||||
API() gpu.API
|
||||
RenderTarget() (gpu.RenderTarget, error)
|
||||
Present() error
|
||||
Refresh() error
|
||||
Release()
|
||||
Lock() error
|
||||
Unlock()
|
||||
}
|
||||
|
||||
// driver is the interface for the platform implementation
|
||||
// of a window.
|
||||
type driver interface {
|
||||
// Event blocks until an event is available and returns it.
|
||||
Event() event.Event
|
||||
// Invalidate requests a FrameEvent.
|
||||
Invalidate()
|
||||
// SetAnimating sets the animation flag. When the window is animating,
|
||||
// FrameEvents are delivered as fast as the display can handle them.
|
||||
SetAnimating(anim bool)
|
||||
// ShowTextInput updates the virtual keyboard state.
|
||||
ShowTextInput(show bool)
|
||||
SetInputHint(mode key.InputHint)
|
||||
NewContext() (context, error)
|
||||
// ReadClipboard requests the clipboard content.
|
||||
ReadClipboard()
|
||||
// WriteClipboard requests a clipboard write.
|
||||
WriteClipboard(mime string, s []byte)
|
||||
// Configure the window.
|
||||
Configure([]Option)
|
||||
// SetCursor updates the current cursor to name.
|
||||
SetCursor(cursor pointer.Cursor)
|
||||
// Perform actions on the window.
|
||||
Perform(system.Action)
|
||||
// EditorStateChanged notifies the driver that the editor state changed.
|
||||
EditorStateChanged(old, new editorState)
|
||||
// Run a function on the window thread.
|
||||
Run(f func())
|
||||
// Frame receives a frame.
|
||||
Frame(frame *op.Ops)
|
||||
// ProcessEvent processes an event.
|
||||
ProcessEvent(e event.Event)
|
||||
}
|
||||
|
||||
type windowRendezvous struct {
|
||||
in chan windowAndConfig
|
||||
out chan windowAndConfig
|
||||
windows chan struct{}
|
||||
}
|
||||
|
||||
type windowAndConfig struct {
|
||||
window *callbacks
|
||||
options []Option
|
||||
}
|
||||
|
||||
func newWindowRendezvous() *windowRendezvous {
|
||||
wr := &windowRendezvous{
|
||||
in: make(chan windowAndConfig),
|
||||
out: make(chan windowAndConfig),
|
||||
windows: make(chan struct{}),
|
||||
}
|
||||
go func() {
|
||||
in := wr.in
|
||||
var window windowAndConfig
|
||||
var out chan windowAndConfig
|
||||
for {
|
||||
select {
|
||||
case w := <-in:
|
||||
window = w
|
||||
out = wr.out
|
||||
case out <- window:
|
||||
}
|
||||
}
|
||||
}()
|
||||
return wr
|
||||
}
|
||||
|
||||
func newEventLoop(w *callbacks, wakeup func()) *eventLoop {
|
||||
return &eventLoop{
|
||||
win: w,
|
||||
wakeup: wakeup,
|
||||
events: make(chan event.Event),
|
||||
invalidates: make(chan struct{}, 1),
|
||||
immediateInvalidates: make(chan struct{}),
|
||||
frames: make(chan *op.Ops),
|
||||
frameAck: make(chan struct{}),
|
||||
driverFuncs: make(chan func(), 1),
|
||||
}
|
||||
}
|
||||
|
||||
// Frame receives a frame and waits for its processing. It is called by
|
||||
// the client goroutine.
|
||||
func (e *eventLoop) Frame(frame *op.Ops) {
|
||||
e.frames <- frame
|
||||
<-e.frameAck
|
||||
}
|
||||
|
||||
// Event returns the next available event. It is called by the client
|
||||
// goroutine.
|
||||
func (e *eventLoop) Event() event.Event {
|
||||
for {
|
||||
evt := <-e.events
|
||||
// Receiving a flushEvent indicates to the platform backend that
|
||||
// all previous events have been processed by the user program.
|
||||
if _, ok := evt.(flushEvent); ok {
|
||||
continue
|
||||
}
|
||||
return evt
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate requests invalidation of the window. It is called by the client
|
||||
// goroutine.
|
||||
func (e *eventLoop) Invalidate() {
|
||||
select {
|
||||
case e.immediateInvalidates <- struct{}{}:
|
||||
// The event loop was waiting, no need for a wakeup.
|
||||
case e.invalidates <- struct{}{}:
|
||||
// The event loop is sleeping, wake it up.
|
||||
e.wakeup()
|
||||
default:
|
||||
// A redraw is pending.
|
||||
}
|
||||
}
|
||||
|
||||
// Run f in the window loop thread. It is called by the client goroutine.
|
||||
func (e *eventLoop) Run(f func()) {
|
||||
e.driverFuncs <- f
|
||||
e.wakeup()
|
||||
}
|
||||
|
||||
// FlushEvents delivers pending events to the client.
|
||||
func (e *eventLoop) FlushEvents() {
|
||||
if e.delivering {
|
||||
return
|
||||
}
|
||||
e.delivering = true
|
||||
defer func() { e.delivering = false }()
|
||||
for {
|
||||
evt, ok := e.win.nextEvent()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
e.deliverEvent(evt)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventLoop) deliverEvent(evt event.Event) {
|
||||
var frames <-chan *op.Ops
|
||||
for {
|
||||
select {
|
||||
case f := <-e.driverFuncs:
|
||||
f()
|
||||
case frame := <-frames:
|
||||
// The client called FrameEvent.Frame.
|
||||
frames = nil
|
||||
e.win.ProcessFrame(frame, e.frameAck)
|
||||
case e.events <- evt:
|
||||
switch evt.(type) {
|
||||
case flushEvent, DestroyEvent:
|
||||
// DestroyEvents are not flushed.
|
||||
return
|
||||
case FrameEvent:
|
||||
frames = e.frames
|
||||
}
|
||||
evt = theFlushEvent
|
||||
case <-e.invalidates:
|
||||
e.win.Invalidate()
|
||||
case <-e.immediateInvalidates:
|
||||
e.win.Invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventLoop) Wakeup() {
|
||||
for {
|
||||
select {
|
||||
case f := <-e.driverFuncs:
|
||||
f()
|
||||
case <-e.invalidates:
|
||||
e.win.Invalidate()
|
||||
case <-e.immediateInvalidates:
|
||||
e.win.Invalidate()
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func walkActions(actions system.Action, do func(system.Action)) {
|
||||
for a := system.Action(1); actions != 0; a <<= 1 {
|
||||
if actions&a != 0 {
|
||||
actions &^= a
|
||||
do(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wakeupEvent) ImplementsEvent() {}
|
||||
func (ConfigEvent) ImplementsEvent() {}
|
||||
1500
pkg/gel/gio/app/os_android.go
Normal file
1500
pkg/gel/gio/app/os_android.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,11 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
package app
|
||||
|
||||
/*
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) void gio_wakeupMainThread(void);
|
||||
__attribute__ ((visibility ("hidden"))) NSUInteger gio_nsstringLength(CFTypeRef str);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_nsstringGetCharacters(CFTypeRef str, unichar *chars, NSUInteger loc, NSUInteger length);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_runOnMain(uintptr_t h);
|
||||
__attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(void);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
|
||||
__attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
|
||||
@@ -16,11 +14,36 @@ __attribute__ ((visibility ("hidden"))) void gio_setDisplayLinkDisplay(CFTypeRef
|
||||
__attribute__ ((visibility ("hidden"))) void gio_hideCursor();
|
||||
__attribute__ ((visibility ("hidden"))) void gio_showCursor();
|
||||
__attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
|
||||
__attribute__ ((visibility ("hidden"))) bool gio_isMainThread();
|
||||
|
||||
static bool isMainThread() {
|
||||
return [NSThread isMainThread];
|
||||
}
|
||||
|
||||
static NSUInteger nsstringLength(CFTypeRef cstr) {
|
||||
NSString *str = (__bridge NSString *)cstr;
|
||||
return [str length];
|
||||
}
|
||||
|
||||
static void nsstringGetCharacters(CFTypeRef cstr, unichar *chars, NSUInteger loc, NSUInteger length) {
|
||||
NSString *str = (__bridge NSString *)cstr;
|
||||
[str getCharacters:chars range:NSMakeRange(loc, length)];
|
||||
}
|
||||
|
||||
static CFTypeRef newNSString(unichar *chars, NSUInteger length) {
|
||||
@autoreleasepool {
|
||||
NSString *s = [NSString string];
|
||||
if (length > 0) {
|
||||
s = [NSString stringWithCharacters:chars length:length];
|
||||
}
|
||||
return CFBridgingRetain(s);
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime/cgo"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -52,50 +75,53 @@ type displayLink struct {
|
||||
// displayLinks maps CFTypeRefs to *displayLinks.
|
||||
var displayLinks sync.Map
|
||||
|
||||
var mainFuncs = make(chan func(), 1)
|
||||
func isMainThread() bool {
|
||||
return bool(C.isMainThread())
|
||||
}
|
||||
|
||||
// runOnMain runs the function on the main thread.
|
||||
func runOnMain(f func()) {
|
||||
if C.gio_isMainThread() {
|
||||
if isMainThread() {
|
||||
f()
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
mainFuncs <- f
|
||||
C.gio_wakeupMainThread()
|
||||
}()
|
||||
C.gio_runOnMain(C.uintptr_t(cgo.NewHandle(f)))
|
||||
}
|
||||
|
||||
//export gio_dispatchMainFuncs
|
||||
func gio_dispatchMainFuncs() {
|
||||
for {
|
||||
select {
|
||||
case f := <-mainFuncs:
|
||||
f()
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
//export gio_runFunc
|
||||
func gio_runFunc(h C.uintptr_t) {
|
||||
handle := cgo.Handle(h)
|
||||
defer handle.Delete()
|
||||
f := handle.Value().(func())
|
||||
f()
|
||||
}
|
||||
|
||||
// nsstringToString converts a NSString to a Go string, and
|
||||
// releases the original string.
|
||||
// nsstringToString converts a NSString to a Go string.
|
||||
func nsstringToString(str C.CFTypeRef) string {
|
||||
if str == 0 {
|
||||
return ""
|
||||
}
|
||||
defer C.CFRelease(str)
|
||||
n := C.gio_nsstringLength(str)
|
||||
n := C.nsstringLength(str)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
chars := make([]uint16, n)
|
||||
C.gio_nsstringGetCharacters(str, (*C.unichar)(unsafe.Pointer(&chars[0])), 0, n)
|
||||
C.nsstringGetCharacters(str, (*C.unichar)(unsafe.Pointer(&chars[0])), 0, n)
|
||||
utf8 := utf16.Decode(chars)
|
||||
return string(utf8)
|
||||
}
|
||||
|
||||
func NewDisplayLink(callback func()) (*displayLink, error) {
|
||||
// stringToNSString converts a Go string to a retained NSString.
|
||||
func stringToNSString(str string) C.CFTypeRef {
|
||||
u16 := utf16.Encode([]rune(str))
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
}
|
||||
return C.newNSString(chars, C.NSUInteger(len(u16)))
|
||||
}
|
||||
|
||||
func newDisplayLink(callback func()) (*displayLink, error) {
|
||||
d := &displayLink{
|
||||
callback: callback,
|
||||
done: make(chan struct{}),
|
||||
@@ -174,51 +200,66 @@ func (d *displayLink) SetDisplayID(did uint64) {
|
||||
}
|
||||
|
||||
//export gio_onFrameCallback
|
||||
func gio_onFrameCallback(dl C.CFTypeRef) {
|
||||
if d, exists := displayLinks.Load(dl); exists {
|
||||
d := d.(*displayLink)
|
||||
if atomic.LoadUint32(&d.running) != 0 {
|
||||
d.callback()
|
||||
}
|
||||
func gio_onFrameCallback(ref C.CFTypeRef) {
|
||||
d, exists := displayLinks.Load(ref)
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
dl := d.(*displayLink)
|
||||
if atomic.LoadUint32(&dl.running) != 0 {
|
||||
dl.callback()
|
||||
}
|
||||
}
|
||||
|
||||
var macosCursorID = [...]byte{
|
||||
pointer.CursorDefault: 0,
|
||||
pointer.CursorNone: 1,
|
||||
pointer.CursorText: 2,
|
||||
pointer.CursorVerticalText: 3,
|
||||
pointer.CursorPointer: 4,
|
||||
pointer.CursorCrosshair: 5,
|
||||
pointer.CursorAllScroll: 6,
|
||||
pointer.CursorColResize: 7,
|
||||
pointer.CursorRowResize: 8,
|
||||
pointer.CursorGrab: 9,
|
||||
pointer.CursorGrabbing: 10,
|
||||
pointer.CursorNotAllowed: 11,
|
||||
pointer.CursorWait: 12,
|
||||
pointer.CursorProgress: 13,
|
||||
pointer.CursorNorthWestResize: 14,
|
||||
pointer.CursorNorthEastResize: 15,
|
||||
pointer.CursorSouthWestResize: 16,
|
||||
pointer.CursorSouthEastResize: 17,
|
||||
pointer.CursorNorthSouthResize: 18,
|
||||
pointer.CursorEastWestResize: 19,
|
||||
pointer.CursorWestResize: 20,
|
||||
pointer.CursorEastResize: 21,
|
||||
pointer.CursorNorthResize: 22,
|
||||
pointer.CursorSouthResize: 23,
|
||||
pointer.CursorNorthEastSouthWestResize: 24,
|
||||
pointer.CursorNorthWestSouthEastResize: 25,
|
||||
}
|
||||
|
||||
// windowSetCursor updates the cursor from the current one to a new one
|
||||
// and returns the new one.
|
||||
func windowSetCursor(from, to pointer.CursorName) pointer.CursorName {
|
||||
func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
|
||||
if from == to {
|
||||
return to
|
||||
}
|
||||
var curID int
|
||||
switch to {
|
||||
default:
|
||||
to = pointer.CursorDefault
|
||||
fallthrough
|
||||
case pointer.CursorDefault:
|
||||
curID = 1
|
||||
case pointer.CursorText:
|
||||
curID = 2
|
||||
case pointer.CursorPointer:
|
||||
curID = 3
|
||||
case pointer.CursorCrossHair:
|
||||
curID = 4
|
||||
case pointer.CursorColResize:
|
||||
curID = 5
|
||||
case pointer.CursorRowResize:
|
||||
curID = 6
|
||||
case pointer.CursorGrab:
|
||||
curID = 7
|
||||
case pointer.CursorNone:
|
||||
runOnMain(func() {
|
||||
C.gio_hideCursor()
|
||||
})
|
||||
if to == pointer.CursorNone {
|
||||
C.gio_hideCursor()
|
||||
return to
|
||||
}
|
||||
runOnMain(func() {
|
||||
if from == pointer.CursorNone {
|
||||
C.gio_showCursor()
|
||||
}
|
||||
C.gio_setCursor(C.NSUInteger(curID))
|
||||
})
|
||||
if from == pointer.CursorNone {
|
||||
C.gio_showCursor()
|
||||
}
|
||||
C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
|
||||
return to
|
||||
}
|
||||
|
||||
func (w *window) wakeup() {
|
||||
runOnMain(func() {
|
||||
w.loop.Wakeup()
|
||||
w.loop.FlushEvents()
|
||||
})
|
||||
}
|
||||
11
pkg/gel/gio/app/os_darwin.m
Normal file
11
pkg/gel/gio/app/os_darwin.m
Normal file
@@ -0,0 +1,11 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
void gio_runOnMain(uintptr_t h) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
gio_runFunc(h);
|
||||
});
|
||||
}
|
||||
446
pkg/gel/gio/app/os_ios.go
Normal file
446
pkg/gel/gio/app/os_ios.go
Normal file
@@ -0,0 +1,446 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build darwin && ios
|
||||
// +build darwin,ios
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -DGLES_SILENCE_DEPRECATION -Werror -Wno-deprecated-declarations -fmodules -fobjc-arc -x objective-c
|
||||
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <UIKit/UIKit.h>
|
||||
#include <stdint.h>
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) int gio_applicationMain(int argc, char *argv[]);
|
||||
__attribute__ ((visibility ("hidden"))) void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle);
|
||||
|
||||
struct drawParams {
|
||||
CGFloat dpi, sdpi;
|
||||
CGFloat width, height;
|
||||
CGFloat top, right, bottom, left;
|
||||
};
|
||||
|
||||
static void writeClipboard(unichar *chars, NSUInteger length) {
|
||||
#if !TARGET_OS_TV
|
||||
@autoreleasepool {
|
||||
NSString *s = [NSString string];
|
||||
if (length > 0) {
|
||||
s = [NSString stringWithCharacters:chars length:length];
|
||||
}
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
p.string = s;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static CFTypeRef readClipboard(void) {
|
||||
#if !TARGET_OS_TV
|
||||
@autoreleasepool {
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
return (__bridge_retained CFTypeRef)p.string;
|
||||
}
|
||||
#else
|
||||
return nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void showTextInput(CFTypeRef viewRef) {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
[view becomeFirstResponder];
|
||||
}
|
||||
|
||||
static void hideTextInput(CFTypeRef viewRef) {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
[view resignFirstResponder];
|
||||
}
|
||||
|
||||
static struct drawParams viewDrawParams(CFTypeRef viewRef) {
|
||||
UIView *v = (__bridge UIView *)viewRef;
|
||||
struct drawParams params;
|
||||
CGFloat scale = v.layer.contentsScale;
|
||||
// Use 163 as the standard ppi on iOS.
|
||||
params.dpi = 163*scale;
|
||||
params.sdpi = params.dpi;
|
||||
UIEdgeInsets insets = v.layoutMargins;
|
||||
if (@available(iOS 11.0, tvOS 11.0, *)) {
|
||||
UIFontMetrics *metrics = [UIFontMetrics defaultMetrics];
|
||||
params.sdpi = [metrics scaledValueForValue:params.sdpi];
|
||||
insets = v.safeAreaInsets;
|
||||
}
|
||||
params.width = v.bounds.size.width*scale;
|
||||
params.height = v.bounds.size.height*scale;
|
||||
params.top = insets.top*scale;
|
||||
params.right = insets.right*scale;
|
||||
params.bottom = insets.bottom*scale;
|
||||
params.left = insets.left*scale;
|
||||
return params;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"image"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/cgo"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/f32"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/event"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/key"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/pointer"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/system"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/transfer"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
)
|
||||
|
||||
type UIKitViewEvent struct {
|
||||
// ViewController is a CFTypeRef for the UIViewController backing a Window.
|
||||
ViewController uintptr
|
||||
}
|
||||
|
||||
type window struct {
|
||||
view C.CFTypeRef
|
||||
w *callbacks
|
||||
displayLink *displayLink
|
||||
loop *eventLoop
|
||||
|
||||
hidden bool
|
||||
cursor pointer.Cursor
|
||||
config Config
|
||||
|
||||
pointerMap []C.CFTypeRef
|
||||
}
|
||||
|
||||
var mainWindow = newWindowRendezvous()
|
||||
|
||||
func init() {
|
||||
// Darwin requires UI operations happen on the main thread only.
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
//export onCreate
|
||||
func onCreate(view, controller C.CFTypeRef) {
|
||||
wopts := <-mainWindow.out
|
||||
w := &window{
|
||||
view: view,
|
||||
w: wopts.window,
|
||||
}
|
||||
w.loop = newEventLoop(w.w, w.wakeup)
|
||||
w.w.SetDriver(w)
|
||||
mainWindow.windows <- struct{}{}
|
||||
dl, err := newDisplayLink(func() {
|
||||
w.draw(false)
|
||||
})
|
||||
if err != nil {
|
||||
w.w.ProcessEvent(DestroyEvent{Err: err})
|
||||
return
|
||||
}
|
||||
w.displayLink = dl
|
||||
C.gio_viewSetHandle(view, C.uintptr_t(cgo.NewHandle(w)))
|
||||
w.Configure(wopts.options)
|
||||
w.ProcessEvent(UIKitViewEvent{ViewController: uintptr(controller)})
|
||||
}
|
||||
|
||||
func viewFor(h C.uintptr_t) *window {
|
||||
return cgo.Handle(h).Value().(*window)
|
||||
}
|
||||
|
||||
//export gio_onDraw
|
||||
func gio_onDraw(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
if w.hidden {
|
||||
return
|
||||
}
|
||||
params := C.viewDrawParams(w.view)
|
||||
if params.width == 0 || params.height == 0 {
|
||||
return
|
||||
}
|
||||
const inchPrDp = 1.0 / 163
|
||||
m := unit.Metric{
|
||||
PxPerDp: float32(params.dpi) * inchPrDp,
|
||||
PxPerSp: float32(params.sdpi) * inchPrDp,
|
||||
}
|
||||
dppp := unit.Dp(1. / m.PxPerDp)
|
||||
w.ProcessEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: int(params.width + .5),
|
||||
Y: int(params.height + .5),
|
||||
},
|
||||
Insets: Insets{
|
||||
Top: unit.Dp(params.top) * dppp,
|
||||
Bottom: unit.Dp(params.bottom) * dppp,
|
||||
Left: unit.Dp(params.left) * dppp,
|
||||
Right: unit.Dp(params.right) * dppp,
|
||||
},
|
||||
Metric: m,
|
||||
},
|
||||
Sync: sync,
|
||||
})
|
||||
}
|
||||
|
||||
//export onStop
|
||||
func onStop(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.hidden = true
|
||||
}
|
||||
|
||||
//export onStart
|
||||
func onStart(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.hidden = false
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
//export onDestroy
|
||||
func onDestroy(h C.uintptr_t) {
|
||||
w := viewFor(h)
|
||||
w.ProcessEvent(UIKitViewEvent{})
|
||||
w.ProcessEvent(DestroyEvent{})
|
||||
w.displayLink.Close()
|
||||
w.displayLink = nil
|
||||
cgo.Handle(h).Delete()
|
||||
w.view = 0
|
||||
}
|
||||
|
||||
//export onFocus
|
||||
func onFocus(h C.uintptr_t, focus int) {
|
||||
w := viewFor(h)
|
||||
w.config.Focused = focus != 0
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
//export onLowMemory
|
||||
func onLowMemory() {
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
}
|
||||
|
||||
//export onUpArrow
|
||||
func onUpArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameUpArrow)
|
||||
}
|
||||
|
||||
//export onDownArrow
|
||||
func onDownArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameDownArrow)
|
||||
}
|
||||
|
||||
//export onLeftArrow
|
||||
func onLeftArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameLeftArrow)
|
||||
}
|
||||
|
||||
//export onRightArrow
|
||||
func onRightArrow(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameRightArrow)
|
||||
}
|
||||
|
||||
//export onDeleteBackward
|
||||
func onDeleteBackward(h C.uintptr_t) {
|
||||
viewFor(h).onKeyCommand(key.NameDeleteBackward)
|
||||
}
|
||||
|
||||
//export onText
|
||||
func onText(h C.uintptr_t, str C.CFTypeRef) {
|
||||
w := viewFor(h)
|
||||
w.w.EditorInsert(nsstringToString(str))
|
||||
}
|
||||
|
||||
//export onTouch
|
||||
func onTouch(h C.uintptr_t, last C.int, touchRef C.CFTypeRef, phase C.NSInteger, x, y C.CGFloat, ti C.double) {
|
||||
var kind pointer.Kind
|
||||
switch phase {
|
||||
case C.UITouchPhaseBegan:
|
||||
kind = pointer.Press
|
||||
case C.UITouchPhaseMoved:
|
||||
kind = pointer.Move
|
||||
case C.UITouchPhaseEnded:
|
||||
kind = pointer.Release
|
||||
case C.UITouchPhaseCancelled:
|
||||
kind = pointer.Cancel
|
||||
default:
|
||||
return
|
||||
}
|
||||
w := viewFor(h)
|
||||
t := time.Duration(float64(ti) * float64(time.Second))
|
||||
p := f32.Point{X: float32(x), Y: float32(y)}
|
||||
w.ProcessEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Touch,
|
||||
PointerID: w.lookupTouch(last != 0, touchRef),
|
||||
Position: p,
|
||||
Time: t,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) ReadClipboard() {
|
||||
cstr := C.readClipboard()
|
||||
defer C.CFRelease(cstr)
|
||||
content := nsstringToString(cstr)
|
||||
w.ProcessEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||
u16 := utf16.Encode([]rune(string(s)))
|
||||
var chars *C.unichar
|
||||
if len(u16) > 0 {
|
||||
chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
|
||||
}
|
||||
C.writeClipboard(chars, C.NSUInteger(len(u16)))
|
||||
}
|
||||
|
||||
func (w *window) Configure([]Option) {
|
||||
// Decorations are never disabled.
|
||||
w.config.Decorated = true
|
||||
w.ProcessEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||
|
||||
func (w *window) Perform(system.Action) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
if anim {
|
||||
w.displayLink.Start()
|
||||
} else {
|
||||
w.displayLink.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
w.cursor = windowSetCursor(w.cursor, cursor)
|
||||
}
|
||||
|
||||
func (w *window) onKeyCommand(name key.Name) {
|
||||
w.ProcessEvent(key.Event{
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
|
||||
// lookupTouch maps an UITouch pointer value to an index. If
|
||||
// last is set, the map is cleared.
|
||||
func (w *window) lookupTouch(last bool, touch C.CFTypeRef) pointer.ID {
|
||||
id := -1
|
||||
for i, ref := range w.pointerMap {
|
||||
if ref == touch {
|
||||
id = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if id == -1 {
|
||||
id = len(w.pointerMap)
|
||||
w.pointerMap = append(w.pointerMap, touch)
|
||||
}
|
||||
if last {
|
||||
w.pointerMap = w.pointerMap[:0]
|
||||
}
|
||||
return pointer.ID(id)
|
||||
}
|
||||
|
||||
func (w *window) contextView() C.CFTypeRef {
|
||||
return w.view
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
if show {
|
||||
C.showTextInput(w.view)
|
||||
} else {
|
||||
C.hideTextInput(w.view)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) SetInputHint(_ key.InputHint) {}
|
||||
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.w.ProcessEvent(e)
|
||||
w.loop.FlushEvents()
|
||||
}
|
||||
|
||||
func (w *window) Event() event.Event {
|
||||
return w.loop.Event()
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.loop.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
w.loop.Run(f)
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.loop.Frame(frame)
|
||||
}
|
||||
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
mainWindow.in <- windowAndConfig{win, options}
|
||||
<-mainWindow.windows
|
||||
}
|
||||
|
||||
var mainMode = mainModeUndefined
|
||||
|
||||
const (
|
||||
mainModeUndefined = iota
|
||||
mainModeExe
|
||||
mainModeLibrary
|
||||
)
|
||||
|
||||
func osMain() {
|
||||
if !isMainThread() {
|
||||
panic("app.Main must be run on the main goroutine")
|
||||
}
|
||||
switch mainMode {
|
||||
case mainModeUndefined:
|
||||
mainMode = mainModeExe
|
||||
var argv []*C.char
|
||||
for _, arg := range os.Args {
|
||||
a := C.CString(arg)
|
||||
defer C.free(unsafe.Pointer(a))
|
||||
argv = append(argv, a)
|
||||
}
|
||||
C.gio_applicationMain(C.int(len(argv)), unsafe.SliceData(argv))
|
||||
case mainModeExe:
|
||||
panic("app.Main may be called only once")
|
||||
case mainModeLibrary:
|
||||
// Do nothing, we're embedded as a library.
|
||||
}
|
||||
}
|
||||
|
||||
//export gio_runMain
|
||||
func gio_runMain() {
|
||||
if !isMainThread() {
|
||||
panic("app.Main must be run on the main goroutine")
|
||||
}
|
||||
switch mainMode {
|
||||
case mainModeUndefined:
|
||||
mainMode = mainModeLibrary
|
||||
runMain()
|
||||
case mainModeExe:
|
||||
// Do nothing, main has already been called.
|
||||
}
|
||||
}
|
||||
|
||||
func (UIKitViewEvent) implementsViewEvent() {}
|
||||
func (UIKitViewEvent) ImplementsEvent() {}
|
||||
func (u UIKitViewEvent) Valid() bool {
|
||||
return u != (UIKitViewEvent{})
|
||||
}
|
||||
@@ -8,7 +8,10 @@
|
||||
#include "_cgo_export.h"
|
||||
#include "framework_ios.h"
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) Class gio_layerClass(void);
|
||||
|
||||
@interface GioView: UIView <UIKeyInput>
|
||||
@property uintptr_t handle;
|
||||
@end
|
||||
|
||||
@implementation GioViewController
|
||||
@@ -23,12 +26,13 @@ CGFloat _keyboardHeight;
|
||||
self.view.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
UIView *drawView = [[GioView alloc] initWithFrame:zeroFrame];
|
||||
[self.view addSubview: drawView];
|
||||
#ifndef TARGET_OS_TV
|
||||
#if !TARGET_OS_TV
|
||||
drawView.multipleTouchEnabled = YES;
|
||||
#endif
|
||||
drawView.preservesSuperviewLayoutMargins = YES;
|
||||
drawView.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
onCreate((__bridge CFTypeRef)drawView);
|
||||
onCreate((__bridge CFTypeRef)drawView, (__bridge CFTypeRef)self);
|
||||
#if !TARGET_OS_TV
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(keyboardWillChange:)
|
||||
name:UIKeyboardWillShowNotification
|
||||
@@ -41,6 +45,7 @@ CGFloat _keyboardHeight;
|
||||
selector:@selector(keyboardWillHide:)
|
||||
name:UIKeyboardWillHideNotification
|
||||
object:nil];
|
||||
#endif
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||
selector: @selector(applicationDidEnterBackground:)
|
||||
name: UIApplicationDidEnterBackgroundNotification
|
||||
@@ -52,33 +57,33 @@ CGFloat _keyboardHeight;
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
UIView *drawView = self.view.subviews[0];
|
||||
if (drawView != nil) {
|
||||
gio_onDraw((__bridge CFTypeRef)drawView);
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
if (view != nil) {
|
||||
onStart(view.handle);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
UIView *drawView = self.view.subviews[0];
|
||||
if (drawView != nil) {
|
||||
onStop((__bridge CFTypeRef)drawView);
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
if (view != nil) {
|
||||
onStop(view.handle);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
[super viewDidDisappear:animated];
|
||||
CFTypeRef viewRef = (__bridge CFTypeRef)self.view.subviews[0];
|
||||
onDestroy(viewRef);
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
onDestroy(view.handle);
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
UIView *view = self.view.subviews[0];
|
||||
GioView *view = (GioView *)self.view.subviews[0];
|
||||
CGRect frame = self.view.bounds;
|
||||
// Adjust view bounds to make room for the keyboard.
|
||||
frame.size.height -= _keyboardHeight;
|
||||
view.frame = frame;
|
||||
gio_onDraw((__bridge CFTypeRef)view);
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
@@ -86,6 +91,7 @@ CGFloat _keyboardHeight;
|
||||
[super didReceiveMemoryWarning];
|
||||
}
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
- (void)keyboardWillChange:(NSNotification *)note {
|
||||
NSDictionary *userInfo = note.userInfo;
|
||||
CGRect f = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
@@ -97,13 +103,13 @@ CGFloat _keyboardHeight;
|
||||
_keyboardHeight = 0.0;
|
||||
[self.view setNeedsLayout];
|
||||
}
|
||||
#endif
|
||||
@end
|
||||
|
||||
static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
||||
static void handleTouches(int last, GioView *view, NSSet<UITouch *> *touches, UIEvent *event) {
|
||||
CGFloat scale = view.contentScaleFactor;
|
||||
NSUInteger i = 0;
|
||||
NSUInteger n = [touches count];
|
||||
CFTypeRef viewRef = (__bridge CFTypeRef)view;
|
||||
for (UITouch *touch in touches) {
|
||||
CFTypeRef touchRef = (__bridge CFTypeRef)touch;
|
||||
i++;
|
||||
@@ -114,7 +120,7 @@ static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIE
|
||||
CGPoint loc = [coalescedTouch locationInView:view];
|
||||
j++;
|
||||
int lastTouch = last && i == n && j == m;
|
||||
onTouch(lastTouch, viewRef, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
||||
onTouch(view.handle, lastTouch, touchRef, touch.phase, loc.x*scale, loc.y*scale, [coalescedTouch timestamp]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,9 +128,11 @@ static void handleTouches(int last, UIView *view, NSSet<UITouch *> *touches, UIE
|
||||
@implementation GioView
|
||||
NSArray<UIKeyCommand *> *_keyCommands;
|
||||
+ (void)onFrameCallback:(CADisplayLink *)link {
|
||||
gio_onFrameCallback((__bridge CFTypeRef)link);
|
||||
gio_onFrameCallback((__bridge CFTypeRef)link);
|
||||
}
|
||||
+ (Class)layerClass {
|
||||
return gio_layerClass();
|
||||
}
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
if (self.window != nil) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
@@ -147,19 +155,16 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
||||
|
||||
- (void)onWindowDidBecomeKey:(NSNotification *)note {
|
||||
if (self.isFirstResponder) {
|
||||
onFocus((__bridge CFTypeRef)self, YES);
|
||||
onFocus(self.handle, YES);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onWindowDidResignKey:(NSNotification *)note {
|
||||
if (self.isFirstResponder) {
|
||||
onFocus((__bridge CFTypeRef)self, NO);
|
||||
onFocus(self.handle, NO);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
}
|
||||
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
handleTouches(0, self, touches, event);
|
||||
}
|
||||
@@ -177,7 +182,7 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
||||
}
|
||||
|
||||
- (void)insertText:(NSString *)text {
|
||||
onText((__bridge CFTypeRef)self, (char *)text.UTF8String);
|
||||
onText(self.handle, (__bridge CFTypeRef)text);
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
@@ -189,23 +194,23 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
||||
}
|
||||
|
||||
- (void)deleteBackward {
|
||||
onDeleteBackward((__bridge CFTypeRef)self);
|
||||
onDeleteBackward(self.handle);
|
||||
}
|
||||
|
||||
- (void)onUpArrow {
|
||||
onUpArrow((__bridge CFTypeRef)self);
|
||||
onUpArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onDownArrow {
|
||||
onDownArrow((__bridge CFTypeRef)self);
|
||||
onDownArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onLeftArrow {
|
||||
onLeftArrow((__bridge CFTypeRef)self);
|
||||
onLeftArrow(self.handle);
|
||||
}
|
||||
|
||||
- (void)onRightArrow {
|
||||
onRightArrow((__bridge CFTypeRef)self);
|
||||
onRightArrow(self.handle);
|
||||
}
|
||||
|
||||
- (NSArray<UIKeyCommand *> *)keyCommands {
|
||||
@@ -229,74 +234,6 @@ NSArray<UIKeyCommand *> *_keyCommands;
|
||||
}
|
||||
@end
|
||||
|
||||
void gio_writeClipboard(unichar *chars, NSUInteger length) {
|
||||
@autoreleasepool {
|
||||
NSString *s = [NSString string];
|
||||
if (length > 0) {
|
||||
s = [NSString stringWithCharacters:chars length:length];
|
||||
}
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
p.string = s;
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_readClipboard(void) {
|
||||
@autoreleasepool {
|
||||
UIPasteboard *p = UIPasteboard.generalPasteboard;
|
||||
return (__bridge_retained CFTypeRef)p.string;
|
||||
}
|
||||
}
|
||||
|
||||
void gio_showTextInput(CFTypeRef viewRef) {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
[view becomeFirstResponder];
|
||||
}
|
||||
|
||||
void gio_hideTextInput(CFTypeRef viewRef) {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
[view resignFirstResponder];
|
||||
}
|
||||
|
||||
void gio_addLayerToView(CFTypeRef viewRef, CFTypeRef layerRef) {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
CALayer *layer = (__bridge CALayer *)layerRef;
|
||||
[view.layer addSublayer:layer];
|
||||
}
|
||||
|
||||
void gio_updateView(CFTypeRef viewRef, CFTypeRef layerRef) {
|
||||
UIView *view = (__bridge UIView *)viewRef;
|
||||
CAEAGLLayer *layer = (__bridge CAEAGLLayer *)layerRef;
|
||||
layer.contentsScale = view.contentScaleFactor;
|
||||
layer.bounds = view.bounds;
|
||||
}
|
||||
|
||||
void gio_removeLayer(CFTypeRef layerRef) {
|
||||
CALayer *layer = (__bridge CALayer *)layerRef;
|
||||
[layer removeFromSuperlayer];
|
||||
}
|
||||
|
||||
struct drawParams gio_viewDrawParams(CFTypeRef viewRef) {
|
||||
UIView *v = (__bridge UIView *)viewRef;
|
||||
struct drawParams params;
|
||||
CGFloat scale = v.layer.contentsScale;
|
||||
// Use 163 as the standard ppi on iOS.
|
||||
params.dpi = 163*scale;
|
||||
params.sdpi = params.dpi;
|
||||
UIEdgeInsets insets = v.layoutMargins;
|
||||
if (@available(iOS 11.0, tvOS 11.0, *)) {
|
||||
UIFontMetrics *metrics = [UIFontMetrics defaultMetrics];
|
||||
params.sdpi = [metrics scaledValueForValue:params.sdpi];
|
||||
insets = v.safeAreaInsets;
|
||||
}
|
||||
params.width = v.bounds.size.width*scale;
|
||||
params.height = v.bounds.size.height*scale;
|
||||
params.top = insets.top*scale;
|
||||
params.right = insets.right*scale;
|
||||
params.bottom = insets.bottom*scale;
|
||||
params.left = insets.left*scale;
|
||||
return params;
|
||||
}
|
||||
|
||||
CFTypeRef gio_createDisplayLink(void) {
|
||||
CADisplayLink *dl = [CADisplayLink displayLinkWithTarget:[GioView class] selector:@selector(onFrameCallback:)];
|
||||
dl.paused = YES;
|
||||
@@ -338,3 +275,28 @@ void gio_showCursor() {
|
||||
void gio_setCursor(NSUInteger curID) {
|
||||
// Not supported.
|
||||
}
|
||||
|
||||
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||
GioView *v = (__bridge GioView *)viewRef;
|
||||
v.handle = handle;
|
||||
}
|
||||
|
||||
@interface _gioAppDelegate : UIResponder <UIApplicationDelegate>
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
@end
|
||||
|
||||
@implementation _gioAppDelegate
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
GioViewController *controller = [[GioViewController alloc] initWithNibName:nil bundle:nil];
|
||||
self.window.rootViewController = controller;
|
||||
[self.window makeKeyAndVisible];
|
||||
return YES;
|
||||
}
|
||||
@end
|
||||
|
||||
int gio_applicationMain(int argc, char *argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([_gioAppDelegate class]));
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,74 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package wm
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall/js"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/internal/f32color"
|
||||
"github.com/p9c/p9/pkg/gel/gio/op"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/f32"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/clipboard"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/event"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/key"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/pointer"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/system"
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/transfer"
|
||||
"github.com/p9c/p9/pkg/gel/gio/unit"
|
||||
)
|
||||
|
||||
type JSViewEvent struct {
|
||||
Element js.Value
|
||||
}
|
||||
|
||||
type contextStatus int
|
||||
|
||||
const (
|
||||
contextStatusOkay contextStatus = iota
|
||||
contextStatusLost
|
||||
contextStatusRestored
|
||||
)
|
||||
|
||||
type window struct {
|
||||
window js.Value
|
||||
document js.Value
|
||||
head js.Value
|
||||
clipboard js.Value
|
||||
cnv js.Value
|
||||
tarea js.Value
|
||||
w Callbacks
|
||||
w *callbacks
|
||||
redraw js.Func
|
||||
clipboardCallback js.Func
|
||||
requestAnimationFrame js.Value
|
||||
browserHistory js.Value
|
||||
visualViewport js.Value
|
||||
screenOrientation js.Value
|
||||
cleanfuncs []func()
|
||||
touches []js.Value
|
||||
composing bool
|
||||
requestFocus bool
|
||||
|
||||
chanAnimation chan struct{}
|
||||
chanRedraw chan struct{}
|
||||
|
||||
mu sync.Mutex
|
||||
size f32.Point
|
||||
config Config
|
||||
inset f32.Point
|
||||
scale float32
|
||||
animating bool
|
||||
// animRequested tracks whether a requestAnimationFrame callback
|
||||
// is pending.
|
||||
animRequested bool
|
||||
wakeups chan struct{}
|
||||
|
||||
contextStatus contextStatus
|
||||
}
|
||||
|
||||
func NewWindow(win Callbacks, opts *Options) error {
|
||||
func newWindow(win *callbacks, options []Option) {
|
||||
doc := js.Global().Get("document")
|
||||
cont := getContainer(doc)
|
||||
cnv := createCanvas(doc)
|
||||
@@ -61,47 +80,43 @@ func NewWindow(win Callbacks, opts *Options) error {
|
||||
document: doc,
|
||||
tarea: tarea,
|
||||
window: js.Global().Get("window"),
|
||||
head: doc.Get("head"),
|
||||
clipboard: js.Global().Get("navigator").Get("clipboard"),
|
||||
wakeups: make(chan struct{}, 1),
|
||||
w: win,
|
||||
}
|
||||
w.w.SetDriver(w)
|
||||
w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
|
||||
w.browserHistory = w.window.Get("history")
|
||||
w.visualViewport = w.window.Get("visualViewport")
|
||||
if w.visualViewport.IsUndefined() {
|
||||
w.visualViewport = w.window
|
||||
}
|
||||
w.chanAnimation = make(chan struct{}, 1)
|
||||
w.chanRedraw = make(chan struct{}, 1)
|
||||
if screen := w.window.Get("screen"); screen.Truthy() {
|
||||
w.screenOrientation = screen.Get("orientation")
|
||||
}
|
||||
w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||
w.chanAnimation <- struct{}{}
|
||||
w.draw(false)
|
||||
return nil
|
||||
})
|
||||
w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
|
||||
content := args[0].String()
|
||||
win.Event(clipboard.Event{Text: content})
|
||||
w.processEvent(transfer.DataEvent{
|
||||
Type: "application/text",
|
||||
Open: func() io.ReadCloser {
|
||||
return io.NopCloser(strings.NewReader(content))
|
||||
},
|
||||
})
|
||||
return nil
|
||||
})
|
||||
w.addEventListeners()
|
||||
w.addHistory()
|
||||
w.Option(opts)
|
||||
w.w = win
|
||||
|
||||
go func() {
|
||||
defer w.cleanup()
|
||||
w.w.SetDriver(w)
|
||||
w.blur()
|
||||
w.w.Event(system.StageEvent{Stage: system.StageRunning})
|
||||
w.resize()
|
||||
w.draw(true)
|
||||
for {
|
||||
select {
|
||||
case <-w.chanAnimation:
|
||||
w.animCallback()
|
||||
case <-w.chanRedraw:
|
||||
w.draw(true)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
w.Configure(options)
|
||||
w.blur()
|
||||
w.processEvent(JSViewEvent{Element: cont})
|
||||
w.resize()
|
||||
w.draw(true)
|
||||
}
|
||||
|
||||
func getContainer(doc js.Value) js.Value {
|
||||
@@ -148,9 +163,25 @@ func (w *window) cleanup() {
|
||||
}
|
||||
|
||||
func (w *window) addEventListeners() {
|
||||
w.addEventListener(w.cnv, "webglcontextlost", func(this js.Value, args []js.Value) interface{} {
|
||||
args[0].Call("preventDefault")
|
||||
w.contextStatus = contextStatusLost
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "webglcontextrestored", func(this js.Value, args []js.Value) interface{} {
|
||||
args[0].Call("preventDefault")
|
||||
w.contextStatus = contextStatusRestored
|
||||
|
||||
// Resize is required to force update the canvas content when restored.
|
||||
w.cnv.Set("width", 0)
|
||||
w.cnv.Set("height", 0)
|
||||
w.resize()
|
||||
w.draw(true)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
|
||||
w.resize()
|
||||
w.chanRedraw <- struct{}{}
|
||||
w.draw(true)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
|
||||
@@ -158,25 +189,11 @@ func (w *window) addEventListeners() {
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
|
||||
ev := &system.CommandEvent{Type: system.CommandBack}
|
||||
w.w.Event(ev)
|
||||
if ev.Cancel {
|
||||
if w.processEvent(key.Event{Name: key.NameBack}) {
|
||||
return w.browserHistory.Call("forward")
|
||||
}
|
||||
|
||||
return w.browserHistory.Call("back")
|
||||
})
|
||||
w.addEventListener(w.document, "visibilitychange", func(this js.Value, args []js.Value) interface{} {
|
||||
ev := system.StageEvent{}
|
||||
switch w.document.Get("visibilityState").String() {
|
||||
case "hidden", "prerender", "unloaded":
|
||||
ev.Stage = system.StagePaused
|
||||
default:
|
||||
ev.Stage = system.StageRunning
|
||||
}
|
||||
w.w.Event(ev)
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
|
||||
w.pointerEvent(pointer.Move, 0, 0, args[0])
|
||||
return nil
|
||||
@@ -196,6 +213,10 @@ func (w *window) addEventListeners() {
|
||||
w.addEventListener(w.cnv, "wheel", func(this js.Value, args []js.Value) interface{} {
|
||||
e := args[0]
|
||||
dx, dy := e.Get("deltaX").Float(), e.Get("deltaY").Float()
|
||||
// horizontal scroll if shift is pressed.
|
||||
if e.Get("shiftKey").Bool() {
|
||||
dx, dy = dy, dx
|
||||
}
|
||||
mode := e.Get("deltaMode").Int()
|
||||
switch mode {
|
||||
case 0x01: // DOM_DELTA_LINE
|
||||
@@ -230,18 +251,20 @@ func (w *window) addEventListeners() {
|
||||
w.touches[i] = js.Null()
|
||||
}
|
||||
w.touches = w.touches[:0]
|
||||
w.w.Event(pointer.Event{
|
||||
Type: pointer.Cancel,
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: pointer.Cancel,
|
||||
Source: pointer.Touch,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
|
||||
w.w.Event(key.FocusEvent{Focus: true})
|
||||
w.config.Focused = true
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
return nil
|
||||
})
|
||||
w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
|
||||
w.w.Event(key.FocusEvent{Focus: false})
|
||||
w.config.Focused = false
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
w.blur()
|
||||
return nil
|
||||
})
|
||||
@@ -286,7 +309,7 @@ func (w *window) addHistory() {
|
||||
func (w *window) flushInput() {
|
||||
val := w.tarea.Get("value").String()
|
||||
w.tarea.Set("value", "")
|
||||
w.w.Event(key.EditEvent{Text: string(val)})
|
||||
w.w.EditorInsert(string(val))
|
||||
}
|
||||
|
||||
func (w *window) blur() {
|
||||
@@ -299,6 +322,29 @@ func (w *window) focus() {
|
||||
w.requestFocus = true
|
||||
}
|
||||
|
||||
func (w *window) keyboard(hint key.InputHint) {
|
||||
var m string
|
||||
switch hint {
|
||||
case key.HintAny:
|
||||
m = "text"
|
||||
case key.HintText:
|
||||
m = "text"
|
||||
case key.HintNumeric:
|
||||
m = "decimal"
|
||||
case key.HintEmail:
|
||||
m = "email"
|
||||
case key.HintURL:
|
||||
m = "url"
|
||||
case key.HintTelephone:
|
||||
m = "tel"
|
||||
case key.HintPassword:
|
||||
m = "password"
|
||||
default:
|
||||
m = "text"
|
||||
}
|
||||
w.tarea.Set("inputMode", m)
|
||||
}
|
||||
|
||||
func (w *window) keyEvent(e js.Value, ks key.State) {
|
||||
k := e.Get("key").String()
|
||||
if n, ok := translateKey(k); ok {
|
||||
@@ -307,10 +353,50 @@ func (w *window) keyEvent(e js.Value, ks key.State) {
|
||||
Modifiers: modifiersFor(e),
|
||||
State: ks,
|
||||
}
|
||||
w.w.Event(cmd)
|
||||
w.processEvent(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) ProcessEvent(e event.Event) {
|
||||
w.processEvent(e)
|
||||
}
|
||||
|
||||
func (w *window) processEvent(e event.Event) bool {
|
||||
if !w.w.ProcessEvent(e) {
|
||||
return false
|
||||
}
|
||||
select {
|
||||
case w.wakeups <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *window) Event() event.Event {
|
||||
for {
|
||||
evt, ok := w.w.nextEvent()
|
||||
if ok {
|
||||
if _, destroy := evt.(DestroyEvent); destroy {
|
||||
w.cleanup()
|
||||
}
|
||||
return evt
|
||||
}
|
||||
<-w.wakeups
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) Invalidate() {
|
||||
w.w.Invalidate()
|
||||
}
|
||||
|
||||
func (w *window) Run(f func()) {
|
||||
f()
|
||||
}
|
||||
|
||||
func (w *window) Frame(frame *op.Ops) {
|
||||
w.w.ProcessFrame(frame, nil)
|
||||
}
|
||||
|
||||
// modifiersFor returns the modifier set for a DOM MouseEvent or
|
||||
// KeyEvent.
|
||||
func modifiersFor(e js.Value) key.Modifiers {
|
||||
@@ -331,15 +417,13 @@ func modifiersFor(e js.Value) key.Modifiers {
|
||||
return mods
|
||||
}
|
||||
|
||||
func (w *window) touchEvent(typ pointer.Type, e js.Value) {
|
||||
func (w *window) touchEvent(kind pointer.Kind, e js.Value) {
|
||||
e.Call("preventDefault")
|
||||
t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond
|
||||
changedTouches := e.Get("changedTouches")
|
||||
n := changedTouches.Length()
|
||||
rect := w.cnv.Call("getBoundingClientRect")
|
||||
w.mu.Lock()
|
||||
scale := w.scale
|
||||
w.mu.Unlock()
|
||||
var mods key.Modifiers
|
||||
if e.Get("shiftKey").Bool() {
|
||||
mods |= key.ModShift
|
||||
@@ -360,8 +444,8 @@ func (w *window) touchEvent(typ pointer.Type, e js.Value) {
|
||||
X: float32(x) * scale,
|
||||
Y: float32(y) * scale,
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Touch,
|
||||
Position: pos,
|
||||
PointerID: pid,
|
||||
@@ -383,15 +467,13 @@ func (w *window) touchIDFor(touch js.Value) pointer.ID {
|
||||
return pid
|
||||
}
|
||||
|
||||
func (w *window) pointerEvent(typ pointer.Type, dx, dy float32, e js.Value) {
|
||||
func (w *window) pointerEvent(kind pointer.Kind, dx, dy float32, e js.Value) {
|
||||
e.Call("preventDefault")
|
||||
x, y := e.Get("clientX").Float(), e.Get("clientY").Float()
|
||||
rect := w.cnv.Call("getBoundingClientRect")
|
||||
x -= rect.Get("left").Float()
|
||||
y -= rect.Get("top").Float()
|
||||
w.mu.Lock()
|
||||
scale := w.scale
|
||||
w.mu.Unlock()
|
||||
pos := f32.Point{
|
||||
X: float32(x) * scale,
|
||||
Y: float32(y) * scale,
|
||||
@@ -412,8 +494,8 @@ func (w *window) pointerEvent(typ pointer.Type, dx, dy float32, e js.Value) {
|
||||
if jbtns&4 != 0 {
|
||||
btns |= pointer.ButtonTertiary
|
||||
}
|
||||
w.w.Event(pointer.Event{
|
||||
Type: typ,
|
||||
w.processEvent(pointer.Event{
|
||||
Kind: kind,
|
||||
Source: pointer.Mouse,
|
||||
Buttons: btns,
|
||||
Position: pos,
|
||||
@@ -439,22 +521,9 @@ func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.F
|
||||
return jsf
|
||||
}
|
||||
|
||||
func (w *window) animCallback() {
|
||||
w.mu.Lock()
|
||||
anim := w.animating
|
||||
w.animRequested = anim
|
||||
if anim {
|
||||
w.requestAnimationFrame.Invoke(w.redraw)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
if anim {
|
||||
w.draw(false)
|
||||
}
|
||||
}
|
||||
func (w *window) EditorStateChanged(old, new editorState) {}
|
||||
|
||||
func (w *window) SetAnimating(anim bool) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
w.animating = anim
|
||||
if anim && !w.animRequested {
|
||||
w.animRequested = true
|
||||
@@ -472,78 +541,140 @@ func (w *window) ReadClipboard() {
|
||||
w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback)
|
||||
}
|
||||
|
||||
func (w *window) WriteClipboard(s string) {
|
||||
func (w *window) WriteClipboard(mime string, s []byte) {
|
||||
if w.clipboard.IsUndefined() {
|
||||
return
|
||||
}
|
||||
if w.clipboard.Get("writeText").IsUndefined() {
|
||||
return
|
||||
}
|
||||
w.clipboard.Call("writeText", s)
|
||||
w.clipboard.Call("writeText", string(s))
|
||||
}
|
||||
|
||||
func (w *window) Option(opts *Options) {
|
||||
if o := opts.WindowMode; o != nil {
|
||||
w.windowMode(*o)
|
||||
func (w *window) Configure(options []Option) {
|
||||
prev := w.config
|
||||
cnf := w.config
|
||||
cnf.apply(unit.Metric{}, options)
|
||||
// Decorations are never disabled.
|
||||
cnf.Decorated = true
|
||||
|
||||
if prev.Title != cnf.Title {
|
||||
w.config.Title = cnf.Title
|
||||
w.document.Set("title", cnf.Title)
|
||||
}
|
||||
if prev.Mode != cnf.Mode {
|
||||
w.windowMode(cnf.Mode)
|
||||
}
|
||||
if prev.NavigationColor != cnf.NavigationColor {
|
||||
w.config.NavigationColor = cnf.NavigationColor
|
||||
w.navigationColor(cnf.NavigationColor)
|
||||
}
|
||||
if prev.Orientation != cnf.Orientation {
|
||||
w.config.Orientation = cnf.Orientation
|
||||
w.orientation(cnf.Orientation)
|
||||
}
|
||||
if cnf.Decorated != prev.Decorated {
|
||||
w.config.Decorated = cnf.Decorated
|
||||
}
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
func (w *window) SetCursor(name pointer.CursorName) {
|
||||
func (w *window) Perform(system.Action) {}
|
||||
|
||||
var webCursor = [...]string{
|
||||
pointer.CursorDefault: "default",
|
||||
pointer.CursorNone: "none",
|
||||
pointer.CursorText: "text",
|
||||
pointer.CursorVerticalText: "vertical-text",
|
||||
pointer.CursorPointer: "pointer",
|
||||
pointer.CursorCrosshair: "crosshair",
|
||||
pointer.CursorAllScroll: "all-scroll",
|
||||
pointer.CursorColResize: "col-resize",
|
||||
pointer.CursorRowResize: "row-resize",
|
||||
pointer.CursorGrab: "grab",
|
||||
pointer.CursorGrabbing: "grabbing",
|
||||
pointer.CursorNotAllowed: "not-allowed",
|
||||
pointer.CursorWait: "wait",
|
||||
pointer.CursorProgress: "progress",
|
||||
pointer.CursorNorthWestResize: "nw-resize",
|
||||
pointer.CursorNorthEastResize: "ne-resize",
|
||||
pointer.CursorSouthWestResize: "sw-resize",
|
||||
pointer.CursorSouthEastResize: "se-resize",
|
||||
pointer.CursorNorthSouthResize: "ns-resize",
|
||||
pointer.CursorEastWestResize: "ew-resize",
|
||||
pointer.CursorWestResize: "w-resize",
|
||||
pointer.CursorEastResize: "e-resize",
|
||||
pointer.CursorNorthResize: "n-resize",
|
||||
pointer.CursorSouthResize: "s-resize",
|
||||
pointer.CursorNorthEastSouthWestResize: "nesw-resize",
|
||||
pointer.CursorNorthWestSouthEastResize: "nwse-resize",
|
||||
}
|
||||
|
||||
func (w *window) SetCursor(cursor pointer.Cursor) {
|
||||
style := w.cnv.Get("style")
|
||||
style.Set("cursor", string(name))
|
||||
style.Set("cursor", webCursor[cursor])
|
||||
}
|
||||
|
||||
func (w *window) ShowTextInput(show bool) {
|
||||
// Run in a goroutine to avoid a deadlock if the
|
||||
// focus change result in an event.
|
||||
go func() {
|
||||
if show {
|
||||
w.focus()
|
||||
} else {
|
||||
w.blur()
|
||||
}
|
||||
}()
|
||||
if show {
|
||||
w.focus()
|
||||
} else {
|
||||
w.blur()
|
||||
}
|
||||
}
|
||||
|
||||
// Close the window. Not implemented for js.
|
||||
func (w *window) Close() {}
|
||||
func (w *window) SetInputHint(mode key.InputHint) {
|
||||
w.keyboard(mode)
|
||||
}
|
||||
|
||||
func (w *window) resize() {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
w.scale = float32(w.window.Get("devicePixelRatio").Float())
|
||||
|
||||
rect := w.cnv.Call("getBoundingClientRect")
|
||||
w.size.X = float32(rect.Get("width").Float()) * w.scale
|
||||
w.size.Y = float32(rect.Get("height").Float()) * w.scale
|
||||
|
||||
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
|
||||
w.inset.X = w.size.X - float32(vx.Float())*w.scale
|
||||
w.inset.Y = w.size.Y - float32(vy.Float())*w.scale
|
||||
size := image.Point{
|
||||
X: int(float32(rect.Get("width").Float()) * w.scale),
|
||||
Y: int(float32(rect.Get("height").Float()) * w.scale),
|
||||
}
|
||||
if size != w.config.Size {
|
||||
w.config.Size = size
|
||||
w.processEvent(ConfigEvent{Config: w.config})
|
||||
}
|
||||
|
||||
if w.size.X == 0 || w.size.Y == 0 {
|
||||
if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
|
||||
w.inset.X = float32(w.config.Size.X) - float32(vx.Float())*w.scale
|
||||
w.inset.Y = float32(w.config.Size.Y) - float32(vy.Float())*w.scale
|
||||
}
|
||||
|
||||
if w.config.Size.X == 0 || w.config.Size.Y == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
w.cnv.Set("width", int(w.size.X+.5))
|
||||
w.cnv.Set("height", int(w.size.Y+.5))
|
||||
w.cnv.Set("width", w.config.Size.X)
|
||||
w.cnv.Set("height", w.config.Size.Y)
|
||||
}
|
||||
|
||||
func (w *window) draw(sync bool) {
|
||||
width, height, insets, metric := w.config()
|
||||
if metric == (unit.Metric{}) || width == 0 || height == 0 {
|
||||
if w.contextStatus == contextStatusLost {
|
||||
return
|
||||
}
|
||||
anim := w.animating
|
||||
w.animRequested = anim
|
||||
if anim {
|
||||
w.requestAnimationFrame.Invoke(w.redraw)
|
||||
} else if !sync {
|
||||
return
|
||||
}
|
||||
size, insets, metric := w.getConfig()
|
||||
if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
w.w.Event(FrameEvent{
|
||||
FrameEvent: system.FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: image.Point{
|
||||
X: width,
|
||||
Y: height,
|
||||
},
|
||||
w.processEvent(frameEvent{
|
||||
FrameEvent: FrameEvent{
|
||||
Now: time.Now(),
|
||||
Size: size,
|
||||
Insets: insets,
|
||||
Metric: metric,
|
||||
},
|
||||
@@ -551,13 +682,12 @@ func (w *window) draw(sync bool) {
|
||||
})
|
||||
}
|
||||
|
||||
func (w *window) config() (int, int, system.Insets, unit.Metric) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
return int(w.size.X + .5), int(w.size.Y + .5), system.Insets{
|
||||
Bottom: unit.Px(w.inset.Y),
|
||||
Right: unit.Px(w.inset.X),
|
||||
func (w *window) getConfig() (image.Point, Insets, unit.Metric) {
|
||||
invscale := unit.Dp(1. / w.scale)
|
||||
return image.Pt(w.config.Size.X, w.config.Size.Y),
|
||||
Insets{
|
||||
Bottom: unit.Dp(w.inset.Y) * invscale,
|
||||
Right: unit.Dp(w.inset.X) * invscale,
|
||||
}, unit.Metric{
|
||||
PxPerDp: w.scale,
|
||||
PxPerSp: w.scale,
|
||||
@@ -567,28 +697,57 @@ func (w *window) config() (int, int, system.Insets, unit.Metric) {
|
||||
func (w *window) windowMode(mode WindowMode) {
|
||||
switch mode {
|
||||
case Windowed:
|
||||
if fs := w.document.Get("fullscreenElement"); !fs.Truthy() {
|
||||
if !w.document.Get("fullscreenElement").Truthy() {
|
||||
return // Browser is already Windowed.
|
||||
}
|
||||
if !w.document.Get("exitFullscreen").Truthy() {
|
||||
return // Browser doesn't support such feature.
|
||||
}
|
||||
w.document.Call("exitFullscreen")
|
||||
w.config.Mode = Windowed
|
||||
case Fullscreen:
|
||||
elem := w.document.Get("documentElement")
|
||||
if !elem.Get("requestFullscreen").Truthy() {
|
||||
return // Browser doesn't support such feature.
|
||||
}
|
||||
elem.Call("requestFullscreen")
|
||||
w.config.Mode = Fullscreen
|
||||
}
|
||||
}
|
||||
|
||||
func Main() {
|
||||
func (w *window) orientation(mode Orientation) {
|
||||
if j := w.screenOrientation; !j.Truthy() || !j.Get("unlock").Truthy() || !j.Get("lock").Truthy() {
|
||||
return // Browser don't support Screen Orientation API.
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case AnyOrientation:
|
||||
w.screenOrientation.Call("unlock")
|
||||
case LandscapeOrientation:
|
||||
w.screenOrientation.Call("lock", "landscape").Call("then", w.redraw)
|
||||
case PortraitOrientation:
|
||||
w.screenOrientation.Call("lock", "portrait").Call("then", w.redraw)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *window) navigationColor(c color.NRGBA) {
|
||||
theme := w.head.Call("querySelector", `meta[name="theme-color"]`)
|
||||
if !theme.Truthy() {
|
||||
theme = w.document.Call("createElement", "meta")
|
||||
theme.Set("name", "theme-color")
|
||||
w.head.Call("appendChild", theme)
|
||||
}
|
||||
rgba := f32color.NRGBAToRGBA(c)
|
||||
theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
|
||||
}
|
||||
|
||||
func osMain() {
|
||||
select {}
|
||||
}
|
||||
|
||||
func translateKey(k string) (string, bool) {
|
||||
var n string
|
||||
func translateKey(k string) (key.Name, bool) {
|
||||
var n key.Name
|
||||
|
||||
switch k {
|
||||
case "ArrowUp":
|
||||
n = key.NameUpArrow
|
||||
@@ -618,15 +777,51 @@ func translateKey(k string) (string, bool) {
|
||||
n = key.NameTab
|
||||
case " ":
|
||||
n = key.NameSpace
|
||||
case "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12":
|
||||
n = k
|
||||
case "F1":
|
||||
n = key.NameF1
|
||||
case "F2":
|
||||
n = key.NameF2
|
||||
case "F3":
|
||||
n = key.NameF3
|
||||
case "F4":
|
||||
n = key.NameF4
|
||||
case "F5":
|
||||
n = key.NameF5
|
||||
case "F6":
|
||||
n = key.NameF6
|
||||
case "F7":
|
||||
n = key.NameF7
|
||||
case "F8":
|
||||
n = key.NameF8
|
||||
case "F9":
|
||||
n = key.NameF9
|
||||
case "F10":
|
||||
n = key.NameF10
|
||||
case "F11":
|
||||
n = key.NameF11
|
||||
case "F12":
|
||||
n = key.NameF12
|
||||
case "Control":
|
||||
n = key.NameCtrl
|
||||
case "Shift":
|
||||
n = key.NameShift
|
||||
case "Alt":
|
||||
n = key.NameAlt
|
||||
case "OS":
|
||||
n = key.NameSuper
|
||||
default:
|
||||
r, s := utf8.DecodeRuneInString(k)
|
||||
// If there is exactly one printable character, return that.
|
||||
if s == len(k) && unicode.IsPrint(r) {
|
||||
return strings.ToUpper(k), true
|
||||
return key.Name(strings.ToUpper(k)), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
func (JSViewEvent) implementsViewEvent() {}
|
||||
func (JSViewEvent) ImplementsEvent() {}
|
||||
func (j JSViewEvent) Valid() bool {
|
||||
return !(j.Element.IsNull() || j.Element.IsUndefined())
|
||||
}
|
||||
1172
pkg/gel/gio/app/os_macos.go
Normal file
1172
pkg/gel/gio/app/os_macos.go
Normal file
File diff suppressed because it is too large
Load Diff
474
pkg/gel/gio/app/os_macos.m
Normal file
474
pkg/gel/gio/app/os_macos.m
Normal file
@@ -0,0 +1,474 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build darwin,!ios
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
#include "_cgo_export.h"
|
||||
|
||||
__attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWithTrans);
|
||||
|
||||
@interface GioAppDelegate : NSObject<NSApplicationDelegate>
|
||||
@end
|
||||
|
||||
@interface GioWindowDelegate : NSObject<NSWindowDelegate>
|
||||
@end
|
||||
|
||||
@interface GioView : NSView <CALayerDelegate,NSTextInputClient>
|
||||
@property uintptr_t handle;
|
||||
@property BOOL presentWithTrans;
|
||||
@end
|
||||
|
||||
@implementation GioWindowDelegate
|
||||
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onDraw(view.handle);
|
||||
}
|
||||
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
gio_onChangeScreen(view.handle, dispID);
|
||||
}
|
||||
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
if ([window firstResponder] == view) {
|
||||
gio_onFocus(view.handle, 1);
|
||||
}
|
||||
}
|
||||
- (void)windowDidResignKey:(NSNotification *)notification {
|
||||
NSWindow *window = (NSWindow *)[notification object];
|
||||
GioView *view = (GioView *)window.contentView;
|
||||
if ([window firstResponder] == view) {
|
||||
gio_onFocus(view.handle, 0);
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
|
||||
NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
|
||||
if (!event.hasPreciseScrollingDeltas) {
|
||||
// dx and dy are in rows and columns.
|
||||
dx *= 10;
|
||||
dy *= 10;
|
||||
}
|
||||
// Origin is in the lower left corner. Convert to upper left.
|
||||
CGFloat height = view.bounds.size.height;
|
||||
gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
|
||||
}
|
||||
|
||||
@implementation GioView
|
||||
- (void)setFrameSize:(NSSize)newSize {
|
||||
[super setFrameSize:newSize];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
// drawRect is called when OpenGL is used, displayLayer otherwise.
|
||||
// Don't know why.
|
||||
- (void)drawRect:(NSRect)r {
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (void)displayLayer:(CALayer *)layer {
|
||||
layer.contentsScale = self.window.backingScaleFactor;
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (CALayer *)makeBackingLayer {
|
||||
CALayer *layer = gio_layerFactory(self.presentWithTrans);
|
||||
layer.delegate = self;
|
||||
return layer;
|
||||
}
|
||||
- (void)viewDidMoveToWindow {
|
||||
gio_onAttached(self.handle, self.window != nil ? 1 : 0);
|
||||
}
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)mouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)rightMouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)rightMouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)otherMouseDown:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_DOWN, 0, 0);
|
||||
}
|
||||
- (void)otherMouseUp:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_UP, 0, 0);
|
||||
}
|
||||
- (void)mouseMoved:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)mouseDragged:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)rightMouseDragged:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)otherMouseDragged:(NSEvent *)event {
|
||||
handleMouse(self, event, MOUSE_MOVE, 0, 0);
|
||||
}
|
||||
- (void)scrollWheel:(NSEvent *)event {
|
||||
CGFloat dx = -event.scrollingDeltaX;
|
||||
CGFloat dy = -event.scrollingDeltaY;
|
||||
handleMouse(self, event, MOUSE_SCROLL, dx, dy);
|
||||
}
|
||||
- (void)keyDown:(NSEvent *)event {
|
||||
NSString *keys = [event charactersIgnoringModifiers];
|
||||
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
|
||||
}
|
||||
- (void)flagsChanged:(NSEvent *)event {
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
gio_onFlagsChanged(self.handle, [event modifierFlags]);
|
||||
}
|
||||
- (void)keyUp:(NSEvent *)event {
|
||||
NSString *keys = [event charactersIgnoringModifiers];
|
||||
gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
|
||||
}
|
||||
- (void)insertText:(id)string {
|
||||
gio_onText(self.handle, (__bridge CFTypeRef)string);
|
||||
}
|
||||
- (void)doCommandBySelector:(SEL)action {
|
||||
if (!gio_onCommandBySelector(self.handle)) {
|
||||
[super doCommandBySelector:action];
|
||||
}
|
||||
}
|
||||
- (BOOL)hasMarkedText {
|
||||
int res = gio_hasMarkedText(self.handle);
|
||||
return res ? YES : NO;
|
||||
}
|
||||
- (NSRange)markedRange {
|
||||
return gio_markedRange(self.handle);
|
||||
}
|
||||
- (NSRange)selectedRange {
|
||||
return gio_selectedRange(self.handle);
|
||||
}
|
||||
- (void)unmarkText {
|
||||
gio_unmarkText(self.handle);
|
||||
}
|
||||
- (void)setMarkedText:(id)string
|
||||
selectedRange:(NSRange)selRange
|
||||
replacementRange:(NSRange)replaceRange {
|
||||
NSString *str;
|
||||
// string is either an NSAttributedString or an NSString.
|
||||
if ([string isKindOfClass:[NSAttributedString class]]) {
|
||||
str = [string string];
|
||||
} else {
|
||||
str = string;
|
||||
}
|
||||
gio_setMarkedText(self.handle, (__bridge CFTypeRef)str, selRange, replaceRange);
|
||||
}
|
||||
- (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
|
||||
return nil;
|
||||
}
|
||||
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
||||
actualRange:(NSRangePointer)actualRange {
|
||||
NSString *str = CFBridgingRelease(gio_substringForProposedRange(self.handle, range, actualRange));
|
||||
return [[NSAttributedString alloc] initWithString:str attributes:nil];
|
||||
}
|
||||
- (void)insertText:(id)string
|
||||
replacementRange:(NSRange)replaceRange {
|
||||
NSString *str;
|
||||
// string is either an NSAttributedString or an NSString.
|
||||
if ([string isKindOfClass:[NSAttributedString class]]) {
|
||||
str = [string string];
|
||||
} else {
|
||||
str = string;
|
||||
}
|
||||
gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange);
|
||||
}
|
||||
- (NSUInteger)characterIndexForPoint:(NSPoint)p {
|
||||
return gio_characterIndexForPoint(self.handle, p);
|
||||
}
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)rng
|
||||
actualRange:(NSRangePointer)actual {
|
||||
NSRect r = gio_firstRectForCharacterRange(self.handle, rng, actual);
|
||||
r = [self convertRect:r toView:nil];
|
||||
return [[self window] convertRectToScreen:r];
|
||||
}
|
||||
- (void)applicationWillUnhide:(NSNotification *)notification {
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (void)applicationDidHide:(NSNotification *)notification {
|
||||
gio_onDraw(self.handle);
|
||||
}
|
||||
- (void)dealloc {
|
||||
gio_onDestroy(self.handle);
|
||||
}
|
||||
- (BOOL) becomeFirstResponder {
|
||||
gio_onFocus(self.handle, 1);
|
||||
return [super becomeFirstResponder];
|
||||
}
|
||||
- (BOOL) resignFirstResponder {
|
||||
gio_onFocus(self.handle, 0);
|
||||
return [super resignFirstResponder];
|
||||
}
|
||||
@end
|
||||
|
||||
// Delegates are weakly referenced from their peers. Nothing
|
||||
// else holds a strong reference to our window delegate, so
|
||||
// keep a single global reference instead.
|
||||
static GioWindowDelegate *globalWindowDel;
|
||||
|
||||
static CVReturn displayLinkCallback(CVDisplayLinkRef dl, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *handle) {
|
||||
gio_onFrameCallback(dl);
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
|
||||
CFTypeRef gio_createDisplayLink(void) {
|
||||
CVDisplayLinkRef dl;
|
||||
CVDisplayLinkCreateWithActiveCGDisplays(&dl);
|
||||
CVDisplayLinkSetOutputCallback(dl, displayLinkCallback, nil);
|
||||
return dl;
|
||||
}
|
||||
|
||||
int gio_startDisplayLink(CFTypeRef dl) {
|
||||
return CVDisplayLinkStart((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
int gio_stopDisplayLink(CFTypeRef dl) {
|
||||
return CVDisplayLinkStop((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
void gio_releaseDisplayLink(CFTypeRef dl) {
|
||||
CVDisplayLinkRelease((CVDisplayLinkRef)dl);
|
||||
}
|
||||
|
||||
void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) {
|
||||
CVDisplayLinkSetCurrentCGDisplay((CVDisplayLinkRef)dl, (CGDirectDisplayID)did);
|
||||
}
|
||||
|
||||
void gio_hideCursor() {
|
||||
@autoreleasepool {
|
||||
[NSCursor hide];
|
||||
}
|
||||
}
|
||||
|
||||
void gio_showCursor() {
|
||||
@autoreleasepool {
|
||||
[NSCursor unhide];
|
||||
}
|
||||
}
|
||||
|
||||
// some cursors are not public, this tries to use a private cursor
|
||||
// and uses fallback when the use of private cursor fails.
|
||||
static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
|
||||
if ([NSCursor respondsToSelector:cursorName]) {
|
||||
id object = [NSCursor performSelector:cursorName];
|
||||
if ([object isKindOfClass:[NSCursor class]]) {
|
||||
[(NSCursor*)object set];
|
||||
return;
|
||||
}
|
||||
}
|
||||
[fallback set];
|
||||
}
|
||||
|
||||
void gio_setCursor(NSUInteger curID) {
|
||||
@autoreleasepool {
|
||||
switch (curID) {
|
||||
case 0: // pointer.CursorDefault
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
// case 1: // pointer.CursorNone
|
||||
case 2: // pointer.CursorText
|
||||
[NSCursor.IBeamCursor set];
|
||||
break;
|
||||
case 3: // pointer.CursorVerticalText
|
||||
[NSCursor.IBeamCursorForVerticalLayout set];
|
||||
break;
|
||||
case 4: // pointer.CursorPointer
|
||||
[NSCursor.pointingHandCursor set];
|
||||
break;
|
||||
case 5: // pointer.CursorCrosshair
|
||||
[NSCursor.crosshairCursor set];
|
||||
break;
|
||||
case 6: // pointer.CursorAllScroll
|
||||
// For some reason, using _moveCursor fails on Monterey.
|
||||
// trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
case 7: // pointer.CursorColResize
|
||||
[NSCursor.resizeLeftRightCursor set];
|
||||
break;
|
||||
case 8: // pointer.CursorRowResize
|
||||
[NSCursor.resizeUpDownCursor set];
|
||||
break;
|
||||
case 9: // pointer.CursorGrab
|
||||
[NSCursor.openHandCursor set];
|
||||
break;
|
||||
case 10: // pointer.CursorGrabbing
|
||||
[NSCursor.closedHandCursor set];
|
||||
break;
|
||||
case 11: // pointer.CursorNotAllowed
|
||||
[NSCursor.operationNotAllowedCursor set];
|
||||
break;
|
||||
case 12: // pointer.CursorWait
|
||||
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
break;
|
||||
case 13: // pointer.CursorProgress
|
||||
trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
|
||||
break;
|
||||
case 14: // pointer.CursorNorthWestResize
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 15: // pointer.CursorNorthEastResize
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 16: // pointer.CursorSouthWestResize
|
||||
trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 17: // pointer.CursorSouthEastResize
|
||||
trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 18: // pointer.CursorNorthSouthResize
|
||||
[NSCursor.resizeUpDownCursor set];
|
||||
break;
|
||||
case 19: // pointer.CursorEastWestResize
|
||||
[NSCursor.resizeLeftRightCursor set];
|
||||
break;
|
||||
case 20: // pointer.CursorWestResize
|
||||
[NSCursor.resizeLeftCursor set];
|
||||
break;
|
||||
case 21: // pointer.CursorEastResize
|
||||
[NSCursor.resizeRightCursor set];
|
||||
break;
|
||||
case 22: // pointer.CursorNorthResize
|
||||
[NSCursor.resizeUpCursor set];
|
||||
break;
|
||||
case 23: // pointer.CursorSouthResize
|
||||
[NSCursor.resizeDownCursor set];
|
||||
break;
|
||||
case 24: // pointer.CursorNorthEastSouthWestResize
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
case 25: // pointer.CursorNorthWestSouthEastResize
|
||||
trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
|
||||
break;
|
||||
default:
|
||||
[NSCursor.arrowCursor set];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height) {
|
||||
@autoreleasepool {
|
||||
NSRect rect = NSMakeRect(0, 0, width, height);
|
||||
NSUInteger styleMask = NSTitledWindowMask |
|
||||
NSResizableWindowMask |
|
||||
NSMiniaturizableWindowMask |
|
||||
NSClosableWindowMask;
|
||||
|
||||
NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
[window setAcceptsMouseMovedEvents:YES];
|
||||
NSView *view = (__bridge NSView *)viewRef;
|
||||
[window setContentView:view];
|
||||
window.delegate = globalWindowDel;
|
||||
return (__bridge_retained CFTypeRef)window;
|
||||
}
|
||||
}
|
||||
|
||||
CFTypeRef gio_createView(int presentWithTrans) {
|
||||
@autoreleasepool {
|
||||
NSRect frame = NSMakeRect(0, 0, 0, 0);
|
||||
GioView* view = [[GioView alloc] initWithFrame:frame];
|
||||
view.presentWithTrans = presentWithTrans ? YES : NO;
|
||||
view.wantsLayer = YES;
|
||||
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||
selector:@selector(applicationWillUnhide:)
|
||||
name:NSApplicationWillUnhideNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:view
|
||||
selector:@selector(applicationDidHide:)
|
||||
name:NSApplicationDidHideNotification
|
||||
object:nil];
|
||||
return CFBridgingRetain(view);
|
||||
}
|
||||
}
|
||||
|
||||
void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
|
||||
@autoreleasepool {
|
||||
GioView *v = (__bridge GioView *)viewRef;
|
||||
v.handle = handle;
|
||||
}
|
||||
}
|
||||
|
||||
@implementation GioAppDelegate
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
}
|
||||
@end
|
||||
|
||||
void gio_main() {
|
||||
@autoreleasepool {
|
||||
[NSApplication sharedApplication];
|
||||
GioAppDelegate *del = [[GioAppDelegate alloc] init];
|
||||
[NSApp setDelegate:del];
|
||||
|
||||
NSMenuItem *mainMenu = [NSMenuItem new];
|
||||
|
||||
NSMenu *menu = [NSMenu new];
|
||||
NSMenuItem *hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide"
|
||||
action:@selector(hide:)
|
||||
keyEquivalent:@"h"];
|
||||
[menu addItem:hideMenuItem];
|
||||
NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
|
||||
action:@selector(terminate:)
|
||||
keyEquivalent:@"q"];
|
||||
[menu addItem:quitMenuItem];
|
||||
[mainMenu setSubmenu:menu];
|
||||
NSMenu *menuBar = [NSMenu new];
|
||||
[menuBar addItem:mainMenu];
|
||||
[NSApp setMainMenu:menuBar];
|
||||
|
||||
globalWindowDel = [[GioWindowDelegate alloc] init];
|
||||
|
||||
[NSApp run];
|
||||
}
|
||||
}
|
||||
|
||||
@interface AppListener : NSObject
|
||||
@end
|
||||
|
||||
static AppListener *appListener;
|
||||
|
||||
@implementation AppListener
|
||||
- (void)launchFinished:(NSNotification *)notification {
|
||||
appListener = nil;
|
||||
gio_onFinishLaunching();
|
||||
}
|
||||
@end
|
||||
|
||||
void gio_init() {
|
||||
@autoreleasepool {
|
||||
appListener = [[AppListener alloc] init];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:appListener
|
||||
selector:@selector(launchFinished:)
|
||||
name:NSApplicationDidFinishLaunchingNotification
|
||||
object:nil];
|
||||
}
|
||||
}
|
||||
99
pkg/gel/gio/app/os_unix.go
Normal file
99
pkg/gel/gio/app/os_unix.go
Normal file
@@ -0,0 +1,99 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
//go:build (linux && !android) || freebsd || openbsd
|
||||
// +build linux,!android freebsd openbsd
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"github.com/p9c/p9/pkg/gel/gio/io/pointer"
|
||||
)
|
||||
|
||||
type X11ViewEvent struct {
|
||||
// Display is a pointer to the X11 Display created by XOpenDisplay.
|
||||
Display unsafe.Pointer
|
||||
// Window is the X11 window ID as returned by XCreateWindow.
|
||||
Window uintptr
|
||||
}
|
||||
|
||||
func (X11ViewEvent) implementsViewEvent() {}
|
||||
func (X11ViewEvent) ImplementsEvent() {}
|
||||
func (x X11ViewEvent) Valid() bool {
|
||||
return x != (X11ViewEvent{})
|
||||
}
|
||||
|
||||
type WaylandViewEvent struct {
|
||||
// Display is the *wl_display returned by wl_display_connect.
|
||||
Display unsafe.Pointer
|
||||
// Surface is the *wl_surface returned by wl_compositor_create_surface.
|
||||
Surface unsafe.Pointer
|
||||
}
|
||||
|
||||
func (WaylandViewEvent) implementsViewEvent() {}
|
||||
func (WaylandViewEvent) ImplementsEvent() {}
|
||||
func (w WaylandViewEvent) Valid() bool {
|
||||
return w != (WaylandViewEvent{})
|
||||
}
|
||||
|
||||
func osMain() {
|
||||
select {}
|
||||
}
|
||||
|
||||
type windowDriver func(*callbacks, []Option) error
|
||||
|
||||
// Instead of creating files with build tags for each combination of wayland +/- x11
|
||||
// let each driver initialize these variables with their own version of createWindow.
|
||||
var wlDriver, x11Driver windowDriver
|
||||
|
||||
func newWindow(window *callbacks, options []Option) {
|
||||
var errFirst error
|
||||
for _, d := range []windowDriver{wlDriver, x11Driver} {
|
||||
if d == nil {
|
||||
continue
|
||||
}
|
||||
err := d(window, options)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if errFirst == nil {
|
||||
errFirst = err
|
||||
}
|
||||
}
|
||||
if errFirst == nil {
|
||||
errFirst = errors.New("app: no window driver available")
|
||||
}
|
||||
window.ProcessEvent(DestroyEvent{Err: errFirst})
|
||||
}
|
||||
|
||||
// xCursor contains mapping from pointer.Cursor to XCursor.
|
||||
var xCursor = [...]string{
|
||||
pointer.CursorDefault: "left_ptr",
|
||||
pointer.CursorNone: "",
|
||||
pointer.CursorText: "xterm",
|
||||
pointer.CursorVerticalText: "vertical-text",
|
||||
pointer.CursorPointer: "hand2",
|
||||
pointer.CursorCrosshair: "crosshair",
|
||||
pointer.CursorAllScroll: "fleur",
|
||||
pointer.CursorColResize: "sb_h_double_arrow",
|
||||
pointer.CursorRowResize: "sb_v_double_arrow",
|
||||
pointer.CursorGrab: "hand1",
|
||||
pointer.CursorGrabbing: "move",
|
||||
pointer.CursorNotAllowed: "crossed_circle",
|
||||
pointer.CursorWait: "watch",
|
||||
pointer.CursorProgress: "left_ptr_watch",
|
||||
pointer.CursorNorthWestResize: "top_left_corner",
|
||||
pointer.CursorNorthEastResize: "top_right_corner",
|
||||
pointer.CursorSouthWestResize: "bottom_left_corner",
|
||||
pointer.CursorSouthEastResize: "bottom_right_corner",
|
||||
pointer.CursorNorthSouthResize: "sb_v_double_arrow",
|
||||
pointer.CursorEastWestResize: "sb_h_double_arrow",
|
||||
pointer.CursorWestResize: "left_side",
|
||||
pointer.CursorEastResize: "right_side",
|
||||
pointer.CursorNorthResize: "top_side",
|
||||
pointer.CursorSouthResize: "bottom_side",
|
||||
pointer.CursorNorthEastSouthWestResize: "fd_double_arrow",
|
||||
pointer.CursorNorthWestSouthEastResize: "bd_double_arrow",
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// +build linux,!android,!nowayland freebsd
|
||||
//go:build ((linux && !android) || freebsd) && !nowayland
|
||||
// +build linux,!android freebsd
|
||||
// +build !nowayland
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include "wayland_xdg_shell.h"
|
||||
#include "wayland_xdg_decoration.h"
|
||||
#include "wayland_text_input.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
@@ -27,6 +30,10 @@ const struct xdg_toplevel_listener gio_xdg_toplevel_listener = {
|
||||
.close = gio_onToplevelClose,
|
||||
};
|
||||
|
||||
const struct zxdg_toplevel_decoration_v1_listener gio_zxdg_toplevel_decoration_v1_listener = {
|
||||
.configure = gio_onToplevelDecorationConfigure,
|
||||
};
|
||||
|
||||
static void xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *wm, uint32_t serial) {
|
||||
xdg_wm_base_pong(wm, serial);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user