init tiramisu

This commit is contained in:
grngxd 2025-06-16 22:58:47 +01:00
commit 967b77c51c
10 changed files with 253 additions and 0 deletions

52
.air.toml Normal file
View file

@ -0,0 +1,52 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "tmp\\main.exe"
cmd = "go build -o ./tmp/main.exe ./example"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", "runtime/out", "node_modules", "dist"]
exclude_file = []
exclude_regex = ["_test.go", "out"]
exclude_unchanged = true
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html", "js", "ts"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = ["bunx tsc -p ./runtime"]
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
silent = false
time = false
[misc]
clean_on_exit = false
[proxy]
app_port = 0
enabled = false
proxy_port = 0
[screen]
clear_on_rebuild = false
keep_scroll = true

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
tmp/
out/

4
README.md Normal file
View file

@ -0,0 +1,4 @@
# 🍥 tiramisu
> ` is tiramisu a cake or a pie?`
Build modern, cross-platform desktop apps in HTML + Go from one codebase.

49
example/main.go Normal file
View file

@ -0,0 +1,49 @@
package main
import (
"fmt"
t "github.com/grngxd/tiramisu"
webview "github.com/webview/webview_go"
)
func main() {
app := t.New(t.TiramisuOptions{
Debug: true,
Width: 1200,
Height: 800,
Title: "Tiramisu Example",
Hints: webview.HintFixed,
})
app.Run(func() {
app.Bind("hello", func(args ...any) (any, error) {
if len(args) == 0 {
return "Hello, World!", nil
}
if len(args) == 1 {
return fmt.Sprintf("Hello, %s!", args[0]), nil
}
return "Hello, unknown!", nil
})
app.HTML(`
<!DOCTYPE html>
<html>
<body>
<h1>Tiramisu Example</h1>
<p>Click the button to see a greeting:</p>
<button onclick="greet()">Greet</button>
<script>
function greet() {
tiramisu.invoke("hello", "world")
.then(response => alert(response))
.catch(console.error)
}
</script>
</body>
</html>
`)
})
}

5
go.mod Normal file
View file

@ -0,0 +1,5 @@
module github.com/grngxd/tiramisu
go 1.23.4
require github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 // indirect

2
go.sum Normal file
View file

@ -0,0 +1,2 @@
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 h1:VQpB2SpK88C6B5lPHTuSZKb2Qee1QWwiFlC5CKY4AW0=
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk=

11
runtime/index.d.ts vendored Normal file
View file

@ -0,0 +1,11 @@
declare global {
interface Window {
invoke: (name: string, ...args: any[]) => Promise<any>;
tiramisu: {
invoke: (name: string, ...args: any[]) => Promise<any>;
};
}
}
export { };

5
runtime/preload.ts Normal file
View file

@ -0,0 +1,5 @@
const tiramisu = {
invoke: window.invoke
}
window.tiramisu = tiramisu

12
runtime/tsconfig.json Normal file
View file

@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": false,
"declaration": true,
"outDir": "./out",
"esModuleInterop": true,
"skipLibCheck": true
},
}

111
tiramisu.go Normal file
View file

@ -0,0 +1,111 @@
package tiramisu
import (
"embed"
"fmt"
wv "github.com/webview/webview_go"
)
type TiramisuOptions struct {
Debug bool
Width int
Height int
Title string
Hints wv.Hint
}
type FuncHandler func(args ...any) (any, error)
type Tiramisu struct {
w wv.WebView
funcs map[string]FuncHandler
}
func New(o TiramisuOptions) *Tiramisu {
w := wv.New(o.Debug)
t := &Tiramisu{
w: w,
funcs: make(map[string]FuncHandler),
}
w.SetSize(o.Width, o.Height, o.Hints)
w.SetTitle(o.Title)
return t
}
func (t *Tiramisu) Run(fn func()) {
defer t.w.Destroy()
t.w.Dispatch(func() {
t.injectJS()
if fn != nil {
fn()
}
})
t.w.Run()
}
func (t *Tiramisu) bind(name string, fn FuncHandler) {
t.w.Bind(name, fn)
}
func (t *Tiramisu) Bind(name string, fn FuncHandler) {
if _, exists := t.funcs[name]; exists {
panic(fmt.Sprintf("function %s is already bound", name))
}
t.funcs[name] = fn
t.bind(name, func(args ...any) (any, error) {
return t.invoke(name, args...)
})
}
func (t *Tiramisu) invoke(name string, args ...any) (any, error) {
fn, ok := t.funcs[name]
if !ok {
return nil, fmt.Errorf("function %s not found", name)
}
result, err := fn(args...)
if err != nil {
return nil, fmt.Errorf("error invoking function %s: %w", name, err)
}
return result, nil
}
func (t *Tiramisu) Eval(js string) {
t.w.Eval(js)
}
func (t *Tiramisu) Evalf(js string, args ...any) {
fmt := fmt.Sprintf(js, args...)
t.w.Eval(fmt)
}
func (t *Tiramisu) HTML(html string) {
t.w.SetHtml(html)
t.injectJS()
}
//go:embed runtime/out/*
var runtimeFS embed.FS
func (t *Tiramisu) injectJS() {
js, err := runtimeFS.ReadFile("runtime/out/preload.js")
if err != nil {
panic(fmt.Sprintf("failed to read preload.js: %v", err))
}
t.w.Eval(string(js))
t.bind("invoke", func(args ...any) (any, error) {
name, ok := args[0].(string)
if !ok {
return nil, fmt.Errorf("first argument must be a string, got %T", args[0])
}
if len(args) == 1 {
return t.invoke(name)
}
return t.invoke(name, args[1:]...)
})
}