init
This commit is contained in:
commit
16c3a01d2b
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/naive
|
||||
.idea/
|
||||
.vs/
|
||||
.vscode/
|
26
LICENSE
Normal file
26
LICENSE
Normal file
@ -0,0 +1,26 @@
|
||||
MIT NON-AI License
|
||||
|
||||
Copyright (c) 2025, Frank Mayer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of the software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
In addition, the following restrictions apply:
|
||||
|
||||
1. The Software and any modifications made to it may not be used for the purpose of training or improving machine learning algorithms,
|
||||
including but not limited to artificial intelligence, natural language processing, or data mining. This condition applies to any derivatives,
|
||||
modifications, or updates based on the Software code. Any usage of the Software in an AI-training dataset is considered a breach of this License.
|
||||
|
||||
2. The Software may not be included in any dataset used for training or improving machine learning algorithms,
|
||||
including but not limited to artificial intelligence, natural language processing, or data mining.
|
||||
|
||||
3. Any person or organization found to be in violation of these restrictions will be subject to legal action and may be held liable
|
||||
for any damages resulting from such use.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Naive
|
||||
|
||||
Abstraction for platform native UI, similar to React Native.
|
6
button.go
Normal file
6
button.go
Normal file
@ -0,0 +1,6 @@
|
||||
package naive
|
||||
|
||||
type ButtonView struct {
|
||||
Content TextContent
|
||||
OnClick func()
|
||||
}
|
18
button_darwin.go
Normal file
18
button_darwin.go
Normal file
@ -0,0 +1,18 @@
|
||||
package naive
|
||||
|
||||
import (
|
||||
"github.com/progrium/darwinkit/helper/action"
|
||||
"github.com/progrium/darwinkit/macos/appkit"
|
||||
"github.com/progrium/darwinkit/objc"
|
||||
)
|
||||
|
||||
func (bv ButtonView) toNative() appkit.IView {
|
||||
nb := appkit.NewButtonWithTitle(bv.Content.Value())
|
||||
if eff, ok := bv.Content.(*Effect[string]); ok {
|
||||
eff.OnChange(nb.SetTitle)
|
||||
}
|
||||
action.Set(nb, func(sender objc.Object) {
|
||||
bv.OnClick()
|
||||
})
|
||||
return nb
|
||||
}
|
41
effect.go
Normal file
41
effect.go
Normal file
@ -0,0 +1,41 @@
|
||||
package naive
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Effect[T any] struct {
|
||||
value T
|
||||
listeners []func(newValue T)
|
||||
}
|
||||
|
||||
var effects []interface{ clearListeners() }
|
||||
|
||||
func clearAllEffectListeners() {
|
||||
for _, eff := range effects {
|
||||
eff.clearListeners()
|
||||
}
|
||||
}
|
||||
|
||||
func UseEffect[T any](value T) *Effect[T] {
|
||||
eff := &Effect[T]{value, nil}
|
||||
effects = append(effects, eff)
|
||||
return eff
|
||||
}
|
||||
|
||||
func (eff *Effect[T]) Value() string {
|
||||
return fmt.Sprintf("%v", eff.value)
|
||||
}
|
||||
|
||||
func (eff *Effect[T]) SetValue(newValue T) {
|
||||
eff.value = newValue
|
||||
for _, listener := range eff.listeners {
|
||||
listener(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (eff *Effect[T]) OnChange(listener func(newValue T)) {
|
||||
eff.listeners = append(eff.listeners, listener)
|
||||
}
|
||||
|
||||
func (eff *Effect[T]) clearListeners() {
|
||||
eff.listeners = eff.listeners[:0]
|
||||
}
|
11
example/go.mod
Normal file
11
example/go.mod
Normal file
@ -0,0 +1,11 @@
|
||||
module example
|
||||
|
||||
go 1.23.3
|
||||
|
||||
require git.frankmayer.dev/tsukinoko-kun/naive v0.0.0-00010101000000-000000000000
|
||||
|
||||
require github.com/progrium/darwinkit v0.5.0 // indirect
|
||||
|
||||
replace git.frankmayer.dev/tsukinoko-kun/naive => ../
|
||||
|
||||
exclude git.frankmayer.dev/tsukinoko-kun/naive v0.0.0
|
4
example/go.sum
Normal file
4
example/go.sum
Normal file
@ -0,0 +1,4 @@
|
||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
|
||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/progrium/darwinkit v0.5.0 h1:SwchcMbTOG1py3CQsINmGlsRmYKdlFrbnv3dE4aXA0s=
|
||||
github.com/progrium/darwinkit v0.5.0/go.mod h1:PxQhZuftnALLkCVaR8LaHtUOfoo4pm8qUDG+3C/sXNs=
|
54
example/main.go
Normal file
54
example/main.go
Normal file
@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.frankmayer.dev/tsukinoko-kun/naive"
|
||||
)
|
||||
|
||||
func main() {
|
||||
Text1 := naive.UseEffect("foo")
|
||||
|
||||
views := make([]naive.View, 2)
|
||||
|
||||
views[0] = naive.StackView{
|
||||
Children: []naive.View{
|
||||
naive.TextView{
|
||||
Content: Text1,
|
||||
},
|
||||
naive.ButtonView{
|
||||
Content: naive.StringTextContent("Back"),
|
||||
OnClick: func() {
|
||||
naive.SetView(views[1])
|
||||
},
|
||||
},
|
||||
},
|
||||
Orientation: naive.LayoutOrientationVertical,
|
||||
}
|
||||
|
||||
views[1] = naive.TabView{
|
||||
Children: []naive.TabViewItem{
|
||||
{
|
||||
Title: naive.StringTextContent("Tab 1️⃣"),
|
||||
Content: naive.StackView{
|
||||
Children: []naive.View{
|
||||
naive.ButtonView{
|
||||
Content: Text1,
|
||||
OnClick: func() {
|
||||
naive.SetView(views[0])
|
||||
},
|
||||
},
|
||||
naive.TextInputView{
|
||||
Value: Text1,
|
||||
Placeholder: Text1,
|
||||
},
|
||||
naive.TextInputView{
|
||||
Placeholder: naive.StringTextContent("just for fun"),
|
||||
},
|
||||
},
|
||||
Orientation: naive.LayoutOrientationVertical,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
naive.StartApp("Meep", views[1])
|
||||
}
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module git.frankmayer.dev/tsukinoko-kun/naive
|
||||
|
||||
go 1.23.3
|
||||
|
||||
require github.com/progrium/darwinkit v0.5.0
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
|
||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/progrium/darwinkit v0.5.0 h1:SwchcMbTOG1py3CQsINmGlsRmYKdlFrbnv3dE4aXA0s=
|
||||
github.com/progrium/darwinkit v0.5.0/go.mod h1:PxQhZuftnALLkCVaR8LaHtUOfoo4pm8qUDG+3C/sXNs=
|
8
inputview.go
Normal file
8
inputview.go
Normal file
@ -0,0 +1,8 @@
|
||||
package naive
|
||||
|
||||
type (
|
||||
TextInputView struct {
|
||||
Value TextContent
|
||||
Placeholder TextContent
|
||||
}
|
||||
)
|
33
inputview_darwin.go
Normal file
33
inputview_darwin.go
Normal file
@ -0,0 +1,33 @@
|
||||
package naive
|
||||
|
||||
import (
|
||||
"github.com/progrium/darwinkit/dispatch"
|
||||
"github.com/progrium/darwinkit/macos/appkit"
|
||||
"github.com/progrium/darwinkit/macos/foundation"
|
||||
)
|
||||
|
||||
func (tiv TextInputView) toNative() appkit.IView {
|
||||
ntiv := appkit.NewTextField()
|
||||
if tiv.Value != nil {
|
||||
ntiv.SetStringValue(tiv.Value.Value())
|
||||
if eff, ok := tiv.Value.(*Effect[string]); ok {
|
||||
tfd := &appkit.TextFieldDelegate{}
|
||||
tfd.SetControlTextDidChange(func(obj foundation.Notification) {
|
||||
dispatch.MainQueue().DispatchAsync(func() {
|
||||
if ntiv.StringValue() != eff.value {
|
||||
eff.SetValue(ntiv.StringValue())
|
||||
}
|
||||
})
|
||||
})
|
||||
ntiv.SetDelegate(tfd)
|
||||
eff.OnChange(ntiv.SetStringValue)
|
||||
}
|
||||
}
|
||||
if tiv.Placeholder != nil {
|
||||
ntiv.SetPlaceholderString(tiv.Placeholder.Value())
|
||||
if eff, ok := tiv.Placeholder.(*Effect[string]); ok {
|
||||
eff.OnChange(ntiv.SetPlaceholderString)
|
||||
}
|
||||
}
|
||||
return ntiv
|
||||
}
|
8
layout.go
Normal file
8
layout.go
Normal file
@ -0,0 +1,8 @@
|
||||
package naive
|
||||
|
||||
type LayoutOrientation uint8
|
||||
|
||||
const (
|
||||
LayoutOrientationHorizontal = iota
|
||||
LayoutOrientationVertical
|
||||
)
|
18
layout_darwin.go
Normal file
18
layout_darwin.go
Normal file
@ -0,0 +1,18 @@
|
||||
package naive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/progrium/darwinkit/macos/appkit"
|
||||
)
|
||||
|
||||
func (lo LayoutOrientation) toNative() appkit.UserInterfaceLayoutOrientation {
|
||||
switch lo {
|
||||
case LayoutOrientationHorizontal:
|
||||
return appkit.UserInterfaceLayoutOrientationHorizontal
|
||||
case LayoutOrientationVertical:
|
||||
return appkit.UserInterfaceLayoutOrientationVertical
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid layout orientation %d", lo))
|
||||
}
|
||||
}
|
20
naive.go
Normal file
20
naive.go
Normal file
@ -0,0 +1,20 @@
|
||||
package naive
|
||||
|
||||
var (
|
||||
appTitle string
|
||||
)
|
||||
|
||||
func StartApp(title string, view View) {
|
||||
appTitle = title
|
||||
startNativeApp(view)
|
||||
}
|
||||
|
||||
func SetView(view View) {
|
||||
clearAllEffectListeners()
|
||||
nativeSetView(view)
|
||||
}
|
||||
|
||||
func SetTitle(title string) {
|
||||
appTitle = title
|
||||
setNativeTitle(title)
|
||||
}
|
97
naive_darwin.go
Normal file
97
naive_darwin.go
Normal file
@ -0,0 +1,97 @@
|
||||
package naive
|
||||
|
||||
import (
|
||||
"github.com/progrium/darwinkit/macos/appkit"
|
||||
"github.com/progrium/darwinkit/macos/foundation"
|
||||
"github.com/progrium/darwinkit/objc"
|
||||
)
|
||||
|
||||
func startNativeApp(view View) {
|
||||
if nativeAppWindow != nil {
|
||||
return
|
||||
}
|
||||
|
||||
app := appkit.Application_SharedApplication()
|
||||
|
||||
delegate := &appkit.ApplicationDelegate{}
|
||||
delegate.SetApplicationDidFinishLaunching(func(notification foundation.Notification) {
|
||||
w := appkit.NewWindowWithSize(720, 440)
|
||||
nativeAppWindow = &w
|
||||
objc.Retain(nativeAppWindow)
|
||||
nativeAppWindow.SetTitle(appTitle)
|
||||
|
||||
nativeAppWindow.SetContentView(view.toNative())
|
||||
|
||||
nativeAppWindow.Center()
|
||||
nativeAppWindow.MakeKeyAndOrderFront(nil)
|
||||
nativeAppWindow.SetFrameAutosaveName(appkit.WindowFrameAutosaveName(appTitle))
|
||||
setSystemBar(app)
|
||||
|
||||
app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular)
|
||||
app.ActivateIgnoringOtherApps(true)
|
||||
|
||||
})
|
||||
delegate.SetApplicationWillFinishLaunching(func(foundation.Notification) {
|
||||
setMainMenu(app)
|
||||
})
|
||||
delegate.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool {
|
||||
return true
|
||||
})
|
||||
|
||||
app.SetDelegate(delegate)
|
||||
app.Run()
|
||||
}
|
||||
|
||||
type (
|
||||
View interface {
|
||||
toNative() appkit.IView
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
nativeAppWindow *appkit.Window
|
||||
)
|
||||
|
||||
func nativeSetView(view View) {
|
||||
nativeAppWindow.SetContentView(view.toNative())
|
||||
}
|
||||
|
||||
func setNativeTitle(title string) {
|
||||
nativeAppWindow.SetTitle(title)
|
||||
}
|
||||
|
||||
func setMainMenu(app appkit.Application) {
|
||||
menu := appkit.NewMenuWithTitle("main")
|
||||
app.SetMainMenu(menu)
|
||||
|
||||
mainMenuItem := appkit.NewMenuItemWithSelector("", "", objc.Selector{})
|
||||
mainMenuMenu := appkit.NewMenuWithTitle("App")
|
||||
mainMenuMenu.AddItem(appkit.NewMenuItemWithAction("Hide", "h", func(sender objc.Object) { app.Hide(nil) }))
|
||||
mainMenuMenu.AddItem(appkit.NewMenuItemWithAction("Quit", "q", func(sender objc.Object) { app.Terminate(nil) }))
|
||||
mainMenuItem.SetSubmenu(mainMenuMenu)
|
||||
menu.AddItem(mainMenuItem)
|
||||
|
||||
testMenuItem := appkit.NewMenuItemWithSelector("", "", objc.Selector{})
|
||||
editMenu := appkit.NewMenuWithTitle("Edit")
|
||||
editMenu.AddItem(appkit.NewMenuItemWithSelector("Select All", "a", objc.Sel("selectAll:")))
|
||||
editMenu.AddItem(appkit.MenuItem_SeparatorItem())
|
||||
editMenu.AddItem(appkit.NewMenuItemWithSelector("Copy", "c", objc.Sel("copy:")))
|
||||
editMenu.AddItem(appkit.NewMenuItemWithSelector("Paste", "v", objc.Sel("paste:")))
|
||||
editMenu.AddItem(appkit.NewMenuItemWithSelector("Cut", "x", objc.Sel("cut:")))
|
||||
editMenu.AddItem(appkit.NewMenuItemWithSelector("Undo", "z", objc.Sel("undo:")))
|
||||
editMenu.AddItem(appkit.NewMenuItemWithSelector("Redo", "Z", objc.Sel("redo:")))
|
||||
testMenuItem.SetSubmenu(editMenu)
|
||||
menu.AddItem(testMenuItem)
|
||||
}
|
||||
|
||||
func setSystemBar(app appkit.Application) {
|
||||
item := appkit.StatusBar_SystemStatusBar().StatusItemWithLength(appkit.VariableStatusItemLength)
|
||||
objc.Retain(&item)
|
||||
img := appkit.Image_ImageWithSystemSymbolNameAccessibilityDescription("multiply.circle.fill", "A multiply symbol inside a filled circle.")
|
||||
item.Button().SetImage(img)
|
||||
|
||||
menu := appkit.NewMenuWithTitle("main")
|
||||
menu.AddItem(appkit.NewMenuItemWithAction("Hide", "h", func(sender objc.Object) { app.Hide(nil) }))
|
||||
menu.AddItem(appkit.NewMenuItemWithAction("Quit", "q", func(sender objc.Object) { app.Terminate(nil) }))
|
||||
item.SetMenu(menu)
|
||||
}
|
6
stackview.go
Normal file
6
stackview.go
Normal file
@ -0,0 +1,6 @@
|
||||
package naive
|
||||
|
||||
type StackView struct {
|
||||
Children []View
|
||||
Orientation LayoutOrientation
|
||||
}
|
13
stackview_darwin.go
Normal file
13
stackview_darwin.go
Normal file
@ -0,0 +1,13 @@
|
||||
package naive
|
||||
|
||||
import "github.com/progrium/darwinkit/macos/appkit"
|
||||
|
||||
func (sv StackView) toNative() appkit.IView {
|
||||
nsv := appkit.NewStackView()
|
||||
nsv.SetOrientation(sv.Orientation.toNative())
|
||||
nsv.SetTranslatesAutoresizingMaskIntoConstraints(true)
|
||||
for _, v := range sv.Children {
|
||||
nsv.AddViewInGravity(v.toNative(), appkit.StackViewGravityTop)
|
||||
}
|
||||
return nsv
|
||||
}
|
12
tabview.go
Normal file
12
tabview.go
Normal file
@ -0,0 +1,12 @@
|
||||
package naive
|
||||
|
||||
type (
|
||||
TabView struct {
|
||||
Children []TabViewItem
|
||||
}
|
||||
|
||||
TabViewItem struct {
|
||||
Title TextContent
|
||||
Content View
|
||||
}
|
||||
)
|
24
tabview_darwin.go
Normal file
24
tabview_darwin.go
Normal file
@ -0,0 +1,24 @@
|
||||
package naive
|
||||
|
||||
import "github.com/progrium/darwinkit/macos/appkit"
|
||||
|
||||
func (tv TabView) toNative() appkit.IView {
|
||||
tabView := appkit.NewTabView()
|
||||
tabView.SetTranslatesAutoresizingMaskIntoConstraints(true)
|
||||
|
||||
for _, tiv := range tv.Children {
|
||||
tabView.AddTabViewItem(tiv.toNative())
|
||||
}
|
||||
|
||||
return tabView
|
||||
}
|
||||
|
||||
func (tvi TabViewItem) toNative() appkit.TabViewItem {
|
||||
ti := appkit.NewTabViewItem()
|
||||
ti.SetLabel(tvi.Title.Value())
|
||||
if eff, ok := tvi.Title.(*Effect[string]); ok {
|
||||
eff.OnChange(ti.SetLabel)
|
||||
}
|
||||
ti.SetView(tvi.Content.toNative())
|
||||
return ti
|
||||
}
|
12
textcontent.go
Normal file
12
textcontent.go
Normal file
@ -0,0 +1,12 @@
|
||||
package naive
|
||||
|
||||
type (
|
||||
StringTextContent string
|
||||
TextContent interface {
|
||||
Value() string
|
||||
}
|
||||
)
|
||||
|
||||
func (stc StringTextContent) Value() string {
|
||||
return string(stc)
|
||||
}
|
5
textview.go
Normal file
5
textview.go
Normal file
@ -0,0 +1,5 @@
|
||||
package naive
|
||||
|
||||
type TextView struct {
|
||||
Content TextContent
|
||||
}
|
14
textview_darwin.go
Normal file
14
textview_darwin.go
Normal file
@ -0,0 +1,14 @@
|
||||
package naive
|
||||
|
||||
import (
|
||||
"github.com/progrium/darwinkit/macos/appkit"
|
||||
)
|
||||
|
||||
func (tv TextView) toNative() appkit.IView {
|
||||
ntv := appkit.NewLabel(tv.Content.Value())
|
||||
if eff, ok := tv.Content.(*Effect[string]); ok {
|
||||
eff.OnChange(ntv.SetStringValue)
|
||||
}
|
||||
|
||||
return ntv
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user