Compare commits

...

3 Commits
v0.1.0 ... main

Author SHA1 Message Date
Sasha Koshka 501eb34922 Moved artist (now art) into another repo 2023-05-03 20:17:48 -04:00
Sasha Koshka 54ea1c283f Changed pkg.go.dev link 2023-05-03 20:12:46 -04:00
Sasha Koshka 33c787d70b Changed import paths 2023-05-03 19:40:30 -04:00
81 changed files with 335 additions and 1429 deletions

View File

@ -20,5 +20,5 @@ it. It will be placed in `~/.local/lib/nasin/plugins`.
You can find out more about how to use Tomo and Nasin by visiting the examples You can find out more about how to use Tomo and Nasin by visiting the examples
directory, or pull up the documentation by running `godoc` within the directory, or pull up the documentation by running `godoc` within the
repository. You can also view it on the web on repository. You can also view it on the web on
[pkg.go.dev](https://pkg.go.dev/git.tebibyte.media/sashakoshka/tomo) (although [pkg.go.dev](https://pkg.go.dev/git.tebibyte.media/tomo/tomo) (although
it may be slightly out of date). it may be slightly out of date).

View File

@ -2,9 +2,9 @@
package ability package ability
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
// Layoutable represents an element that needs to perform layout calculations // Layoutable represents an element that needs to perform layout calculations
// before it can draw itself. // before it can draw itself.
@ -24,7 +24,7 @@ type Container interface {
// the specified canvas. The bounds of this canvas specify the area that // the specified canvas. The bounds of this canvas specify the area that
// is actually drawn to, while the Entity bounds specify the actual area // is actually drawn to, while the Entity bounds specify the actual area
// of the element. // of the element.
DrawBackground (artist.Canvas) DrawBackground (art.Canvas)
// HandleChildMinimumSizeChange is called when a child's minimum size is // HandleChildMinimumSizeChange is called when a child's minimum size is
// changed. // changed.

View File

@ -1,63 +0,0 @@
// Package artutil provides utility functions for working with graphical types
// defined in artist, canvas, and image.
package artutil
import "image"
import "image/color"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/shatter"
// Fill fills the destination canvas with the given pattern.
func Fill (destination artist.Canvas, source artist.Pattern) (updated image.Rectangle) {
source.Draw(destination, destination.Bounds())
return destination.Bounds()
}
// DrawClip lets you draw several subsets of a pattern at once.
func DrawClip (
destination artist.Canvas,
source artist.Pattern,
bounds image.Rectangle,
subsets ...image.Rectangle,
) (
updatedRegion image.Rectangle,
) {
for _, subset := range subsets {
source.Draw(artist.Cut(destination, subset), bounds)
updatedRegion = updatedRegion.Union(subset)
}
return
}
// DrawShatter is like an inverse of DrawClip, drawing nothing in the areas
// specified by "rocks".
func DrawShatter (
destination artist.Canvas,
source artist.Pattern,
bounds image.Rectangle,
rocks ...image.Rectangle,
) (
updatedRegion image.Rectangle,
) {
tiles := shatter.Shatter(bounds, rocks...)
return DrawClip(destination, source, bounds, tiles...)
}
// AllocateSample returns a new canvas containing the result of a pattern. The
// resulting canvas can be sourced from shape drawing functions. I beg of you
// please do not call this every time you need to draw a shape with a pattern on
// it because that is horrible and cruel to the computer.
func AllocateSample (source artist.Pattern, width, height int) artist.Canvas {
allocated := artist.NewBasicCanvas(width, height)
Fill(allocated, source)
return allocated
}
// Hex creates a color.RGBA value from an RGBA integer value.
func Hex (color uint32) (c color.RGBA) {
c.A = uint8(color)
c.B = uint8(color >> 8)
c.G = uint8(color >> 16)
c.R = uint8(color >> 24)
return
}

View File

@ -1,111 +0,0 @@
package artist
import "image"
import "image/draw"
import "image/color"
// Image represents an immutable canvas.
type Image interface {
image.Image
RGBAAt (x, y int) color.RGBA
}
// Canvas is like draw.Image but is also able to return a raw pixel buffer for
// more efficient drawing. This interface can be easily satisfied using a
// BasicCanvas struct.
type Canvas interface {
draw.Image
Buffer () (data []color.RGBA, stride int)
}
// BasicCanvas is a general purpose implementation of tomo.Canvas.
type BasicCanvas struct {
pix []color.RGBA
stride int
rect image.Rectangle
}
// NewBasicCanvas creates a new basic canvas with the specified width and
// height, allocating a buffer for it.
func NewBasicCanvas (width, height int) (canvas BasicCanvas) {
canvas.pix = make([]color.RGBA, height * width)
canvas.stride = width
canvas.rect = image.Rect(0, 0, width, height)
return
}
// FromImage creates a new BasicCanvas from an image.Image.
func FromImage (img image.Image) (canvas BasicCanvas) {
bounds := img.Bounds()
canvas = NewBasicCanvas(bounds.Dx(), bounds.Dy())
point := image.Point { }
for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
canvasPoint := point.Sub(bounds.Min)
canvas.Set (
canvasPoint.X, canvasPoint.Y,
img.At(point.X, point.Y))
}}
return
}
// you know what it do
func (canvas BasicCanvas) Bounds () (bounds image.Rectangle) {
return canvas.rect
}
// you know what it do
func (canvas BasicCanvas) At (x, y int) (color.Color) {
if !image.Pt(x, y).In(canvas.rect) { return nil }
return canvas.pix[x + y * canvas.stride]
}
// you know what it do
func (canvas BasicCanvas) ColorModel () (model color.Model) {
return color.RGBAModel
}
// you know what it do
func (canvas BasicCanvas) Set (x, y int, c color.Color) {
if !image.Pt(x, y).In(canvas.rect) { return }
r, g, b, a := c.RGBA()
canvas.pix[x + y * canvas.stride] = color.RGBA {
R: uint8(r >> 8),
G: uint8(g >> 8),
B: uint8(b >> 8),
A: uint8(a >> 8),
}
}
// you know what it do
func (canvas BasicCanvas) Buffer () (data []color.RGBA, stride int) {
return canvas.pix, canvas.stride
}
// Reallocate efficiently reallocates the canvas. The data within will be
// garbage. This method will do nothing if this is a cut image.
func (canvas *BasicCanvas) Reallocate (width, height int) {
if canvas.rect.Min != (image.Point { }) { return }
previousLen := len(canvas.pix)
newLen := width * height
bigger := newLen > previousLen
smaller := newLen < previousLen / 2
if bigger || smaller {
canvas.pix = make (
[]color.RGBA,
((height * width) / 4096) * 4096 + 4096)
}
canvas.stride = width
canvas.rect = image.Rect(0, 0, width, height)
}
// Cut returns a sub-canvas of a given canvas.
func Cut (canvas Canvas, bounds image.Rectangle) (reduced BasicCanvas) {
// println(canvas.Bounds().String(), bounds.String())
bounds = bounds.Intersect(canvas.Bounds())
if bounds.Empty() { return }
reduced.rect = bounds
reduced.pix, reduced.stride = canvas.Buffer()
return
}

View File

@ -1,2 +0,0 @@
// Package artist provides a simple 2D drawing library for canvas.Canvas.
package artist

View File

@ -1,13 +0,0 @@
package artist
import "image"
import "image/color"
type Icon interface {
// Draw draws the icon to the destination canvas at the specified point,
// using the specified color (if the icon is monochrome).
Draw (destination Canvas, color color.RGBA, at image.Point)
// Bounds returns the bounds of the icon.
Bounds () image.Rectangle
}

View File

@ -1,82 +0,0 @@
package artist
import "image"
// Side represents one side of a rectangle.
type Side int; const (
SideTop Side = iota
SideRight
SideBottom
SideLeft
)
// Inset represents an inset amount for all four sides of a rectangle. The top
// side is at index zero, the right at index one, the bottom at index two, and
// the left at index three. These values may be negative.
type Inset [4]int
// I allows you to create an inset in a CSS-ish way:
//
// - One argument: all sides are set to this value
// - Two arguments: the top and bottom sides are set to the first value, and
// the left and right sides are set to the second value.
// - Three arguments: the top side is set by the first value, the left and
// right sides are set by the second vaue, and the bottom side is set by the
// third value.
// - Four arguments: each value corresponds to a side.
//
// This function will panic if an argument count that isn't one of these is
// given.
func I (sides ...int) Inset {
switch len(sides) {
case 1: return Inset { sides[0], sides[0], sides[0], sides[0] }
case 2: return Inset { sides[0], sides[1], sides[0], sides[1] }
case 3: return Inset { sides[0], sides[1], sides[2], sides[1] }
case 4: return Inset { sides[0], sides[1], sides[2], sides[3] }
default: panic("I: illegal argument count.")
}
}
// Apply returns the given rectangle, shrunk on all four sides by the given
// inset. If a measurment of the inset is negative, that side will instead be
// expanded outward. If the rectangle's dimensions cannot be reduced any
// further, an empty rectangle near its center will be returned.
func (inset Inset) Apply (bigger image.Rectangle) (smaller image.Rectangle) {
smaller = bigger
if smaller.Dx() < inset[3] + inset[1] {
smaller.Min.X = (smaller.Min.X + smaller.Max.X) / 2
smaller.Max.X = smaller.Min.X
} else {
smaller.Min.X += inset[3]
smaller.Max.X -= inset[1]
}
if smaller.Dy() < inset[0] + inset[2] {
smaller.Min.Y = (smaller.Min.Y + smaller.Max.Y) / 2
smaller.Max.Y = smaller.Min.Y
} else {
smaller.Min.Y += inset[0]
smaller.Max.Y -= inset[2]
}
return
}
// Inverse returns a negated version of the inset.
func (inset Inset) Inverse () (prime Inset) {
return Inset {
inset[0] * -1,
inset[1] * -1,
inset[2] * -1,
inset[3] * -1,
}
}
// Horizontal returns the sum of SideRight and SideLeft.
func (inset Inset) Horizontal () int {
return inset[SideRight] + inset[SideLeft]
}
// Vertical returns the sum of SideTop and SideBottom.
func (inset Inset) Vertical () int {
return inset[SideTop] + inset[SideBottom]
}

View File

@ -1,13 +0,0 @@
package artist
import "image"
// Pattern is capable of drawing to a canvas within the bounds of a given
// clipping rectangle.
type Pattern interface {
// Draw draws the pattern onto the destination canvas, using the
// specified bounds. The given bounds can be smaller or larger than the
// bounds of the destination canvas. The destination canvas can be cut
// using canvas.Cut() to draw only a specific subset of a pattern.
Draw (destination Canvas, bounds image.Rectangle)
}

View File

@ -1,89 +0,0 @@
package patterns
import "image"
import "git.tebibyte.media/sashakoshka/tomo/artist"
// Border is a pattern that behaves similarly to border-image in CSS. It divides
// a source canvas into nine sections...
//
// Inset[1]
// ┌──┴──┐
// ┌─┌─────┬─────┬─────┐
// Inset[0]─┤ │ 0 │ 1 │ 2 │
// └─├─────┼─────┼─────┤
// │ 3 │ 4 │ 5 │
// ├─────┼─────┼─────┤─┐
// │ 6 │ 7 │ 8 │ ├─Inset[2]
// └─────┴─────┴─────┘─┘
// └──┬──┘
// Inset[3]
//
// ... Where the bounds of section 4 are defined as the application of the
// pattern's inset to the canvas's bounds. The bounds of the other eight
// sections are automatically sized around it.
//
// When drawn to a destination canvas, the bounds of sections 1, 3, 4, 5, and 7
// are expanded or contracted to fit the given drawing bounds. All sections are
// rendered as if they are Texture patterns, meaning these flexible sections
// will repeat to fill in any empty space.
//
// This pattern can be used to make a static image texture into something that
// responds well to being resized.
type Border struct {
artist.Canvas
artist.Inset
}
// Draw draws the border pattern onto the destination canvas within the given
// bounds.
func (pattern Border) Draw (destination artist.Canvas, bounds image.Rectangle) {
drawBounds := bounds.Canon().Intersect(destination.Bounds())
if drawBounds.Empty() { return }
srcSections := nonasect(pattern.Bounds(), pattern.Inset)
srcTextures := [9]Texture { }
for index, section := range srcSections {
srcTextures[index].Canvas = artist.Cut(pattern, section)
}
dstSections := nonasect(bounds, pattern.Inset)
for index, section := range dstSections {
srcTextures[index].Draw(destination, section)
}
}
func nonasect (bounds image.Rectangle, inset artist.Inset) [9]image.Rectangle {
center := inset.Apply(bounds)
return [9]image.Rectangle {
// top
image.Rectangle {
bounds.Min,
center.Min },
image.Rect (
center.Min.X, bounds.Min.Y,
center.Max.X, center.Min.Y),
image.Rect (
center.Max.X, bounds.Min.Y,
bounds.Max.X, center.Min.Y),
// center
image.Rect (
bounds.Min.X, center.Min.Y,
center.Min.X, center.Max.Y),
center,
image.Rect (
center.Max.X, center.Min.Y,
bounds.Max.X, center.Max.Y),
// bottom
image.Rect (
bounds.Min.X, center.Max.Y,
center.Min.X, bounds.Max.Y),
image.Rect (
center.Min.X, center.Max.Y,
center.Max.X, bounds.Max.Y),
image.Rect (
center.Max.X, center.Max.Y,
bounds.Max.X, bounds.Max.Y),
}
}

View File

@ -1,3 +0,0 @@
// Package patterns provides a basic set of types that satisfy the
// artist.Pattern interface.
package patterns

View File

@ -1,77 +0,0 @@
package patterns
import "image"
import "git.tebibyte.media/sashakoshka/tomo/artist"
// Texture is a pattern that tiles the content of a canvas both horizontally and
// vertically.
type Texture struct {
artist.Canvas
}
// Draw tiles the pattern's canvas within the given bounds. The minimum
// point of the pattern's canvas will be lined up with the minimum point of the
// bounding rectangle.
func (pattern Texture) Draw (destination artist.Canvas, bounds image.Rectangle) {
dstBounds := bounds.Canon().Intersect(destination.Bounds())
if dstBounds.Empty() { return }
dstData, dstStride := destination.Buffer()
srcData, srcStride := pattern.Buffer()
srcBounds := pattern.Bounds()
// offset is a vector that is added to points in destination space to
// convert them to points in source space
offset := srcBounds.Min.Sub(bounds.Min)
// calculate the starting position in source space
srcPoint := dstBounds.Min.Add(offset)
srcPoint.X = wrap(srcPoint.X, srcBounds.Min.X, srcBounds.Max.X)
srcPoint.Y = wrap(srcPoint.Y, srcBounds.Min.Y, srcBounds.Max.Y)
srcStartPoint := srcPoint
// for each row
dstPoint := image.Point { }
for dstPoint.Y = dstBounds.Min.Y; dstPoint.Y < dstBounds.Max.Y; dstPoint.Y ++ {
srcPoint.X = srcStartPoint.X
dstPoint.X = dstBounds.Min.X
dstYComponent := dstPoint.Y * dstStride
srcYComponent := srcPoint.Y * srcStride
// for each pixel in the row
for {
// draw pixel
dstIndex := dstYComponent + dstPoint.X
srcIndex := srcYComponent + srcPoint.X
dstData[dstIndex] = srcData[srcIndex]
// increment X in source space. wrap to start if out of
// bounds.
srcPoint.X ++
if srcPoint.X >= srcBounds.Max.X {
srcPoint.X = srcBounds.Min.X
}
// increment X in destination space. stop drawing this
// row if out of bounds.
dstPoint.X ++
if dstPoint.X >= dstBounds.Max.X {
break
}
}
// increment row in source space. wrap to start if out of
// bounds.
srcPoint.Y ++
if srcPoint.Y >= srcBounds.Max.Y {
srcPoint.Y = srcBounds.Min.Y
}
}
}
func wrap (value, min, max int) int {
difference := max - min
value = (value - min) % difference
if value < 0 { value += difference }
return value + min
}

View File

@ -1,20 +0,0 @@
package patterns
import "image"
import "image/color"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
import "git.tebibyte.media/sashakoshka/tomo/artist/artutil"
// Uniform is a pattern that draws a solid color.
type Uniform color.RGBA
// Draw fills the bounding rectangle with the pattern's color.
func (pattern Uniform) Draw (destination artist.Canvas, bounds image.Rectangle) {
shapes.FillColorRectangle(destination, color.RGBA(pattern), bounds)
}
// Uhex creates a new Uniform pattern from an RGBA integer value.
func Uhex (color uint32) (uniform Uniform) {
return Uniform(artutil.Hex(color))
}

View File

@ -1,11 +0,0 @@
// Package shapes provides some basic shape drawing routines.
//
// A word about using patterns with shape routines:
//
// Most drawing routines have a version that samples from other canvases, and a
// version that samples from a solid color. None of these routines can use
// patterns directly, but it is entirely possible to have a pattern draw to an
// off-screen canvas and then draw a shape based on that canvas. As a little
// bonus, you can save the canvas for later so you don't have to render the
// pattern again when you need to redraw the shape.
package shapes

View File

@ -1,231 +0,0 @@
package shapes
import "math"
import "image"
import "image/color"
import "git.tebibyte.media/sashakoshka/tomo/artist"
// TODO: redo fill ellipse, stroke ellipse, etc. so that it only takes in
// destination and source, using the bounds of destination as the bounds of the
// ellipse and the bounds of source as the "clipping rectangle". Line up the Min
// of both canvases.
func FillEllipse (
destination artist.Canvas,
source artist.Canvas,
bounds image.Rectangle,
) (
updatedRegion image.Rectangle,
) {
dstData, dstStride := destination.Buffer()
srcData, srcStride := source.Buffer()
offset := source.Bounds().Min.Sub(destination.Bounds().Min)
drawBounds :=
source.Bounds().Sub(offset).
Intersect(destination.Bounds()).
Intersect(bounds)
if bounds.Empty() { return }
updatedRegion = bounds
point := image.Point { }
for point.Y = drawBounds.Min.Y; point.Y < drawBounds.Max.Y; point.Y ++ {
for point.X = drawBounds.Min.X; point.X < drawBounds.Max.X; point.X ++ {
if inEllipse(point, bounds) {
offsetPoint := point.Add(offset)
dstIndex := point.X + point.Y * dstStride
srcIndex := offsetPoint.X + offsetPoint.Y * srcStride
dstData[dstIndex] = srcData[srcIndex]
}
}}
return
}
func StrokeEllipse (
destination artist.Canvas,
source artist.Canvas,
bounds image.Rectangle,
weight int,
) {
if weight < 1 { return }
dstData, dstStride := destination.Buffer()
srcData, srcStride := source.Buffer()
drawBounds := destination.Bounds().Inset(weight - 1)
offset := source.Bounds().Min.Sub(destination.Bounds().Min)
if drawBounds.Empty() { return }
context := ellipsePlottingContext {
plottingContext: plottingContext {
dstData: dstData,
dstStride: dstStride,
srcData: srcData,
srcStride: srcStride,
weight: weight,
offset: offset,
bounds: bounds,
},
radii: image.Pt(drawBounds.Dx() / 2, drawBounds.Dy() / 2),
}
context.center = drawBounds.Min.Add(context.radii)
context.plotEllipse()
}
type ellipsePlottingContext struct {
plottingContext
radii image.Point
center image.Point
}
func (context ellipsePlottingContext) plotEllipse () {
x := float64(0)
y := float64(context.radii.Y)
// region 1 decision parameter
decision1 :=
float64(context.radii.Y * context.radii.Y) -
float64(context.radii.X * context.radii.X * context.radii.Y) +
(0.25 * float64(context.radii.X) * float64(context.radii.X))
decisionX := float64(2 * context.radii.Y * context.radii.Y * int(x))
decisionY := float64(2 * context.radii.X * context.radii.X * int(y))
// draw region 1
for decisionX < decisionY {
points := []image.Point {
image.Pt(-int(x) + context.center.X, -int(y) + context.center.Y),
image.Pt( int(x) + context.center.X, -int(y) + context.center.Y),
image.Pt(-int(x) + context.center.X, int(y) + context.center.Y),
image.Pt( int(x) + context.center.X, int(y) + context.center.Y),
}
if context.srcData == nil {
context.plotColor(points[0])
context.plotColor(points[1])
context.plotColor(points[2])
context.plotColor(points[3])
} else {
context.plotSource(points[0])
context.plotSource(points[1])
context.plotSource(points[2])
context.plotSource(points[3])
}
if (decision1 < 0) {
x ++
decisionX += float64(2 * context.radii.Y * context.radii.Y)
decision1 += decisionX + float64(context.radii.Y * context.radii.Y)
} else {
x ++
y --
decisionX += float64(2 * context.radii.Y * context.radii.Y)
decisionY -= float64(2 * context.radii.X * context.radii.X)
decision1 +=
decisionX - decisionY +
float64(context.radii.Y * context.radii.Y)
}
}
// region 2 decision parameter
decision2 :=
float64(context.radii.Y * context.radii.Y) * (x + 0.5) * (x + 0.5) +
float64(context.radii.X * context.radii.X) * (y - 1) * (y - 1) -
float64(context.radii.X * context.radii.X * context.radii.Y * context.radii.Y)
// draw region 2
for y >= 0 {
points := []image.Point {
image.Pt( int(x) + context.center.X, int(y) + context.center.Y),
image.Pt(-int(x) + context.center.X, int(y) + context.center.Y),
image.Pt( int(x) + context.center.X, -int(y) + context.center.Y),
image.Pt(-int(x) + context.center.X, -int(y) + context.center.Y),
}
if context.srcData == nil {
context.plotColor(points[0])
context.plotColor(points[1])
context.plotColor(points[2])
context.plotColor(points[3])
} else {
context.plotSource(points[0])
context.plotSource(points[1])
context.plotSource(points[2])
context.plotSource(points[3])
}
if decision2 > 0 {
y --
decisionY -= float64(2 * context.radii.X * context.radii.X)
decision2 += float64(context.radii.X * context.radii.X) - decisionY
} else {
y --
x ++
decisionX += float64(2 * context.radii.Y * context.radii.Y)
decisionY -= float64(2 * context.radii.X * context.radii.X)
decision2 +=
decisionX - decisionY +
float64(context.radii.X * context.radii.X)
}
}
}
// FillColorEllipse fills an ellipse within the destination canvas with a solid
// color.
func FillColorEllipse (
destination artist.Canvas,
color color.RGBA,
bounds image.Rectangle,
) (
updatedRegion image.Rectangle,
) {
dstData, dstStride := destination.Buffer()
realBounds := bounds
bounds = bounds.Intersect(destination.Bounds()).Canon()
if bounds.Empty() { return }
updatedRegion = bounds
point := image.Point { }
for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
if inEllipse(point, realBounds) {
dstData[point.X + point.Y * dstStride] = color
}
}}
return
}
// StrokeColorEllipse is similar to FillColorEllipse, but it draws an inset
// outline of an ellipse instead.
func StrokeColorEllipse (
destination artist.Canvas,
color color.RGBA,
bounds image.Rectangle,
weight int,
) (
updatedRegion image.Rectangle,
) {
if weight < 1 { return }
dstData, dstStride := destination.Buffer()
insetBounds := bounds.Inset(weight - 1)
context := ellipsePlottingContext {
plottingContext: plottingContext {
dstData: dstData,
dstStride: dstStride,
color: color,
weight: weight,
bounds: bounds.Intersect(destination.Bounds()),
},
radii: image.Pt(insetBounds.Dx() / 2, insetBounds.Dy() / 2),
}
context.center = insetBounds.Min.Add(context.radii)
context.plotEllipse()
return
}
func inEllipse (point image.Point, bounds image.Rectangle) bool {
point = point.Sub(bounds.Min)
x := (float64(point.X) + 0.5) / float64(bounds.Dx()) - 0.5
y := (float64(point.Y) + 0.5) / float64(bounds.Dy()) - 0.5
return math.Hypot(x, y) <= 0.5
}

View File

@ -1,110 +0,0 @@
package shapes
import "image"
import "image/color"
import "git.tebibyte.media/sashakoshka/tomo/artist"
// ColorLine draws a line from one point to another with the specified weight
// and color.
func ColorLine (
destination artist.Canvas,
color color.RGBA,
weight int,
min image.Point,
max image.Point,
) (
updatedRegion image.Rectangle,
) {
updatedRegion = image.Rectangle { Min: min, Max: max }.Canon()
updatedRegion.Max.X ++
updatedRegion.Max.Y ++
data, stride := destination.Buffer()
bounds := destination.Bounds()
context := linePlottingContext {
plottingContext: plottingContext {
dstData: data,
dstStride: stride,
color: color,
weight: weight,
bounds: bounds,
},
min: min,
max: max,
}
if abs(max.Y - min.Y) < abs(max.X - min.X) {
if max.X < min.X { context.swap() }
context.lineLow()
} else {
if max.Y < min.Y { context.swap() }
context.lineHigh()
}
return
}
type linePlottingContext struct {
plottingContext
min image.Point
max image.Point
}
func (context *linePlottingContext) swap () {
temp := context.max
context.max = context.min
context.min = temp
}
func (context linePlottingContext) lineLow () {
deltaX := context.max.X - context.min.X
deltaY := context.max.Y - context.min.Y
yi := 1
if deltaY < 0 {
yi = -1
deltaY *= -1
}
D := (2 * deltaY) - deltaX
point := context.min
for ; point.X < context.max.X; point.X ++ {
context.plotColor(point)
if D > 0 {
D += 2 * (deltaY - deltaX)
point.Y += yi
} else {
D += 2 * deltaY
}
}
}
func (context linePlottingContext) lineHigh () {
deltaX := context.max.X - context.min.X
deltaY := context.max.Y - context.min.Y
xi := 1
if deltaX < 0 {
xi = -1
deltaX *= -1
}
D := (2 * deltaX) - deltaY
point := context.min
for ; point.Y < context.max.Y; point.Y ++ {
context.plotColor(point)
if D > 0 {
point.X += xi
D += 2 * (deltaX - deltaY)
} else {
D += 2 * deltaX
}
}
}
func abs (n int) int {
if n < 0 { n *= -1}
return n
}

View File

@ -1,47 +0,0 @@
package shapes
import "image"
import "image/color"
// FIXME? drawing a ton of overlapping squares might be a bit wasteful.
type plottingContext struct {
dstData []color.RGBA
dstStride int
srcData []color.RGBA
srcStride int
color color.RGBA
weight int
offset image.Point
bounds image.Rectangle
}
func (context plottingContext) square (center image.Point) (square image.Rectangle) {
return image.Rect(0, 0, context.weight, context.weight).
Sub(image.Pt(context.weight / 2, context.weight / 2)).
Add(center).
Intersect(context.bounds)
}
func (context plottingContext) plotColor (center image.Point) {
square := context.square(center)
for y := square.Min.Y; y < square.Max.Y; y ++ {
for x := square.Min.X; x < square.Max.X; x ++ {
context.dstData[x + y * context.dstStride] = context.color
}}
}
func (context plottingContext) plotSource (center image.Point) {
square := context.square(center)
for y := square.Min.Y; y < square.Max.Y; y ++ {
for x := square.Min.X; x < square.Max.X; x ++ {
// we offset srcIndex here because we have already applied the
// offset to the square, and we need to reverse that to get the
// proper source coordinates.
srcIndex :=
x + context.offset.X +
(y + context.offset.Y) * context.dstStride
dstIndex := x + y * context.dstStride
context.dstData[dstIndex] = context.srcData [srcIndex]
}}
}

View File

@ -1,130 +0,0 @@
package shapes
import "image"
import "image/color"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/shatter"
// TODO: return updatedRegion for all routines in this package
func FillRectangle (
destination artist.Canvas,
source artist.Canvas,
bounds image.Rectangle,
) (
updatedRegion image.Rectangle,
) {
dstData, dstStride := destination.Buffer()
srcData, srcStride := source.Buffer()
offset := source.Bounds().Min.Sub(destination.Bounds().Min)
drawBounds :=
source.Bounds().Sub(offset).
Intersect(destination.Bounds()).
Intersect(bounds)
if drawBounds.Empty() { return }
updatedRegion = drawBounds
point := image.Point { }
for point.Y = drawBounds.Min.Y; point.Y < drawBounds.Max.Y; point.Y ++ {
for point.X = drawBounds.Min.X; point.X < drawBounds.Max.X; point.X ++ {
offsetPoint := point.Add(offset)
dstIndex := point.X + point.Y * dstStride
srcIndex := offsetPoint.X + offsetPoint.Y * srcStride
dstData[dstIndex] = srcData[srcIndex]
}}
return
}
func StrokeRectangle (
destination artist.Canvas,
source artist.Canvas,
bounds image.Rectangle,
weight int,
) (
updatedRegion image.Rectangle,
) {
insetBounds := bounds.Inset(weight)
if insetBounds.Empty() {
return FillRectangle(destination, source, bounds)
}
return FillRectangleShatter(destination, source, bounds, insetBounds)
}
// FillRectangleShatter is like FillRectangle, but it does not draw in areas
// specified in "rocks".
func FillRectangleShatter (
destination artist.Canvas,
source artist.Canvas,
bounds image.Rectangle,
rocks ...image.Rectangle,
) (
updatedRegion image.Rectangle,
) {
tiles := shatter.Shatter(bounds, rocks...)
for _, tile := range tiles {
FillRectangle (
artist.Cut(destination, tile),
source, tile)
updatedRegion = updatedRegion.Union(tile)
}
return
}
// FillColorRectangle fills a rectangle within the destination canvas with a
// solid color.
func FillColorRectangle (
destination artist.Canvas,
color color.RGBA,
bounds image.Rectangle,
) (
updatedRegion image.Rectangle,
) {
dstData, dstStride := destination.Buffer()
bounds = bounds.Canon().Intersect(destination.Bounds())
if bounds.Empty() { return }
updatedRegion = bounds
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
dstData[x + y * dstStride] = color
}}
return
}
// FillColorRectangleShatter is like FillColorRectangle, but it does not draw in
// areas specified in "rocks".
func FillColorRectangleShatter (
destination artist.Canvas,
color color.RGBA,
bounds image.Rectangle,
rocks ...image.Rectangle,
) (
updatedRegion image.Rectangle,
) {
tiles := shatter.Shatter(bounds, rocks...)
for _, tile := range tiles {
FillColorRectangle(destination, color, tile)
updatedRegion = updatedRegion.Union(tile)
}
return
}
// StrokeColorRectangle is similar to FillColorRectangle, but it draws an inset
// outline of the given rectangle instead.
func StrokeColorRectangle (
destination artist.Canvas,
color color.RGBA,
bounds image.Rectangle,
weight int,
) (
updatedRegion image.Rectangle,
) {
insetBounds := bounds.Inset(weight)
if insetBounds.Empty() {
return FillColorRectangle(destination, color, bounds)
}
return FillColorRectangleShatter(destination, color, bounds, insetBounds)
}

View File

@ -1,7 +1,7 @@
package config package config
import "time" import "time"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
// Default specifies default configuration values. // Default specifies default configuration values.
type Default struct { } type Default struct { }

View File

@ -7,16 +7,16 @@ import _ "image/png"
import "image/color" import "image/color"
import "golang.org/x/image/font" import "golang.org/x/image/font"
import "golang.org/x/image/font/basicfont" import "golang.org/x/image/font/basicfont"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/data" import "tomo/data"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/artist/artutil" import "art/artutil"
import "git.tebibyte.media/sashakoshka/tomo/artist/patterns" import "art/patterns"
//go:embed assets/default.png //go:embed assets/default.png
var defaultAtlasBytes []byte var defaultAtlasBytes []byte
var defaultAtlas artist.Canvas var defaultAtlas art.Canvas
var defaultTextures [7][7]artist.Pattern var defaultTextures [7][7]art.Pattern
//go:embed assets/wintergreen-icons-small.png //go:embed assets/wintergreen-icons-small.png
var defaultIconsSmallAtlasBytes []byte var defaultIconsSmallAtlasBytes []byte
var defaultIconsSmall [640]binaryIcon var defaultIconsSmall [640]binaryIcon
@ -24,15 +24,15 @@ var defaultIconsSmall [640]binaryIcon
var defaultIconsLargeAtlasBytes []byte var defaultIconsLargeAtlasBytes []byte
var defaultIconsLarge [640]binaryIcon var defaultIconsLarge [640]binaryIcon
func atlasCell (col, row int, border artist.Inset) { func atlasCell (col, row int, border art.Inset) {
bounds := image.Rect(0, 0, 8, 8).Add(image.Pt(col, row).Mul(8)) bounds := image.Rect(0, 0, 8, 8).Add(image.Pt(col, row).Mul(8))
defaultTextures[col][row] = patterns.Border { defaultTextures[col][row] = patterns.Border {
Canvas: artist.Cut(defaultAtlas, bounds), Canvas: art.Cut(defaultAtlas, bounds),
Inset: border, Inset: border,
} }
} }
func atlasCol (col int, border artist.Inset) { func atlasCol (col int, border art.Inset) {
for index, _ := range defaultTextures[col] { for index, _ := range defaultTextures[col] {
atlasCell(col, index, border) atlasCell(col, index, border)
} }
@ -43,7 +43,7 @@ type binaryIcon struct {
stride int stride int
} }
func (icon binaryIcon) Draw (destination artist.Canvas, color color.RGBA, at image.Point) { func (icon binaryIcon) Draw (destination art.Canvas, color color.RGBA, at image.Point) {
bounds := icon.Bounds().Add(at).Intersect(destination.Bounds()) bounds := icon.Bounds().Add(at).Intersect(destination.Bounds())
point := image.Point { } point := image.Point { }
data, stride := destination.Buffer() data, stride := destination.Buffer()
@ -85,15 +85,15 @@ func binaryIconFrom (source image.Image, clip image.Rectangle) (icon binaryIcon)
func init () { func init () {
defaultAtlasImage, _, _ := image.Decode(bytes.NewReader(defaultAtlasBytes)) defaultAtlasImage, _, _ := image.Decode(bytes.NewReader(defaultAtlasBytes))
defaultAtlas = artist.FromImage(defaultAtlasImage) defaultAtlas = art.FromImage(defaultAtlasImage)
atlasCol(0, artist.I(0)) atlasCol(0, art.I(0))
atlasCol(1, artist.I(3)) atlasCol(1, art.I(3))
atlasCol(2, artist.I(1)) atlasCol(2, art.I(1))
atlasCol(3, artist.I(1)) atlasCol(3, art.I(1))
atlasCol(4, artist.I(1)) atlasCol(4, art.I(1))
atlasCol(5, artist.I(3)) atlasCol(5, art.I(3))
atlasCol(6, artist.I(1)) atlasCol(6, art.I(1))
// set up small icons // set up small icons
defaultIconsSmallAtlasImage, _, _ := image.Decode ( defaultIconsSmallAtlasImage, _, _ := image.Decode (
@ -139,7 +139,7 @@ func (Default) FontFace (style tomo.FontStyle, size tomo.FontSize, c tomo.Case)
} }
// Icon returns an icon from the default set corresponding to the given name. // Icon returns an icon from the default set corresponding to the given name.
func (Default) Icon (id tomo.Icon, size tomo.IconSize, c tomo.Case) artist.Icon { func (Default) Icon (id tomo.Icon, size tomo.IconSize, c tomo.Case) art.Icon {
if size == tomo.IconSizeLarge { if size == tomo.IconSizeLarge {
if id < 0 || int(id) >= len(defaultIconsLarge) { if id < 0 || int(id) >= len(defaultIconsLarge) {
return nil return nil
@ -157,14 +157,14 @@ func (Default) Icon (id tomo.Icon, size tomo.IconSize, c tomo.Case) artist.Icon
// MimeIcon returns an icon from the default set corresponding to the given mime. // MimeIcon returns an icon from the default set corresponding to the given mime.
// type. // type.
func (Default) MimeIcon (data.Mime, tomo.IconSize, tomo.Case) artist.Icon { func (Default) MimeIcon (data.Mime, tomo.IconSize, tomo.Case) art.Icon {
// TODO // TODO
return nil return nil
} }
// Pattern returns a pattern from the default theme corresponding to the given // Pattern returns a pattern from the default theme corresponding to the given
// pattern ID. // pattern ID.
func (Default) Pattern (id tomo.Pattern, state tomo.State, c tomo.Case) artist.Pattern { func (Default) Pattern (id tomo.Pattern, state tomo.State, c tomo.Case) art.Pattern {
offset := 0; switch { offset := 0; switch {
case state.Disabled: offset = 1 case state.Disabled: offset = 1
case state.Pressed && state.On: offset = 4 case state.Pressed && state.On: offset = 4
@ -224,11 +224,11 @@ func (Default) Color (id tomo.Color, state tomo.State, c tomo.Case) color.RGBA {
} }
// Padding returns the default padding value for the given pattern. // Padding returns the default padding value for the given pattern.
func (Default) Padding (id tomo.Pattern, c tomo.Case) artist.Inset { func (Default) Padding (id tomo.Pattern, c tomo.Case) art.Inset {
switch id { switch id {
case tomo.PatternGutter: return artist.I(0) case tomo.PatternGutter: return art.I(0)
case tomo.PatternLine: return artist.I(1) case tomo.PatternLine: return art.I(1)
default: return artist.I(6) default: return art.I(6)
} }
} }

View File

@ -1,6 +1,6 @@
package tomo package tomo
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
// Element represents a basic on-screen object. Extended element interfaces are // Element represents a basic on-screen object. Extended element interfaces are
// defined in the ability module. // defined in the ability module.
@ -8,7 +8,7 @@ type Element interface {
// Draw causes the element to draw to the specified canvas. The bounds // Draw causes the element to draw to the specified canvas. The bounds
// of this canvas specify the area that is actually drawn to, while the // of this canvas specify the area that is actually drawn to, while the
// Entity bounds specify the actual area of the element. // Entity bounds specify the actual area of the element.
Draw (artist.Canvas) Draw (art.Canvas)
// Entity returns this element's entity. // Entity returns this element's entity.
Entity () Entity Entity () Entity

View File

@ -1,9 +1,9 @@
package elements package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/shatter" import "art/shatter"
var boxCase = tomo.C("tomo", "box") var boxCase = tomo.C("tomo", "box")
@ -61,7 +61,7 @@ func NewVBox (space Space, children ...tomo.Element) (element *Box) {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *Box) Draw (destination artist.Canvas) { func (element *Box) Draw (destination art.Canvas) {
rocks := make([]image.Rectangle, element.entity.CountChildren()) rocks := make([]image.Rectangle, element.entity.CountChildren())
for index := 0; index < element.entity.CountChildren(); index ++ { for index := 0; index < element.entity.CountChildren(); index ++ {
rocks[index] = element.entity.Child(index).Entity().Bounds() rocks[index] = element.entity.Child(index).Entity().Bounds()
@ -69,7 +69,7 @@ func (element *Box) Draw (destination artist.Canvas) {
tiles := shatter.Shatter(element.entity.Bounds(), rocks...) tiles := shatter.Shatter(element.entity.Bounds(), rocks...)
for _, tile := range tiles { for _, tile := range tiles {
element.entity.DrawBackground(artist.Cut(destination, tile)) element.entity.DrawBackground(art.Cut(destination, tile))
} }
} }
@ -126,7 +126,7 @@ func (element *Box) AdoptExpand (children ...tomo.Element) {
// DrawBackground draws this element's background pattern to the specified // DrawBackground draws this element's background pattern to the specified
// destination canvas. // destination canvas.
func (element *Box) DrawBackground (destination artist.Canvas) { func (element *Box) DrawBackground (destination art.Canvas) {
element.entity.DrawBackground(destination) element.entity.DrawBackground(destination)
} }

View File

@ -1,10 +1,10 @@
package elements package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "tomo/textdraw"
var buttonCase = tomo.C("tomo", "button") var buttonCase = tomo.C("tomo", "button")
@ -42,7 +42,7 @@ func (element *Button) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *Button) Draw (destination artist.Canvas) { func (element *Button) Draw (destination art.Canvas) {
state := element.state() state := element.state()
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
pattern := element.entity.Theme().Pattern(tomo.PatternButton, state, buttonCase) pattern := element.entity.Theme().Pattern(tomo.PatternButton, state, buttonCase)

View File

@ -1,8 +1,8 @@
package elements package elements
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/artist/artutil" import "art/artutil"
var cellCase = tomo.C("tomo", "cell") var cellCase = tomo.C("tomo", "cell")
@ -32,7 +32,7 @@ func (element *Cell) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *Cell) Draw (destination artist.Canvas) { func (element *Cell) Draw (destination art.Canvas) {
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
pattern := element.entity.Theme().Pattern(tomo.PatternTableCell, element.state(), cellCase) pattern := element.entity.Theme().Pattern(tomo.PatternTableCell, element.state(), cellCase)
if element.child == nil { if element.child == nil {
@ -56,7 +56,7 @@ func (element *Cell) Layout () {
// DrawBackground draws this element's background pattern to the specified // DrawBackground draws this element's background pattern to the specified
// destination canvas. // destination canvas.
func (element *Cell) DrawBackground (destination artist.Canvas) { func (element *Cell) DrawBackground (destination art.Canvas) {
element.entity.Theme().Pattern(tomo.PatternTableCell, element.state(), cellCase). element.entity.Theme().Pattern(tomo.PatternTableCell, element.state(), cellCase).
Draw(destination, element.entity.Bounds()) Draw(destination, element.entity.Bounds())
} }

View File

@ -1,10 +1,10 @@
package elements package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "tomo/textdraw"
var checkboxCase = tomo.C("tomo", "checkbox") var checkboxCase = tomo.C("tomo", "checkbox")
@ -39,7 +39,7 @@ func (element *Checkbox) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *Checkbox) Draw (destination artist.Canvas) { func (element *Checkbox) Draw (destination art.Canvas) {
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min) boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min)

View File

@ -1,11 +1,11 @@
package elements package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/ability" import "tomo/ability"
import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "tomo/textdraw"
var comboBoxCase = tomo.C("tomo", "comboBox") var comboBoxCase = tomo.C("tomo", "comboBox")
@ -53,7 +53,7 @@ func (element *ComboBox) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *ComboBox) Draw (destination artist.Canvas) { func (element *ComboBox) Draw (destination art.Canvas) {
state := element.state() state := element.state()
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
pattern := element.entity.Theme().Pattern(tomo.PatternButton, state, comboBoxCase) pattern := element.entity.Theme().Pattern(tomo.PatternButton, state, comboBoxCase)

View File

@ -1,6 +1,6 @@
package elements package elements
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
type scratchEntry struct { type scratchEntry struct {
expand bool expand bool

View File

@ -2,11 +2,11 @@ package elements
import "image" import "image"
import "path/filepath" import "path/filepath"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/ability" import "tomo/ability"
import "git.tebibyte.media/sashakoshka/tomo/shatter" import "art/shatter"
// TODO: base on flow implementation of list. also be able to switch to a table // TODO: base on flow implementation of list. also be able to switch to a table
// variant for a more information dense view. // variant for a more information dense view.
@ -52,7 +52,7 @@ func NewDirectory (
return return
} }
func (element *Directory) Draw (destination artist.Canvas) { func (element *Directory) Draw (destination art.Canvas) {
rocks := make([]image.Rectangle, element.entity.CountChildren()) rocks := make([]image.Rectangle, element.entity.CountChildren())
for index := 0; index < element.entity.CountChildren(); index ++ { for index := 0; index < element.entity.CountChildren(); index ++ {
rocks[index] = element.entity.Child(index).Entity().Bounds() rocks[index] = element.entity.Child(index).Entity().Bounds()
@ -60,7 +60,7 @@ func (element *Directory) Draw (destination artist.Canvas) {
tiles := shatter.Shatter(element.entity.Bounds(), rocks...) tiles := shatter.Shatter(element.entity.Bounds(), rocks...)
for _, tile := range tiles { for _, tile := range tiles {
element.DrawBackground(artist.Cut(destination, tile)) element.DrawBackground(art.Cut(destination, tile))
} }
} }
@ -199,7 +199,7 @@ func (element *Directory) ScrollAxes () (horizontal, vertical bool) {
return false, true return false, true
} }
func (element *Directory) DrawBackground (destination artist.Canvas) { func (element *Directory) DrawBackground (destination art.Canvas) {
element.entity.Theme().Pattern(tomo.PatternPinboard, tomo.State { }, directoryCase). element.entity.Theme().Pattern(tomo.PatternPinboard, tomo.State { }, directoryCase).
Draw(destination, element.entity.Bounds()) Draw(destination, element.entity.Bounds())
} }

View File

@ -1,10 +1,10 @@
package elements package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/ability" import "tomo/ability"
import "git.tebibyte.media/sashakoshka/tomo/shatter" import "art/shatter"
var documentCase = tomo.C("tomo", "document") var documentCase = tomo.C("tomo", "document")
@ -33,7 +33,7 @@ func NewDocument (children ...tomo.Element) (element *Document) {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *Document) Draw (destination artist.Canvas) { func (element *Document) Draw (destination art.Canvas) {
rocks := make([]image.Rectangle, element.entity.CountChildren()) rocks := make([]image.Rectangle, element.entity.CountChildren())
for index := 0; index < element.entity.CountChildren(); index ++ { for index := 0; index < element.entity.CountChildren(); index ++ {
rocks[index] = element.entity.Child(index).Entity().Bounds() rocks[index] = element.entity.Child(index).Entity().Bounds()
@ -41,7 +41,7 @@ func (element *Document) Draw (destination artist.Canvas) {
tiles := shatter.Shatter(element.entity.Bounds(), rocks...) tiles := shatter.Shatter(element.entity.Bounds(), rocks...)
for _, tile := range tiles { for _, tile := range tiles {
element.entity.DrawBackground(artist.Cut(destination, tile)) element.entity.DrawBackground(art.Cut(destination, tile))
} }
} }
@ -132,7 +132,7 @@ func (element *Document) HandleChildFlexibleHeightChange (child ability.Flexible
// DrawBackground draws this element's background pattern to the specified // DrawBackground draws this element's background pattern to the specified
// destination canvas. // destination canvas.
func (element *Document) DrawBackground (destination artist.Canvas) { func (element *Document) DrawBackground (destination art.Canvas) {
element.entity.DrawBackground(destination) element.entity.DrawBackground(destination)
} }

View File

@ -3,9 +3,9 @@ package elements
import "time" import "time"
import "io/fs" import "io/fs"
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
var fileCase = tomo.C("files", "file") var fileCase = tomo.C("files", "file")
@ -45,7 +45,7 @@ func (element *File) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *File) Draw (destination artist.Canvas) { func (element *File) Draw (destination art.Canvas) {
// background // background
state := element.state() state := element.state()
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
@ -199,7 +199,7 @@ func (element *File) state () tomo.State {
} }
} }
func (element *File) icon () artist.Icon { func (element *File) icon () art.Icon {
return element.entity.Theme().Icon(element.iconID, tomo.IconSizeLarge, fileCase) return element.entity.Theme().Icon(element.iconID, tomo.IconSizeLarge, fileCase)
} }

View File

@ -4,9 +4,9 @@ import "time"
import "math" import "math"
import "image" import "image"
import "image/color" import "image/color"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" import "art/shapes"
var clockCase = tomo.C("tomo", "clock") var clockCase = tomo.C("tomo", "clock")
@ -30,7 +30,7 @@ func (element *AnalogClock) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *AnalogClock) Draw (destination artist.Canvas) { func (element *AnalogClock) Draw (destination art.Canvas) {
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
state := tomo.State { } state := tomo.State { }
@ -71,7 +71,7 @@ func (element *AnalogClock) HandleThemeChange () {
} }
func (element *AnalogClock) radialLine ( func (element *AnalogClock) radialLine (
destination artist.Canvas, destination art.Canvas,
source color.RGBA, source color.RGBA,
inner float64, inner float64,
outer float64, outer float64,

View File

@ -1,11 +1,11 @@
package fun package fun
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/artist/artutil" import "art/artutil"
import "git.tebibyte.media/sashakoshka/tomo/elements/fun/music" import "tomo/elements/fun/music"
var pianoCase = tomo.C("tomo", "piano") var pianoCase = tomo.C("tomo", "piano")
var flatCase = tomo.C("tomo", "piano", "flatKey") var flatCase = tomo.C("tomo", "piano", "flatKey")
@ -57,7 +57,7 @@ func (element *Piano) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *Piano) Draw (destination artist.Canvas) { func (element *Piano) Draw (destination art.Canvas) {
element.recalculate() element.recalculate()
state := tomo.State { state := tomo.State {
@ -304,7 +304,7 @@ func (element *Piano) recalculate () {
} }
func (element *Piano) drawFlat ( func (element *Piano) drawFlat (
destination artist.Canvas, destination art.Canvas,
bounds image.Rectangle, bounds image.Rectangle,
pressed bool, pressed bool,
state tomo.State, state tomo.State,
@ -315,7 +315,7 @@ func (element *Piano) drawFlat (
} }
func (element *Piano) drawSharp ( func (element *Piano) drawSharp (
destination artist.Canvas, destination art.Canvas,
bounds image.Rectangle, bounds image.Rectangle,
pressed bool, pressed bool,
state tomo.State, state tomo.State,

View File

@ -1,8 +1,8 @@
package elements package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
var iconCase = tomo.C("tomo", "icon") var iconCase = tomo.C("tomo", "icon")
@ -44,7 +44,7 @@ func (element *Icon) HandleThemeChange () {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *Icon) Draw (destination artist.Canvas) { func (element *Icon) Draw (destination art.Canvas) {
if element.entity == nil { return } if element.entity == nil { return }
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
@ -65,7 +65,7 @@ func (element *Icon) Draw (destination artist.Canvas) {
} }
} }
func (element *Icon) icon () artist.Icon { func (element *Icon) icon () art.Icon {
return element.entity.Theme().Icon(element.id, element.size, iconCase) return element.entity.Theme().Icon(element.id, element.size, iconCase)
} }

View File

@ -1,21 +1,21 @@
package elements package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/artist/patterns" import "art/patterns"
// TODO: this element is lame need to make it better // TODO: this element is lame need to make it better
// Image is an element capable of displaying an image. // Image is an element capable of displaying an image.
type Image struct { type Image struct {
entity tomo.Entity entity tomo.Entity
buffer artist.Canvas buffer art.Canvas
} }
// NewImage creates a new image element. // NewImage creates a new image element.
func NewImage (image image.Image) (element *Image) { func NewImage (image image.Image) (element *Image) {
element = &Image { buffer: artist.FromImage(image) } element = &Image { buffer: art.FromImage(image) }
element.entity = tomo.GetBackend().NewEntity(element) element.entity = tomo.GetBackend().NewEntity(element)
bounds := element.buffer.Bounds() bounds := element.buffer.Bounds()
element.entity.SetMinimumSize(bounds.Dx(), bounds.Dy()) element.entity.SetMinimumSize(bounds.Dx(), bounds.Dy())
@ -28,7 +28,7 @@ func (element *Image) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *Image) Draw (destination artist.Canvas) { func (element *Image) Draw (destination art.Canvas) {
if element.entity == nil { return } if element.entity == nil { return }
(patterns.Texture { Canvas: element.buffer }). (patterns.Texture { Canvas: element.buffer }).
Draw(destination, element.entity.Bounds()) Draw(destination, element.entity.Bounds())

View File

@ -2,11 +2,11 @@ package elements
import "image" import "image"
import "golang.org/x/image/math/fixed" import "golang.org/x/image/math/fixed"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/data" import "tomo/data"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "tomo/textdraw"
var labelCase = tomo.C("tomo", "label") var labelCase = tomo.C("tomo", "label")
@ -48,7 +48,7 @@ func (element *Label) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *Label) Draw (destination artist.Canvas) { func (element *Label) Draw (destination art.Canvas) {
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
if element.wrap { if element.wrap {

View File

@ -1,6 +1,6 @@
package elements package elements
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
// Numeric is a type constraint representing a number. // Numeric is a type constraint representing a number.
type Numeric interface { type Numeric interface {

View File

@ -1,11 +1,11 @@
package elements package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/ability" import "tomo/ability"
import "git.tebibyte.media/sashakoshka/tomo/artist/artutil" import "art/artutil"
type list struct { type list struct {
container container
@ -61,7 +61,7 @@ func (element *list) init (children ...tomo.Element) {
element.Adopt(children...) element.Adopt(children...)
} }
func (element *list) Draw (destination artist.Canvas) { func (element *list) Draw (destination art.Canvas) {
rocks := make([]image.Rectangle, element.entity.CountChildren()) rocks := make([]image.Rectangle, element.entity.CountChildren())
for index := 0; index < element.entity.CountChildren(); index ++ { for index := 0; index < element.entity.CountChildren(); index ++ {
rocks[index] = element.entity.Child(index).Entity().Bounds() rocks[index] = element.entity.Child(index).Entity().Bounds()
@ -274,7 +274,7 @@ func (element *list) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
func (element *list) HandleKeyUp(key input.Key, modifiers input.Modifiers) { } func (element *list) HandleKeyUp(key input.Key, modifiers input.Modifiers) { }
func (element *list) DrawBackground (destination artist.Canvas) { func (element *list) DrawBackground (destination art.Canvas) {
element.entity.DrawBackground(destination) element.entity.DrawBackground(destination)
} }

View File

@ -1,8 +1,8 @@
package elements package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
var progressBarCase = tomo.C("tomo", "progressBar") var progressBarCase = tomo.C("tomo", "progressBar")
@ -29,7 +29,7 @@ func (element *ProgressBar) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *ProgressBar) Draw (destination artist.Canvas) { func (element *ProgressBar) Draw (destination art.Canvas) {
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
pattern := element.entity.Theme().Pattern(tomo.PatternSunken, tomo.State { }, progressBarCase) pattern := element.entity.Theme().Pattern(tomo.PatternSunken, tomo.State { }, progressBarCase)

View File

@ -1,10 +1,10 @@
package elements package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/ability" import "tomo/ability"
var scrollCase = tomo.C("tomo", "scroll") var scrollCase = tomo.C("tomo", "scroll")
@ -76,7 +76,7 @@ func (element *Scroll) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *Scroll) Draw (destination artist.Canvas) { func (element *Scroll) Draw (destination art.Canvas) {
if element.horizontal != nil && element.vertical != nil { if element.horizontal != nil && element.vertical != nil {
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
bounds.Min = image.Pt ( bounds.Min = image.Pt (
@ -84,7 +84,7 @@ func (element *Scroll) Draw (destination artist.Canvas) {
bounds.Max.Y - element.horizontal.Entity().Bounds().Dy()) bounds.Max.Y - element.horizontal.Entity().Bounds().Dy())
state := tomo.State { } state := tomo.State { }
deadArea := element.entity.Theme().Pattern(tomo.PatternDead, state, scrollCase) deadArea := element.entity.Theme().Pattern(tomo.PatternDead, state, scrollCase)
deadArea.Draw(artist.Cut(destination, bounds), bounds) deadArea.Draw(art.Cut(destination, bounds), bounds)
} }
} }
@ -131,7 +131,7 @@ func (element *Scroll) Layout () {
// DrawBackground draws this element's background pattern to the specified // DrawBackground draws this element's background pattern to the specified
// destination canvas. // destination canvas.
func (element *Scroll) DrawBackground (destination artist.Canvas) { func (element *Scroll) DrawBackground (destination art.Canvas) {
element.entity.DrawBackground(destination) element.entity.DrawBackground(destination)
} }

View File

@ -1,9 +1,9 @@
package elements package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
// ScrollBar is an element similar to Slider, but it has special behavior that // ScrollBar is an element similar to Slider, but it has special behavior that
// makes it well suited for controlling the viewport position on one axis of a // makes it well suited for controlling the viewport position on one axis of a
@ -63,7 +63,7 @@ func (element *ScrollBar) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *ScrollBar) Draw (destination artist.Canvas) { func (element *ScrollBar) Draw (destination art.Canvas) {
element.recalculate() element.recalculate()
bounds := element.entity.Bounds() bounds := element.entity.Bounds()

View File

@ -1,9 +1,9 @@
package elements package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
// Slider is a slider control with a floating point value between zero and one. // Slider is a slider control with a floating point value between zero and one.
type Slider struct { type Slider struct {
@ -59,7 +59,7 @@ func (element *slider) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *slider) Draw (destination artist.Canvas) { func (element *slider) Draw (destination art.Canvas) {
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
element.track = element.entity.Theme().Padding(tomo.PatternGutter, element.c).Apply(bounds) element.track = element.entity.Theme().Padding(tomo.PatternGutter, element.c).Apply(bounds)
if element.vertical { if element.vertical {

View File

@ -1,7 +1,7 @@
package elements package elements
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
var spacerCase = tomo.C("tomo", "spacer") var spacerCase = tomo.C("tomo", "spacer")
@ -32,7 +32,7 @@ func (element *Spacer) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *Spacer) Draw (destination artist.Canvas) { func (element *Spacer) Draw (destination art.Canvas) {
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
if element.line { if element.line {

View File

@ -1,10 +1,10 @@
package elements package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "tomo/textdraw"
var switchCase = tomo.C("tomo", "switch") var switchCase = tomo.C("tomo", "switch")
@ -44,7 +44,7 @@ func (element *Switch) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *Switch) Draw (destination artist.Canvas) { func (element *Switch) Draw (destination art.Canvas) {
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
handleBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min) handleBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min)
gutterBounds := image.Rect(0, 0, bounds.Dy() * 2, bounds.Dy()).Add(bounds.Min) gutterBounds := image.Rect(0, 0, bounds.Dy() * 2, bounds.Dy()).Add(bounds.Min)

View File

@ -4,21 +4,21 @@ import "fmt"
import "time" import "time"
import "image" import "image"
import "image/color" import "image/color"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/shatter" import "art/shatter"
import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "tomo/textdraw"
import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" import "art/shapes"
import "git.tebibyte.media/sashakoshka/tomo/artist/artutil" import "art/artutil"
import "git.tebibyte.media/sashakoshka/tomo/artist/patterns" import "art/patterns"
// Artist is an element that displays shapes and patterns drawn by the artist // Artist is an element that displays shapes and patterns drawn by the art
// package in order to test it. // package in order to test it.
type Artist struct { type Artist struct {
entity tomo.Entity entity tomo.Entity
} }
// NewArtist creates a new artist test element. // NewArtist creates a new art test element.
func NewArtist () (element *Artist) { func NewArtist () (element *Artist) {
element = &Artist { } element = &Artist { }
element.entity = tomo.GetBackend().NewEntity(element) element.entity = tomo.GetBackend().NewEntity(element)
@ -30,7 +30,7 @@ func (element *Artist) Entity () tomo.Entity {
return element.entity return element.entity
} }
func (element *Artist) Draw (destination artist.Canvas) { func (element *Artist) Draw (destination art.Canvas) {
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
patterns.Uhex(0x000000FF).Draw(destination, bounds) patterns.Uhex(0x000000FF).Draw(destination, bounds)
@ -81,7 +81,7 @@ func (element *Artist) Draw (destination artist.Canvas) {
} }
tiles := shatter.Shatter(c41.Bounds(), rocks...) tiles := shatter.Shatter(c41.Bounds(), rocks...)
for index, tile := range tiles { for index, tile := range tiles {
[]artist.Pattern { []art.Pattern {
patterns.Uhex(0xFF0000FF), patterns.Uhex(0xFF0000FF),
patterns.Uhex(0x00FF00FF), patterns.Uhex(0x00FF00FF),
patterns.Uhex(0xFF00FFFF), patterns.Uhex(0xFF00FFFF),
@ -115,26 +115,26 @@ func (element *Artist) Draw (destination artist.Canvas) {
c03 := element.cellAt(destination, 0, 3) c03 := element.cellAt(destination, 0, 3)
patterns.Border { patterns.Border {
Canvas: element.thingy(c42), Canvas: element.thingy(c42),
Inset: artist.Inset { 8, 8, 8, 8 }, Inset: art.Inset { 8, 8, 8, 8 },
}.Draw(c03, c03.Bounds()) }.Draw(c03, c03.Bounds())
// 1, 3 // 1, 3
c13 := element.cellAt(destination, 1, 3) c13 := element.cellAt(destination, 1, 3)
patterns.Border { patterns.Border {
Canvas: element.thingy(c42), Canvas: element.thingy(c42),
Inset: artist.Inset { 8, 8, 8, 8 }, Inset: art.Inset { 8, 8, 8, 8 },
}.Draw(c13, c13.Bounds().Inset(10)) }.Draw(c13, c13.Bounds().Inset(10))
// 2, 3 // 2, 3
c23 := element.cellAt(destination, 2, 3) c23 := element.cellAt(destination, 2, 3)
patterns.Border { patterns.Border {
Canvas: element.thingy(c42), Canvas: element.thingy(c42),
Inset: artist.Inset { 8, 8, 8, 8 }, Inset: art.Inset { 8, 8, 8, 8 },
}.Draw(c23, c23.Bounds()) }.Draw(c23, c23.Bounds())
patterns.Border { patterns.Border {
Canvas: element.thingy(c42), Canvas: element.thingy(c42),
Inset: artist.Inset { 8, 8, 8, 8 }, Inset: art.Inset { 8, 8, 8, 8 },
}.Draw(artist.Cut(c23, c23.Bounds().Inset(16)), c23.Bounds()) }.Draw(art.Cut(c23, c23.Bounds().Inset(16)), c23.Bounds())
// how long did that take to render? // how long did that take to render?
drawTime := time.Since(drawStart) drawTime := time.Since(drawStart)
@ -142,7 +142,7 @@ func (element *Artist) Draw (destination artist.Canvas) {
textDrawer.SetFace(element.entity.Theme().FontFace ( textDrawer.SetFace(element.entity.Theme().FontFace (
tomo.FontStyleRegular, tomo.FontStyleRegular,
tomo.FontSizeNormal, tomo.FontSizeNormal,
tomo.C("tomo", "artist"))) tomo.C("tomo", "art")))
textDrawer.SetText ([]rune (fmt.Sprintf ( textDrawer.SetText ([]rune (fmt.Sprintf (
"%dms\n%dus", "%dms\n%dus",
drawTime.Milliseconds(), drawTime.Milliseconds(),
@ -152,7 +152,7 @@ func (element *Artist) Draw (destination artist.Canvas) {
image.Pt(bounds.Min.X + 8, bounds.Max.Y - 24)) image.Pt(bounds.Min.X + 8, bounds.Max.Y - 24))
} }
func (element *Artist) colorLines (destination artist.Canvas, weight int, bounds image.Rectangle) { func (element *Artist) colorLines (destination art.Canvas, weight int, bounds image.Rectangle) {
bounds = bounds.Inset(4) bounds = bounds.Inset(4)
c := artutil.Hex(0xFFFFFFFF) c := artutil.Hex(0xFFFFFFFF)
shapes.ColorLine(destination, c, weight, bounds.Min, bounds.Max) shapes.ColorLine(destination, c, weight, bounds.Min, bounds.Max)
@ -186,18 +186,18 @@ func (element *Artist) colorLines (destination artist.Canvas, weight int, bounds
image.Pt(bounds.Min.X + bounds.Dx() / 2, bounds.Max.Y)) image.Pt(bounds.Min.X + bounds.Dx() / 2, bounds.Max.Y))
} }
func (element *Artist) cellAt (destination artist.Canvas, x, y int) (artist.Canvas) { func (element *Artist) cellAt (destination art.Canvas, x, y int) (art.Canvas) {
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
cellBounds := image.Rectangle { } cellBounds := image.Rectangle { }
cellBounds.Min = bounds.Min cellBounds.Min = bounds.Min
cellBounds.Max.X = bounds.Min.X + bounds.Dx() / 5 cellBounds.Max.X = bounds.Min.X + bounds.Dx() / 5
cellBounds.Max.Y = bounds.Min.Y + (bounds.Dy() - 48) / 4 cellBounds.Max.Y = bounds.Min.Y + (bounds.Dy() - 48) / 4
return artist.Cut (destination, cellBounds.Add (image.Pt ( return art.Cut (destination, cellBounds.Add (image.Pt (
x * cellBounds.Dx(), x * cellBounds.Dx(),
y * cellBounds.Dy()))) y * cellBounds.Dy())))
} }
func (element *Artist) thingy (destination artist.Canvas) (result artist.Canvas) { func (element *Artist) thingy (destination art.Canvas) (result art.Canvas) {
bounds := destination.Bounds() bounds := destination.Bounds()
bounds = image.Rect(0, 0, 32, 32).Add(bounds.Min) bounds = image.Rect(0, 0, 32, 32).Add(bounds.Min)
shapes.FillColorRectangle(destination, artutil.Hex(0x440000FF), bounds) shapes.FillColorRectangle(destination, artutil.Hex(0x440000FF), bounds)
@ -205,5 +205,5 @@ func (element *Artist) thingy (destination artist.Canvas) (result artist.Canvas)
shapes.StrokeColorRectangle(destination, artutil.Hex(0x004400FF), bounds.Inset(4), 1) shapes.StrokeColorRectangle(destination, artutil.Hex(0x004400FF), bounds.Inset(4), 1)
shapes.FillColorRectangle(destination, artutil.Hex(0x004444FF), bounds.Inset(12)) shapes.FillColorRectangle(destination, artutil.Hex(0x004444FF), bounds.Inset(12))
shapes.StrokeColorRectangle(destination, artutil.Hex(0x888888FF), bounds.Inset(8), 1) shapes.StrokeColorRectangle(destination, artutil.Hex(0x888888FF), bounds.Inset(8), 1)
return artist.Cut(destination, bounds) return art.Cut(destination, bounds)
} }

View File

@ -1,11 +1,11 @@
package testing package testing
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" import "art/shapes"
import "git.tebibyte.media/sashakoshka/tomo/artist/artutil" import "art/artutil"
var mouseCase = tomo.C("tomo", "mouse") var mouseCase = tomo.C("tomo", "mouse")
@ -29,7 +29,7 @@ func (element *Mouse) Entity () tomo.Entity {
return element.entity return element.entity
} }
func (element *Mouse) Draw (destination artist.Canvas) { func (element *Mouse) Draw (destination art.Canvas) {
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
accent := element.entity.Theme().Color ( accent := element.entity.Theme().Color (
tomo.ColorAccent, tomo.ColorAccent,

View File

@ -3,14 +3,14 @@ package elements
import "io" import "io"
import "time" import "time"
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/data" import "tomo/data"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "tomo/textdraw"
import "git.tebibyte.media/sashakoshka/tomo/textmanip" import "tomo/textmanip"
import "git.tebibyte.media/sashakoshka/tomo/fixedutil" import "tomo/fixedutil"
import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" import "art/shapes"
var textBoxCase = tomo.C("tomo", "textBox") var textBoxCase = tomo.C("tomo", "textBox")
@ -60,13 +60,13 @@ func (element *TextBox) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *TextBox) Draw (destination artist.Canvas) { func (element *TextBox) Draw (destination art.Canvas) {
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
state := element.state() state := element.state()
pattern := element.entity.Theme().Pattern(tomo.PatternInput, state, textBoxCase) pattern := element.entity.Theme().Pattern(tomo.PatternInput, state, textBoxCase)
padding := element.entity.Theme().Padding(tomo.PatternInput, textBoxCase) padding := element.entity.Theme().Padding(tomo.PatternInput, textBoxCase)
innerCanvas := artist.Cut(destination, padding.Apply(bounds)) innerCanvas := art.Cut(destination, padding.Apply(bounds))
pattern.Draw(destination, bounds) pattern.Draw(destination, bounds)
offset := element.textOffset() offset := element.textOffset()
@ -208,8 +208,8 @@ func (element *TextBox) textOffset () image.Point {
innerBounds := padding.Apply(bounds) innerBounds := padding.Apply(bounds)
textHeight := element.valueDrawer.LineHeight().Round() textHeight := element.valueDrawer.LineHeight().Round()
return bounds.Min.Add (image.Pt ( return bounds.Min.Add (image.Pt (
padding[artist.SideLeft] - element.scroll, padding[art.SideLeft] - element.scroll,
padding[artist.SideTop] + (innerBounds.Dy() - textHeight) / 2)) padding[art.SideTop] + (innerBounds.Dy() - textHeight) / 2))
} }
func (element *TextBox) atPosition (position image.Point) int { func (element *TextBox) atPosition (position image.Point) int {

View File

@ -1,10 +1,10 @@
package elements package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "tomo/textdraw"
var toggleButtonCase = tomo.C("tomo", "toggleButton") var toggleButtonCase = tomo.C("tomo", "toggleButton")
@ -47,7 +47,7 @@ func (element *ToggleButton) Entity () tomo.Entity {
} }
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *ToggleButton) Draw (destination artist.Canvas) { func (element *ToggleButton) Draw (destination art.Canvas) {
state := element.state() state := element.state()
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
pattern := element.entity.Theme().Pattern(tomo.PatternButton, state, toggleButtonCase) pattern := element.entity.Theme().Pattern(tomo.PatternButton, state, toggleButtonCase)

View File

@ -1,7 +1,7 @@
package tomo package tomo
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
// Entity is a handle given to elements by the backend. Extended entity // Entity is a handle given to elements by the backend. Extended entity
// interfaces are defined in the ability module. // interfaces are defined in the ability module.
@ -33,7 +33,7 @@ type Entity interface {
// labels. If there is no parent element (that is, the element is // labels. If there is no parent element (that is, the element is
// directly inside of the window), the backend will draw a default // directly inside of the window), the backend will draw a default
// background pattern. // background pattern.
DrawBackground (artist.Canvas) DrawBackground (art.Canvas)
// --- Behaviors relating to parenting --- // --- Behaviors relating to parenting ---

View File

@ -5,11 +5,11 @@ import "image"
import _ "image/png" import _ "image/png"
import _ "image/gif" import _ "image/gif"
import _ "image/jpeg" import _ "image/jpeg"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/data" import "tomo/data"
import "git.tebibyte.media/sashakoshka/tomo/nasin" import "tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/popups" import "tomo/popups"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "tomo/elements"
var validImageTypes = []data.Mime { var validImageTypes = []data.Mime {
data.M("image", "png"), data.M("image", "png"),

View File

@ -3,9 +3,9 @@ package main
import "os" import "os"
import "image" import "image"
import _ "image/png" import _ "image/png"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/nasin" import "tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "tomo/elements"
func main () { func main () {
nasin.Run(Application { }) nasin.Run(Application { })

View File

@ -1,8 +1,8 @@
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/nasin" import "tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/elements/testing" import "tomo/elements/testing"
import "git.tebibyte.media/sashakoshka/ezprof/ez" import "git.tebibyte.media/sashakoshka/ezprof/ez"
func main () { func main () {

View File

@ -2,9 +2,9 @@ package main
import "os" import "os"
import "path/filepath" import "path/filepath"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/nasin" import "tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "tomo/elements"
func main () { func main () {
nasin.Run(Application { }) nasin.Run(Application { })

View File

@ -1,10 +1,10 @@
package main package main
import "time" import "time"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/nasin" import "tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "tomo/elements"
import "git.tebibyte.media/sashakoshka/tomo/elements/fun" import "tomo/elements/fun"
func main () { func main () {
nasin.Run(Application { }) nasin.Run(Application { })

View File

@ -1,8 +1,8 @@
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/nasin" import "tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "tomo/elements"
func main () { func main () {
nasin.Run(Application { }) nasin.Run(Application { })

View File

@ -5,10 +5,10 @@ import "image"
import "bytes" import "bytes"
import _ "image/png" import _ "image/png"
import "github.com/jezek/xgbutil/gopher" import "github.com/jezek/xgbutil/gopher"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/nasin" import "tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/popups" import "tomo/popups"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "tomo/elements"
func main () { func main () {
nasin.Run(Application { }) nasin.Run(Application { })

View File

@ -1,9 +1,9 @@
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/nasin" import "tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/popups" import "tomo/popups"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "tomo/elements"
func main () { func main () {
nasin.Run(Application { }) nasin.Run(Application { })

View File

@ -1,8 +1,8 @@
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/nasin" import "tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "tomo/elements"
func main () { func main () {
nasin.Run(Application { }) nasin.Run(Application { })

View File

@ -1,11 +1,11 @@
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/nasin" import "tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/popups" import "tomo/popups"
import "git.tebibyte.media/sashakoshka/tomo/ability" import "tomo/ability"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "tomo/elements"
import "git.tebibyte.media/sashakoshka/tomo/elements/testing" import "tomo/elements/testing"
func main () { func main () {
nasin.Run(Application { }) nasin.Run(Application { })

View File

@ -2,9 +2,9 @@ package main
import "fmt" import "fmt"
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/nasin" import "tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "tomo/elements"
func main () { func main () {
nasin.Run(Application { }) nasin.Run(Application { })

View File

@ -1,9 +1,9 @@
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/nasin" import "tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/popups" import "tomo/popups"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "tomo/elements"
func main () { func main () {
nasin.Run(Application { }) nasin.Run(Application { })

View File

@ -1,10 +1,10 @@
package main package main
import "time" import "time"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/nasin" import "tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/popups" import "tomo/popups"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "tomo/elements"
func main () { func main () {
nasin.Run(Application { }) nasin.Run(Application { })

View File

@ -1,8 +1,8 @@
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/nasin" import "tomo/nasin"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "tomo/elements"
func main () { func main () {
nasin.Run(Application { }) nasin.Run(Application { })

5
go.mod
View File

@ -1,8 +1,11 @@
module git.tebibyte.media/sashakoshka/tomo module tomo
go 1.19 go 1.19
replace art => git.tebibyte.media/tomo/art v1.0.0
require ( require (
art v1.0.0
git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309044548-401cba83602b git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309044548-401cba83602b
github.com/jezek/xgbutil v0.0.0-20230403164920-e2f86723ca07 github.com/jezek/xgbutil v0.0.0-20230403164920-e2f86723ca07
golang.org/x/image v0.7.0 golang.org/x/image v0.7.0

2
go.sum
View File

@ -1,5 +1,7 @@
git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309044548-401cba83602b h1:vPFKR7vjN1VrMdMtpATMrKQobz/cqbPiRrA1EbtG6PM= git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309044548-401cba83602b h1:vPFKR7vjN1VrMdMtpATMrKQobz/cqbPiRrA1EbtG6PM=
git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309044548-401cba83602b/go.mod h1:cpXX8SAUDEvZX5m7scoyruavUhEqQ1SByfWzPFHkTbg= git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309044548-401cba83602b/go.mod h1:cpXX8SAUDEvZX5m7scoyruavUhEqQ1SByfWzPFHkTbg=
git.tebibyte.media/tomo/art v1.0.0 h1:dnP19Irgiho5WH0Im0l5vFYj5sDh4nqVk8hvG+6kW4M=
git.tebibyte.media/tomo/art v1.0.0/go.mod h1:pDQKkKWv/81CLTyH743zCzRWiri05+/4H6eNugarzvE=
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA= github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA=
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ= 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 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g=

View File

@ -2,7 +2,7 @@ package nasin
import "image" import "image"
import "errors" import "errors"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
// Application represents a Tomo/Nasin application. // Application represents a Tomo/Nasin application.
type Application interface { type Application interface {

View File

@ -6,7 +6,7 @@ import "os"
// the generic extract function we have here for extra type safety goodness. // the generic extract function we have here for extra type safety goodness.
import "plugin" import "plugin"
import "path/filepath" import "path/filepath"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
type backendFactory func () (tomo.Backend, error) type backendFactory func () (tomo.Backend, error)
var factories []backendFactory var factories []backendFactory

View File

@ -1,8 +1,8 @@
// Plugin wintergreen provides a calm, bluish green theme. // Plugin wintergreen provides a calm, bluish green theme.
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/plugins/wintergreen/wintergreen" import "tomo/plugins/wintergreen/wintergreen"
func Expects () tomo.Version { func Expects () tomo.Version {
return tomo.Version { 0, 0, 0 } return tomo.Version { 0, 0, 0 }

View File

@ -7,16 +7,16 @@ import _ "image/png"
import "image/color" import "image/color"
import "golang.org/x/image/font" import "golang.org/x/image/font"
import "golang.org/x/image/font/basicfont" import "golang.org/x/image/font/basicfont"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/data" import "tomo/data"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/artist/artutil" import "art/artutil"
import "git.tebibyte.media/sashakoshka/tomo/artist/patterns" import "art/patterns"
//go:embed assets/wintergreen.png //go:embed assets/wintergreen.png
var defaultAtlasBytes []byte var defaultAtlasBytes []byte
var defaultAtlas artist.Canvas var defaultAtlas art.Canvas
var defaultTextures [17][9]artist.Pattern var defaultTextures [17][9]art.Pattern
//go:embed assets/wintergreen-icons-small.png //go:embed assets/wintergreen-icons-small.png
var defaultIconsSmallAtlasBytes []byte var defaultIconsSmallAtlasBytes []byte
var defaultIconsSmall [640]binaryIcon var defaultIconsSmall [640]binaryIcon
@ -24,15 +24,15 @@ var defaultIconsSmall [640]binaryIcon
var defaultIconsLargeAtlasBytes []byte var defaultIconsLargeAtlasBytes []byte
var defaultIconsLarge [640]binaryIcon var defaultIconsLarge [640]binaryIcon
func atlasCell (col, row int, border artist.Inset) { func atlasCell (col, row int, border art.Inset) {
bounds := image.Rect(0, 0, 16, 16).Add(image.Pt(col, row).Mul(16)) bounds := image.Rect(0, 0, 16, 16).Add(image.Pt(col, row).Mul(16))
defaultTextures[col][row] = patterns.Border { defaultTextures[col][row] = patterns.Border {
Canvas: artist.Cut(defaultAtlas, bounds), Canvas: art.Cut(defaultAtlas, bounds),
Inset: border, Inset: border,
} }
} }
func atlasCol (col int, border artist.Inset) { func atlasCol (col int, border art.Inset) {
for index, _ := range defaultTextures[col] { for index, _ := range defaultTextures[col] {
atlasCell(col, index, border) atlasCell(col, index, border)
} }
@ -43,7 +43,7 @@ type binaryIcon struct {
stride int stride int
} }
func (icon binaryIcon) Draw (destination artist.Canvas, color color.RGBA, at image.Point) { func (icon binaryIcon) Draw (destination art.Canvas, color color.RGBA, at image.Point) {
bounds := icon.Bounds().Add(at).Intersect(destination.Bounds()) bounds := icon.Bounds().Add(at).Intersect(destination.Bounds())
point := image.Point { } point := image.Point { }
data, stride := destination.Buffer() data, stride := destination.Buffer()
@ -85,43 +85,43 @@ func binaryIconFrom (source image.Image, clip image.Rectangle) (icon binaryIcon)
func init () { func init () {
defaultAtlasImage, _, _ := image.Decode(bytes.NewReader(defaultAtlasBytes)) defaultAtlasImage, _, _ := image.Decode(bytes.NewReader(defaultAtlasBytes))
defaultAtlas = artist.FromImage(defaultAtlasImage) defaultAtlas = art.FromImage(defaultAtlasImage)
// PatternDead // PatternDead
atlasCol(0, artist.Inset { }) atlasCol(0, art.Inset { })
// PatternRaised // PatternRaised
atlasCol(1, artist.Inset { 6, 6, 6, 6 }) atlasCol(1, art.Inset { 6, 6, 6, 6 })
// PatternSunken // PatternSunken
atlasCol(2, artist.Inset { 4, 4, 4, 4 }) atlasCol(2, art.Inset { 4, 4, 4, 4 })
// PatternPinboard // PatternPinboard
atlasCol(3, artist.Inset { 2, 2, 2, 2 }) atlasCol(3, art.Inset { 2, 2, 2, 2 })
// PatternButton // PatternButton
atlasCol(4, artist.Inset { 6, 6, 6, 6 }) atlasCol(4, art.Inset { 6, 6, 6, 6 })
// PatternInput // PatternInput
atlasCol(5, artist.Inset { 4, 4, 4, 4 }) atlasCol(5, art.Inset { 4, 4, 4, 4 })
// PatternGutter // PatternGutter
atlasCol(6, artist.Inset { 7, 7, 7, 7 }) atlasCol(6, art.Inset { 7, 7, 7, 7 })
// PatternHandle // PatternHandle
atlasCol(7, artist.Inset { 3, 3, 3, 3 }) atlasCol(7, art.Inset { 3, 3, 3, 3 })
// PatternLine // PatternLine
atlasCol(8, artist.Inset { 1, 1, 1, 1 }) atlasCol(8, art.Inset { 1, 1, 1, 1 })
// PatternMercury // PatternMercury
atlasCol(13, artist.Inset { 2, 2, 2, 2 }) atlasCol(13, art.Inset { 2, 2, 2, 2 })
// PatternTableHead: // PatternTableHead:
atlasCol(14, artist.Inset { 4, 4, 4, 4 }) atlasCol(14, art.Inset { 4, 4, 4, 4 })
// PatternTableCell: // PatternTableCell:
atlasCol(15, artist.Inset { 4, 4, 4, 4 }) atlasCol(15, art.Inset { 4, 4, 4, 4 })
// PatternLamp: // PatternLamp:
atlasCol(16, artist.Inset { 4, 3, 4, 3 }) atlasCol(16, art.Inset { 4, 3, 4, 3 })
// PatternButton: basic.checkbox // PatternButton: basic.checkbox
atlasCol(9, artist.Inset { 3, 3, 3, 3 }) atlasCol(9, art.Inset { 3, 3, 3, 3 })
// PatternRaised: basic.listEntry // PatternRaised: basic.listEntry
atlasCol(10, artist.Inset { 3, 3, 3, 3 }) atlasCol(10, art.Inset { 3, 3, 3, 3 })
// PatternRaised: fun.flatKey // PatternRaised: fun.flatKey
atlasCol(11, artist.Inset { 3, 3, 5, 3 }) atlasCol(11, art.Inset { 3, 3, 5, 3 })
// PatternRaised: fun.sharpKey // PatternRaised: fun.sharpKey
atlasCol(12, artist.Inset { 3, 3, 4, 3 }) atlasCol(12, art.Inset { 3, 3, 4, 3 })
// set up small icons // set up small icons
defaultIconsSmallAtlasImage, _, _ := image.Decode ( defaultIconsSmallAtlasImage, _, _ := image.Decode (
@ -164,7 +164,7 @@ func (Theme) FontFace (style tomo.FontStyle, size tomo.FontSize, c tomo.Case) fo
return basicfont.Face7x13 return basicfont.Face7x13
} }
func (Theme) Icon (id tomo.Icon, size tomo.IconSize, c tomo.Case) artist.Icon { func (Theme) Icon (id tomo.Icon, size tomo.IconSize, c tomo.Case) art.Icon {
if size == tomo.IconSizeLarge { if size == tomo.IconSizeLarge {
if id < 0 || int(id) >= len(defaultIconsLarge) { if id < 0 || int(id) >= len(defaultIconsLarge) {
return nil return nil
@ -180,12 +180,12 @@ func (Theme) Icon (id tomo.Icon, size tomo.IconSize, c tomo.Case) artist.Icon {
} }
} }
func (Theme) MimeIcon (data.Mime, tomo.IconSize, tomo.Case) artist.Icon { func (Theme) MimeIcon (data.Mime, tomo.IconSize, tomo.Case) art.Icon {
// TODO // TODO
return nil return nil
} }
func (Theme) Pattern (id tomo.Pattern, state tomo.State, c tomo.Case) artist.Pattern { func (Theme) Pattern (id tomo.Pattern, state tomo.State, c tomo.Case) art.Pattern {
offset := 0; switch { offset := 0; switch {
case state.Disabled: offset = 1 case state.Disabled: offset = 1
case state.Pressed && state.On: offset = 4 case state.Pressed && state.On: offset = 4
@ -254,31 +254,31 @@ func (Theme) Color (id tomo.Color, state tomo.State, c tomo.Case) color.RGBA {
} [id]) } [id])
} }
func (Theme) Padding (id tomo.Pattern, c tomo.Case) artist.Inset { func (Theme) Padding (id tomo.Pattern, c tomo.Case) art.Inset {
switch id { switch id {
case tomo.PatternSunken: case tomo.PatternSunken:
if c.Match("tomo", "progressBar", "") { if c.Match("tomo", "progressBar", "") {
return artist.I(2, 1, 1, 2) return art.I(2, 1, 1, 2)
} else if c.Match("tomo", "list", "") { } else if c.Match("tomo", "list", "") {
return artist.I(2) return art.I(2)
} else if c.Match("tomo", "flowList", "") { } else if c.Match("tomo", "flowList", "") {
return artist.I(2) return art.I(2)
} else { } else {
return artist.I(8) return art.I(8)
} }
case tomo.PatternPinboard: case tomo.PatternPinboard:
if c.Match("tomo", "piano", "") { if c.Match("tomo", "piano", "") {
return artist.I(2) return art.I(2)
} else { } else {
return artist.I(8) return art.I(8)
} }
case tomo.PatternTableCell: return artist.I(5) case tomo.PatternTableCell: return art.I(5)
case tomo.PatternTableHead: return artist.I(5) case tomo.PatternTableHead: return art.I(5)
case tomo.PatternGutter: return artist.I(0) case tomo.PatternGutter: return art.I(0)
case tomo.PatternLine: return artist.I(1) case tomo.PatternLine: return art.I(1)
case tomo.PatternMercury: return artist.I(5) case tomo.PatternMercury: return art.I(5)
case tomo.PatternLamp: return artist.I(5, 5, 5, 6) case tomo.PatternLamp: return art.I(5, 5, 5, 6)
default: return artist.I(8) default: return art.I(8)
} }
} }

View File

@ -1,8 +1,8 @@
// Plugin x provides the X11 backend as a plugin. // Plugin x provides the X11 backend as a plugin.
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/plugins/x/x" import "tomo/plugins/x/x"
func Expects () tomo.Version { func Expects () tomo.Version {
return tomo.Version { 0, 0, 0 } return tomo.Version { 0, 0, 0 }

View File

@ -3,7 +3,7 @@ package x
import "unicode" import "unicode"
import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgb/xproto"
import "github.com/jezek/xgbutil/keybind" import "github.com/jezek/xgbutil/keybind"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
// when making changes to this file, look at keysymdef.h and // when making changes to this file, look at keysymdef.h and
// https://tronche.com/gui/x/xlib/input/keyboard-encoding.html // https://tronche.com/gui/x/xlib/input/keyboard-encoding.html

View File

@ -1,9 +1,9 @@
package x package x
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/ability" import "tomo/ability"
type entity struct { type entity struct {
backend *backend backend *backend
@ -159,7 +159,7 @@ func (entity *entity) SetMinimumSize (width, height int) {
} }
} }
func (entity *entity) DrawBackground (destination artist.Canvas) { func (entity *entity) DrawBackground (destination art.Canvas) {
if entity.parent != nil { if entity.parent != nil {
entity.parent.element.(ability.Container).DrawBackground(destination) entity.parent.element.(ability.Container).DrawBackground(destination)
} else if entity.window != nil { } else if entity.window != nil {

View File

@ -1,9 +1,9 @@
package x package x
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/ability" import "tomo/ability"
import "github.com/jezek/xgbutil" import "github.com/jezek/xgbutil"
import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgb/xproto"

View File

@ -7,7 +7,7 @@ import "github.com/jezek/xgbutil"
import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgb/xproto"
import "github.com/jezek/xgbutil/xprop" import "github.com/jezek/xgbutil/xprop"
import "github.com/jezek/xgbutil/xevent" import "github.com/jezek/xgbutil/xevent"
import "git.tebibyte.media/sashakoshka/tomo/data" import "tomo/data"
const clipboardName = "CLIPBOARD" const clipboardName = "CLIPBOARD"

View File

@ -6,7 +6,7 @@ import "github.com/jezek/xgbutil"
import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgb/xproto"
import "github.com/jezek/xgbutil/xprop" import "github.com/jezek/xgbutil/xprop"
import "github.com/jezek/xgbutil/xevent" import "github.com/jezek/xgbutil/xevent"
import "git.tebibyte.media/sashakoshka/tomo/data" import "tomo/data"
type selectionClaim struct { type selectionClaim struct {
window *window window *window

View File

@ -1,8 +1,8 @@
package x package x
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
import "git.tebibyte.media/sashakoshka/tomo/ability" import "tomo/ability"
type entitySet map[*entity] struct { } type entitySet map[*entity] struct { }
@ -22,7 +22,7 @@ func (set entitySet) Add (entity *entity) {
type system struct { type system struct {
child *entity child *entity
focused *entity focused *entity
canvas artist.BasicCanvas canvas art.BasicCanvas
invalidateIgnore bool invalidateIgnore bool
drawingInvalid entitySet drawingInvalid entitySet
@ -167,7 +167,7 @@ func (system *system) draw () {
for entity := range system.drawingInvalid { for entity := range system.drawingInvalid {
if entity.clippedBounds.Empty() { continue } if entity.clippedBounds.Empty() { continue }
entity.element.Draw (artist.Cut ( entity.element.Draw (art.Cut (
system.canvas, system.canvas,
entity.clippedBounds)) entity.clippedBounds))
finalBounds = finalBounds.Union(entity.clippedBounds) finalBounds = finalBounds.Union(entity.clippedBounds)

View File

@ -11,9 +11,9 @@ import "github.com/jezek/xgbutil/xwindow"
import "github.com/jezek/xgbutil/keybind" import "github.com/jezek/xgbutil/keybind"
import "github.com/jezek/xgbutil/mousebind" import "github.com/jezek/xgbutil/mousebind"
import "github.com/jezek/xgbutil/xgraphics" import "github.com/jezek/xgbutil/xgraphics"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/data" import "tomo/data"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
type mainWindow struct { *window } type mainWindow struct { *window }
type menuWindow struct { *window } type menuWindow struct { *window }
@ -414,7 +414,7 @@ func (window *window) pasteAndPush (region image.Rectangle) {
} }
func (window *window) paste (region image.Rectangle) { func (window *window) paste (region image.Rectangle) {
canvas := artist.Cut(window.canvas, region) canvas := art.Cut(window.canvas, region)
data, stride := canvas.Buffer() data, stride := canvas.Buffer()
bounds := canvas.Bounds().Intersect(window.xCanvas.Bounds()) bounds := canvas.Bounds().Intersect(window.xCanvas.Bounds())

View File

@ -1,8 +1,8 @@
package x package x
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import defaultTheme "git.tebibyte.media/sashakoshka/tomo/default/theme" import defaultTheme "tomo/default/theme"
import defaultConfig "git.tebibyte.media/sashakoshka/tomo/default/config" import defaultConfig "tomo/default/config"
import "github.com/jezek/xgbutil" import "github.com/jezek/xgbutil"
import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgb/xproto"

View File

@ -1,8 +1,8 @@
package popups package popups
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "tomo"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "tomo/elements"
// DialogKind defines the semantic role of a dialog window. // DialogKind defines the semantic role of a dialog window.
type DialogKind int type DialogKind int

View File

@ -1,97 +0,0 @@
// Package shatter provides boolean operations for image.Rectangle.
package shatter
import "image"
// Shatter takes in a bounding rectangle, and several rectangles to be
// subtracted from it. It returns a slice of rectangles that tile together to
// make up the difference between them. This is intended to be used for figuring
// out which areas of a container element's background are covered by other
// elements so it doesn't waste CPU cycles drawing to those areas.
func Shatter (
glass image.Rectangle,
rocks ...image.Rectangle,
) (
tiles []image.Rectangle,
) {
// in this function, the metaphor of throwing several rocks at a sheet
// of glass is used to illustrate the concept.
tiles = []image.Rectangle { glass }
for _, rock := range rocks {
// check each tile to see if the rock has collided with it
tileLen := len(tiles)
for tileIndex := 0; tileIndex < tileLen; tileIndex ++ {
tile := tiles[tileIndex]
if !rock.Overlaps(tile) { continue }
newTiles, n := shatterOnce(tile, rock)
if n > 0 {
// the tile was shattered into one or more sub
// tiles
tiles[tileIndex] = newTiles[0]
tiles = append(tiles, newTiles[1:n]...)
} else {
// the tile was entirely obscured by the rock
// and must be wholly removed
tiles = remove(tiles, tileIndex)
tileIndex --
tileLen --
}
}
}
return
}
func shatterOnce (glass, rock image.Rectangle) (tiles [4]image.Rectangle, n int) {
rock = rock.Intersect(glass)
// |'''''''''''|
// | |
// |###|'''| |
// |###|___| |
// | |
// |___________|
if rock.Min.X > glass.Min.X { tiles[n] = image.Rect (
glass.Min.X, rock.Min.Y,
rock.Min.X, rock.Max.Y,
); n ++ }
// |'''''''''''|
// | |
// | |'''|###|
// | |___|###|
// | |
// |___________|
if rock.Max.X < glass.Max.X { tiles[n] = image.Rect (
rock.Max.X, rock.Min.Y,
glass.Max.X, rock.Max.Y,
); n ++ }
// |###########|
// |###########|
// | |'''| |
// | |___| |
// | |
// |___________|
if rock.Min.Y > glass.Min.Y { tiles[n] = image.Rect (
glass.Min.X, glass.Min.Y,
glass.Max.X, rock.Min.Y,
); n ++ }
// |'''''''''''|
// | |
// | |'''| |
// | |___| |
// |###########|
// |###########|
if rock.Max.Y < glass.Max.Y { tiles[n] = image.Rect (
glass.Min.X, rock.Max.Y,
glass.Max.X, glass.Max.Y,
); n ++ }
return
}
func remove[ELEMENT any] (slice []ELEMENT, s int) []ELEMENT {
return append(slice[:s], slice[s + 1:]...)
}

View File

@ -5,7 +5,7 @@ import "unicode"
import "image/draw" import "image/draw"
import "image/color" import "image/color"
import "golang.org/x/image/math/fixed" import "golang.org/x/image/math/fixed"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
// Drawer is an extended TypeSetter that is able to draw text. Much like // Drawer is an extended TypeSetter that is able to draw text. Much like
// TypeSetter, It has no constructor and its zero value can be used safely. // TypeSetter, It has no constructor and its zero value can be used safely.
@ -13,7 +13,7 @@ type Drawer struct { TypeSetter }
// Draw draws the drawer's text onto the specified canvas at the given offset. // Draw draws the drawer's text onto the specified canvas at the given offset.
func (drawer Drawer) Draw ( func (drawer Drawer) Draw (
destination artist.Canvas, destination art.Canvas,
color color.RGBA, color color.RGBA,
offset image.Point, offset image.Point,
) ( ) (

View File

@ -3,8 +3,8 @@ package tomo
import "image" import "image"
import "image/color" import "image/color"
import "golang.org/x/image/font" import "golang.org/x/image/font"
import "git.tebibyte.media/sashakoshka/tomo/data" import "tomo/data"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "art"
// Color lits a number of cannonical colors, each with its own ID. // Color lits a number of cannonical colors, each with its own ID.
type Color int; const ( type Color int; const (
@ -309,7 +309,7 @@ type Hints struct {
// StaticInset defines an inset rectangular area in the middle of the // StaticInset defines an inset rectangular area in the middle of the
// pattern that does not change between PatternStates. If the inset is // pattern that does not change between PatternStates. If the inset is
// zero on all sides, this hint does not apply. // zero on all sides, this hint does not apply.
StaticInset artist.Inset StaticInset art.Inset
// Uniform specifies a singular color for the entire pattern. If the // Uniform specifies a singular color for the entire pattern. If the
// alpha channel is zero, this hint does not apply. // alpha channel is zero, this hint does not apply.
@ -322,15 +322,15 @@ type Theme interface {
FontFace (FontStyle, FontSize, Case) font.Face FontFace (FontStyle, FontSize, Case) font.Face
// Icon returns an appropriate icon given an icon name, size, and case. // Icon returns an appropriate icon given an icon name, size, and case.
Icon (Icon, IconSize, Case) artist.Icon Icon (Icon, IconSize, Case) art.Icon
// Icon returns an appropriate icon given a file mime type, size, and, // Icon returns an appropriate icon given a file mime type, size, and,
// case. // case.
MimeIcon (data.Mime, IconSize, Case) artist.Icon MimeIcon (data.Mime, IconSize, Case) art.Icon
// Pattern returns an appropriate pattern given a pattern name, case, // Pattern returns an appropriate pattern given a pattern name, case,
// and state. // and state.
Pattern (Pattern, State, Case) artist.Pattern Pattern (Pattern, State, Case) art.Pattern
// Color returns an appropriate pattern given a color name, case, and // Color returns an appropriate pattern given a color name, case, and
// state. // state.
@ -338,7 +338,7 @@ type Theme interface {
// Padding returns how much space should be between the bounds of a // Padding returns how much space should be between the bounds of a
// pattern whatever an element draws inside of it. // pattern whatever an element draws inside of it.
Padding (Pattern, Case) artist.Inset Padding (Pattern, Case) art.Inset
// Margin returns the left/right (x) and top/bottom (y) margins that // Margin returns the left/right (x) and top/bottom (y) margins that
// should be put between any self-contained objects drawn within this // should be put between any self-contained objects drawn within this

View File

@ -1,7 +1,7 @@
package tomo package tomo
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/data" import "tomo/data"
// TODO: add support for the icon window because imagine if we allowed // TODO: add support for the icon window because imagine if we allowed
// applications to display live updating information readouts on their icons. // applications to display live updating information readouts on their icons.