diff --git a/artist/artist.go b/artist/artist.go index f956174..d2f4bd4 100644 --- a/artist/artist.go +++ b/artist/artist.go @@ -1,2 +1,52 @@ package artist +import "image" +import "image/color" + +// Pattern is capable of generating a pattern pixel by pixel. +type Pattern interface { + // AtWhen returns the color of the pixel located at (x, y) relative to + // the origin point of the pattern (0, 0), when the pattern has the + // specified width and height. Patterns may ignore the width and height + // parameters, but it may be useful for some patterns such as gradients. + AtWhen (x, y, width, height int) (color.RGBA) +} + +// Texture is a struct that allows an image to be converted into a tiling +// texture pattern. +type Texture struct { + data []color.RGBA + width, height int +} + +// NewTexture converts an image into a texture. +func NewTexture (source image.Image) (texture Texture) { + bounds := source.Bounds() + texture.width = bounds.Dx() + texture.height = bounds.Dy() + texture.data = make([]color.RGBA, texture.width * texture.height) + + index := 0 + for y := bounds.Min.Y; y < bounds.Max.Y; y ++ { + for x := bounds.Min.X; x < bounds.Max.X; x ++ { + r, g, b, a := source.At(x, y).RGBA() + texture.data[index] = color.RGBA { + uint8(r >> 8), + uint8(g >> 8), + uint8(b >> 8), + uint8(a >> 8), + } + index ++ + }} + return +} + +// AtWhen returns the color at the specified x and y coordinates, wrapped to the +// image's width. the width and height are ignored. +func (texture Texture) AtWhen (x, y, width, height int) (pixel color.RGBA) { + x %= texture.width + y %= texture.height + if x < 0 { x += texture.width } + if y < 0 { y += texture.height } + return texture.data[x + y * texture.width] +} diff --git a/artist/chisel.go b/artist/chisel.go index 1fe1a16..873046b 100644 --- a/artist/chisel.go +++ b/artist/chisel.go @@ -7,10 +7,10 @@ import "git.tebibyte.media/sashakoshka/tomo" // ShadingProfile contains shading information that can be used to draw chiseled // objects. type ShadingProfile struct { - Highlight tomo.Image - Shadow tomo.Image - Stroke tomo.Image - Fill tomo.Image + Highlight Pattern + Shadow Pattern + Stroke Pattern + Fill Pattern StrokeWeight int ShadingWeight int } @@ -43,6 +43,7 @@ func ChiseledRectangle ( strokeWeight := profile.StrokeWeight shadingWeight := profile.ShadingWeight + data, stride := destination.Buffer() bounds = bounds.Canon() updatedRegion = bounds @@ -59,11 +60,6 @@ func ChiseledRectangle ( fillBounds.Max = fillBounds.Max.Sub(shadingWeightVector) fillBounds = fillBounds.Canon() - strokeImageMin := stroke.Bounds().Min - highlightImageMin := highlight.Bounds().Min - shadowImageMin := shadow.Bounds().Min - fillImageMin := fill.Bounds().Min - width := float64(bounds.Dx()) height := float64(bounds.Dy()) @@ -75,11 +71,10 @@ func ChiseledRectangle ( point := image.Point { x, y } switch { case point.In(fillBounds): - pixel = fill.RGBAAt ( - xx - strokeWeight - shadingWeight + - fillImageMin.X, - yy - strokeWeight - shadingWeight + - fillImageMin.Y) + pixel = fill.AtWhen ( + xx - strokeWeight - shadingWeight, + yy - strokeWeight - shadingWeight, + fillBounds.Dx(), fillBounds.Dy()) case point.In(shadingBounds): var highlighted bool @@ -97,27 +92,21 @@ func ChiseledRectangle ( width - float64(xx) > float64(yy) } - + + shadingSource := shadow if highlighted { - pixel = highlight.RGBAAt ( - xx - strokeWeight + - highlightImageMin.X, - yy - strokeWeight + - highlightImageMin.Y) - } else { - pixel = shadow.RGBAAt ( - xx - strokeWeight + - shadowImageMin.X, - yy - strokeWeight + - shadowImageMin.Y) + shadingSource = highlight } - + pixel = shadingSource.AtWhen ( + xx - strokeWeight, + yy - strokeWeight, + shadingBounds.Dx(), + shadingBounds.Dy()) default: - pixel = stroke.RGBAAt ( - xx + strokeImageMin.X, - yy + strokeImageMin.Y) + pixel = stroke.AtWhen ( + xx, yy, bounds.Dx(), bounds.Dy()) } - destination.SetRGBA(x, y, pixel) + data[x + y * stride] = pixel xx ++ } yy ++ diff --git a/artist/line.go b/artist/line.go index 7370520..51458bb 100644 --- a/artist/line.go +++ b/artist/line.go @@ -5,7 +5,7 @@ import "git.tebibyte.media/sashakoshka/tomo" func Line ( destination tomo.Canvas, - source tomo.Image, + source Pattern, weight int, min image.Point, max image.Point, @@ -17,6 +17,8 @@ func Line ( updatedRegion = image.Rectangle { Min: min, Max: max }.Canon() updatedRegion.Max.X ++ updatedRegion.Max.Y ++ + width := updatedRegion.Dx() + height := updatedRegion.Dy() if abs(max.Y - min.Y) < abs(max.X - min.X) { @@ -26,7 +28,7 @@ func Line ( min = max max = temp } - lineLow(destination, source, weight, min, max) + lineLow(destination, source, weight, min, max, width, height) } else { if max.Y < min.Y { @@ -34,18 +36,21 @@ func Line ( min = max max = temp } - lineHigh(destination, source, weight, min, max) + lineHigh(destination, source, weight, min, max, width, height) } return } func lineLow ( destination tomo.Canvas, - source tomo.Image, + source Pattern, weight int, min image.Point, max image.Point, + width, height int, ) { + data, stride := destination.Buffer() + deltaX := max.X - min.X deltaY := max.Y - min.Y yi := 1 @@ -59,7 +64,7 @@ func lineLow ( y := min.Y for x := min.X; x < max.X; x ++ { - destination.SetRGBA(x, y, source.RGBAAt(x, y)) + data[x + y * stride] = source.AtWhen(x, y, width, height) if D > 0 { y += yi D += 2 * (deltaY - deltaX) @@ -71,11 +76,14 @@ func lineLow ( func lineHigh ( destination tomo.Canvas, - source tomo.Image, + source Pattern, weight int, min image.Point, max image.Point, + width, height int, ) { + data, stride := destination.Buffer() + deltaX := max.X - min.X deltaY := max.Y - min.Y xi := 1 @@ -89,7 +97,7 @@ func lineHigh ( x := min.X for y := min.Y; y < max.Y; y ++ { - destination.SetRGBA(x, y, source.RGBAAt(x, y)) + data[x + y * stride] = source.AtWhen(x, y, width, height) if D > 0 { x += xi D += 2 * (deltaX - deltaY) diff --git a/artist/rectangle.go b/artist/rectangle.go index 3a56bdb..3b613be 100644 --- a/artist/rectangle.go +++ b/artist/rectangle.go @@ -1,105 +1,51 @@ package artist import "image" -import "image/color" import "git.tebibyte.media/sashakoshka/tomo" -// Paste transfers one image onto another, offset by the specified point. +// Paste transfers one canvas onto another, offset by the specified point. func Paste ( destination tomo.Canvas, - source tomo.Image, + source tomo.Canvas, offset image.Point, ) ( updatedRegion image.Rectangle, ) { - sourceBounds := source.Bounds().Canon() + dstData, dstStride := destination.Buffer() + srcData, srcStride := source.Buffer() + + sourceBounds := + source.Bounds().Canon(). + Intersect(destination.Bounds().Sub(offset)) + if sourceBounds.Empty() { return } + updatedRegion = sourceBounds.Add(offset) for y := sourceBounds.Min.Y; y < sourceBounds.Max.Y; y ++ { for x := sourceBounds.Min.X; x < sourceBounds.Max.X; x ++ { - destination.SetRGBA ( - x + offset.X, y + offset.Y, - source.RGBAAt(x, y)) + dstData[x + offset.X + (y + offset.Y) * dstStride] = + srcData[x + y * srcStride] }} return } -// Rectangle draws a rectangle with an inset border. If the border image is nil, -// no border will be drawn. Likewise, if the fill image is nil, the rectangle -// will have no fill. -func Rectangle ( +func FillRectangle ( destination tomo.Canvas, - fill tomo.Image, - stroke tomo.Image, - weight int, + source Pattern, bounds image.Rectangle, ) ( updatedRegion image.Rectangle, ) { - bounds = bounds.Canon() + data, stride := destination.Buffer() + bounds = bounds.Canon().Intersect(destination.Bounds()).Canon() + if bounds.Empty() { return } updatedRegion = bounds - fillBounds := bounds - fillBounds.Min = fillBounds.Min.Add(image.Point { weight, weight }) - fillBounds.Max = fillBounds.Max.Sub(image.Point { weight, weight }) - fillBounds = fillBounds.Canon() - - for y := bounds.Min.Y; y < bounds.Max.Y; y ++ { - for x := bounds.Min.X; x < bounds.Max.X; x ++ { - var pixel color.RGBA - if (image.Point { x, y }).In(fillBounds) { - pixel = fill.RGBAAt(x, y) - } else { - pixel = stroke.RGBAAt(x, y) - } - destination.SetRGBA(x, y, pixel) + width, height := bounds.Dx(), bounds.Dy() + for y := 0; y < height; y ++ { + for x := 0; x < width; x ++ { + data[x + bounds.Min.X + (y + bounds.Min.Y) * stride] = + source.AtWhen(x, y, width, height) }} - - return -} - -// OffsetRectangle is the same as Rectangle, but offsets the border image to the -// top left corner of the border and the fill image to the top left corner of -// the fill. -func OffsetRectangle ( - destination tomo.Canvas, - fill tomo.Image, - stroke tomo.Image, - weight int, - bounds image.Rectangle, -) ( - updatedRegion image.Rectangle, -) { - bounds = bounds.Canon() - updatedRegion = bounds - - fillBounds := bounds - fillBounds.Min = fillBounds.Min.Add(image.Point { weight, weight }) - fillBounds.Max = fillBounds.Max.Sub(image.Point { weight, weight }) - fillBounds = fillBounds.Canon() - - strokeImageMin := stroke.Bounds().Min - fillImageMin := fill.Bounds().Min - - yy := 0 - for y := bounds.Min.Y; y < bounds.Max.Y; y ++ { - xx := 0 - for x := bounds.Min.X; x < bounds.Max.X; x ++ { - var pixel color.RGBA - if (image.Point { x, y }).In(fillBounds) { - pixel = fill.RGBAAt ( - xx - weight + fillImageMin.X, - yy - weight + fillImageMin.Y) - } else { - pixel = stroke.RGBAAt ( - xx + strokeImageMin.X, - yy + strokeImageMin.Y) - } - destination.SetRGBA(x, y, pixel) - xx ++ - } - yy ++ - } - return } diff --git a/artist/text.go b/artist/text.go index 1afd741..b1c1e61 100644 --- a/artist/text.go +++ b/artist/text.go @@ -3,7 +3,7 @@ package artist // import "fmt" import "image" import "unicode" -import "image/draw" +// import "image/draw" import "golang.org/x/image/font" import "golang.org/x/image/math/fixed" import "git.tebibyte.media/sashakoshka/tomo" @@ -95,34 +95,35 @@ func (drawer *TextDrawer) SetAlignment (align Align) { // Draw draws the drawer's text onto the specified canvas at the given offset. func (drawer *TextDrawer) Draw ( destination tomo.Canvas, - source tomo.Image, + source Pattern, offset image.Point, ) ( updatedRegion image.Rectangle, ) { if !drawer.layoutClean { drawer.recalculate() } - for _, word := range drawer.layout { - for _, character := range word.text { - destinationRectangle, - mask, maskPoint, _, ok := drawer.face.Glyph ( - fixed.P ( - offset.X + word.position.X + character.x, - offset.Y + word.position.Y), - character.character) - if !ok { continue } + // TODO: reimplement a version of draw mask that takes in a pattern + // for _, word := range drawer.layout { + // for _, character := range word.text { + // destinationRectangle, + // mask, maskPoint, _, ok := drawer.face.Glyph ( + // fixed.P ( + // offset.X + word.position.X + character.x, + // offset.Y + word.position.Y), + // character.character) + // if !ok { continue } // FIXME: clip destination rectangle if we are on the cusp of // the maximum height. - draw.DrawMask ( - destination, - destinationRectangle, - source, image.Point { }, - mask, maskPoint, - draw.Over) + // draw.DrawMask ( + // destination, + // destinationRectangle, + // source, image.Point { }, + // mask, maskPoint, + // draw.Over) - updatedRegion = updatedRegion.Union(destinationRectangle) - }} + // updatedRegion = updatedRegion.Union(destinationRectangle) + // }} return } diff --git a/artist/uniform.go b/artist/uniform.go index 2f99385..e50923e 100644 --- a/artist/uniform.go +++ b/artist/uniform.go @@ -3,8 +3,8 @@ package artist import "image" import "image/color" -// Uniform is an infinite-sized Image of uniform color. It implements the -// color.Color, color.Model, and tomo.Image interfaces. +// Uniform is an infinite-sized pattern of uniform color. It implements the +// color.Color, color.Model, and image.Image interfaces. type Uniform struct { C color.RGBA } @@ -29,13 +29,11 @@ func (uniform *Uniform) RGBA () (r, g, b, a uint32) { } func (uniform *Uniform) ColorModel () (model color.Model) { - model = uniform - return + return uniform } -func (uniform *Uniform) Convert (in color.Color) (out color.Color) { - out = uniform.C - return +func (uniform *Uniform) Convert (in color.Color) (c color.Color) { + return uniform.C } func (uniform *Uniform) Bounds () (rectangle image.Rectangle) { @@ -45,13 +43,11 @@ func (uniform *Uniform) Bounds () (rectangle image.Rectangle) { } func (uniform *Uniform) At (x, y int) (c color.Color) { - c = uniform.C - return + return uniform.C } -func (uniform *Uniform) RGBAAt (x, y int) (c color.RGBA) { - c = uniform.C - return +func (uniform *Uniform) AtWhen (x, y, width, height int) (c color.RGBA) { + return uniform.C } func (uniform *Uniform) RGBA64At (x, y int) (c color.RGBA64) { @@ -59,13 +55,10 @@ func (uniform *Uniform) RGBA64At (x, y int) (c color.RGBA64) { g := uint16(uniform.C.G) << 8 | uint16(uniform.C.G) b := uint16(uniform.C.B) << 8 | uint16(uniform.C.B) a := uint16(uniform.C.A) << 8 | uint16(uniform.C.A) - - c = color.RGBA64 { R: r, G: g, B: b, A: a } - return + return color.RGBA64 { R: r, G: g, B: b, A: a } } // Opaque scans the entire image and reports whether it is fully opaque. func (uniform *Uniform) Opaque () (opaque bool) { - opaque = uniform.C.A == 0xFF - return + return uniform.C.A == 0xFF } diff --git a/artist/wrap.go b/artist/wrap.go index e6a4ed5..530e53f 100644 --- a/artist/wrap.go +++ b/artist/wrap.go @@ -1,99 +1 @@ package artist - -import "git.tebibyte.media/sashakoshka/tomo" - -import "image" -import "image/draw" -import "image/color" - -// WrappedImage wraps an image.Image and allows it to satisfy tomo.Image. -type WrappedImage struct { Underlying image.Image } - -// WrapImage wraps a generic image.Image and allows it to satisfy tomo.Image. -// Do not use this function to wrap images that already satisfy tomo.Image, -// because the resulting wrapped image will be rather slow in comparison. -func WrapImage (underlying image.Image) (wrapped tomo.Image) { - wrapped = WrappedImage { Underlying: underlying } - return -} - -func (wrapped WrappedImage) Bounds () (bounds image.Rectangle) { - bounds = wrapped.Underlying.Bounds() - return -} - -func (wrapped WrappedImage) ColorModel () (model color.Model) { - model = wrapped.Underlying.ColorModel() - return -} - -func (wrapped WrappedImage) At (x, y int) (pixel color.Color) { - pixel = wrapped.Underlying.At(x, y) - return -} - -func (wrapped WrappedImage) RGBAAt (x, y int) (pixel color.RGBA) { - r, g, b, a := wrapped.Underlying.At(x, y).RGBA() - pixel.R = uint8(r >> 8) - pixel.G = uint8(g >> 8) - pixel.B = uint8(b >> 8) - pixel.A = uint8(a >> 8) - return -} - -// WrappedCanvas wraps a draw.Image and allows it to satisfy tomo.Canvas. -type WrappedCanvas struct { Underlying draw.Image } - -// WrapCanvas wraps a generic draw.Image and allows it to satisfy tomo.Canvas. -// Do not use this function to wrap images that already satisfy tomo.Canvas, -// because the resulting wrapped image will be rather slow in comparison. -func WrapCanvas (underlying draw.Image) (wrapped tomo.Canvas) { - wrapped = WrappedCanvas { Underlying: underlying } - return -} - -func (wrapped WrappedCanvas) Bounds () (bounds image.Rectangle) { - bounds = wrapped.Underlying.Bounds() - return -} - -func (wrapped WrappedCanvas) ColorModel () (model color.Model) { - model = wrapped.Underlying.ColorModel() - return -} - -func (wrapped WrappedCanvas) At (x, y int) (pixel color.Color) { - pixel = wrapped.Underlying.At(x, y) - return -} - -func (wrapped WrappedCanvas) RGBAAt (x, y int) (pixel color.RGBA) { - r, g, b, a := wrapped.Underlying.At(x, y).RGBA() - pixel.R = uint8(r >> 8) - pixel.G = uint8(g >> 8) - pixel.B = uint8(b >> 8) - pixel.A = uint8(a >> 8) - return -} - -func (wrapped WrappedCanvas) Set (x, y int, pixel color.Color) { - wrapped.Underlying.Set(x, y, pixel) -} - -func (wrapped WrappedCanvas) SetRGBA (x, y int, pixel color.RGBA) { - wrapped.Underlying.Set(x, y, pixel) -} - -// ToRGBA clones an existing image.Image into an image.RGBA struct, which -// directly satisfies tomo.Image. This is useful for things like icons and -// textures. -func ToRGBA (input image.Image) (output *image.RGBA) { - bounds := input.Bounds() - output = image.NewRGBA(bounds) - - for y := bounds.Min.Y; y < bounds.Max.Y; y ++ { - for x := bounds.Min.X; x < bounds.Max.X; x ++ { - output.Set(x, y, input.At(x, y)) - }} - return -} diff --git a/backends/x/window.go b/backends/x/window.go index 7e59760..b2f3f7b 100644 --- a/backends/x/window.go +++ b/backends/x/window.go @@ -187,8 +187,9 @@ func (window *Window) reallocateCanvas () { } func (window *Window) redrawChildEntirely () { + data, stride := window.child.Buffer() window.xCanvas.For (func (x, y int) (c xgraphics.BGRA) { - rgba := window.child.RGBAAt(x, y) + rgba := data[x + y * stride] c.R, c.G, c.B, c.A = rgba.R, rgba.G, rgba.B, rgba.A return }) @@ -206,13 +207,14 @@ func (window *Window) resizeChildToFit () { window.redrawChildEntirely() } -func (window *Window) childDrawCallback (region tomo.Image) { +func (window *Window) childDrawCallback (region tomo.Canvas) { if window.skipChildDrawCallback { return } + data, stride := region.Buffer() bounds := region.Bounds() for x := bounds.Min.X; x < bounds.Max.X; x ++ { for y := bounds.Min.Y; y < bounds.Max.Y; y ++ { - rgba := region.RGBAAt(x, y) + rgba := data[x + y * stride] window.xCanvas.SetBGRA (x, y, xgraphics.BGRA { R: rgba.R, G: rgba.G, diff --git a/canvas.go b/canvas.go new file mode 100644 index 0000000..f51c74a --- /dev/null +++ b/canvas.go @@ -0,0 +1,70 @@ +package tomo + +import "image" +import "image/draw" +import "image/color" + +// Canvas is like Image but also requires Set and SetRGBA methods. This +// interface can be easily satisfied using an image.RGBA 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 +} + +// 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 +} + +// Cut returns a sub-canvas of a given canvas. +func Cut (canvas Canvas, bounds image.Rectangle) (reduced BasicCanvas) { + bounds = bounds.Intersect(canvas.Bounds()) + if bounds.Empty() { return } + reduced.rect = bounds + reduced.pix, reduced.stride = canvas.Buffer() + return +} diff --git a/elements/basic/container.go b/elements/basic/container.go index 0ae02eb..886f98c 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -57,7 +57,7 @@ func (element *Container) Adopt (child tomo.Element, expand bool) { return }, - Draw: func (region tomo.Image) { + Draw: func (region tomo.Canvas) { element.drawChildRegion(child, region) }, }) @@ -318,10 +318,9 @@ func (element *Container) recalculate () { func (element *Container) draw () { bounds := element.core.Bounds() - artist.Rectangle ( + artist.FillRectangle ( element.core, theme.BackgroundImage(), - nil, 0, bounds) for _, entry := range element.children { @@ -329,7 +328,7 @@ func (element *Container) draw () { } } -func (element *Container) drawChildRegion (child tomo.Element, region tomo.Image) { +func (element *Container) drawChildRegion (child tomo.Element, region tomo.Canvas) { if element.warping { return } for _, entry := range element.children { if entry.Element == child { diff --git a/elements/basic/label.go b/elements/basic/label.go index f9b9186..6304afd 100644 --- a/elements/basic/label.go +++ b/elements/basic/label.go @@ -93,10 +93,9 @@ func (element *Label) updateMinimumSize () { func (element *Label) draw () { bounds := element.core.Bounds() - artist.Rectangle ( + artist.FillRectangle ( element.core, theme.BackgroundImage(), - nil, 0, bounds) textBounds := element.drawer.LayoutBounds() diff --git a/elements/core/core.go b/elements/core/core.go index e678227..cb12959 100644 --- a/elements/core/core.go +++ b/elements/core/core.go @@ -7,7 +7,7 @@ import "git.tebibyte.media/sashakoshka/tomo" // Core is a struct that implements some core functionality common to most // widgets. It is meant to be embedded directly into a struct. type Core struct { - canvas *image.RGBA + canvas tomo.BasicCanvas parent tomo.Element metrics struct { @@ -32,20 +32,19 @@ func (core Core) ColorModel () (model color.Model) { } func (core Core) At (x, y int) (pixel color.Color) { - if core.canvas == nil { return color.RGBA { } } - pixel = core.canvas.At(x, y) - return -} - -func (core Core) RGBAAt (x, y int) (pixel color.RGBA) { - if core.canvas == nil { return color.RGBA { } } - pixel = core.canvas.RGBAAt(x, y) - return + return core.canvas.At(x, y) } func (core Core) Bounds () (bounds image.Rectangle) { - if core.canvas != nil { bounds = core.canvas.Bounds() } - return + return core.canvas.Bounds() +} + +func (core Core) Set (x, y int, c color.Color) () { + core.canvas.Set(x, y, c) +} + +func (core Core) Buffer () (data []color.RGBA, stride int) { + return core.canvas.Buffer() } func (core Core) Selectable () (selectable bool) { @@ -72,13 +71,12 @@ func (core Core) MinimumSize () (width, height int) { // be used as a canvas. It must not be directly embedded into an element, but // instead kept as a private member. type CoreControl struct { - *image.RGBA + tomo.BasicCanvas core *Core } -func (control CoreControl) HasImage () (has bool) { - has = control.RGBA != nil - return +func (control CoreControl) HasImage () (empty bool) { + return !control.Bounds().Empty() } func (control CoreControl) Select () (granted bool) { @@ -98,7 +96,7 @@ func (control CoreControl) SetSelectable (selectable bool) { } func (control CoreControl) PushRegion (bounds image.Rectangle) { - control.core.hooks.RunDraw(control.SubImage(bounds).(*image.RGBA)) + control.core.hooks.RunDraw(tomo.Cut(control, bounds)) } func (control CoreControl) PushAll () { @@ -108,8 +106,8 @@ func (control CoreControl) PushAll () { func (control *CoreControl) AllocateCanvas (width, height int) { core := control.core width, height, _ = control.ConstrainSize(width, height) - core.canvas = image.NewRGBA(image.Rect (0, 0, width, height)) - control.RGBA = core.canvas + core.canvas = tomo.NewBasicCanvas(width, height) + control.BasicCanvas = core.canvas } func (control CoreControl) SetMinimumSize (width, height int) { @@ -125,19 +123,17 @@ func (control CoreControl) SetMinimumSize (width, height int) { // if there is an image buffer, and the current size is less // than this new minimum size, send core.parent a resize event. - if control.HasImage() { - bounds := control.Bounds() - imageWidth, - imageHeight, - constrained := control.ConstrainSize ( - bounds.Dx(), - bounds.Dy()) - if constrained { - core.parent.Handle (tomo.EventResize { - Width: imageWidth, - Height: imageHeight, - }) - } + bounds := control.Bounds() + imageWidth, + imageHeight, + constrained := control.ConstrainSize ( + bounds.Dx(), + bounds.Dy()) + if constrained { + core.parent.Handle (tomo.EventResize { + Width: imageWidth, + Height: imageHeight, + }) } } diff --git a/elements/fun/clock.go b/elements/fun/clock.go index d4f7f0a..6a97314 100644 --- a/elements/fun/clock.go +++ b/elements/fun/clock.go @@ -71,7 +71,7 @@ func (element *AnalogClock) draw () { } func (element *AnalogClock) radialLine ( - source tomo.Image, + source artist.Pattern, inner float64, outer float64, radian float64, diff --git a/elements/testing/mouse.go b/elements/testing/mouse.go index 30a7c3b..6c4a90f 100644 --- a/elements/testing/mouse.go +++ b/elements/testing/mouse.go @@ -13,7 +13,7 @@ type Mouse struct { *core.Core core core.CoreControl drawing bool - color tomo.Image + color artist.Pattern lastMousePos image.Point } @@ -33,11 +33,11 @@ func (element *Mouse) Handle (event tomo.Event) { element.core.AllocateCanvas ( resizeEvent.Width, resizeEvent.Height) - artist.Rectangle ( + artist.FillRectangle ( element.core, theme.AccentImage(), - artist.NewUniform(color.Black), - 1, element.Bounds()) + element.Bounds()) + // TODO: draw a stroked rectangle around the edges artist.Line ( element.core, artist.NewUniform(color.White), 1, image.Pt(1, 1), diff --git a/theme/theme.go b/theme/theme.go index b638a4e..6ee9921 100644 --- a/theme/theme.go +++ b/theme/theme.go @@ -3,7 +3,6 @@ package theme import "image" import "image/color" import "golang.org/x/image/font" -import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/defaultfont" @@ -151,34 +150,34 @@ func InputProfile (enabled bool, selected bool) artist.ShadingProfile { // BackgroundImage returns the texture/color used for the fill of // BackgroundProfile. -func BackgroundImage () tomo.Image { +func BackgroundImage () artist.Pattern { return backgroundImage } // RaisedImage returns the texture/color used for the fill of RaisedProfile. -func RaisedImage () tomo.Image { +func RaisedImage () artist.Pattern { return raisedImage } // InputImage returns the texture/color used for the fill of InputProfile. -func InputImage () tomo.Image { +func InputImage () artist.Pattern { return inputImage } // ForegroundImage returns the texture/color text and monochromatic icons should // be drawn with. -func ForegroundImage () tomo.Image { +func ForegroundImage () artist.Pattern { return foregroundImage } // DisabledForegroundImage returns the texture/color text and monochromatic // icons should be drawn with if they are disabled. -func DisabledForegroundImage () tomo.Image { +func DisabledForegroundImage () artist.Pattern { return disabledForegroundImage } // AccentImage returns the accent texture/color. -func AccentImage () tomo.Image { +func AccentImage () artist.Pattern { return accentImage } diff --git a/tomo.go b/tomo.go index 8005841..134eccc 100644 --- a/tomo.go +++ b/tomo.go @@ -2,25 +2,6 @@ package tomo import "image" import "errors" -import "image/draw" -import "image/color" - -// Image represents a simple image buffer that fulfills the image.Image -// interface while also having methods that do away with the use of the -// color.Color interface to facilitate more efficient drawing. This interface -// can be easily satisfied using an image.RGBA struct. -type Image interface { - image.Image - RGBAAt (x, y int) (c color.RGBA) -} - -// Canvas is like Image but also requires Set and SetRGBA methods. This -// interface can be easily satisfied using an image.RGBA struct. -type Canvas interface { - draw.Image - RGBAAt (x, y int) (c color.RGBA) - SetRGBA (x, y int, c color.RGBA) -} // ParentHooks is a struct that contains callbacks that let child elements send // information to their parent element without the child element knowing @@ -29,7 +10,7 @@ type Canvas interface { type ParentHooks struct { // Draw is called when a part of the child element's surface is updated. // The updated region will be passed to the callback as a sub-image. - Draw func (region Image) + Draw func (region Canvas) // MinimumSizeChange is called when the child element's minimum width // and/or height changes. When this function is called, the element will @@ -49,7 +30,7 @@ type ParentHooks struct { } // RunDraw runs the Draw hook if it is not nil. If it is nil, it does nothing. -func (hooks ParentHooks) RunDraw (region Image) { +func (hooks ParentHooks) RunDraw (region Canvas) { if hooks.Draw != nil { hooks.Draw(region) } @@ -82,10 +63,10 @@ func (hooks ParentHooks) RunSelectabilityChange (selectable bool) { // Element represents a basic on-screen object. type Element interface { - // Element must implement the Image interface. Elements should start out - // with a completely blank image buffer, and only set its size and draw + // Element must implement the Canvas interface. Elements should start + // out with a completely blank buffer, and only allocate memory and draw // on it for the first time when sent an EventResize event. - Image + Canvas // Handle handles an event, propagating it to children if necessary. Handle (event Event)