initial
This commit is contained in:
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user