x-backend #2
| @ -2,11 +2,10 @@ | ||||
| 
 | ||||
| Stone is a backend-agnostic application framework designed to: | ||||
| 
 | ||||
| - Combine the simplicity of developing TUI programs with the input capabilities | ||||
|   of GUI programs | ||||
| - Combine the simplicity and ease of development inherent to TUI programs with the extended capabilities of GUI programs | ||||
| - Be adaptable to run virtually anywhere | ||||
| 
 | ||||
| Currently, the only supported backend is | ||||
| [pixel](https://github.com/faiface/pixel), but it is very easy to write and link | ||||
| [X](https://github.com/jezek/xgbutil), but it is very easy to write and link | ||||
| your own. Stone will automatically run through the list of registered backends | ||||
| and instantiate the first one that doesn't throw an error. | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| package stone | ||||
| 
 | ||||
| import "image" | ||||
| import "image/color" | ||||
| 
 | ||||
| // Application represents an application. | ||||
| @ -7,17 +8,11 @@ type Application struct { | ||||
| 	DamageBuffer | ||||
| 	 | ||||
| 	title string | ||||
| 	icons []image.Image | ||||
| 	backend Backend | ||||
| 	config Config | ||||
| } | ||||
| 
 | ||||
| // SetTitle sets the application's title. If in a window, it will appear as the | ||||
| // window's name. | ||||
| func (application *Application) SetTitle (title string) { | ||||
| 	application.title = title | ||||
| 	application.backend.SetTitle(title) | ||||
| } | ||||
| 
 | ||||
| // Run initializes the application, starts it, and then returns a channel that | ||||
| // broadcasts events. If no suitable backend can be found, an error is returned. | ||||
| func (application *Application) Run () ( | ||||
| @ -37,8 +32,10 @@ func (application *Application) Run () ( | ||||
| 		color.RGBA { R: 0x2E, G: 0x34, B: 0x40, A: 0xFF }, | ||||
| 		color.RGBA { R: 0xA8, G: 0x55, B: 0x5D, A: 0xFF }, | ||||
| 	} | ||||
| 	application.config.fontName = "" | ||||
| 	application.config.fontSize = 11 | ||||
| 	 | ||||
| 	application.config.padding = 4 | ||||
| 	application.config.padding = 2 | ||||
| 
 | ||||
| 	application.backend, err = instantiateBackend(application) | ||||
| 	if err != nil { return } | ||||
| @ -49,6 +46,41 @@ func (application *Application) Run () ( | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Draw "commits" changes made in the buffer to the display. | ||||
| func (application *Application) Draw () { | ||||
| 	application.backend.Draw() | ||||
| } | ||||
| 
 | ||||
| // SetTitle sets the application's title. If in a window, it will appear as the | ||||
| // window's name. | ||||
| func (application *Application) SetTitle (title string) (err error) { | ||||
| 	application.title = title | ||||
| 	if application.backend != nil { | ||||
| 		err = application.backend.SetTitle(title) | ||||
| 	} | ||||
| 
 | ||||
| 	return  | ||||
| } | ||||
| 
 | ||||
| func (application *Application) Title () (title string) { | ||||
| 	title = application.title | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (application *Application) SetIcon (sizes []image.Image) (err error) { | ||||
| 	application.icons = sizes | ||||
| 	if application.backend != nil { | ||||
| 		err = application.backend.SetIcon(sizes) | ||||
| 	} | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (application *Application) Icon () (sizes []image.Image) { | ||||
| 	sizes = application.icons | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Config returns a pointer to the application's configuration. | ||||
| func (application *Application) Config () (config *Config) { | ||||
| 	config = &application.config | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								assets/scaffold16.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/scaffold16.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 589 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/scaffold32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/scaffold32.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.2 KiB | 
| @ -5,8 +5,9 @@ import "errors" | ||||
| 
 | ||||
| type Backend interface { | ||||
| 	Run      (channel chan(Event)) | ||||
| 	SetTitle (title string) | ||||
| 	SetIcon  (icons []image.Image) | ||||
| 	SetTitle (title string) (err error) | ||||
| 	SetIcon  (icons []image.Image) (err error) | ||||
| 	Draw     () | ||||
| } | ||||
| 
 | ||||
| type BackendFactory func (application *Application) (backend Backend, err error) | ||||
|  | ||||
| @ -1,321 +0,0 @@ | ||||
| package pixel | ||||
| 
 | ||||
| import "time" | ||||
| import "golang.org/x/image/font" | ||||
| import "github.com/faiface/pixel" | ||||
| import "github.com/faiface/pixel/text" | ||||
| import "github.com/faiface/pixel/imdraw" | ||||
| import "github.com/faiface/pixel/pixelgl" | ||||
| import "golang.org/x/image/font/basicfont" | ||||
| import "git.tebibyte.media/sashakoshka/stone" | ||||
| 
 | ||||
| // Backend represents an instance of the pixel backend | ||||
| type Backend struct { | ||||
| 	window            *pixelgl.Window | ||||
| 	boundsDirty       bool | ||||
| 	windowBounds      pixel.Vec | ||||
| 	fontFace          font.Face | ||||
| 	application       *stone.Application | ||||
| 	config            *stone.Config | ||||
| 	backgroundStamper *imdraw.IMDraw | ||||
| 	fontAtlas         *text.Atlas | ||||
| 	textDrawer        *text.Text | ||||
| 	showBounds        bool | ||||
| 	showCellBounds    bool | ||||
| 
 | ||||
| 	metrics struct { | ||||
| 		cellWidth  int | ||||
| 		cellHeight int | ||||
| 		padding    int | ||||
| 		paddingX   int | ||||
| 		paddingY   int | ||||
| 		descent    int | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Run satisfies the Run method of the Backend interface. Due to the nature of | ||||
| // pixel, this will forcibly bind to the main thread. | ||||
| func (backend *Backend) Run (callback func (application *stone.Application)) { | ||||
| 	// backend.showBounds     = true | ||||
| 	// backend.showCellBounds = true | ||||
| 	 | ||||
| 	if backend.fontFace == nil { | ||||
| 		backend.fontFace = basicfont.Face7x13 | ||||
| 	} | ||||
| 
 | ||||
| 	backend.backgroundStamper = imdraw.New(nil) | ||||
| 	backend.fontAtlas = text.NewAtlas(backend.fontFace, text.ASCII) | ||||
| 	backend.textDrawer = text.New(pixel.V(0, 0), backend.fontAtlas) | ||||
| 
 | ||||
| 	backend.metrics.descent    = int(backend.fontAtlas.Descent()) | ||||
| 	backend.metrics.cellHeight = int(backend.fontAtlas.LineHeight()) | ||||
| 	// FIXME?: this might not be the best way to get the cell width | ||||
| 	faceAdvance, ok := backend.fontFace.GlyphAdvance('M') | ||||
| 	if ok { | ||||
| 		backend.metrics.cellWidth = faceAdvance.Round() | ||||
| 	} else { | ||||
| 		backend.metrics.cellWidth = backend.metrics.cellHeight / 2 | ||||
| 	} | ||||
| 	 | ||||
| 	pixelgl.Run (func () { | ||||
| 		// construct the window, and all that | ||||
| 		var err error | ||||
| 		backend.window, err = pixelgl.NewWindow (pixelgl.WindowConfig { | ||||
| 			Resizable:   true, | ||||
| 			Undecorated: true, | ||||
| 			VSync:       true, | ||||
| 			NoIconify:   true, | ||||
| 			Title:       backend.application.Title(), | ||||
| 			Bounds:      backend.calculateWindowSize(), | ||||
| 		}) | ||||
| 		backend.Poll() | ||||
| 
 | ||||
| 		// TODO: this should return the error and not panic | ||||
| 		if err != nil { panic(err.Error()) } | ||||
| 		callback(backend.application) | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // Await fulfills the Await method of the Backend interface. | ||||
| func (backend *Backend) Await (timeout time.Duration) (keepRunning bool) { | ||||
| 	if backend.window == nil { | ||||
| 		panic("call to Backend.Await before window exists") | ||||
| 	} | ||||
| 
 | ||||
| 	backend.draw() | ||||
| 	backend.window.UpdateInputWait(timeout) | ||||
| 	backend.processEvents() | ||||
| 	keepRunning = !backend.window.Closed() | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Poll fulfills the Poll method of the Backend interface. | ||||
| func (backend *Backend) Poll () (keepRunning bool) { | ||||
| 	if backend.window == nil { | ||||
| 		panic("call to Backend.Poll before window exists") | ||||
| 	} | ||||
| 
 | ||||
| 	backend.draw() | ||||
| 	backend.window.UpdateInput() | ||||
| 	backend.processEvents() | ||||
| 	keepRunning = !backend.window.Closed() | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // SetTitle fulfills the SetTitle method of the Backend interface. | ||||
| func (backend *Backend) SetTitle (title string) { | ||||
| 	if backend.window != nil { | ||||
| 		backend.window.SetTitle(title) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // JustPressed fulfills the JustPressed method of the Backend interface. | ||||
| func (backend *Backend) JustPressed (button stone.Button) (pressed bool) { | ||||
| 	pressed = backend.window.JustPressed(pixelgl.Button(button)) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // JustReleased fulfills the JustReleased method of the Backend interface. | ||||
| func (backend *Backend) JustReleased (button stone.Button) (released bool) { | ||||
| 	released = backend.window.JustReleased(pixelgl.Button(button)) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Pressed fulfills the Pressed method of the Backend interface. | ||||
| func (backend *Backend) Pressed (button stone.Button) (pressed bool) { | ||||
| 	pressed = backend.window.Pressed(pixelgl.Button(button)) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Repeated fulfills the Repeated method of the Backend interface. | ||||
| func (backend *Backend) Repeated (button stone.Button) (repeated bool) { | ||||
| 	repeated = backend.window.Repeated(pixelgl.Button(button)) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Typed fulfills the Typed method of the Backend interface. | ||||
| func (backend *Backend) Typed () (text string) { | ||||
| 	text = backend.window.Typed() | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Resized fulfills the Resized method of the Backend interface. | ||||
| func (backend *Backend) Resized () (resized bool) { | ||||
| 	resized = backend.boundsDirty | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // MousePosition fulfills the MousePosition method of the Backend interface. | ||||
| func (backend *Backend) MousePosition () (x, y int) { | ||||
| 	vector := backend.window.MousePosition() | ||||
| 	x = int ( | ||||
| 		(vector.X - float64(backend.metrics.paddingX)) / | ||||
| 		float64(backend.metrics.cellWidth)) | ||||
| 	y = int ( | ||||
| 		(backend.windowBounds.Y - | ||||
| 			vector.Y - | ||||
| 			float64(backend.metrics.paddingY)) / | ||||
| 		float64(backend.metrics.cellHeight)) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // draw renders updates to the screen. | ||||
| func (backend *Backend) draw () { | ||||
| 	// didDrawing := false | ||||
| 	width, height := backend.application.Size() | ||||
| 	 | ||||
| 	if backend.boundsDirty { | ||||
| 		backend.window.Clear ( | ||||
| 			backend.config.Color(stone.ColorApplication)) | ||||
| 		backend.boundsDirty = false | ||||
| 		// didDrawing = true | ||||
| 	} else { | ||||
| 		// clear out dirty cells before drawing them. this is in an else | ||||
| 		// block because if the bounds were dirty, we have already | ||||
| 		// cleared the entire screen. | ||||
| 
 | ||||
| 		backend.backgroundStamper.Clear() | ||||
| 		backend.backgroundStamper.Color = | ||||
| 			backend.config.Color(stone.ColorApplication) | ||||
| 		for x := 0; x < width; x ++ { | ||||
| 			for y := 0; y < height; y ++ { | ||||
| 				clean := backend.application.Clean(x, y) | ||||
| 				if clean { continue } | ||||
| 
 | ||||
| 				backend.backgroundStamper.Push ( | ||||
| 					backend.vectorAtPosition(x, y), | ||||
| 					backend.vectorAtPosition(x + 1, y + 1)) | ||||
| 				backend.backgroundStamper.Rectangle(0) | ||||
| 				// didDrawing = true | ||||
| 
 | ||||
| 				if backend.showCellBounds { | ||||
| 					backend.backgroundStamper.Color = | ||||
| 					backend.config.Color(stone.ColorForeground) | ||||
| 					backend.backgroundStamper.Push ( | ||||
| 						backend.vectorAtPosition(x, y), | ||||
| 						backend.vectorAtPosition(x + 1, y + 1)) | ||||
| 					backend.backgroundStamper.Rectangle(1) | ||||
| 					backend.backgroundStamper.Color = | ||||
| 						backend.config.Color(stone.ColorApplication) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		backend.backgroundStamper.Draw(backend.window) | ||||
| 	} | ||||
| 
 | ||||
| 	backend.textDrawer.Clear() | ||||
| 	backend.textDrawer.Color =  | ||||
| 		backend.config.Color(stone.ColorForeground) | ||||
| 	for x := 0; x < width; x ++ { | ||||
| 		for y := 0; y < height; y ++ { | ||||
| 			clean := backend.application.Clean(x, y) | ||||
| 			if clean { continue } | ||||
| 			backend.application.MarkClean(x, y) | ||||
| 
 | ||||
| 			cell := backend.application.Cell(x, y) | ||||
| 			content := cell.Rune() | ||||
| 			if content < 32 { continue } | ||||
| 			 | ||||
| 			// draw cell | ||||
| 			backend.textDrawer.Dot = pixel.V ( | ||||
| 				float64 ( | ||||
| 					x * backend.metrics.cellWidth + | ||||
| 					backend.metrics.paddingX), | ||||
| 				backend.windowBounds.Y - float64 ( | ||||
| 					(y + 1) * backend.metrics.cellHeight + | ||||
| 					backend.metrics.paddingY - | ||||
| 					backend.metrics.descent)) | ||||
| 			 | ||||
| 			backend.textDrawer.WriteRune(content) | ||||
| 			backend.textDrawer.Draw(backend.window, pixel.IM) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// draw a rectangle around the buffer if we are showing bounds | ||||
| 	if backend.showBounds { | ||||
| 		backend.backgroundStamper.Clear() | ||||
| 		backend.backgroundStamper.Color = | ||||
| 			backend.config.Color(stone.ColorBackground) | ||||
| 		backend.backgroundStamper.Push ( | ||||
| 			backend.vectorAtPosition(0, 0), | ||||
| 			backend.vectorAtPosition(width, height)) | ||||
| 		backend.backgroundStamper.Rectangle(1) | ||||
| 		backend.backgroundStamper.Draw(backend.window) | ||||
| 	} | ||||
| 
 | ||||
| 	backend.window.SwapBuffers() | ||||
| } | ||||
| 
 | ||||
| // processEvents reacts to events recieved from pixel, resizing and | ||||
| // recalculating things as need be. | ||||
| func (backend *Backend) processEvents () { | ||||
| 	newBounds := backend.window.Bounds().Max | ||||
| 	backend.boundsDirty = backend.windowBounds != newBounds | ||||
| 	backend.windowBounds = newBounds | ||||
| 	 | ||||
| 	if backend.boundsDirty { | ||||
| 		// calculate padding | ||||
| 		backend.metrics.padding = | ||||
| 			backend.config.Padding() * | ||||
| 			backend.metrics.cellWidth | ||||
| 		deadArea := float64(backend.metrics.padding * 2) | ||||
| 
 | ||||
| 		// calculate new width and height for buffer | ||||
| 		width := int ( | ||||
| 			(backend.windowBounds.X - deadArea) / | ||||
| 			float64(backend.metrics.cellWidth)) | ||||
| 		height := int ( | ||||
| 			(backend.windowBounds.Y - deadArea) / | ||||
| 			float64(backend.metrics.cellHeight)) | ||||
| 		backend.application.SetSize(width, height) | ||||
| 
 | ||||
| 		// position buffer in center of screen | ||||
| 		frameWidth  := width * backend.metrics.cellWidth | ||||
| 		frameHeight := height * backend.metrics.cellHeight | ||||
| 		backend.metrics.paddingX = (int(backend.windowBounds.X) - frameWidth)  / 2 | ||||
| 		backend.metrics.paddingY = (int(backend.windowBounds.Y) - frameHeight) / 2 | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // vectorAtPosition generates a pixel vector at the top left of the specified | ||||
| // cell. | ||||
| func (backend *Backend) vectorAtPosition (x, y int) (vector pixel.Vec) { | ||||
| 	vector = pixel.V ( | ||||
| 		float64 ( | ||||
| 			x * backend.metrics.cellWidth + | ||||
| 			backend.metrics.paddingX), | ||||
| 		backend.windowBounds.Y - float64 ( | ||||
| 			y * backend.metrics.cellHeight + | ||||
| 			backend.metrics.paddingY)) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // calculateWindowSize calculates window bounds based on the internal buffer | ||||
| // size. | ||||
| func (backend *Backend) calculateWindowSize () (bounds pixel.Rect) { | ||||
| 	width, height := backend.application.Size() | ||||
| 	bounds = pixel.R ( | ||||
| 		0, 0, | ||||
| 		float64 ( | ||||
| 			width  * backend.metrics.cellWidth + | ||||
| 			backend.metrics.padding * 2), | ||||
| 		float64 ( | ||||
| 			height * backend.metrics.cellHeight + | ||||
| 			backend.metrics.padding * 2)) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // factory instantiates a pixel backend. | ||||
| func factory (application *stone.Application) (output stone.Backend, err error) { | ||||
| 	backend := &Backend { | ||||
| 		application: application, | ||||
| 		config:      application.Config(), | ||||
| 	} | ||||
| 	output = backend | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // init registers this backend when the program starts. | ||||
| func init () { | ||||
| 	stone.RegisterBackend(factory) | ||||
| } | ||||
							
								
								
									
										142
									
								
								backends/x/draw.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								backends/x/draw.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,142 @@ | ||||
| package x | ||||
| 
 | ||||
| import "image" | ||||
| import "image/draw" | ||||
| import "golang.org/x/image/math/fixed" | ||||
| 
 | ||||
| import "git.tebibyte.media/sashakoshka/stone" | ||||
| 
 | ||||
| func (backend *Backend) Draw () { | ||||
| 	backend.drawLock.Lock() | ||||
| 	defer backend.drawLock.Unlock() | ||||
| 
 | ||||
| 	boundsChanged := | ||||
| 		backend.memory.windowWidth  != backend.metrics.windowWidth || | ||||
| 		backend.memory.windowHeight != backend.metrics.windowHeight | ||||
| 	backend.memory.windowWidth  = backend.metrics.windowWidth | ||||
| 	backend.memory.windowHeight = backend.metrics.windowHeight | ||||
| 
 | ||||
| 	if boundsChanged { | ||||
| 		backend.reallocateCanvas() | ||||
| 		backend.drawCells(true) | ||||
| 		backend.canvas.XDraw() | ||||
| 		backend.canvas.XPaint(backend.window.Id) | ||||
| 	} else { | ||||
| 		backend.updateWindowAreas(backend.drawCells(false)...) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) updateWindowAreas (areas ...image.Rectangle) { | ||||
| 	backend.canvas.XPaintRects(backend.window.Id, areas...) | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) drawRune (x, y int, character rune) { | ||||
| 	// TODO: cache these draws as non-transparent buffers with the | ||||
| 	// application background color as the background. that way, we won't | ||||
| 	// need to redraw the characters *or* composite them. | ||||
| 	 | ||||
| 	fillRectangle ( | ||||
| 		&image.Uniform { | ||||
| 			C: backend.config.Color(stone.ColorApplication), | ||||
| 		}, | ||||
| 		backend.canvas, | ||||
| 		backend.boundsOfCell(x, y)) | ||||
| 
 | ||||
| 	if character < 32 { return } | ||||
| 	 | ||||
| 	origin := backend.originOfCell(x, y + 1) | ||||
| 	destinationRectangle, mask, maskPoint, _, _ := backend.font.face.Glyph ( | ||||
| 		fixed.Point26_6 { | ||||
| 			X: fixed.I(origin.X), | ||||
| 			Y: fixed.I(origin.Y - backend.metrics.descent), | ||||
| 		}, | ||||
| 		character) | ||||
| 
 | ||||
| 	if backend.drawCellBounds {	 | ||||
| 		strokeRectangle ( | ||||
| 			&image.Uniform { | ||||
| 				C: backend.config.Color(stone.ColorForeground), | ||||
| 			}, | ||||
| 			backend.canvas, | ||||
| 			backend.boundsOfCell(x, y)) | ||||
| 	} | ||||
| 		 | ||||
| 	draw.DrawMask ( | ||||
| 		backend.canvas, | ||||
| 		destinationRectangle, | ||||
| 		&image.Uniform { | ||||
| 			C: backend.config.Color(stone.ColorForeground), | ||||
| 		}, | ||||
| 		image.Point { }, | ||||
| 		mask, | ||||
| 		maskPoint, | ||||
| 		draw.Over) | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) drawCells (forceRedraw bool) (areas []image.Rectangle) { | ||||
| 	width, height := backend.application.Size() | ||||
| 	for y := 0; y < height; y ++ { | ||||
| 	for x := 0; x < width;  x ++ { | ||||
| 		if !forceRedraw && backend.application.Clean(x, y) { continue } | ||||
| 		backend.application.MarkClean(x, y) | ||||
| 
 | ||||
| 		cell := backend.application.Cell(x, y) | ||||
| 		content := cell.Rune() | ||||
| 
 | ||||
| 		if forceRedraw && content < 32 { continue } | ||||
| 
 | ||||
| 		areas = append(areas, backend.boundsOfCell(x, y)) | ||||
| 		backend.drawRune(x, y, content) | ||||
| 	}} | ||||
| 
 | ||||
| 	if backend.drawBufferBounds && forceRedraw { | ||||
| 		strokeRectangle ( | ||||
| 			&image.Uniform { | ||||
| 				C: backend.config.Color(stone.ColorForeground), | ||||
| 			}, | ||||
| 			backend.canvas, | ||||
| 			image.Rectangle { | ||||
| 				Min: backend.originOfCell(0, 0), | ||||
| 				Max: backend.originOfCell(width, height), | ||||
| 			}) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func fillRectangle ( | ||||
| 	source      image.Image, | ||||
| 	destination draw.Image, | ||||
| 	bounds      image.Rectangle, | ||||
| ) { | ||||
| 	for y := bounds.Min.Y; y < bounds.Max.Y; y ++ { | ||||
| 	for x := bounds.Min.X; x < bounds.Max.X; x ++ { | ||||
| 		destination.Set(x, y, source.At(x, y)) | ||||
| 	}} | ||||
| } | ||||
| 
 | ||||
| func strokeRectangle ( | ||||
| 	source      image.Image, | ||||
| 	destination draw.Image, | ||||
| 	bounds      image.Rectangle, | ||||
| ) { | ||||
| 	x := 0 | ||||
| 	y := bounds.Min.Y | ||||
| 	for x = bounds.Min.X; x < bounds.Max.X; x ++ { | ||||
| 		destination.Set(x, y, source.At(x, y)) | ||||
| 	} | ||||
| 
 | ||||
| 	y = bounds.Max.Y - 1 | ||||
| 	for x = bounds.Min.X; x < bounds.Max.X; x ++ { | ||||
| 		destination.Set(x, y, source.At(x, y)) | ||||
| 	} | ||||
| 
 | ||||
| 	x = bounds.Min.X | ||||
| 	for y = bounds.Min.Y; y < bounds.Max.Y; y ++ { | ||||
| 		destination.Set(x, y, source.At(x, y)) | ||||
| 	} | ||||
| 
 | ||||
| 	x = bounds.Max.X - 1 | ||||
| 	for y = bounds.Min.Y; y < bounds.Max.Y; y ++ { | ||||
| 		destination.Set(x, y, source.At(x, y)) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										131
									
								
								backends/x/event.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								backends/x/event.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | ||||
| package x | ||||
| 
 | ||||
| import "image" | ||||
| 
 | ||||
| import "github.com/jezek/xgbutil" | ||||
| import "github.com/jezek/xgb/xproto" | ||||
| import "github.com/jezek/xgbutil/xevent" | ||||
| 
 | ||||
| import "git.tebibyte.media/sashakoshka/stone" | ||||
| 
 | ||||
| func (backend *Backend) Run (channel chan(stone.Event)) { | ||||
| 	backend.channel = channel | ||||
| 	xevent.Main(backend.connection) | ||||
| 	backend.shutDown() | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| func (backend *Backend) handleConfigureNotify ( | ||||
| 	connection *xgbutil.XUtil, | ||||
| 	event xevent.ConfigureNotifyEvent, | ||||
| ) { | ||||
| 	configureEvent := *event.ConfigureNotifyEvent | ||||
| 	 | ||||
| 	newWidth  := int(configureEvent.Width) | ||||
| 	newHeight := int(configureEvent.Height) | ||||
| 	sizeChanged := | ||||
| 		backend.metrics.windowWidth  != newWidth || | ||||
| 		backend.metrics.windowHeight != newHeight | ||||
| 	backend.metrics.windowWidth  = newWidth | ||||
| 	backend.metrics.windowHeight = newHeight | ||||
| 
 | ||||
| 	if sizeChanged { | ||||
| 		configureEvent = | ||||
| 			backend.compressConfigureNotify(configureEvent) | ||||
| 
 | ||||
| 		// resize buffer | ||||
| 		width, height := backend.calculateBufferSize() | ||||
| 		backend.application.SetSize(width, height) | ||||
| 
 | ||||
| 		// position buffer in the center of the screen | ||||
| 		frameWidth  := width  * backend.metrics.cellWidth | ||||
| 		frameHeight := height * backend.metrics.cellHeight | ||||
| 		backend.metrics.paddingX = | ||||
| 			(backend.metrics.windowWidth - frameWidth) / 2 | ||||
| 		backend.metrics.paddingY = | ||||
| 			(backend.metrics.windowHeight - frameHeight) / 2 | ||||
| 		 | ||||
| 		backend.channel <- stone.EventResize { } | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) handleButtonPress ( | ||||
| 	connection *xgbutil.XUtil, | ||||
| 	event xevent.ButtonPressEvent, | ||||
| ) { | ||||
| 	buttonEvent := *event.ButtonPressEvent | ||||
| 	backend.channel <- stone.EventPress { | ||||
| 		Button: stone.Button(buttonEvent.Detail + 127), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) handleButtonRelease ( | ||||
| 	connection *xgbutil.XUtil, | ||||
| 	event xevent.ButtonReleaseEvent, | ||||
| ) { | ||||
| 	buttonEvent := *event.ButtonReleaseEvent | ||||
| 	backend.channel <- stone.EventRelease { | ||||
| 		Button: stone.Button(buttonEvent.Detail + 127), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) handleKeyPress ( | ||||
| 	connection *xgbutil.XUtil, | ||||
| 	event xevent.KeyPressEvent, | ||||
| ) { | ||||
| 	keyEvent := *event.KeyPressEvent | ||||
| 	button   := backend.keycodeToButton(keyEvent.Detail, keyEvent.State) | ||||
| 	backend.channel <- stone.EventPress { Button: button } | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) handleKeyRelease ( | ||||
| 	connection *xgbutil.XUtil, | ||||
| 	event xevent.KeyReleaseEvent, | ||||
| ) { | ||||
| 	keyEvent := *event.KeyReleaseEvent | ||||
| 	button   := backend.keycodeToButton(keyEvent.Detail, keyEvent.State) | ||||
| 	backend.channel <- stone.EventRelease { Button: button } | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) handleMotionNotify ( | ||||
| 	connection *xgbutil.XUtil, | ||||
| 	event xevent.MotionNotifyEvent, | ||||
| ) { | ||||
| 	motionEvent := *event.MotionNotifyEvent | ||||
| 	x, y := backend.cellAt (image.Point { | ||||
| 		X: int(motionEvent.EventX), | ||||
| 		Y: int(motionEvent.EventY), | ||||
| 	}) | ||||
| 	backend.channel <- stone.EventMouseMove { | ||||
| 		X: x, | ||||
| 		Y: y, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) compressConfigureNotify ( | ||||
| 	firstEvent xproto.ConfigureNotifyEvent, | ||||
| ) ( | ||||
| 	lastEvent xproto.ConfigureNotifyEvent, | ||||
| ) { | ||||
| 	backend.connection.Sync() | ||||
| 	xevent.Read(backend.connection, false) | ||||
| 	lastEvent = firstEvent | ||||
| 	 | ||||
| 	for index, untypedEvent := range xevent.Peek(backend.connection) { | ||||
| 		if untypedEvent.Err != nil { continue } | ||||
| 		 | ||||
| 		typedEvent, ok := untypedEvent.Event.(xproto.ConfigureNotifyEvent) | ||||
| 		if !ok { continue } | ||||
| 
 | ||||
| 		lastEvent = typedEvent | ||||
| 		defer func (index int) { | ||||
| 			xevent.DequeueAt(backend.connection, index) | ||||
| 		} (index) | ||||
| 	} | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) shutDown () { | ||||
| 	backend.channel <- stone.EventQuit { } | ||||
| } | ||||
							
								
								
									
										159
									
								
								backends/x/factory.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								backends/x/factory.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,159 @@ | ||||
| package x | ||||
| 
 | ||||
| import "os" | ||||
| import "golang.org/x/image/font" | ||||
| import "golang.org/x/image/font/opentype" | ||||
| import "golang.org/x/image/font/basicfont" | ||||
| 
 | ||||
| import "github.com/jezek/xgbutil" | ||||
| import "github.com/jezek/xgb/xproto" | ||||
| import "github.com/jezek/xgbutil/icccm" | ||||
| import "github.com/jezek/xgbutil/xevent" | ||||
| import "github.com/jezek/xgbutil/xwindow" | ||||
| import "github.com/jezek/xgbutil/keybind" | ||||
| import "github.com/jezek/xgbutil/xgraphics" | ||||
| 
 | ||||
| import "git.tebibyte.media/sashakoshka/stone" | ||||
| 
 | ||||
| import "github.com/flopp/go-findfont" | ||||
| 
 | ||||
| // factory instantiates an X backend. | ||||
| func factory (application *stone.Application) (output stone.Backend, err error) { | ||||
| 	backend := &Backend { | ||||
| 		application: application, | ||||
| 		config:      application.Config(), | ||||
| 	} | ||||
| 
 | ||||
| 	// load font | ||||
| 	backend.font.face = findAndLoadFont ( | ||||
| 		backend.config.FontName(), | ||||
| 		float64(backend.config.FontSize())) | ||||
| 	if backend.font.face == nil { | ||||
| 		backend.font.face = basicfont.Face7x13 | ||||
| 	} | ||||
| 
 | ||||
| 	// pre-calculate colors | ||||
| 	for index := 0; index < len(backend.colors); index ++ { | ||||
| 		color := backend.config.Color(stone.Color(index)) | ||||
| 		r, g, b, a := color.RGBA() | ||||
| 		r >>= 8 | ||||
| 		g >>= 8 | ||||
| 		b >>= 8 | ||||
| 		a >>= 8 | ||||
| 		backend.colors[index] = xgraphics.BGRA { | ||||
| 			R: uint8(r), | ||||
| 			G: uint8(g), | ||||
| 			B: uint8(b), | ||||
| 			A: uint8(a), | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// calculate metrics | ||||
| 	metrics := backend.font.face.Metrics() | ||||
| 	glyphAdvance, _ := backend.font.face.GlyphAdvance('M') | ||||
| 	backend.metrics.cellWidth  = glyphAdvance.Round() | ||||
| 	backend.metrics.cellHeight = metrics.Height.Round() | ||||
| 	backend.metrics.descent    = metrics.Descent.Round() | ||||
| 	backend.metrics.padding = | ||||
| 		backend.config.Padding() * | ||||
| 		backend.metrics.cellHeight | ||||
| 	backend.metrics.paddingX = backend.metrics.padding | ||||
| 	backend.metrics.paddingY = backend.metrics.padding | ||||
| 	backend.metrics.windowWidth, | ||||
| 	backend.metrics.windowHeight = backend.calculateWindowSize() | ||||
| 
 | ||||
| 	// connect to X | ||||
| 	backend.connection, err = xgbutil.NewConn() | ||||
| 	if err != nil { return } | ||||
| 	backend.window, err = xwindow.Generate(backend.connection) | ||||
| 	if err != nil { return } | ||||
| 	keybind.Initialize(backend.connection) | ||||
| 
 | ||||
| 	// create the window | ||||
| 	backend.window.Create ( | ||||
| 		backend.connection.RootWin(), | ||||
| 		0, 0, | ||||
| 		backend.metrics.windowWidth, backend.metrics.windowHeight, | ||||
| 		0) | ||||
| 	backend.window.Map() | ||||
| 	// TODO: also listen to mouse movement (compressed) and mouse and | ||||
| 	// keyboard buttons (uncompressed) | ||||
| 	err = backend.window.Listen ( | ||||
| 		xproto.EventMaskStructureNotify, | ||||
| 		xproto.EventMaskPointerMotion, | ||||
| 		xproto.EventMaskKeyPress, | ||||
| 		xproto.EventMaskKeyRelease, | ||||
| 		xproto.EventMaskButtonPress, | ||||
| 		xproto.EventMaskButtonRelease) | ||||
| 	backend.SetTitle(application.Title()) | ||||
| 	backend.SetIcon(application.Icon()) | ||||
| 	if err != nil { return } | ||||
| 
 | ||||
| 	// set minimum dimensions | ||||
| 	minWidth :=  | ||||
| 		backend.metrics.cellWidth  + backend.metrics.padding * 2 | ||||
| 	minHeight :=  | ||||
| 		backend.metrics.cellHeight + backend.metrics.padding * 2 | ||||
| 	err = icccm.WmNormalHintsSet ( | ||||
| 		backend.connection, | ||||
| 		backend.window.Id, | ||||
| 		&icccm.NormalHints { | ||||
| 			Flags:     icccm.SizeHintPMinSize, | ||||
| 			MinWidth:  uint(minWidth), | ||||
| 			MinHeight: uint(minHeight), | ||||
| 		}) | ||||
| 	if err != nil { return } | ||||
| 
 | ||||
| 	// create a canvas | ||||
| 	backend.reallocateCanvas() | ||||
| 
 | ||||
| 	// attatch graceful close handler | ||||
| 	backend.window.WMGracefulClose (func (window *xwindow.Window) { | ||||
| 		backend.window.Destroy() | ||||
| 		backend.shutDown() | ||||
| 	}) | ||||
| 
 | ||||
| 	// attatch event handlers | ||||
| 	xevent.ConfigureNotifyFun(backend.handleConfigureNotify). | ||||
| 		Connect(backend.connection, backend.window.Id) | ||||
| 	xevent.ButtonPressFun(backend.handleButtonPress). | ||||
| 		Connect(backend.connection, backend.window.Id) | ||||
| 	xevent.ButtonReleaseFun(backend.handleButtonRelease). | ||||
| 		Connect(backend.connection, backend.window.Id) | ||||
| 	xevent.MotionNotifyFun(backend.handleMotionNotify). | ||||
| 		Connect(backend.connection, backend.window.Id) | ||||
| 	xevent.KeyPressFun(backend.handleKeyPress). | ||||
| 		Connect(backend.connection, backend.window.Id) | ||||
| 	xevent.KeyReleaseFun(backend.handleKeyRelease). | ||||
| 		Connect(backend.connection, backend.window.Id) | ||||
| 
 | ||||
| 	// uncomment these to draw debug bounds | ||||
| 	// backend.drawCellBounds = true | ||||
| 	// backend.drawBufferBounds = true | ||||
| 	 | ||||
| 	output = backend | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func findAndLoadFont (name string, size float64) (face font.Face) { | ||||
| 	if name == "" { return } | ||||
| 	fontPath, err := findfont.Find(name) | ||||
| 	if err != nil { return } | ||||
| 	fontFile, err := os.Open(fontPath) | ||||
| 	if err != nil { return } | ||||
| 	fontObject, err := opentype.ParseReaderAt(fontFile) | ||||
| 	if err != nil { return } | ||||
| 	face, err = opentype.NewFace (fontObject, &opentype.FaceOptions { | ||||
| 		Size: size, | ||||
| 		DPI: 96, | ||||
| 		Hinting: font.HintingFull, | ||||
| 	}) | ||||
| 	if err != nil { face = nil } | ||||
| 	 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // init registers this backend when the program starts. | ||||
| func init () { | ||||
| 	stone.RegisterBackend(factory) | ||||
| } | ||||
							
								
								
									
										187
									
								
								backends/x/unicode.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								backends/x/unicode.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,187 @@ | ||||
| package x | ||||
| 
 | ||||
| import "unicode" | ||||
| import "github.com/jezek/xgb/xproto" | ||||
| import "github.com/jezek/xgbutil/keybind" | ||||
| import "git.tebibyte.media/sashakoshka/stone" | ||||
| 
 | ||||
| // when making changes to this file, look at keysymdef.h and | ||||
| // https://tronche.com/gui/x/xlib/input/keyboard-encoding.html | ||||
| 
 | ||||
| var buttonCodeTable = map[xproto.Keysym] stone.Button { | ||||
| 	0xFFFFFF: stone.ButtonUnknown, | ||||
| 
 | ||||
| 	0xFF63: stone.KeyInsert, | ||||
| 	0xFF67: stone.KeyMenu, | ||||
| 	0xFF61: stone.KeyPrintScreen, | ||||
| 	0xFF6B: stone.KeyPause, | ||||
| 	0xFFE5: stone.KeyCapsLock, | ||||
| 	0xFF14: stone.KeyScrollLock, | ||||
| 	0xFF7F: stone.KeyNumLock, | ||||
| 	0xFF08: stone.KeyBackspace, | ||||
| 	0xFF09: stone.KeyTab, | ||||
| 	0xFF0D: stone.KeyEnter, | ||||
| 	0xFF1B: stone.KeyEscape, | ||||
| 	 | ||||
| 	0xFF52: stone.KeyUp, | ||||
| 	0xFF54: stone.KeyDown, | ||||
| 	0xFF51: stone.KeyLeft, | ||||
| 	0xFF53: stone.KeyRight, | ||||
| 	0xFF55: stone.KeyPageUp, | ||||
| 	0xFF56: stone.KeyPageDown, | ||||
| 	0xFF50: stone.KeyHome, | ||||
| 	0xFF57: stone.KeyEnd, | ||||
| 	 | ||||
| 	0xFFE1: stone.KeyLeftShift, | ||||
| 	0xFFE2: stone.KeyRightShift, | ||||
| 	0xFFE3: stone.KeyLeftControl, | ||||
| 	0xFFE4: stone.KeyRightControl, | ||||
| 	0xFFE9: stone.KeyLeftAlt, | ||||
| 	0xFFEA: stone.KeyRightAlt, | ||||
| 	0xFFEB: stone.KeyLeftSuper, | ||||
| 	0xFFEC: stone.KeyRightSuper, | ||||
| 	 | ||||
| 	0xFFFF: stone.KeyDelete, | ||||
| 	 | ||||
| 	0xFFBE: stone.KeyF1, | ||||
| 	0xFFBF: stone.KeyF2, | ||||
| 	0xFFC0: stone.KeyF3, | ||||
| 	0xFFC1: stone.KeyF4, | ||||
| 	0xFFC2: stone.KeyF5, | ||||
| 	0xFFC3: stone.KeyF6, | ||||
| 	0xFFC4: stone.KeyF7, | ||||
| 	0xFFC5: stone.KeyF8, | ||||
| 	0xFFC6: stone.KeyF9, | ||||
| 	0xFFC7: stone.KeyF10, | ||||
| 	0xFFC8: stone.KeyF11, | ||||
| 	0xFFC9: stone.KeyF12, | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) keycodeToButton ( | ||||
| 	keycode xproto.Keycode, | ||||
| 	state   uint16, | ||||
| ) ( | ||||
| 	button stone.Button, | ||||
| ) { | ||||
| 	// FIXME: also set shift to true if the lock modifier is on and the lock | ||||
| 	// modifier is interpreted as shiftLock | ||||
| 	shift    := state & xproto.ModMaskShift > 0 | ||||
| 
 | ||||
| 	// FIXME: only set this to true if the lock modifier is on and the lock | ||||
| 	// modifier is interpreted as capsLock | ||||
| 	capsLock := state & xproto.ModMaskLock  > 0 | ||||
| 
 | ||||
| 	symbol1 := keybind.KeysymGet(backend.connection, keycode, 0) | ||||
| 	symbol2 := keybind.KeysymGet(backend.connection, keycode, 1) | ||||
| 	symbol3 := keybind.KeysymGet(backend.connection, keycode, 2) | ||||
| 	symbol4 := keybind.KeysymGet(backend.connection, keycode, 3) | ||||
| 
 | ||||
| 	cased := false | ||||
| 
 | ||||
| 	// third paragraph | ||||
| 	switch { | ||||
| 	case symbol2 == 0 && symbol3 == 0 && symbol4 == 0: | ||||
| 		symbol3 = symbol1 | ||||
| 	case symbol3 == 0 && symbol4 == 0: | ||||
| 		symbol3 = symbol1 | ||||
| 		symbol2 = symbol2 | ||||
| 	case symbol4 == 0: | ||||
| 		symbol4 = 0 | ||||
| 	} | ||||
| 
 | ||||
| 	symbol1Rune := keysymToRune(symbol1) | ||||
| 	symbol2Rune := keysymToRune(symbol2) | ||||
| 	symbol3Rune := keysymToRune(symbol3) | ||||
| 	symbol4Rune := keysymToRune(symbol4) | ||||
| 
 | ||||
| 	// FIXME: we ignore mode switch stuff | ||||
| 	_ = symbol4Rune | ||||
| 
 | ||||
| 	// fourth paragraph | ||||
| 	if symbol2 == 0 { | ||||
| 		upper := unicode.IsUpper(symbol1Rune) | ||||
| 		lower := unicode.IsLower(symbol1Rune) | ||||
| 		if upper || lower { | ||||
| 			symbol1Rune = unicode.ToLower(symbol1Rune) | ||||
| 			symbol2Rune = unicode.ToUpper(symbol1Rune) | ||||
| 			cased = true | ||||
| 		} else { | ||||
| 			symbol2 = symbol1 | ||||
| 		} | ||||
| 	} | ||||
| 	if symbol4 == 0 { | ||||
| 		upper := unicode.IsUpper(symbol3Rune) | ||||
| 		lower := unicode.IsLower(symbol3Rune) | ||||
| 		if upper || lower { | ||||
| 			symbol3Rune = unicode.ToLower(symbol3Rune) | ||||
| 			symbol4Rune = unicode.ToUpper(symbol3Rune) | ||||
| 			cased = true | ||||
| 		} else { | ||||
| 			symbol4 = symbol3 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	var selectedKeysym xproto.Keysym | ||||
| 	var selectedRune   rune | ||||
| 	 | ||||
| 	// big ol list in the middle | ||||
| 	switch { | ||||
| 	// FIXME: take into account numlock | ||||
| 	case !shift && !capsLock: | ||||
| 		selectedKeysym = symbol1 | ||||
| 		selectedRune   = symbol1Rune | ||||
| 		 | ||||
| 	case !shift && capsLock: | ||||
| 		if cased && unicode.IsLower(symbol1Rune) { | ||||
| 			selectedRune = symbol2Rune | ||||
| 		} else { | ||||
| 			selectedKeysym = symbol1 | ||||
| 			selectedRune   = symbol1Rune | ||||
| 		} | ||||
| 		 | ||||
| 	case shift && capsLock: | ||||
| 		if cased && unicode.IsLower(symbol2Rune) { | ||||
| 			selectedRune = unicode.ToUpper(symbol2Rune) | ||||
| 		} else { | ||||
| 			selectedKeysym = symbol2 | ||||
| 			selectedRune   = symbol2Rune | ||||
| 		} | ||||
| 		 | ||||
| 	case shift: | ||||
| 		selectedKeysym = symbol2 | ||||
| 		selectedRune   = symbol2Rune | ||||
| 	} | ||||
| 
 | ||||
| 	// look up in table | ||||
| 	var isControl bool | ||||
| 	button, isControl = buttonCodeTable[selectedKeysym] | ||||
| 
 | ||||
| 	// if it wasn't found,  | ||||
| 	if !isControl { | ||||
| 		button = stone.Button(selectedRune) | ||||
| 	} | ||||
| 	 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func keysymToRune (keysym xproto.Keysym) (character rune) { | ||||
| 	// X keysyms like 0xFF.. or 0xFE.. are non-character keys. these cannot | ||||
| 	// be converted so we return a zero. | ||||
| 	if (keysym >> 8) == 0xFF || (keysym >> 8) == 0xFE { | ||||
| 		character = 0 | ||||
| 		return | ||||
| 	} | ||||
| 	 | ||||
| 	// some X keysyms have a single bit set to 1 here. i believe this is to | ||||
| 	// prevent conflicts with existing codes. if we mask it off we will get | ||||
| 	// a correct utf-32 code point. | ||||
| 	if keysym & 0xF000000 == 0x1000000 { | ||||
| 		character = rune(keysym & 0x0111111) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// if none of these things happened, we can safely (i think) assume that | ||||
| 	// the keysym is an exact utf-32 code point. | ||||
| 	character = rune(keysym) | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										178
									
								
								backends/x/x.go
									
									
									
									
									
								
							
							
						
						
									
										178
									
								
								backends/x/x.go
									
									
									
									
									
								
							| @ -1,10 +1,13 @@ | ||||
| package x | ||||
| 
 | ||||
| // import "fmt" | ||||
| import "sync" | ||||
| import "image" | ||||
| import "golang.org/x/image/font" | ||||
| 
 | ||||
| // import "github.com/jezek/xgb" | ||||
| import "github.com/jezek/xgbutil" | ||||
| // import "github.com/jezek/xgbutil/ewmh" | ||||
| import "github.com/jezek/xgbutil/xevent" | ||||
| import "github.com/jezek/xgbutil/ewmh" | ||||
| import "github.com/jezek/xgbutil/xwindow" | ||||
| import "github.com/jezek/xgbutil/xgraphics" | ||||
| 
 | ||||
| @ -18,13 +21,20 @@ type Backend struct { | ||||
| 	canvas      *xgraphics.Image | ||||
| 	channel     chan(stone.Event) | ||||
| 
 | ||||
| 	ping struct { | ||||
| 		before chan(struct { }) | ||||
| 		after  chan(struct { }) | ||||
| 		quit   chan(struct { }) | ||||
| 	drawCellBounds   bool | ||||
| 	drawBufferBounds bool | ||||
| 
 | ||||
| 	drawLock sync.Mutex | ||||
| 
 | ||||
| 	font struct { | ||||
| 		face font.Face | ||||
| 	} | ||||
| 
 | ||||
| 	colors [4]xgraphics.BGRA | ||||
| 	 | ||||
| 	metrics struct { | ||||
| 		windowWidth  int | ||||
| 		windowHeight int | ||||
| 		cellWidth    int | ||||
| 		cellHeight   int | ||||
| 		padding      int | ||||
| @ -32,33 +42,53 @@ type Backend struct { | ||||
| 		paddingY     int | ||||
| 		descent      int | ||||
| 	} | ||||
| 
 | ||||
| 	memory struct { | ||||
| 		windowWidth  int | ||||
| 		windowHeight int | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) Run (channel chan(stone.Event)) { | ||||
| 	backend.channel = channel | ||||
| 	 | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <- backend.ping.before: | ||||
| 			<- backend.ping.after | ||||
| 
 | ||||
| 		case <- backend.ping.quit: | ||||
| 			backend.shutDown() | ||||
| func (backend *Backend) SetTitle (title string) (err error) { | ||||
| 	err = ewmh.WmNameSet(backend.connection, backend.window.Id, title) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) SetIcon (icons []image.Image) (err error) { | ||||
| 	wmIcons := []ewmh.WmIcon { } | ||||
| 	 | ||||
| 	for _, icon := range icons { | ||||
| 		width  := icon.Bounds().Max.X | ||||
| 		height := icon.Bounds().Max.Y | ||||
| 		wmIcon := ewmh.WmIcon { | ||||
| 			Width:  uint(width), | ||||
| 			Height: uint(height), | ||||
| 			Data:   make ([]uint, width * height), | ||||
| 		} | ||||
| 
 | ||||
| 		// manually convert image data beacuse of course we have to do | ||||
| 		// this | ||||
| 		index := 0 | ||||
| 		for y := 0; y < height; y ++ { | ||||
| 		for x := 0; x < width;  x ++ { | ||||
| 			r, g, b, a := icon.At(x, y).RGBA() | ||||
| 			r >>= 8 | ||||
| 			g >>= 8 | ||||
| 			b >>= 8 | ||||
| 			a >>= 8 | ||||
| 			wmIcon.Data[index] = | ||||
| 				(uint(a) << 24) | | ||||
| 				(uint(r) << 16) | | ||||
| 				(uint(g) << 8)  | | ||||
| 				(uint(b) << 0) | ||||
| 			index ++ | ||||
| 		}} | ||||
| 		 | ||||
| 		wmIcons = append(wmIcons, wmIcon) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) SetTitle (title string) { | ||||
| 	 | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) SetIcon (icons []image.Image) { | ||||
| 	 | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) shutDown () { | ||||
| 	backend.channel <- stone.EventQuit { } | ||||
| 	err = ewmh.WmIconSet(backend.connection, backend.window.Id, wmIcons) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // calculateWindowSize calculates window bounds based on the internal buffer | ||||
| @ -74,54 +104,56 @@ func (backend *Backend) calculateWindowSize () (x, y int) { | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // factory instantiates an X backend. | ||||
| func factory (application *stone.Application) (output stone.Backend, err error) { | ||||
| 	backend := &Backend { | ||||
| 		application: application, | ||||
| 		config:      application.Config(), | ||||
| 	} | ||||
| 
 | ||||
| 	// calculate metrics | ||||
| 	// TODO: base these off of font metrics | ||||
| 	backend.metrics.cellWidth  = 8 | ||||
| 	backend.metrics.cellHeight = 16 | ||||
| 	backend.metrics.padding = | ||||
| 		backend.config.Padding() * | ||||
| func (backend *Backend) calculateBufferSize () (width, height int) { | ||||
| 	width = | ||||
| 		(backend.metrics.windowWidth - backend.metrics.padding * 2) / | ||||
| 		backend.metrics.cellWidth | ||||
| 	height = | ||||
| 		(backend.metrics.windowHeight - backend.metrics.padding * 2) / | ||||
| 		backend.metrics.cellHeight | ||||
| 	backend.metrics.paddingX = backend.metrics.padding | ||||
| 	backend.metrics.paddingY = backend.metrics.padding | ||||
| 
 | ||||
| 	// connect to X | ||||
| 	backend.connection, err = xgbutil.NewConn() | ||||
| 	if err != nil { return } | ||||
| 	backend.window, err = xwindow.Generate(backend.connection) | ||||
| 	if err != nil { return } | ||||
| 
 | ||||
| 	// create the window | ||||
| 	windowWidth, windowHeight := backend.calculateWindowSize() | ||||
| 	backend.window.Create ( | ||||
| 		backend.connection.RootWin(), | ||||
| 		0, 0, windowWidth, windowHeight, | ||||
| 		0) | ||||
| 	backend.window.Map() | ||||
| 
 | ||||
| 	// attatch graceful close handler | ||||
| 	backend.window.WMGracefulClose (func (window *xwindow.Window) { | ||||
| 		backend.window.Destroy() | ||||
| 		backend.shutDown() | ||||
| 	}) | ||||
| 
 | ||||
| 	// start event loop | ||||
| 	backend.ping.before, | ||||
| 	backend.ping.after, | ||||
| 	backend.ping.quit = xevent.MainPing(backend.connection) | ||||
| 	 | ||||
| 	output = backend | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // init registers this backend when the program starts. | ||||
| func init () { | ||||
| 	stone.RegisterBackend(factory) | ||||
| func (backend *Backend) reallocateCanvas () { | ||||
| 	if backend.canvas != nil { | ||||
| 		backend.canvas.Destroy() | ||||
| 	} | ||||
| 	backend.canvas = xgraphics.New ( | ||||
| 		backend.connection, | ||||
| 		image.Rect ( | ||||
| 			0, 0, | ||||
| 			backend.metrics.windowWidth, | ||||
| 			backend.metrics.windowHeight)) | ||||
| 	backend.canvas.For (func (x, y int) xgraphics.BGRA { | ||||
| 		return backend.colors[stone.ColorApplication] | ||||
| 	}) | ||||
| 	 | ||||
| 	backend.canvas.XSurfaceSet(backend.window.Id) | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) cellAt (onScreen image.Point) (x, y int) { | ||||
| 	x = (onScreen.X - backend.metrics.paddingX) / backend.metrics.cellWidth | ||||
| 	y = (onScreen.Y - backend.metrics.paddingY) / backend.metrics.cellHeight | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) cellSubImage (x, y int) (cell *xgraphics.Image) { | ||||
| 	cell = backend.canvas.SubImage(backend.boundsOfCell(x, y)).(*xgraphics.Image) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) originOfCell (x, y int) (origin image.Point) { | ||||
| 	origin = image.Point { | ||||
| 		X: x * backend.metrics.cellWidth  + backend.metrics.paddingX, | ||||
| 		Y: y * backend.metrics.cellHeight + backend.metrics.paddingY, | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (backend *Backend) boundsOfCell (x, y int) (bounds image.Rectangle) { | ||||
| 	bounds = image.Rectangle { | ||||
| 		Min: backend.originOfCell(x, y), | ||||
| 		Max: backend.originOfCell(x + 1, y + 1), | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
							
								
								
									
										4
									
								
								event.go
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								event.go
									
									
									
									
									
								
							| @ -3,8 +3,8 @@ package stone | ||||
| type Event interface { } | ||||
| 
 | ||||
| type EventQuit      struct { } | ||||
| type EventPress     Button | ||||
| type EventRelease   Button | ||||
| type EventPress     struct { Button } | ||||
| type EventRelease   struct { Button } | ||||
| type EventResize    struct { } | ||||
| type EventMouseMove struct { | ||||
| 	X int | ||||
|  | ||||
							
								
								
									
										67
									
								
								examples/draw/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								examples/draw/main.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | ||||
| package main | ||||
| 
 | ||||
| import "os" | ||||
| import "image" | ||||
| import _ "image/png" | ||||
| import "git.tebibyte.media/sashakoshka/stone" | ||||
| import _ "git.tebibyte.media/sashakoshka/stone/backends/x" | ||||
| 
 | ||||
| var application = &stone.Application { } | ||||
| var mousePressed bool | ||||
| 
 | ||||
| func main () { | ||||
| 	application.SetTitle("hellorld") | ||||
| 	application.SetSize(32, 16) | ||||
| 
 | ||||
| 	iconFile16, err := os.Open("assets/scaffold16.png") | ||||
| 	if err != nil { panic(err) } | ||||
| 	icon16, _, err := image.Decode(iconFile16) | ||||
| 	if err != nil { panic(err) } | ||||
| 	iconFile16.Close() | ||||
| 	iconFile32, err := os.Open("assets/scaffold32.png") | ||||
| 	if err != nil { panic(err) } | ||||
| 	icon32, _, err := image.Decode(iconFile32) | ||||
| 	if err != nil { panic(err) } | ||||
| 	iconFile16.Close() | ||||
| 	 | ||||
| 	application.SetIcon([]image.Image { icon16, icon32 }) | ||||
| 	 | ||||
| 	channel, err := application.Run() | ||||
| 	if err != nil { panic(err) } | ||||
| 	 | ||||
| 	application.Draw() | ||||
| 
 | ||||
| 	for { | ||||
| 		event := <- channel | ||||
| 		switch event.(type) { | ||||
| 		case stone.EventQuit: | ||||
| 			os.Exit(0) | ||||
| 
 | ||||
| 		case stone.EventPress: | ||||
| 			button := event.(stone.EventPress).Button | ||||
| 			if button == stone.MouseButtonLeft { | ||||
| 				mousePressed = true | ||||
| 				application.SetRune(0, 0, '+') | ||||
| 				application.Draw() | ||||
| 			} | ||||
| 
 | ||||
| 		case stone.EventRelease: | ||||
| 			button := event.(stone.EventRelease).Button | ||||
| 			if button == stone.MouseButtonLeft { | ||||
| 				mousePressed = false | ||||
| 				application.SetRune(0, 0, 0) | ||||
| 				application.Draw() | ||||
| 			} | ||||
| 
 | ||||
| 		case stone.EventMouseMove: | ||||
| 			event := event.(stone.EventMouseMove) | ||||
| 			if mousePressed { | ||||
| 				application.SetRune(event.X, event.Y, '#') | ||||
| 				application.Draw() | ||||
| 			} | ||||
| 
 | ||||
| 		case stone.EventResize: | ||||
| 			application.Draw() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -3,23 +3,56 @@ package main | ||||
| import "os" | ||||
| import "fmt" | ||||
| import "time" | ||||
| import "image" | ||||
| import _ "image/png" | ||||
| import "git.tebibyte.media/sashakoshka/stone" | ||||
| import _ "git.tebibyte.media/sashakoshka/stone/backends/x" | ||||
| 
 | ||||
| var application = &stone.Application { } | ||||
| var currentTime = time.Time { } | ||||
| var tickPing    = make(chan(struct { })) | ||||
| 
 | ||||
| func main () { | ||||
| 	application  := &stone.Application { } | ||||
| 	application.SetTitle("hellorld") | ||||
| 	application.SetSize(12, 2) | ||||
| 
 | ||||
| 	iconFile16, err := os.Open("assets/scaffold16.png") | ||||
| 	if err != nil { panic(err) } | ||||
| 	icon16, _, err := image.Decode(iconFile16) | ||||
| 	if err != nil { panic(err) } | ||||
| 	iconFile16.Close() | ||||
| 	iconFile32, err := os.Open("assets/scaffold32.png") | ||||
| 	if err != nil { panic(err) } | ||||
| 	icon32, _, err := image.Decode(iconFile32) | ||||
| 	if err != nil { panic(err) } | ||||
| 	iconFile16.Close() | ||||
| 	 | ||||
| 	application.SetIcon([]image.Image { icon16, icon32 }) | ||||
| 	 | ||||
| 	channel, err := application.Run() | ||||
| 	if err != nil { panic(err) } | ||||
| 	 | ||||
| 	currentTime := time.Time { } | ||||
| 	redraw() | ||||
| 	go tick() | ||||
| 
 | ||||
| 	for { | ||||
| 		event := <- channel | ||||
| 		select { | ||||
| 		case <- tickPing: | ||||
| 			redraw() | ||||
| 		 | ||||
| 		case event := <- channel: | ||||
| 			switch event.(type) { | ||||
| 			case stone.EventQuit: | ||||
| 				os.Exit(0) | ||||
| 
 | ||||
| 			case stone.EventResize: | ||||
| 				redraw() | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func redraw () { | ||||
| 	currentTime = time.Now() | ||||
| 
 | ||||
| 	application.ResetDot() | ||||
| @ -37,6 +70,13 @@ func main () { | ||||
| 	application.SetRune(5, 1, ':') | ||||
| 	application.SetRune(6, 1, rune(second / 10 + 48)) | ||||
| 	application.SetRune(7, 1, rune(second % 10 + 48)) | ||||
| 		} | ||||
| 	 | ||||
| 	application.Draw() | ||||
| } | ||||
| 
 | ||||
| func tick () { | ||||
| 	for { | ||||
| 		tickPing <- struct { } { } | ||||
| 		time.Sleep(time.Second) | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										56
									
								
								examples/type/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								examples/type/main.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| package main | ||||
| 
 | ||||
| import "os" | ||||
| import "image" | ||||
| import _ "image/png" | ||||
| import "git.tebibyte.media/sashakoshka/stone" | ||||
| import _ "git.tebibyte.media/sashakoshka/stone/backends/x" | ||||
| 
 | ||||
| var application = &stone.Application { } | ||||
| var caret = 0 | ||||
| 
 | ||||
| func main () { | ||||
| 	application.SetTitle("hellorld") | ||||
| 	application.SetSize(32, 16) | ||||
| 
 | ||||
| 	iconFile16, err := os.Open("assets/scaffold16.png") | ||||
| 	if err != nil { panic(err) } | ||||
| 	icon16, _, err := image.Decode(iconFile16) | ||||
| 	if err != nil { panic(err) } | ||||
| 	iconFile16.Close() | ||||
| 	iconFile32, err := os.Open("assets/scaffold32.png") | ||||
| 	if err != nil { panic(err) } | ||||
| 	icon32, _, err := image.Decode(iconFile32) | ||||
| 	if err != nil { panic(err) } | ||||
| 	iconFile16.Close() | ||||
| 	 | ||||
| 	application.SetIcon([]image.Image { icon16, icon32 }) | ||||
| 	 | ||||
| 	channel, err := application.Run() | ||||
| 	if err != nil { panic(err) } | ||||
| 	 | ||||
| 	application.Draw() | ||||
| 
 | ||||
| 	for { | ||||
| 		event := <- channel | ||||
| 		switch event.(type) { | ||||
| 		case stone.EventQuit: | ||||
| 			os.Exit(0) | ||||
| 
 | ||||
| 		case stone.EventPress: | ||||
| 			button := event.(stone.EventPress).Button | ||||
| 			if button.Printable() { | ||||
| 				application.SetRune(caret, 0, rune(button)) | ||||
| 				caret ++ | ||||
| 				width, _ := application.Size() | ||||
| 				if caret >= width { | ||||
| 					caret = 0 | ||||
| 				} | ||||
| 				application.Draw() | ||||
| 			} | ||||
| 
 | ||||
| 		case stone.EventResize: | ||||
| 			application.Draw() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										13
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								go.mod
									
									
									
									
									
								
							| @ -3,19 +3,14 @@ module git.tebibyte.media/sashakoshka/stone | ||||
| go 1.18 | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/faiface/pixel v0.10.0 | ||||
| 	github.com/flopp/go-findfont v0.1.0 | ||||
| 	github.com/jezek/xgb v1.1.0 | ||||
| 	github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66 | ||||
| 	golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff | ||||
| 	golang.org/x/image v0.1.0 | ||||
| ) | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 // indirect | ||||
| 	github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 // indirect | ||||
| 	github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 // indirect | ||||
| 	github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 // indirect | ||||
| 	github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 // indirect | ||||
| 	github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 // indirect | ||||
| 	github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 // indirect | ||||
| 	github.com/jezek/xgb v1.1.0 // indirect | ||||
| 	github.com/pkg/errors v0.8.1 // indirect | ||||
| 	golang.org/x/text v0.4.0 // indirect | ||||
| ) | ||||
|  | ||||
							
								
								
									
										54
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								go.sum
									
									
									
									
									
								
							| @ -2,34 +2,36 @@ github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJ | ||||
| github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ= | ||||
| github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g= | ||||
| github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0= | ||||
| github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 h1:FvZ0mIGh6b3kOITxUnxS3tLZMh7yEoHo75v3/AgUqg0= | ||||
| github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380/go.mod h1:zqnPFFIuYFFxl7uH2gYByJwIVKG7fRqlqQCbzAnHs9g= | ||||
| github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q= | ||||
| github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M= | ||||
| github.com/faiface/pixel v0.10.0 h1:EHm3ZdQw2Ck4y51cZqFfqQpwLqNHOoXwbNEc9Dijql0= | ||||
| github.com/faiface/pixel v0.10.0/go.mod h1:lU0YYcW77vL0F1CG8oX51GXurymL45MXd57otHNLK7A= | ||||
| github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw= | ||||
| github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= | ||||
| github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 h1:b+9H1GAsx5RsjvDFLoS5zkNBzIQMuVKUYQDmxU3N5XE= | ||||
| github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | ||||
| github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 h1:THttjeRn1iiz69E875U6gAik8KTWk/JYAHoSVpUxBBI= | ||||
| github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= | ||||
| github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= | ||||
| github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= | ||||
| github.com/flopp/go-findfont v0.1.0 h1:lPn0BymDUtJo+ZkV01VS3661HL6F4qFlkhcJN55u6mU= | ||||
| github.com/flopp/go-findfont v0.1.0/go.mod h1:wKKxRDjD024Rh7VMwoU90i6ikQRCr+JTHB5n4Ejkqvw= | ||||
| github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk= | ||||
| github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= | ||||
| github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66 h1:+wPhoJD8EH0/bXipIq8Lc2z477jfox9zkXPCJdhvHj8= | ||||
| github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66/go.mod h1:KACeV+k6b+aoLTVrrurywEbu3UpqoQcQywj4qX8aQKM= | ||||
| github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | ||||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= | ||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||
| golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | ||||
| golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff h1:+2zgJKVDVAz/BWSsuniCmU1kLCjL88Z8/kv39xCI9NQ= | ||||
| golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | ||||
| github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk= | ||||
| golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= | ||||
| golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||||
| golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||
| golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= | ||||
| golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
|  | ||||
							
								
								
									
										199
									
								
								input.go
									
									
									
									
									
								
							
							
						
						
									
										199
									
								
								input.go
									
									
									
									
									
								
							| @ -1,143 +1,78 @@ | ||||
| package stone | ||||
| 
 | ||||
| // These should be identical to the glfw keys | ||||
| import "unicode" | ||||
| 
 | ||||
| type Button int | ||||
| 
 | ||||
| const ( | ||||
| 	KeyUnknown      Button = -1 | ||||
| 	ButtonUnknown   Button = 0 | ||||
| 	 | ||||
| 	KeySpace        Button = 162 | ||||
| 	KeyApostrophe   Button = 161 | ||||
| 	KeyComma        Button = 96 | ||||
| 	KeyMinus        Button = 93 | ||||
| 	KeyPeriod       Button = 92 | ||||
| 	KeySlash        Button = 91 | ||||
| 	Key0            Button = 90 | ||||
| 	Key1            Button = 89 | ||||
| 	Key2            Button = 88 | ||||
| 	Key3            Button = 87 | ||||
| 	Key4            Button = 86 | ||||
| 	Key5            Button = 85 | ||||
| 	Key6            Button = 84 | ||||
| 	Key7            Button = 83 | ||||
| 	Key8            Button = 82 | ||||
| 	Key9            Button = 81 | ||||
| 	KeySemicolon    Button = 80 | ||||
| 	KeyEqual        Button = 79 | ||||
| 	KeyA            Button = 78 | ||||
| 	KeyB            Button = 77 | ||||
| 	KeyC            Button = 76 | ||||
| 	KeyD            Button = 75 | ||||
| 	KeyE            Button = 74 | ||||
| 	KeyF            Button = 73 | ||||
| 	KeyG            Button = 72 | ||||
| 	KeyH            Button = 71 | ||||
| 	KeyI            Button = 70 | ||||
| 	KeyJ            Button = 69 | ||||
| 	KeyK            Button = 68 | ||||
| 	KeyL            Button = 67 | ||||
| 	KeyM            Button = 66 | ||||
| 	KeyN            Button = 65 | ||||
| 	KeyO            Button = 61 | ||||
| 	KeyP            Button = 59 | ||||
| 	KeyQ            Button = 57 | ||||
| 	KeyR            Button = 56 | ||||
| 	KeyS            Button = 55 | ||||
| 	KeyT            Button = 54 | ||||
| 	KeyU            Button = 53 | ||||
| 	KeyV            Button = 52 | ||||
| 	KeyW            Button = 51 | ||||
| 	KeyX            Button = 50 | ||||
| 	KeyY            Button = 49 | ||||
| 	KeyZ            Button = 48 | ||||
| 	KeyLeftBracket  Button = 47 | ||||
| 	KeyBackslash    Button = 46 | ||||
| 	KeyRightBracket Button = 45 | ||||
| 	KeyGraveAccent  Button = 44 | ||||
| 	KeyWorld1       Button = 39 | ||||
| 	KeyWorld2       Button = 32 | ||||
| 	KeyInsert       Button = 1 | ||||
| 	KeyMenu         Button = 2 | ||||
| 	KeyPrintScreen  Button = 3 | ||||
| 	KeyPause        Button = 4 | ||||
| 	KeyCapsLock     Button = 5 | ||||
| 	KeyScrollLock   Button = 6 | ||||
| 	KeyNumLock      Button = 7 | ||||
| 	KeyBackspace    Button = 8 | ||||
| 	KeyTab          Button = 9 | ||||
| 	KeyEnter        Button = 10 | ||||
| 	KeyEscape       Button = 11 | ||||
| 	 | ||||
| 	KeyEscape       Button = 348 | ||||
| 	KeyEnter        Button = 347 | ||||
| 	KeyTab          Button = 346 | ||||
| 	KeyBackspace    Button = 345 | ||||
| 	KeyInsert       Button = 344 | ||||
| 	KeyDelete       Button = 343 | ||||
| 	KeyRight        Button = 342 | ||||
| 	KeyLeft         Button = 341 | ||||
| 	KeyDown         Button = 340 | ||||
| 	KeyUp           Button = 336 | ||||
| 	KeyPageUp       Button = 335 | ||||
| 	KeyPageDown     Button = 334 | ||||
| 	KeyHome         Button = 333 | ||||
| 	KeyEnd          Button = 332 | ||||
| 	KeyCapsLock     Button = 331 | ||||
| 	KeyScrollLock   Button = 330 | ||||
| 	KeyNumLock      Button = 329 | ||||
| 	KeyPrintScreen  Button = 328 | ||||
| 	KeyPause        Button = 327 | ||||
| 	KeyF1           Button = 326 | ||||
| 	KeyF2           Button = 325 | ||||
| 	KeyF3           Button = 324 | ||||
| 	KeyF4           Button = 323 | ||||
| 	KeyF5           Button = 322 | ||||
| 	KeyF6           Button = 321 | ||||
| 	KeyF7           Button = 320 | ||||
| 	KeyF8           Button = 314 | ||||
| 	KeyF9           Button = 313 | ||||
| 	KeyF10          Button = 312 | ||||
| 	KeyF11          Button = 311 | ||||
| 	KeyF12          Button = 310 | ||||
| 	KeyF13          Button = 309 | ||||
| 	KeyF14          Button = 308 | ||||
| 	KeyF15          Button = 307 | ||||
| 	KeyF16          Button = 306 | ||||
| 	KeyF17          Button = 305 | ||||
| 	KeyF18          Button = 304 | ||||
| 	KeyF19          Button = 303 | ||||
| 	KeyF20          Button = 302 | ||||
| 	KeyF21          Button = 301 | ||||
| 	KeyF22          Button = 300 | ||||
| 	KeyF23          Button = 299 | ||||
| 	KeyF24          Button = 298 | ||||
| 	KeyF25          Button = 297 | ||||
| 	KeyKP0          Button = 296 | ||||
| 	KeyKP1          Button = 295 | ||||
| 	KeyKP2          Button = 294 | ||||
| 	KeyKP3          Button = 293 | ||||
| 	KeyKP4          Button = 292 | ||||
| 	KeyKP5          Button = 291 | ||||
| 	KeyKP6          Button = 290 | ||||
| 	KeyKP7          Button = 284 | ||||
| 	KeyKP8          Button = 283 | ||||
| 	KeyKP9          Button = 282 | ||||
| 	KeyKPDecimal    Button = 281 | ||||
| 	KeyKPDivide     Button = 280 | ||||
| 	KeyKPMultiply   Button = 269 | ||||
| 	KeyKPSubtract   Button = 268 | ||||
| 	KeyKPAdd        Button = 267 | ||||
| 	KeyKPEnter      Button = 266 | ||||
| 	KeyKPEqual      Button = 265 | ||||
| 	KeyLeftShift    Button = 264 | ||||
| 	KeyLeftControl  Button = 263 | ||||
| 	KeyLeftAlt      Button = 262 | ||||
| 	KeyLeftSuper    Button = 261 | ||||
| 	KeyRightShift   Button = 260 | ||||
| 	KeyRightControl Button = 259 | ||||
| 	KeyRightAlt     Button = 258 | ||||
| 	KeyRightSuper   Button = 257 | ||||
| 	KeyMenu         Button = 256 | ||||
| 	KeyUp       Button = 12 | ||||
| 	KeyDown     Button = 13 | ||||
| 	KeyLeft     Button = 14 | ||||
| 	KeyRight    Button = 15 | ||||
| 	KeyPageUp   Button = 16 | ||||
| 	KeyPageDown Button = 17 | ||||
| 	KeyHome     Button = 18 | ||||
| 	KeyEnd      Button = 19 | ||||
| 	 | ||||
| 	MouseButton1      Button = 0 | ||||
| 	MouseButton2      Button = 1 | ||||
| 	MouseButton3      Button = 2 | ||||
| 	MouseButton4      Button = 3 | ||||
| 	MouseButton5      Button = 4 | ||||
| 	MouseButton6      Button = 5 | ||||
| 	MouseButton7      Button = 6 | ||||
| 	MouseButton8      Button = 7 | ||||
| 	KeyLeftShift    Button = 20 | ||||
| 	KeyRightShift   Button = 21 | ||||
| 	KeyLeftControl  Button = 22 | ||||
| 	KeyRightControl Button = 23 | ||||
| 	KeyLeftAlt      Button = 24 | ||||
| 	KeyRightAlt     Button = 25	 | ||||
| 	KeyLeftSuper    Button = 26 | ||||
| 	KeyRightSuper   Button = 27 | ||||
| 	 | ||||
| 	KeyDelete       Button = 127 | ||||
| 	 | ||||
| 	MouseButton1           Button = 128 | ||||
| 	MouseButton2           Button = 129 | ||||
| 	MouseButton3           Button = 130 | ||||
| 	MouseButton4           Button = 131 | ||||
| 	MouseButton5           Button = 132 | ||||
| 	MouseButton6           Button = 133 | ||||
| 	MouseButton7           Button = 134 | ||||
| 	MouseButton8           Button = 135 | ||||
| 	MouseButton9           Button = 136 | ||||
| 	MouseButtonLeft        Button = MouseButton1 | ||||
| 	MouseButtonRight  Button = MouseButton2 | ||||
| 	MouseButtonMiddle Button = MouseButton3 | ||||
| 	MouseButtonMiddle      Button = MouseButton2 | ||||
| 	MouseButtonRight       Button = MouseButton3 | ||||
| 	MouseButtonScrollUp    Button = MouseButton4 | ||||
| 	MouseButtonScrollDown  Button = MouseButton5 | ||||
| 	MouseButtonScrollLeft  Button = MouseButton6 | ||||
| 	MouseButtonScrollRight Button = MouseButton7 | ||||
| 	MouseButtonBack        Button = MouseButton8 | ||||
| 	MouseButtonForward     Button = MouseButton9 | ||||
| 	 | ||||
| 	KeyF1  Button = 144 | ||||
| 	KeyF2  Button = 145 | ||||
| 	KeyF3  Button = 146 | ||||
| 	KeyF4  Button = 147 | ||||
| 	KeyF5  Button = 148 | ||||
| 	KeyF6  Button = 149 | ||||
| 	KeyF7  Button = 150 | ||||
| 	KeyF8  Button = 151 | ||||
| 	KeyF9  Button = 152 | ||||
| 	KeyF10 Button = 153 | ||||
| 	KeyF11 Button = 154 | ||||
| 	KeyF12 Button = 155 | ||||
| ) | ||||
| 
 | ||||
| func (button Button) Printable () (printable bool) { | ||||
| 	printable = unicode.IsPrint(rune(button)) | ||||
| 	return | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user