This commit is contained in:
roodletoof
2024-09-14 15:16:36 +02:00
commit d5d9badb05
6 changed files with 295 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
go run . temp.png
+14
View File
@@ -0,0 +1,14 @@
module github.com/roodletoof/whiteboard
go 1.23.1
require github.com/hajimehoshi/ebiten/v2 v2.7.9
require (
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 // indirect
github.com/ebitengine/hideconsole v1.0.0 // indirect
github.com/ebitengine/purego v0.7.0 // indirect
github.com/jezek/xgb v1.1.1 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
)
+16
View File
@@ -0,0 +1,16 @@
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU=
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M=
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc=
github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/hajimehoshi/ebiten/v2 v2.7.9 h1:DYH/usAa9dMHcGkBIIEApJsVqDekrJBxYHmsBuly8Iw=
github.com/hajimehoshi/ebiten/v2 v2.7.9/go.mod h1:Ulbq5xDmdx47P24EJ+Mb31Zps7vQq+guieG9mghQUaA=
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+35
View File
@@ -0,0 +1,35 @@
package main
import "github.com/hajimehoshi/ebiten/v2"
type keymap []ebiten.Key
var resize = keymap{
ebiten.KeyD,
ebiten.KeyK,
}
var drawBlack = keymap{
ebiten.KeyF,
ebiten.KeyJ,
}
var drawWhite = keymap{
ebiten.KeyS,
ebiten.KeyL,
}
var undo = keymap{
ebiten.KeyZ,
ebiten.KeyU,
}
var redo = keymap{
ebiten.KeyX,
ebiten.KeyR,
}
func (k keymap) check(checker func(ebiten.Key) bool) bool {
for _, key := range k {
if checker(key) {
return true
}
}
return false
}
+134
View File
@@ -0,0 +1,134 @@
package main
import (
"image/color"
"log"
"math"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/vector"
)
const width = 512
const height = 512
type appOption func(a *app)
type app struct {
strokes []stroke
width float32
canvas *ebiten.Image
sizeAnchorPos Vec2
historyIndex uintptr
}
func (a *app) Draw(screen *ebiten.Image) {
screen.Fill(color.White)
opts := ebiten.DrawImageOptions{}
screen.DrawImage(a.canvas, &opts)
cursor := cursorPosition()
vector.StrokeCircle(
screen,
cursor.x,
cursor.y,
a.width/2,
3,
color.White,
true,
)
vector.StrokeCircle(
screen,
cursor.x,
cursor.y,
a.width/2,
1,
color.Black,
true,
)
}
func (a *app) Layout(_, _ int) (screenWidth int, screenHeight int) {
return width, height
}
func (a *app) Update() error {
check_any_drawing_input := func(checker func(ebiten.Key) bool) bool {
return drawWhite.check(checker) || drawBlack.check(checker)
}
is_drawing := check_any_drawing_input(ebiten.IsKeyPressed)
just_started_drawing := check_any_drawing_input(inpututil.IsKeyJustPressed)
var clr color.Color
if drawBlack.check(ebiten.IsKeyPressed) {
clr = color.Black
} else if drawWhite.check(ebiten.IsKeyPressed) {
clr = color.White
}
if just_started_drawing {
if clr == nil {
log.Fatal("drawing color in update is nil") // should never happen
}
a.strokes = append(a.strokes, stroke{
points: []Vec2{},
width: a.width,
color: clr,
})
}
// TODO current history pointer is not updated as it should
// TODO tests
if len(a.strokes) > 0 {
currentStroke := &a.strokes[len(a.strokes)-1]
if is_drawing {
currentStroke.appendIfMoved(cursorPosition())
currentStroke.drawLast(a.canvas)
}
}
if resize.check(inpututil.IsKeyJustPressed) {
a.sizeAnchorPos = cursorPosition()
}
if resize.check(ebiten.IsKeyPressed) {
prevPos := a.sizeAnchorPos
currPos := cursorPosition()
reltivePos := Vec2{
prevPos.x - currPos.x,
prevPos.y - currPos.y,
}
length := float32(math.Sqrt(
float64(reltivePos.x) * float64(reltivePos.x) + float64(reltivePos.y) * float64(reltivePos.y),
))
a.width = length * 2.0
}
return nil
}
func newApp( opts ...appOption ) app {
a := app{}
for _, opt := range opts {
opt(&a)
}
return a
}
func main() {
ebiten.SetWindowTitle("whiteboard")
ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
ebiten.CursorPosition()
whiteboardApp := newApp( func(a *app) {
a.width = 10
a.canvas = ebiten.NewImage(width, height)
a.canvas.Fill(color.White)
})
ebiten.RunGame(&whiteboardApp)
}
+95
View File
@@ -0,0 +1,95 @@
package main
import (
"errors"
color_lib "image/color"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
)
type Vec2 struct {
x, y float32
}
func cursorPosition() Vec2 {
x, y := ebiten.CursorPosition()
return Vec2{
x: float32(x),
y: float32(y),
}
}
type stroke struct {
points []Vec2
width float32
color color_lib.Color
}
func (s *stroke) appendIfMoved(point Vec2) {
if len(s.points) != 0 {
lastPoint := s.points[len(s.points)-1]
if lastPoint.x == point.x && lastPoint.y == point.y {
return
}
}
s.points = append(s.points, point)
}
func (s *stroke) drawLast(screen *ebiten.Image) error {
if s.color == nil {
return errors.New("stroke has nil color")
}
if len(s.points) == 0 {
return nil
}
from := s.points[len(s.points)-1]
vector.DrawFilledCircle(screen, from.x, from.y, s.width/2, s.color, true)
if len(s.points) == 1 {
return nil
}
to := s.points[len(s.points)-2]
vector.StrokeLine(
screen,
from.x,
from.y,
to.x,
to.y,
s.width,
s.color,
true,
)
vector.DrawFilledCircle(screen, to.x, to.y, s.width/2, s.color, true)
return nil
}
func (s *stroke) draw(screen *ebiten.Image) error {
if s.color == nil {
return errors.New("stroke has nil color")
}
from := s.points[0]
vector.DrawFilledCircle(screen, from.x, from.y, s.width/2, s.color, true)
for _, to := range s.points[1:] {
vector.StrokeLine(
screen,
from.x,
from.y,
to.x,
to.y,
s.width,
s.color,
true,
)
vector.DrawFilledCircle(screen, to.x, to.y, s.width/2, s.color, true)
from = to
}
return nil
}