Skip to content

Commit 4d4dfb7

Browse files
committed
Initial version.
1 parent 7757409 commit 4d4dfb7

21 files changed

+1784
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Ide
2+
.idea
3+
14
# If you prefer the allow list template instead of the deny list, see community template:
25
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
36
#

adapter.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package crt
2+
3+
import "github.com/hajimehoshi/ebiten/v2"
4+
5+
type WindowSize struct {
6+
Width int
7+
Height int
8+
}
9+
10+
type MouseButton struct {
11+
Button ebiten.MouseButton
12+
X int
13+
Y int
14+
Shift bool
15+
Alt bool
16+
Ctrl bool
17+
JustPressed bool
18+
JustReleased bool
19+
}
20+
21+
type MouseMotion struct {
22+
X int
23+
Y int
24+
}
25+
26+
type MouseWheel struct {
27+
X int
28+
Y int
29+
DX float64
30+
DY float64
31+
Shift bool
32+
Alt bool
33+
Ctrl bool
34+
}
35+
36+
type KeyPress struct {
37+
Key ebiten.Key
38+
Runes []rune
39+
Shift bool
40+
Alt bool
41+
Ctrl bool
42+
}
43+
44+
type InputAdapter interface {
45+
HandleMouseButton(button MouseButton)
46+
HandleMouseMotion(motion MouseMotion)
47+
HandleMouseWheel(wheel MouseWheel)
48+
HandleKeyPress()
49+
HandleWindowSize(size WindowSize)
50+
}

adapter_empty.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package crt
2+
3+
type EmptyAdapter struct{}
4+
5+
func NewEmptyAdapter() *EmptyAdapter {
6+
return &EmptyAdapter{}
7+
}
8+
9+
func (e *EmptyAdapter) HandleMouseButton(button MouseButton) {
10+
11+
}
12+
13+
func (e *EmptyAdapter) HandleMouseMotion(motion MouseMotion) {
14+
15+
}
16+
17+
func (e *EmptyAdapter) HandleMouseWheel(wheel MouseWheel) {
18+
19+
}
20+
21+
func (e *EmptyAdapter) HandleKeyPress() {
22+
23+
}
24+
25+
func (e *EmptyAdapter) HandleWindowSize(size WindowSize) {
26+
27+
}

bubbletea/adapter_bubbletea.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package bubbletea
2+
3+
import (
4+
"github.com/BigJk/crt"
5+
tea "github.com/charmbracelet/bubbletea"
6+
"github.com/hajimehoshi/ebiten/v2"
7+
"github.com/hajimehoshi/ebiten/v2/inpututil"
8+
"strings"
9+
)
10+
11+
var ebitenToTeaKeys = map[ebiten.Key]tea.KeyType{
12+
ebiten.KeyEnter: tea.KeyEnter,
13+
ebiten.KeyTab: tea.KeyTab,
14+
ebiten.KeySpace: tea.KeySpace,
15+
ebiten.KeyBackspace: tea.KeyBackspace,
16+
ebiten.KeyDelete: tea.KeyDelete,
17+
ebiten.KeyHome: tea.KeyHome,
18+
ebiten.KeyEnd: tea.KeyEnd,
19+
ebiten.KeyPageUp: tea.KeyPgUp,
20+
ebiten.KeyArrowUp: tea.KeyUp,
21+
ebiten.KeyArrowDown: tea.KeyDown,
22+
ebiten.KeyArrowLeft: tea.KeyLeft,
23+
ebiten.KeyArrowRight: tea.KeyRight,
24+
ebiten.KeyEscape: tea.KeyEscape,
25+
}
26+
27+
var ebitenToTeaRunes = map[ebiten.Key][]rune{
28+
ebiten.Key1: {'1'},
29+
ebiten.Key2: {'2'},
30+
ebiten.Key3: {'3'},
31+
ebiten.Key4: {'4'},
32+
ebiten.Key5: {'5'},
33+
ebiten.Key6: {'6'},
34+
ebiten.Key7: {'7'},
35+
ebiten.Key8: {'8'},
36+
ebiten.Key9: {'9'},
37+
ebiten.Key0: {'0'},
38+
}
39+
40+
var ebitenToTeaMouse = map[ebiten.MouseButton]tea.MouseEventType{
41+
ebiten.MouseButtonLeft: tea.MouseLeft,
42+
ebiten.MouseButtonMiddle: tea.MouseMiddle,
43+
ebiten.MouseButtonRight: tea.MouseRight,
44+
}
45+
46+
type BubbleTeaAdapter struct {
47+
prog *tea.Program
48+
}
49+
50+
func NewBubbleTeaAdapter(prog *tea.Program) *BubbleTeaAdapter {
51+
return &BubbleTeaAdapter{prog: prog}
52+
}
53+
54+
func (b *BubbleTeaAdapter) HandleMouseMotion(motion crt.MouseMotion) {
55+
b.prog.Send(tea.MouseMsg{
56+
X: motion.X,
57+
Y: motion.Y,
58+
Alt: false,
59+
Ctrl: false,
60+
Type: tea.MouseMotion,
61+
})
62+
}
63+
64+
func (b *BubbleTeaAdapter) HandleMouseButton(button crt.MouseButton) {
65+
b.prog.Send(tea.MouseMsg{
66+
X: button.X,
67+
Y: button.Y,
68+
Alt: ebiten.IsKeyPressed(ebiten.KeyAlt),
69+
Ctrl: ebiten.IsKeyPressed(ebiten.KeyControl),
70+
Type: ebitenToTeaMouse[button.Button],
71+
})
72+
}
73+
74+
func (b *BubbleTeaAdapter) HandleMouseWheel(wheel crt.MouseWheel) {
75+
if wheel.DY > 0 {
76+
b.prog.Send(tea.MouseMsg{
77+
X: wheel.X,
78+
Y: wheel.Y,
79+
Alt: ebiten.IsKeyPressed(ebiten.KeyAlt),
80+
Ctrl: ebiten.IsKeyPressed(ebiten.KeyControl),
81+
Type: tea.MouseWheelUp,
82+
})
83+
} else if wheel.DY < 0 {
84+
b.prog.Send(tea.MouseMsg{
85+
X: wheel.X,
86+
Y: wheel.Y,
87+
Alt: ebiten.IsKeyPressed(ebiten.KeyAlt),
88+
Ctrl: ebiten.IsKeyPressed(ebiten.KeyControl),
89+
Type: tea.MouseWheelDown,
90+
})
91+
}
92+
}
93+
94+
func (b *BubbleTeaAdapter) HandleKeyPress() {
95+
var keys []ebiten.Key
96+
keys = inpututil.AppendJustReleasedKeys(keys)
97+
98+
for _, k := range keys {
99+
runes := []rune(strings.ToLower(k.String()))
100+
if val, ok := ebitenToTeaRunes[k]; ok {
101+
runes = val
102+
}
103+
b.prog.Send(tea.KeyMsg{
104+
Type: tea.KeyRunes,
105+
Runes: runes,
106+
Alt: ebiten.IsKeyPressed(ebiten.KeyAlt),
107+
})
108+
}
109+
110+
for k, v := range ebitenToTeaKeys {
111+
if inpututil.IsKeyJustReleased(k) {
112+
runes := []rune(strings.ToLower(k.String()))
113+
b.prog.Send(tea.KeyMsg{
114+
Type: v,
115+
Runes: runes,
116+
Alt: ebiten.IsKeyPressed(ebiten.KeyAlt),
117+
})
118+
}
119+
}
120+
}
121+
122+
func (b *BubbleTeaAdapter) HandleWindowSize(size crt.WindowSize) {
123+
b.prog.Send(tea.WindowSizeMsg{
124+
Width: size.Width,
125+
Height: size.Height,
126+
})
127+
}

bubbletea/bubbletea.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package bubbletea
2+
3+
import (
4+
"fmt"
5+
"github.com/BigJk/crt"
6+
tea "github.com/charmbracelet/bubbletea"
7+
"github.com/charmbracelet/lipgloss"
8+
"github.com/muesli/termenv"
9+
"image/color"
10+
"os"
11+
"syscall"
12+
)
13+
14+
func init() {
15+
lipgloss.SetColorProfile(termenv.TrueColor)
16+
}
17+
18+
type fakeEnviron struct{}
19+
20+
func (f fakeEnviron) Environ() []string {
21+
return []string{"TERM", "COLORTERM"}
22+
}
23+
24+
func (f fakeEnviron) Getenv(s string) string {
25+
switch s {
26+
case "TERM":
27+
return "xterm-256color"
28+
case "COLORTERM":
29+
return "truecolor"
30+
}
31+
return ""
32+
}
33+
34+
func Window(width int, height int, fonts crt.Fonts, model tea.Model, defaultBg color.Color, options ...tea.ProgramOption) (*crt.Window, error) {
35+
gameInput := crt.NewConcurrentRW()
36+
gameOutput := crt.NewConcurrentRW()
37+
38+
go gameInput.Run()
39+
go gameOutput.Run()
40+
41+
prog := tea.NewProgram(
42+
model,
43+
append([]tea.ProgramOption{
44+
tea.WithMouseAllMotion(),
45+
tea.WithInput(gameInput),
46+
tea.WithOutput(termenv.NewOutput(gameOutput, termenv.WithEnvironment(fakeEnviron{}), termenv.WithTTY(true), termenv.WithProfile(termenv.TrueColor), termenv.WithColorCache(true))),
47+
tea.WithANSICompressor(),
48+
}, options...)...,
49+
)
50+
51+
go func() {
52+
if _, err := prog.Run(); err != nil {
53+
fmt.Printf("Alas, there's been an error: %v", err)
54+
os.Exit(1)
55+
}
56+
57+
_ = syscall.Kill(syscall.Getpid(), syscall.SIGINT)
58+
}()
59+
60+
return crt.NewGame(width, height, fonts, gameOutput, NewBubbleTeaAdapter(prog), defaultBg)
61+
}

cell.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package crt
2+
3+
import "image/color"
4+
5+
// FontWeight is the weight of a font at a certain terminal cell.
6+
type FontWeight byte
7+
8+
const (
9+
// FontWeightNormal is the default font weight.
10+
FontWeightNormal FontWeight = iota
11+
12+
// FontWeightBold is a bold font weight.
13+
FontWeightBold
14+
15+
// FontWeightItalic is an italic font weight.
16+
FontWeightItalic
17+
)
18+
19+
// GridCell is a single cell in the terminal grid.
20+
type GridCell struct {
21+
Char rune
22+
Fg color.Color
23+
Bg color.Color
24+
Weight FontWeight
25+
}

0 commit comments

Comments
 (0)