From c7e44633b1c8c599be1ef35a7a09ddb48e833e99 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 23 Feb 2023 14:44:54 -0500 Subject: [PATCH 01/21] Updated Pattern interface --- artist/pattern.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/artist/pattern.go b/artist/pattern.go index ef87caa..d3fce47 100644 --- a/artist/pattern.go +++ b/artist/pattern.go @@ -1,12 +1,16 @@ package artist -import "image/color" +import "image" +import "git.tebibyte.media/sashakoshka/tomo/canvas" -// Pattern is capable of generating a pattern pixel by pixel. +// Pattern is capable of drawing to a canvas within the bounds of a given +// clipping rectangle. 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) + // Draw draws to destination, using the bounds of destination as a width + // and height for things like gradients, bevels, etc. The pattern may + // not draw outside the union of destination.Bounds() and clip. The + // clipping rectangle effectively takes a subset of the pattern. To + // change the bounds of the pattern itself, use canvas.Cut() on the + // destination before passing it to Draw(). + Draw (destination canvas.Canvas, clip image.Rectangle) } From 0ba3c982c4264a5dd11aa4aca8e03e519925b078 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 23 Feb 2023 15:00:44 -0500 Subject: [PATCH 02/21] Added some utility functions to pattern --- artist/pattern.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/artist/pattern.go b/artist/pattern.go index d3fce47..b13b7af 100644 --- a/artist/pattern.go +++ b/artist/pattern.go @@ -14,3 +14,28 @@ type Pattern interface { // destination before passing it to Draw(). Draw (destination canvas.Canvas, clip image.Rectangle) } + +// Draw lets you use several clipping rectangles to draw a pattern. +func Draw ( + destination canvas.Canvas, + source Pattern, + clips ...image.Rectangle, +) { + for _, clip := range clips { + source.Draw(destination, clip) + } +} + +// DrawBounds is like Draw, but lets you specify an overall bounding rectangle +// for the pattern. The destination is cut to this rectangle. +func DrawBounds ( + destination canvas.Canvas, + bounds image.Rectangle, + source Pattern, + clips ...image.Rectangle, +) { + cut := canvas.Cut(destination, bounds) + for _, clip := range clips { + source.Draw(cut, clip) + } +} From 48237f5687361bc588599d88083504a6f1c9288c Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 23 Feb 2023 17:44:53 -0500 Subject: [PATCH 03/21] Add AllocateSample --- artist/pattern.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/artist/pattern.go b/artist/pattern.go index b13b7af..2a41d41 100644 --- a/artist/pattern.go +++ b/artist/pattern.go @@ -39,3 +39,13 @@ func DrawBounds ( source.Draw(cut, clip) } } + +// 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 Pattern, width, height int) (allocated canvas.Canvas) { + allocated = canvas.NewBasicCanvas(width, height) + source.Draw(allocated, allocated.Bounds()) + return +} From d167559830ddad6266eaa8c5ef05b055cb18fbca Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 23 Feb 2023 20:55:19 -0500 Subject: [PATCH 04/21] Got rectangles all sorted --- artist/doc.go | 3 - artist/{ => patterns}/bevel.go | 0 artist/{ => patterns}/bordered.go | 0 artist/{ => patterns}/checkered.go | 0 artist/{ => patterns}/circlebordered.go | 0 artist/{ => patterns}/dotted.go | 0 artist/{ => patterns}/gradient.go | 0 artist/{ => patterns}/noise.go | 0 artist/{ => patterns}/split.go | 0 artist/{ => patterns}/striped.go | 0 artist/{ => patterns}/texture.go | 0 artist/{ => patterns}/uniform.go | 0 artist/{ => patterns}/wrap.go | 0 artist/rectangle.go | 130 ------------------------ artist/shapes/doc.go | 11 ++ artist/{ => shapes}/ellipse.go | 9 +- artist/{ => shapes}/line.go | 4 +- artist/shapes/rectangle.go | 84 +++++++++++++++ 18 files changed, 102 insertions(+), 139 deletions(-) rename artist/{ => patterns}/bevel.go (100%) rename artist/{ => patterns}/bordered.go (100%) rename artist/{ => patterns}/checkered.go (100%) rename artist/{ => patterns}/circlebordered.go (100%) rename artist/{ => patterns}/dotted.go (100%) rename artist/{ => patterns}/gradient.go (100%) rename artist/{ => patterns}/noise.go (100%) rename artist/{ => patterns}/split.go (100%) rename artist/{ => patterns}/striped.go (100%) rename artist/{ => patterns}/texture.go (100%) rename artist/{ => patterns}/uniform.go (100%) rename artist/{ => patterns}/wrap.go (100%) delete mode 100644 artist/rectangle.go create mode 100644 artist/shapes/doc.go rename artist/{ => shapes}/ellipse.go (96%) rename artist/{ => shapes}/line.go (98%) create mode 100644 artist/shapes/rectangle.go diff --git a/artist/doc.go b/artist/doc.go index 388f29e..c0a0a6f 100644 --- a/artist/doc.go +++ b/artist/doc.go @@ -1,5 +1,2 @@ // Package artist provides a simple 2D drawing library for canvas.Canvas. -// Artist's drawing functions take in things called patterns, which are sampled -// as a source in order to color and texture drawn shapes. Patterns can be -// mixed together and composited to create new, more complex patterns. package artist diff --git a/artist/bevel.go b/artist/patterns/bevel.go similarity index 100% rename from artist/bevel.go rename to artist/patterns/bevel.go diff --git a/artist/bordered.go b/artist/patterns/bordered.go similarity index 100% rename from artist/bordered.go rename to artist/patterns/bordered.go diff --git a/artist/checkered.go b/artist/patterns/checkered.go similarity index 100% rename from artist/checkered.go rename to artist/patterns/checkered.go diff --git a/artist/circlebordered.go b/artist/patterns/circlebordered.go similarity index 100% rename from artist/circlebordered.go rename to artist/patterns/circlebordered.go diff --git a/artist/dotted.go b/artist/patterns/dotted.go similarity index 100% rename from artist/dotted.go rename to artist/patterns/dotted.go diff --git a/artist/gradient.go b/artist/patterns/gradient.go similarity index 100% rename from artist/gradient.go rename to artist/patterns/gradient.go diff --git a/artist/noise.go b/artist/patterns/noise.go similarity index 100% rename from artist/noise.go rename to artist/patterns/noise.go diff --git a/artist/split.go b/artist/patterns/split.go similarity index 100% rename from artist/split.go rename to artist/patterns/split.go diff --git a/artist/striped.go b/artist/patterns/striped.go similarity index 100% rename from artist/striped.go rename to artist/patterns/striped.go diff --git a/artist/texture.go b/artist/patterns/texture.go similarity index 100% rename from artist/texture.go rename to artist/patterns/texture.go diff --git a/artist/uniform.go b/artist/patterns/uniform.go similarity index 100% rename from artist/uniform.go rename to artist/patterns/uniform.go diff --git a/artist/wrap.go b/artist/patterns/wrap.go similarity index 100% rename from artist/wrap.go rename to artist/patterns/wrap.go diff --git a/artist/rectangle.go b/artist/rectangle.go deleted file mode 100644 index 5adbe72..0000000 --- a/artist/rectangle.go +++ /dev/null @@ -1,130 +0,0 @@ -package artist - -import "image" -import "git.tebibyte.media/sashakoshka/tomo/canvas" -import "git.tebibyte.media/sashakoshka/tomo/shatter" - -// Paste transfers one canvas onto another, offset by the specified point. -func Paste ( - destination canvas.Canvas, - source canvas.Canvas, - offset image.Point, -) ( - updatedRegion image.Rectangle, -) { - 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 ++ { - dstData[x + offset.X + (y + offset.Y) * dstStride] = - srcData[x + y * srcStride] - }} - - return -} - -// FillRectangle draws a filled rectangle with the specified pattern. -func FillRectangle ( - destination canvas.Canvas, - source Pattern, - bounds image.Rectangle, -) ( - updatedRegion image.Rectangle, -) { - return FillRectangleClip(destination, source, bounds, bounds) -} - -// FillRectangleClip is similar to FillRectangle, but it clips the pattern to -// a specified rectangle mask. That is—the pattern will be queried as if it -// were drawn without the mask, but only the area specified by the intersection -// of bounds and mask will be drawn to. -func FillRectangleClip ( - destination canvas.Canvas, - source Pattern, - bounds image.Rectangle, - mask image.Rectangle, -) ( - updatedRegion image.Rectangle, -) { - data, stride := destination.Buffer() - realBounds := bounds - bounds = - bounds.Canon(). - Intersect(mask.Canon()). - Intersect(destination.Bounds()) - if bounds.Empty() { return } - updatedRegion = bounds - - realWidth, realHeight := realBounds.Dx(), realBounds.Dy() - patternOffset := realBounds.Min.Sub(bounds.Min) - - 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 - patternOffset.X, y - patternOffset.Y, - realWidth, realHeight) - }} - return -} - -// FillRectangleShatter shatters a bounding rectangle and draws its tiles in one -// fell swoop. -func FillRectangleShatter ( - destination canvas.Canvas, - source Pattern, - glass image.Rectangle, - rocks ...image.Rectangle, -) ( - updatedRegions []image.Rectangle, -) { - tiles := shatter.Shatter(glass, rocks...) - for _, tile := range tiles { - FillRectangleClip(destination, source, glass, tile) - } - return tiles -} - -// StrokeRectangle draws the outline of a rectangle with the specified line -// weight and pattern. -func StrokeRectangle ( - destination canvas.Canvas, - source Pattern, - weight int, - bounds image.Rectangle, -) { - bounds = bounds.Canon() - insetBounds := bounds.Inset(weight) - if insetBounds.Empty() { - FillRectangle(destination, source, bounds) - return - } - - // top - FillRectangle (destination, source, image.Rect ( - bounds.Min.X, bounds.Min.Y, - bounds.Max.X, insetBounds.Min.Y)) - - // bottom - FillRectangle (destination, source, image.Rect ( - bounds.Min.X, insetBounds.Max.Y, - bounds.Max.X, bounds.Max.Y)) - - // left - FillRectangle (destination, source, image.Rect ( - bounds.Min.X, insetBounds.Min.Y, - insetBounds.Min.X, insetBounds.Max.Y)) - - // right - FillRectangle (destination, source, image.Rect ( - insetBounds.Max.X, insetBounds.Min.Y, - bounds.Max.X, insetBounds.Max.Y)) -} diff --git a/artist/shapes/doc.go b/artist/shapes/doc.go new file mode 100644 index 0000000..ff03446 --- /dev/null +++ b/artist/shapes/doc.go @@ -0,0 +1,11 @@ +// Package shapes provides some basic shape drawing routines. +// +// A word about patterns: +// +// 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 diff --git a/artist/ellipse.go b/artist/shapes/ellipse.go similarity index 96% rename from artist/ellipse.go rename to artist/shapes/ellipse.go index 7dc9d4c..39b6f13 100644 --- a/artist/ellipse.go +++ b/artist/shapes/ellipse.go @@ -1,14 +1,15 @@ -package artist +package shapes import "math" import "image" import "image/color" +import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/canvas" // FillEllipse draws a filled ellipse with the specified pattern. func FillEllipse ( destination canvas.Canvas, - source Pattern, + source artist.Pattern, bounds image.Rectangle, ) ( updatedRegion image.Rectangle, @@ -37,7 +38,7 @@ func FillEllipse ( // and pattern. func StrokeEllipse ( destination canvas.Canvas, - source Pattern, + source artist.Pattern, weight int, bounds image.Rectangle, ) { @@ -130,7 +131,7 @@ func StrokeEllipse ( type ellipsePlottingContext struct { data []color.RGBA stride int - source Pattern + source artist.Pattern width, height int weight int bounds image.Rectangle diff --git a/artist/line.go b/artist/shapes/line.go similarity index 98% rename from artist/line.go rename to artist/shapes/line.go index 733c626..2b92c59 100644 --- a/artist/line.go +++ b/artist/shapes/line.go @@ -1,4 +1,4 @@ -package artist +package shapes import "image" import "image/color" @@ -10,7 +10,7 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas" // pattern. func Line ( destination canvas.Canvas, - source Pattern, + source canvas.Canvas, weight int, min image.Point, max image.Point, diff --git a/artist/shapes/rectangle.go b/artist/shapes/rectangle.go new file mode 100644 index 0000000..3569b19 --- /dev/null +++ b/artist/shapes/rectangle.go @@ -0,0 +1,84 @@ +package shapes + +import "image" +import "git.tebibyte.media/sashakoshka/tomo/canvas" +import "git.tebibyte.media/sashakoshka/tomo/shatter" + +// FillRectangle draws a rectangular subset of one canvas onto the other. The +// offset point defines where the origin point of the source canvas is +// positioned in relation to the origin point of the destination canvas. To +// prevent the entire source canvas from being drawn, it must be cut with +// canvas.Cut(). +func FillRectangle ( + destination canvas.Canvas, + source canvas.Canvas, + offset image.Point, +) ( + updatedRegion image.Rectangle, +) { + 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 ++ { + dstData[x + offset.X + (y + offset.Y) * dstStride] = + srcData[x + y * srcStride] + }} + + return +} + +// StrokeRectangle is similar to FillRectangle, but it draws an inset outline of +// the source canvas onto the destination canvas. To prevent the entire source +// canvas's bounds from being used, it must be cut with canvas.Cut(). +func StrokeRectangle ( + destination canvas.Canvas, + source canvas.Canvas, + offset image.Point, + weight int, +) { + bounds := source.Bounds() + insetBounds := bounds.Inset(weight) + if insetBounds.Empty() { + FillRectangle(destination, source, offset) + return + } + + top := image.Rect ( + bounds.Min.X, bounds.Min.Y, + bounds.Max.X, insetBounds.Min.Y) + bottom := image.Rect ( + bounds.Min.X, insetBounds.Max.Y, + bounds.Max.X, bounds.Max.Y) + left := image.Rect ( + bounds.Min.X, insetBounds.Min.Y, + insetBounds.Min.X, insetBounds.Max.Y) + right := image.Rect ( + insetBounds.Max.X, insetBounds.Min.Y, + bounds.Max.X, insetBounds.Max.Y) + + FillRectangle (destination, canvas.Cut(source, top), offset) + FillRectangle (destination, canvas.Cut(source, bottom), offset) + FillRectangle (destination, canvas.Cut(source, left), offset) + FillRectangle (destination, canvas.Cut(source, right), offset) +} + +// FillRectangleShatter is like FillRectangle, but it does not draw in areas +// specified in "rocks". +func FillRectangleShatter ( + destination canvas.Canvas, + source canvas.Canvas, + offset image.Point, + rocks []image.Rectangle, +) { + tiles := shatter.Shatter(source.Bounds()) + for _, tile := range tiles { + tile + } +} From 79ab1c8ac0b16c148c439319123f38ca338a10d2 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Fri, 24 Feb 2023 02:26:34 -0500 Subject: [PATCH 05/21] Existing shape routines have been reimplemented --- artist/shapes/doc.go | 2 +- artist/shapes/ellipse.go | 85 ++++++++++++--------- artist/shapes/line.go | 149 +++++++++++++++++-------------------- artist/shapes/rectangle.go | 34 ++------- 4 files changed, 128 insertions(+), 142 deletions(-) diff --git a/artist/shapes/doc.go b/artist/shapes/doc.go index ff03446..98eb8f6 100644 --- a/artist/shapes/doc.go +++ b/artist/shapes/doc.go @@ -1,6 +1,6 @@ // Package shapes provides some basic shape drawing routines. // -// A word about patterns: +// 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 diff --git a/artist/shapes/ellipse.go b/artist/shapes/ellipse.go index 39b6f13..654b905 100644 --- a/artist/shapes/ellipse.go +++ b/artist/shapes/ellipse.go @@ -3,19 +3,25 @@ package shapes import "math" import "image" import "image/color" -import "git.tebibyte.media/sashakoshka/tomo/artist" +// import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/canvas" -// FillEllipse draws a filled ellipse with the specified pattern. +// FillEllipse draws the content of one canvas onto another, clipped by an +// ellipse stretched to the bounds of the source canvas. The offset point +// defines where the origin point of the source canvas is positioned in relation +// to the origin point of the destination canvas. To prevent the entire source +// canvas's bounds from being used, it must be cut with canvas.Cut(). func FillEllipse ( destination canvas.Canvas, - source artist.Pattern, - bounds image.Rectangle, + source canvas.Canvas, + offset image.Point, ) ( updatedRegion image.Rectangle, ) { - bounds = bounds.Canon() - data, stride := destination.Buffer() + dstData, dstStride := destination.Buffer() + srcData, srcStride := source.Buffer() + + bounds := source.Bounds() realWidth, realHeight := bounds.Dx(), bounds.Dy() bounds = bounds.Intersect(destination.Bounds()).Canon() if bounds.Empty() { return } @@ -27,35 +33,38 @@ func FillEllipse ( xf := (float64(x) + 0.5) / float64(realWidth) - 0.5 yf := (float64(y) + 0.5) / float64(realHeight) - 0.5 if math.Sqrt(xf * xf + yf * yf) <= 0.5 { - data[x + bounds.Min.X + (y + bounds.Min.Y) * stride] = - source.AtWhen(x, y, realWidth, realHeight) + dstData[x + offset.X + (y + offset.Y) * dstStride] = + srcData[x + y * srcStride] } }} return } -// StrokeEllipse draws the outline of an ellipse with the specified line weight -// and pattern. +// StrokeRectangle is similar to FillEllipse, but it draws an elliptical inset +// outline of the source canvas onto the destination canvas. To prevent the +// entire source canvas's bounds from being used, it must be cut with +// canvas.Cut(). func StrokeEllipse ( destination canvas.Canvas, - source artist.Pattern, - weight int, - bounds image.Rectangle, + source canvas.Canvas, + offset image.Point, + weight int, ) { if weight < 1 { return } - data, stride := destination.Buffer() - bounds = bounds.Canon().Inset(weight - 1) - width, height := bounds.Dx(), bounds.Dy() + dstData, dstStride := destination.Buffer() + srcData, srcStride := source.Buffer() + + bounds := source.Bounds().Inset(weight - 1) context := ellipsePlottingContext { - data: data, - stride: stride, - source: source, - width: width, - height: height, - weight: weight, - bounds: bounds, + dstData: dstData, + dstStride: dstStride, + srcData: srcData, + srcStride: srcStride, + weight: weight, + offset: offset, + bounds: bounds.Intersect(destination.Bounds()), } bounds.Max.X -= 1 @@ -129,18 +138,26 @@ func StrokeEllipse ( } type ellipsePlottingContext struct { - data []color.RGBA - stride int - source artist.Pattern - width, height int - weight int - bounds image.Rectangle + dstData []color.RGBA + dstStride int + srcData []color.RGBA + srcStride int + weight int + offset image.Point + bounds image.Rectangle } func (context ellipsePlottingContext) plot (x, y int) { - if (image.Point { x, y }).In(context.bounds) { - squareAround ( - context.data, context.stride, context.source, x, y, - context.width, context.height, context.weight) - } + square := + image.Rect(0, 0, context.weight, context.weight). + Sub(image.Pt(context.weight / 2, context.weight / 2)). + Add(image.Pt(x, y)). + Intersect(context.bounds) + + for y := square.Min.Y; y < square.Min.Y; y ++ { + for x := square.Min.X; x < square.Min.X; x ++ { + context.dstData[x + y * context.dstStride] = + context.srcData [ + x + y * context.dstStride] + }} } diff --git a/artist/shapes/line.go b/artist/shapes/line.go index 2b92c59..85ed642 100644 --- a/artist/shapes/line.go +++ b/artist/shapes/line.go @@ -6,14 +6,14 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas" // TODO: draw thick lines more efficiently -// Line draws a line from one point to another with the specified weight and -// pattern. -func Line ( +// ColorLine draws a line from one point to another with the specified weight +// and color. +func ColorLine ( destination canvas.Canvas, - source canvas.Canvas, - weight int, - min image.Point, - max image.Point, + color color.RGBA, + weight int, + min image.Point, + max image.Point, ) ( updatedRegion image.Rectangle, ) { @@ -21,43 +21,49 @@ 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) { + data, stride := destination.Buffer() + bounds := destination.Bounds() + context := linePlottingContext { + 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() - if max.X < min.X { - temp := min - min = max - max = temp - } - lineLow(destination, source, weight, min, max, width, height) } else { - - if max.Y < min.Y { - temp := min - min = max - max = temp - } - lineHigh(destination, source, weight, min, max, width, height) + if max.Y < min.Y { context.swap() } + context.lineHigh() } return } -func lineLow ( - destination canvas.Canvas, - source Pattern, - weight int, - min image.Point, - max image.Point, - width, height int, -) { - data, stride := destination.Buffer() - bounds := destination.Bounds() +type linePlottingContext struct { + dstData []color.RGBA + dstStride int + color color.RGBA + weight int + bounds image.Rectangle + min image.Point + max image.Point +} - deltaX := max.X - min.X - deltaY := max.Y - min.Y +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 { @@ -66,34 +72,23 @@ func lineLow ( } D := (2 * deltaY) - deltaX - y := min.Y + point := context.min - for x := min.X; x < max.X; x ++ { - if !(image.Point { x, y }).In(bounds) { break } - squareAround(data, stride, source, x, y, width, height, weight) - // data[x + y * stride] = source.AtWhen(x, y, width, height) + for ; point.X < context.max.X; point.X ++ { + if !point.In(context.bounds) { break } + context.plot(point) if D > 0 { - y += yi D += 2 * (deltaY - deltaX) + point.Y += yi } else { D += 2 * deltaY } } } -func lineHigh ( - destination canvas.Canvas, - source Pattern, - weight int, - min image.Point, - max image.Point, - width, height int, -) { - data, stride := destination.Buffer() - bounds := destination.Bounds() - - deltaX := max.X - min.X - deltaY := max.Y - min.Y +func (context linePlottingContext) lineHigh () { + deltaX := context.max.X - context.min.X + deltaY := context.max.Y - context.min.Y xi := 1 if deltaX < 0 { @@ -102,14 +97,13 @@ func lineHigh ( } D := (2 * deltaX) - deltaY - x := min.X + point := context.min - for y := min.Y; y < max.Y; y ++ { - if !(image.Point { x, y }).In(bounds) { break } - squareAround(data, stride, source, x, y, width, height, weight) - // data[x + y * stride] = source.AtWhen(x, y, width, height) + for ; point.Y < context.max.Y; point.Y ++ { + if !point.In(context.bounds) { break } + context.plot(point) if D > 0 { - x += xi + point.X += xi D += 2 * (deltaX - deltaY) } else { D += 2 * deltaX @@ -117,27 +111,20 @@ func lineHigh ( } } -func abs (in int) (out int) { - if in < 0 { in *= -1} - out = in - return +func abs (n int) int { + if n < 0 { n *= -1} + return n } -// TODO: this method of doing things sucks and can cause a segfault. we should -// not be doing it this way -func squareAround ( - data []color.RGBA, - stride int, - source Pattern, - x, y, patternWidth, patternHeight, diameter int, -) { - minY := y - diameter + 1 - minX := x - diameter + 1 - maxY := y + diameter - maxX := x + diameter - for y = minY; y < maxY; y ++ { - for x = minX; x < maxX; x ++ { - data[x + y * stride] = - source.AtWhen(x, y, patternWidth, patternHeight) +func (context linePlottingContext) plot (center image.Point) { + square := + image.Rect(0, 0, context.weight, context.weight). + Sub(image.Pt(context.weight / 2, context.weight / 2)). + Add(center). + Intersect(context.bounds) + + for y := square.Min.Y; y < square.Min.Y; y ++ { + for x := square.Min.X; x < square.Min.X; x ++ { + context.dstData[x + y * context.dstStride] = context.color }} } diff --git a/artist/shapes/rectangle.go b/artist/shapes/rectangle.go index 3569b19..2b59a59 100644 --- a/artist/shapes/rectangle.go +++ b/artist/shapes/rectangle.go @@ -4,11 +4,10 @@ import "image" import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/shatter" -// FillRectangle draws a rectangular subset of one canvas onto the other. The -// offset point defines where the origin point of the source canvas is -// positioned in relation to the origin point of the destination canvas. To -// prevent the entire source canvas from being drawn, it must be cut with -// canvas.Cut(). +// FillRectangle draws the content of one canvas onto another. The offset point +// defines where the origin point of the source canvas is positioned in relation +// to the origin point of the destination canvas. To prevent the entire source +// canvas from being drawn, it must be cut with canvas.Cut(). func FillRectangle ( destination canvas.Canvas, source canvas.Canvas, @@ -49,24 +48,7 @@ func StrokeRectangle ( FillRectangle(destination, source, offset) return } - - top := image.Rect ( - bounds.Min.X, bounds.Min.Y, - bounds.Max.X, insetBounds.Min.Y) - bottom := image.Rect ( - bounds.Min.X, insetBounds.Max.Y, - bounds.Max.X, bounds.Max.Y) - left := image.Rect ( - bounds.Min.X, insetBounds.Min.Y, - insetBounds.Min.X, insetBounds.Max.Y) - right := image.Rect ( - insetBounds.Max.X, insetBounds.Min.Y, - bounds.Max.X, insetBounds.Max.Y) - - FillRectangle (destination, canvas.Cut(source, top), offset) - FillRectangle (destination, canvas.Cut(source, bottom), offset) - FillRectangle (destination, canvas.Cut(source, left), offset) - FillRectangle (destination, canvas.Cut(source, right), offset) + FillRectangleShatter(destination, source, offset, insetBounds) } // FillRectangleShatter is like FillRectangle, but it does not draw in areas @@ -75,10 +57,10 @@ func FillRectangleShatter ( destination canvas.Canvas, source canvas.Canvas, offset image.Point, - rocks []image.Rectangle, + rocks ...image.Rectangle, ) { - tiles := shatter.Shatter(source.Bounds()) + tiles := shatter.Shatter(source.Bounds(), rocks...) for _, tile := range tiles { - tile + FillRectangle(destination, canvas.Cut(source, tile), offset) } } From 211219eb0146d22b46a7dbb4c656cdc696f69428 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Fri, 24 Feb 2023 02:51:24 -0500 Subject: [PATCH 06/21] Ellipse and line share code --- artist/shapes/ellipse.go | 45 ++++++++-------------------------------- artist/shapes/line.go | 44 +++++++++++++-------------------------- artist/shapes/plot.go | 40 +++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 66 deletions(-) create mode 100644 artist/shapes/plot.go diff --git a/artist/shapes/ellipse.go b/artist/shapes/ellipse.go index 654b905..e4b46a5 100644 --- a/artist/shapes/ellipse.go +++ b/artist/shapes/ellipse.go @@ -2,8 +2,6 @@ package shapes import "math" import "image" -import "image/color" -// import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/canvas" // FillEllipse draws the content of one canvas onto another, clipped by an @@ -57,7 +55,7 @@ func StrokeEllipse ( bounds := source.Bounds().Inset(weight - 1) - context := ellipsePlottingContext { + context := plottingContext { dstData: dstData, dstStride: dstStride, srcData: srcData, @@ -88,10 +86,10 @@ func StrokeEllipse ( // draw region 1 for decisionX < decisionY { - context.plot( int(x) + center.X, int(y) + center.Y) - context.plot(-int(x) + center.X, int(y) + center.Y) - context.plot( int(x) + center.X, -int(y) + center.Y) - context.plot(-int(x) + center.X, -int(y) + center.Y) + context.plotSource(image.Pt( int(x) + center.X, int(y) + center.Y)) + context.plotSource(image.Pt(-int(x) + center.X, int(y) + center.Y)) + context.plotSource(image.Pt( int(x) + center.X, -int(y) + center.Y)) + context.plotSource(image.Pt(-int(x) + center.X, -int(y) + center.Y)) if (decision1 < 0) { x ++ @@ -116,10 +114,10 @@ func StrokeEllipse ( // draw region 2 for y >= 0 { - context.plot( int(x) + center.X, int(y) + center.Y) - context.plot(-int(x) + center.X, int(y) + center.Y) - context.plot( int(x) + center.X, -int(y) + center.Y) - context.plot(-int(x) + center.X, -int(y) + center.Y) + context.plotSource(image.Pt( int(x) + center.X, int(y) + center.Y)) + context.plotSource(image.Pt(-int(x) + center.X, int(y) + center.Y)) + context.plotSource(image.Pt( int(x) + center.X, -int(y) + center.Y)) + context.plotSource(image.Pt(-int(x) + center.X, -int(y) + center.Y)) if decision2 > 0 { y -- @@ -136,28 +134,3 @@ func StrokeEllipse ( } } } - -type ellipsePlottingContext struct { - dstData []color.RGBA - dstStride int - srcData []color.RGBA - srcStride int - weight int - offset image.Point - bounds image.Rectangle -} - -func (context ellipsePlottingContext) plot (x, y int) { - square := - image.Rect(0, 0, context.weight, context.weight). - Sub(image.Pt(context.weight / 2, context.weight / 2)). - Add(image.Pt(x, y)). - Intersect(context.bounds) - - for y := square.Min.Y; y < square.Min.Y; y ++ { - for x := square.Min.X; x < square.Min.X; x ++ { - context.dstData[x + y * context.dstStride] = - context.srcData [ - x + y * context.dstStride] - }} -} diff --git a/artist/shapes/line.go b/artist/shapes/line.go index 85ed642..6d4050e 100644 --- a/artist/shapes/line.go +++ b/artist/shapes/line.go @@ -17,7 +17,6 @@ func ColorLine ( ) ( updatedRegion image.Rectangle, ) { - updatedRegion = image.Rectangle { Min: min, Max: max }.Canon() updatedRegion.Max.X ++ updatedRegion.Max.Y ++ @@ -25,13 +24,15 @@ func ColorLine ( data, stride := destination.Buffer() bounds := destination.Bounds() context := linePlottingContext { - dstData: data, - dstStride: stride, - color: color, - weight: weight, - bounds: bounds, - min: min, - max: max, + 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) { @@ -46,13 +47,9 @@ func ColorLine ( } type linePlottingContext struct { - dstData []color.RGBA - dstStride int - color color.RGBA - weight int - bounds image.Rectangle - min image.Point - max image.Point + plottingContext + min image.Point + max image.Point } func (context *linePlottingContext) swap () { @@ -76,7 +73,7 @@ func (context linePlottingContext) lineLow () { for ; point.X < context.max.X; point.X ++ { if !point.In(context.bounds) { break } - context.plot(point) + context.plotColor(point) if D > 0 { D += 2 * (deltaY - deltaX) point.Y += yi @@ -101,7 +98,7 @@ func (context linePlottingContext) lineHigh () { for ; point.Y < context.max.Y; point.Y ++ { if !point.In(context.bounds) { break } - context.plot(point) + context.plotColor(point) if D > 0 { point.X += xi D += 2 * (deltaX - deltaY) @@ -115,16 +112,3 @@ func abs (n int) int { if n < 0 { n *= -1} return n } - -func (context linePlottingContext) plot (center image.Point) { - square := - image.Rect(0, 0, context.weight, context.weight). - Sub(image.Pt(context.weight / 2, context.weight / 2)). - Add(center). - Intersect(context.bounds) - - for y := square.Min.Y; y < square.Min.Y; y ++ { - for x := square.Min.X; x < square.Min.X; x ++ { - context.dstData[x + y * context.dstStride] = context.color - }} -} diff --git a/artist/shapes/plot.go b/artist/shapes/plot.go new file mode 100644 index 0000000..a35751a --- /dev/null +++ b/artist/shapes/plot.go @@ -0,0 +1,40 @@ +package shapes + +import "image" +import "image/color" + +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) 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.Min.Y; y ++ { + for x := square.Min.X; x < square.Min.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.Min.Y; y ++ { + for x := square.Min.X; x < square.Min.X; x ++ { + context.dstData[x + y * context.dstStride] = + context.srcData [ + x + y * context.dstStride] + }} +} From bf2fdb5eaa3a510b4ef1b4ccbd1469f50181c8e5 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Fri, 24 Feb 2023 16:31:42 -0500 Subject: [PATCH 07/21] Ellipse and rectangle have both color and source routines --- artist/shapes/ellipse.go | 202 +++++++++++++++++++++++++++---------- artist/shapes/line.go | 2 - artist/shapes/plot.go | 14 ++- artist/shapes/rectangle.go | 57 ++++++++++- 4 files changed, 215 insertions(+), 60 deletions(-) diff --git a/artist/shapes/ellipse.go b/artist/shapes/ellipse.go index e4b46a5..3acc2e2 100644 --- a/artist/shapes/ellipse.go +++ b/artist/shapes/ellipse.go @@ -2,6 +2,7 @@ package shapes import "math" import "image" +import "image/color" import "git.tebibyte.media/sashakoshka/tomo/canvas" // FillEllipse draws the content of one canvas onto another, clipped by an @@ -19,26 +20,25 @@ func FillEllipse ( dstData, dstStride := destination.Buffer() srcData, srcStride := source.Buffer() - bounds := source.Bounds() - realWidth, realHeight := bounds.Dx(), bounds.Dy() - bounds = bounds.Intersect(destination.Bounds()).Canon() + bounds := source.Bounds().Intersect(destination.Bounds()).Canon() + realBounds := source.Bounds() if bounds.Empty() { return } updatedRegion = bounds - width, height := bounds.Dx(), bounds.Dy() - for y := 0; y < height; y ++ { - for x := 0; x < width; x ++ { - xf := (float64(x) + 0.5) / float64(realWidth) - 0.5 - yf := (float64(y) + 0.5) / float64(realHeight) - 0.5 - if math.Sqrt(xf * xf + yf * yf) <= 0.5 { - dstData[x + offset.X + (y + offset.Y) * dstStride] = - srcData[x + y * srcStride] + 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) { + offsetPoint := point.Add(offset) + dstIndex := offsetPoint.X + (offsetPoint.Y) * dstStride + srcIndex := point.X + point.Y * srcStride + dstData[dstIndex] = srcData[srcIndex] } }} return } -// StrokeRectangle is similar to FillEllipse, but it draws an elliptical inset +// StrokeEllipse is similar to FillEllipse, but it draws an elliptical inset // outline of the source canvas onto the destination canvas. To prevent the // entire source canvas's bounds from being used, it must be cut with // canvas.Cut(). @@ -55,82 +55,176 @@ func StrokeEllipse ( bounds := source.Bounds().Inset(weight - 1) - context := plottingContext { - dstData: dstData, - dstStride: dstStride, - srcData: srcData, - srcStride: srcStride, - weight: weight, - offset: offset, - bounds: bounds.Intersect(destination.Bounds()), + context := ellipsePlottingContext { + plottingContext: plottingContext { + dstData: dstData, + dstStride: dstStride, + srcData: srcData, + srcStride: srcStride, + weight: weight, + offset: offset, + bounds: bounds.Intersect(destination.Bounds()), + }, + radii: image.Pt(bounds.Dx() / 2 - 1, bounds.Dy() / 2 - 1), } - - bounds.Max.X -= 1 - bounds.Max.Y -= 1 + context.center = bounds.Min.Add(context.radii) + context.plotEllipse() +} - radii := image.Pt ( - bounds.Dx() / 2, - bounds.Dy() / 2) - center := bounds.Min.Add(radii) +type ellipsePlottingContext struct { + plottingContext + radii image.Point + center image.Point +} +func (context ellipsePlottingContext) plotEllipse () { x := float64(0) - y := float64(radii.Y) + y := float64(context.radii.Y) // region 1 decision parameter decision1 := - float64(radii.Y * radii.Y) - - float64(radii.X * radii.X * radii.Y) + - (0.25 * float64(radii.X) * float64(radii.X)) - decisionX := float64(2 * radii.Y * radii.Y * int(x)) - decisionY := float64(2 * radii.X * radii.X * int(y)) + 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 { - context.plotSource(image.Pt( int(x) + center.X, int(y) + center.Y)) - context.plotSource(image.Pt(-int(x) + center.X, int(y) + center.Y)) - context.plotSource(image.Pt( int(x) + center.X, -int(y) + center.Y)) - context.plotSource(image.Pt(-int(x) + center.X, -int(y) + center.Y)) + 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 * radii.Y * radii.Y) - decision1 += decisionX + float64(radii.Y * radii.Y) + decisionX += float64(2 * context.radii.Y * context.radii.Y) + decision1 += decisionX + float64(context.radii.Y * context.radii.Y) } else { x ++ y -- - decisionX += float64(2 * radii.Y * radii.Y) - decisionY -= float64(2 * radii.X * radii.X) + decisionX += float64(2 * context.radii.Y * context.radii.Y) + decisionY -= float64(2 * context.radii.X * context.radii.X) decision1 += decisionX - decisionY + - float64(radii.Y * radii.Y) + float64(context.radii.Y * context.radii.Y) } } // region 2 decision parameter decision2 := - float64(radii.Y * radii.Y) * (x + 0.5) * (x + 0.5) + - float64(radii.X * radii.X) * (y - 1) * (y - 1) - - float64(radii.X * radii.X * radii.Y * radii.Y) + 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 { - context.plotSource(image.Pt( int(x) + center.X, int(y) + center.Y)) - context.plotSource(image.Pt(-int(x) + center.X, int(y) + center.Y)) - context.plotSource(image.Pt( int(x) + center.X, -int(y) + center.Y)) - context.plotSource(image.Pt(-int(x) + center.X, -int(y) + center.Y)) + 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 * radii.X * radii.X) - decision2 += float64(radii.X * radii.X) - decisionY + decisionY -= float64(2 * context.radii.X * context.radii.X) + decision2 += float64(context.radii.X * context.radii.X) - decisionY } else { y -- x ++ - decisionX += float64(2 * radii.Y * radii.Y) - decisionY -= float64(2 * radii.X * radii.X) + decisionX += float64(2 * context.radii.Y * context.radii.Y) + decisionY -= float64(2 * context.radii.X * context.radii.X) decision2 += decisionX - decisionY + - float64(radii.X * radii.X) + float64(context.radii.X * context.radii.X) } } } + +// FillColorEllipse fills an ellipse within the destination canvas with a solid +// color. +func FillColorEllipse ( + destination canvas.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 canvas.Canvas, + color color.RGBA, + bounds image.Rectangle, + weight int, +) ( + updatedRegion image.Rectangle, +) { + if weight < 1 { return } + + dstData, dstStride := destination.Buffer() + bounds = bounds.Inset(weight - 1) + + context := ellipsePlottingContext { + plottingContext: plottingContext { + dstData: dstData, + dstStride: dstStride, + color: color, + weight: weight, + bounds: bounds.Intersect(destination.Bounds()), + }, + radii: image.Pt(bounds.Dx() / 2 - 1, bounds.Dy() / 2 - 1), + } + context.center = bounds.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 +} diff --git a/artist/shapes/line.go b/artist/shapes/line.go index 6d4050e..1ebfbbb 100644 --- a/artist/shapes/line.go +++ b/artist/shapes/line.go @@ -4,8 +4,6 @@ import "image" import "image/color" import "git.tebibyte.media/sashakoshka/tomo/canvas" -// TODO: draw thick lines more efficiently - // ColorLine draws a line from one point to another with the specified weight // and color. func ColorLine ( diff --git a/artist/shapes/plot.go b/artist/shapes/plot.go index a35751a..b629bd9 100644 --- a/artist/shapes/plot.go +++ b/artist/shapes/plot.go @@ -3,6 +3,8 @@ 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 @@ -18,6 +20,7 @@ func (context plottingContext) square (center image.Point) image.Rectangle { return image.Rect(0, 0, context.weight, context.weight). Sub(image.Pt(context.weight / 2, context.weight / 2)). Add(center). + Add(context.offset). Intersect(context.bounds) } @@ -33,8 +36,13 @@ func (context plottingContext) plotSource (center image.Point) { square := context.square(center) for y := square.Min.Y; y < square.Min.Y; y ++ { for x := square.Min.X; x < square.Min.X; x ++ { - context.dstData[x + y * context.dstStride] = - context.srcData [ - x + y * context.dstStride] + // 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] }} } diff --git a/artist/shapes/rectangle.go b/artist/shapes/rectangle.go index 2b59a59..9e6f86f 100644 --- a/artist/shapes/rectangle.go +++ b/artist/shapes/rectangle.go @@ -1,9 +1,12 @@ package shapes import "image" +import "image/color" import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/shatter" +// TODO: return updatedRegion for all routines in this package + // FillRectangle draws the content of one canvas onto another. The offset point // defines where the origin point of the source canvas is positioned in relation // to the origin point of the destination canvas. To prevent the entire source @@ -59,8 +62,60 @@ func FillRectangleShatter ( offset image.Point, rocks ...image.Rectangle, ) { - tiles := shatter.Shatter(source.Bounds(), rocks...) + tiles := shatter.Shatter(source.Bounds().Sub(offset), rocks...) for _, tile := range tiles { FillRectangle(destination, canvas.Cut(source, tile), offset) } } + +// FillColorRectangle fills a rectangle within the destination canvas with a +// solid color. +func FillColorRectangle ( + destination canvas.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 canvas.Canvas, + color color.RGBA, + bounds image.Rectangle, + rocks ...image.Rectangle, +) { + tiles := shatter.Shatter(bounds, rocks...) + for _, tile := range tiles { + FillColorRectangle(destination, color, tile) + } +} + +// StrokeColorRectangle is similar to FillColorRectangle, but it draws an inset +// outline of the given rectangle instead. +func StrokeColorRectangle ( + destination canvas.Canvas, + color color.RGBA, + bounds image.Rectangle, + weight int, +) { + insetBounds := bounds.Inset(weight) + if insetBounds.Empty() { + FillColorRectangle(destination, color, bounds) + return + } + FillColorRectangleShatter(destination, color, bounds, insetBounds) +} From 81090267a645b16f1290996268f23a805743f6d0 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sat, 25 Feb 2023 18:41:16 -0500 Subject: [PATCH 08/21] Created new patterns --- {theme => artist}/inset.go | 10 ++- artist/patterns/bevel.go | 46 ------------- artist/patterns/border.go | 64 +++++++++++++++++ artist/patterns/bordered.go | 111 ------------------------------ artist/patterns/checkered.go | 51 -------------- artist/patterns/circlebordered.go | 33 --------- artist/patterns/dotted.go | 30 -------- artist/patterns/gradient.go | 45 ------------ artist/patterns/noise.go | 33 --------- artist/patterns/split.go | 43 ------------ artist/patterns/striped.go | 37 ---------- artist/patterns/texture.go | 56 +++++++-------- artist/patterns/uniform.go | 68 ++---------------- artist/patterns/wrap.go | 27 -------- 14 files changed, 105 insertions(+), 549 deletions(-) rename {theme => artist}/inset.go (89%) delete mode 100644 artist/patterns/bevel.go create mode 100644 artist/patterns/border.go delete mode 100644 artist/patterns/bordered.go delete mode 100644 artist/patterns/checkered.go delete mode 100644 artist/patterns/circlebordered.go delete mode 100644 artist/patterns/dotted.go delete mode 100644 artist/patterns/gradient.go delete mode 100644 artist/patterns/noise.go delete mode 100644 artist/patterns/split.go delete mode 100644 artist/patterns/striped.go delete mode 100644 artist/patterns/wrap.go diff --git a/theme/inset.go b/artist/inset.go similarity index 89% rename from theme/inset.go rename to artist/inset.go index 1cace51..d552eda 100644 --- a/theme/inset.go +++ b/artist/inset.go @@ -1,7 +1,15 @@ -package theme +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. diff --git a/artist/patterns/bevel.go b/artist/patterns/bevel.go deleted file mode 100644 index 2e7ec91..0000000 --- a/artist/patterns/bevel.go +++ /dev/null @@ -1,46 +0,0 @@ -package artist - -import "image/color" - -// Beveled is a pattern that has a highlight section and a shadow section. -type Beveled [2]Pattern - -// AtWhen satisfies the Pattern interface. -func (pattern Beveled) AtWhen (x, y, width, height int) (c color.RGBA) { - return QuadBeveled { - pattern[0], - pattern[1], - pattern[1], - pattern[0], - }.AtWhen(x, y, width, height) -} - -// QuadBeveled is like Beveled, but with four sides. A pattern can be specified -// for each one. -type QuadBeveled [4]Pattern - -// AtWhen satisfies the Pattern interface. -func (pattern QuadBeveled) AtWhen (x, y, width, height int) (c color.RGBA) { - bottom := y > height / 2 - right := x > width / 2 - top := !bottom - left := !right - side := 0 - - switch { - case top && left: - if x < y { side = 3 } else { side = 0 } - - case top && right: - if width - x > y { side = 0 } else { side = 1 } - - case bottom && left: - if x < height - y { side = 3 } else { side = 2 } - - case bottom && right: - if width - x > height - y { side = 2 } else { side = 1 } - - } - - return pattern[side].AtWhen(x, y, width, height) -} diff --git a/artist/patterns/border.go b/artist/patterns/border.go new file mode 100644 index 0000000..0b23370 --- /dev/null +++ b/artist/patterns/border.go @@ -0,0 +1,64 @@ +package patterns + +import "image" +import "git.tebibyte.media/sashakoshka/tomo/canvas" +import "git.tebibyte.media/sashakoshka/tomo/artist" + +type Border struct { + canvas.Canvas + artist.Inset +} + +func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) { + bounds := clip.Canon().Intersect(destination.Bounds()) + if bounds.Empty() { return } + + srcSections := nonasect(pattern.Bounds(), pattern.Inset) + srcTextures := [9]Texture { } + for index, section := range srcSections { + srcTextures[index] = Texture { + Canvas: canvas.Cut(pattern, section), + } + } + + dstSections := nonasect(destination.Bounds(), pattern.Inset) + for index, section := range dstSections { + srcTextures[index].Draw(canvas.Cut(destination, section), clip) + } +} + +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), + } +} diff --git a/artist/patterns/bordered.go b/artist/patterns/bordered.go deleted file mode 100644 index 08a2a39..0000000 --- a/artist/patterns/bordered.go +++ /dev/null @@ -1,111 +0,0 @@ -package artist - -import "image" -import "image/color" - -// Bordered is a pattern with a border and a fill. -type Bordered struct { - Fill Pattern - Stroke -} - -// AtWhen satisfies the Pattern interface. -func (pattern Bordered) AtWhen (x, y, width, height int) (c color.RGBA) { - outerBounds := image.Rectangle { Max: image.Point { width, height }} - innerBounds := outerBounds.Inset(pattern.Weight) - if (image.Point { x, y }).In (innerBounds) { - return pattern.Fill.AtWhen ( - x - pattern.Weight, - y - pattern.Weight, - innerBounds.Dx(), innerBounds.Dy()) - } else { - return pattern.Stroke.AtWhen(x, y, width, height) - } -} - -// Stroke represents a stoke that has a weight and a pattern. -type Stroke struct { - Weight int - Pattern -} - -type borderInternal struct { - weight int - stroke Pattern - bounds image.Rectangle - dx, dy int -} - -// MultiBordered is a pattern that allows multiple borders of different lengths -// to be inset within one another. The final border is treated as a fill color, -// and its weight does not matter. -type MultiBordered struct { - borders []borderInternal - lastWidth, lastHeight int - maxBorder int -} - -// NewMultiBordered creates a new MultiBordered pattern from the given list of -// borders. -func NewMultiBordered (borders ...Stroke) (multi *MultiBordered) { - internalBorders := make([]borderInternal, len(borders)) - for index, border := range borders { - internalBorders[index].weight = border.Weight - internalBorders[index].stroke = border.Pattern - } - return &MultiBordered { borders: internalBorders } -} - -// AtWhen satisfies the Pattern interface. -func (multi *MultiBordered) AtWhen (x, y, width, height int) (c color.RGBA) { - if multi.lastWidth != width || multi.lastHeight != height { - multi.recalculate(width, height) - } - point := image.Point { x, y } - for index := multi.maxBorder; index >= 0; index -- { - border := multi.borders[index] - if point.In(border.bounds) { - return border.stroke.AtWhen ( - point.X - border.bounds.Min.X, - point.Y - border.bounds.Min.Y, - border.dx, border.dy) - } - } - return -} - -func (multi *MultiBordered) recalculate (width, height int) { - bounds := image.Rect (0, 0, width, height) - multi.maxBorder = 0 - for index, border := range multi.borders { - multi.maxBorder = index - multi.borders[index].bounds = bounds - multi.borders[index].dx = bounds.Dx() - multi.borders[index].dy = bounds.Dy() - bounds = bounds.Inset(border.weight) - if bounds.Empty() { break } - } -} - -// Padded is a pattern that surrounds a central fill pattern with a border that -// can have a different width for each side. -type Padded struct { - Fill Pattern - Stroke Pattern - Sides []int -} - -// AtWhen satisfies the Pattern interface. -func (pattern Padded) AtWhen (x, y, width, height int) (c color.RGBA) { - innerBounds := image.Rect ( - pattern.Sides[3], pattern.Sides[0], - width - pattern.Sides[1], height - pattern.Sides[2]) - if (image.Point { x, y }).In (innerBounds) { - return pattern.Fill.AtWhen ( - x - pattern.Sides[3], - y - pattern.Sides[0], - innerBounds.Dx(), innerBounds.Dy()) - } else { - return pattern.Stroke.AtWhen(x, y, width, height) - } -} diff --git a/artist/patterns/checkered.go b/artist/patterns/checkered.go deleted file mode 100644 index c02f795..0000000 --- a/artist/patterns/checkered.go +++ /dev/null @@ -1,51 +0,0 @@ -package artist - -import "image/color" - -// Checkered is a pattern that produces a grid of two alternating colors. -type Checkered struct { - First Pattern - Second Pattern - CellWidth, CellHeight int -} - -// AtWhen satisfies the Pattern interface. -func (pattern Checkered) AtWhen (x, y, width, height int) (c color.RGBA) { - twidth := pattern.CellWidth * 2 - theight := pattern.CellHeight * 2 - x %= twidth - y %= theight - if x < 0 { x += twidth } - if y < 0 { x += theight } - - n := 0 - if x >= pattern.CellWidth { n ++ } - if y >= pattern.CellHeight { n ++ } - - x %= pattern.CellWidth - y %= pattern.CellHeight - - if n % 2 == 0 { - return pattern.First.AtWhen ( - x, y, pattern.CellWidth, pattern.CellHeight) - } else { - return pattern.Second.AtWhen ( - x, y, pattern.CellWidth, pattern.CellHeight) - } -} - -// Tiled is a pattern that tiles another pattern accross a grid. -type Tiled struct { - Pattern - CellWidth, CellHeight int -} - -// AtWhen satisfies the Pattern interface. -func (pattern Tiled) AtWhen (x, y, width, height int) (c color.RGBA) { - x %= pattern.CellWidth - y %= pattern.CellHeight - if x < 0 { x += pattern.CellWidth } - if y < 0 { y += pattern.CellHeight } - return pattern.Pattern.AtWhen ( - x, y, pattern.CellWidth, pattern.CellHeight) -} diff --git a/artist/patterns/circlebordered.go b/artist/patterns/circlebordered.go deleted file mode 100644 index 6727477..0000000 --- a/artist/patterns/circlebordered.go +++ /dev/null @@ -1,33 +0,0 @@ -package artist - -import "math" -import "image/color" - -// EllipticallyBordered is a pattern with a border and a fill that is elliptical -// in shape. -type EllipticallyBordered struct { - Fill Pattern - Stroke -} - -// AtWhen satisfies the Pattern interface. -func (pattern EllipticallyBordered) AtWhen (x, y, width, height int) (c color.RGBA) { - xf := (float64(x) + 0.5) / float64(width ) * 2 - 1 - yf := (float64(y) + 0.5) / float64(height) * 2 - 1 - distance := math.Sqrt(xf * xf + yf * yf) - - var radius float64 - if width < height { - // vertical - radius = 1 - float64(pattern.Weight * 2) / float64(width) - } else { - // horizontal - radius = 1 - float64(pattern.Weight * 2) / float64(height) - } - - if distance < radius { - return pattern.Fill.AtWhen(x, y, width, height) - } else { - return pattern.Stroke.AtWhen(x, y, width, height) - } -} diff --git a/artist/patterns/dotted.go b/artist/patterns/dotted.go deleted file mode 100644 index 90a3bea..0000000 --- a/artist/patterns/dotted.go +++ /dev/null @@ -1,30 +0,0 @@ -package artist - -import "math" -import "image/color" - -// Dotted is a pattern that produces a grid of circles. -type Dotted struct { - Background Pattern - Foreground Pattern - Size int - Spacing int -} - -// AtWhen satisfies the Pattern interface. -func (pattern Dotted) AtWhen (x, y, width, height int) (c color.RGBA) { - xm := x % pattern.Spacing - ym := y % pattern.Spacing - if xm < 0 { xm += pattern.Spacing } - if ym < 0 { xm += pattern.Spacing } - radius := float64(pattern.Size) / 2 - spacing := float64(pattern.Spacing) / 2 - 0.5 - xf := float64(xm) - spacing - yf := float64(ym) - spacing - - if math.Sqrt(xf * xf + yf * yf) > radius { - return pattern.Background.AtWhen(x, y, width, height) - } else { - return pattern.Foreground.AtWhen(x, y, width, height) - } -} diff --git a/artist/patterns/gradient.go b/artist/patterns/gradient.go deleted file mode 100644 index c89ff29..0000000 --- a/artist/patterns/gradient.go +++ /dev/null @@ -1,45 +0,0 @@ -package artist - -import "image/color" - -// Gradient is a pattern that interpolates between two colors. -type Gradient struct { - First Pattern - Second Pattern - Orientation -} - -// AtWhen satisfies the Pattern interface. -func (pattern Gradient) AtWhen (x, y, width, height int) (c color.RGBA) { - var position float64 - switch pattern.Orientation { - case OrientationVertical: - position = float64(y) / float64(height) - case OrientationDiagonalRight: - position = (float64(width - x) / float64(width) + - float64(y) / float64(height)) / 2 - case OrientationHorizontal: - position = float64(x) / float64(width) - case OrientationDiagonalLeft: - position = (float64(x) / float64(width) + - float64(y) / float64(height)) / 2 - } - - firstColor := pattern.First.AtWhen(x, y, width, height) - secondColor := pattern.Second.AtWhen(x, y, width, height) - return LerpRGBA(firstColor, secondColor, position) -} - -// Lerp linearally interpolates between two integer values. -func Lerp (first, second int, fac float64) (n int) { - return int(float64(first) * (1 - fac) + float64(second) * fac) -} - -// LerpRGBA linearally interpolates between two color.RGBA values. -func LerpRGBA (first, second color.RGBA, fac float64) (c color.RGBA) { - return color.RGBA { - R: uint8(Lerp(int(first.R), int(second.R), fac)), - G: uint8(Lerp(int(first.G), int(second.G), fac)), - B: uint8(Lerp(int(first.G), int(second.B), fac)), - } -} diff --git a/artist/patterns/noise.go b/artist/patterns/noise.go deleted file mode 100644 index aa31126..0000000 --- a/artist/patterns/noise.go +++ /dev/null @@ -1,33 +0,0 @@ -package artist - -import "image/color" - -// Noisy is a pattern that randomly interpolates between two patterns in a -// deterministic fashion. -type Noisy struct { - Low Pattern - High Pattern - Seed uint32 - Harsh bool -} - -// AtWhen satisfies the pattern interface. -func (pattern Noisy) AtWhen (x, y, width, height int) (c color.RGBA) { - // FIXME: this will occasionally generate "clumps" - special := uint32(x + y * 348905) - special += (pattern.Seed + 1) * 15485863 - random := (special * special * special % 2038074743) - fac := float64(random) / 2038074743.0 - - if pattern.Harsh { - if fac > 0.5 { - return pattern.High.AtWhen(x, y, width, height) - } else { - return pattern.Low.AtWhen(x, y, width, height) - } - } else { - return LerpRGBA ( - pattern.Low.AtWhen(x, y, width, height), - pattern.High.AtWhen(x, y, width, height), fac) - } -} diff --git a/artist/patterns/split.go b/artist/patterns/split.go deleted file mode 100644 index 353c4b5..0000000 --- a/artist/patterns/split.go +++ /dev/null @@ -1,43 +0,0 @@ -package artist - -import "image/color" - -// Orientation specifies an eight-way pattern orientation. -type Orientation int - -const ( - OrientationVertical Orientation = iota - OrientationDiagonalRight - OrientationHorizontal - OrientationDiagonalLeft -) - -// Split is a pattern that is divided in half between two sub-patterns. -type Split struct { - First Pattern - Second Pattern - Orientation -} - -// AtWhen satisfies the Pattern interface. -func (pattern Split) AtWhen (x, y, width, height int) (c color.RGBA) { - var first bool - switch pattern.Orientation { - case OrientationVertical: - first = x < width / 2 - case OrientationDiagonalRight: - first = float64(x) / float64(width) + - float64(y) / float64(height) < 1 - case OrientationHorizontal: - first = y < height / 2 - case OrientationDiagonalLeft: - first = float64(width - x) / float64(width) + - float64(y) / float64(height) < 1 - } - - if first { - return pattern.First.AtWhen(x, y, width, height) - } else { - return pattern.Second.AtWhen(x, y, width, height) - } -} diff --git a/artist/patterns/striped.go b/artist/patterns/striped.go deleted file mode 100644 index 24fe0dc..0000000 --- a/artist/patterns/striped.go +++ /dev/null @@ -1,37 +0,0 @@ -package artist - -import "image/color" - -// Striped is a pattern that produces stripes of two alternating colors. -type Striped struct { - First Stroke - Second Stroke - Orientation -} - -// AtWhen satisfies the Pattern interface. -func (pattern Striped) AtWhen (x, y, width, height int) (c color.RGBA) { - position := 0 - switch pattern.Orientation { - case OrientationVertical: - position = x - case OrientationDiagonalRight: - position = x + y - case OrientationHorizontal: - position = y - case OrientationDiagonalLeft: - position = x - y - } - - phase := pattern.First.Weight + pattern.Second.Weight - position %= phase - if position < 0 { - position += phase - } - - if position < pattern.First.Weight { - return pattern.First.AtWhen(x, y, width, height) - } else { - return pattern.Second.AtWhen(x, y, width, height) - } -} diff --git a/artist/patterns/texture.go b/artist/patterns/texture.go index f2d0ae2..5a97b8b 100644 --- a/artist/patterns/texture.go +++ b/artist/patterns/texture.go @@ -1,43 +1,37 @@ -package artist +package patterns import "image" -import "image/color" +import "git.tebibyte.media/sashakoshka/tomo/canvas" -// Texture is a struct that allows an image to be converted into a tiling -// texture pattern. +// Texture is a pattern that tiles the content of a canvas both horizontally and +// vertically. type Texture struct { - data []color.RGBA - width, height int + canvas.Canvas } -// 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 +// Draw tiles the pattern's canvas within the clipping bounds. The minimum +// points of the pattern's canvas and the destination canvas will be lined up. +func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) { + bounds := clip.Canon().Intersect(destination.Bounds()) + if bounds.Empty() { return } + + dstData, dstStride := destination.Buffer() + srcData, srcStride := pattern.Buffer() + srcBounds := pattern.Bounds() + 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 ++ + dstIndex := x + y * dstStride + srcIndex := + wrap(x - bounds.Min.X, srcBounds.Min.X, srcBounds.Max.X) + + wrap(x - bounds.Min.Y, srcBounds.Min.Y, srcBounds.Max.Y) * srcStride + dstData[dstIndex] = srcData[srcIndex] }} - 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] +func wrap (value, min, max int) int { + difference := max - min + value = (value - min) % difference + if value < 0 { value += difference } + return value + min } diff --git a/artist/patterns/uniform.go b/artist/patterns/uniform.go index 74c1d0b..0aedbca 100644 --- a/artist/patterns/uniform.go +++ b/artist/patterns/uniform.go @@ -1,68 +1,14 @@ -package artist +package patterns import "image" import "image/color" +import "git.tebibyte.media/sashakoshka/tomo/canvas" +import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" -// Uniform is an infinite-sized pattern of uniform color. It implements the -// Pattern, color.Color, color.Model, and image.Image interfaces. +// Uniform is a pattern that draws a solid color. type Uniform color.RGBA -// NewUniform returns a new Uniform pattern of the given color. -func NewUniform (c color.Color) (uniform Uniform) { - r, g, b, a := c.RGBA() - uniform.R = uint8(r >> 8) - uniform.G = uint8(g >> 8) - uniform.B = uint8(b >> 8) - uniform.A = uint8(a >> 8) - return -} - -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 -} - -// Uhex creates a new Uniform pattern from an RGBA integer value. -func Uhex (color uint32) (uniform Uniform) { - return NewUniform(hex(color)) -} - -// ColorModel satisfies the image.Image interface. -func (uniform Uniform) ColorModel () (model color.Model) { - return uniform -} - -// Convert satisfies the color.Model interface. -func (uniform Uniform) Convert (in color.Color) (c color.Color) { - return color.RGBA(uniform) -} - -// Bounds satisfies the image.Image interface. -func (uniform Uniform) Bounds () (rectangle image.Rectangle) { - rectangle.Min = image.Point { -1e9, -1e9 } - rectangle.Max = image.Point { 1e9, 1e9 } - return -} - -// At satisfies the image.Image interface. -func (uniform Uniform) At (x, y int) (c color.Color) { - return color.RGBA(uniform) -} - -// AtWhen satisfies the Pattern interface. -func (uniform Uniform) AtWhen (x, y, width, height int) (c color.RGBA) { - return color.RGBA(uniform) -} - -// RGBA satisfies the color.Color interface. -func (uniform Uniform) RGBA () (r, g, b, a uint32) { - return color.RGBA(uniform).RGBA() -} - -// Opaque scans the entire image and reports whether it is fully opaque. -func (uniform Uniform) Opaque () (opaque bool) { - return uniform.A == 0xFF +// Draw fills the clipping rectangle with the pattern's color. +func (pattern Uniform) Draw (destination canvas.Canvas, clip image.Rectangle) { + shapes.FillColorRectangle(destination, color.RGBA(pattern), clip) } diff --git a/artist/patterns/wrap.go b/artist/patterns/wrap.go deleted file mode 100644 index 5cf0520..0000000 --- a/artist/patterns/wrap.go +++ /dev/null @@ -1,27 +0,0 @@ -package artist - -import "image" -import "image/color" - -// WrappedPattern is a pattern that is able to behave like an image.Image. -type WrappedPattern struct { - Pattern - Width, Height int -} - -// At satisfies the image.Image interface. -func (pattern WrappedPattern) At (x, y int) (c color.Color) { - return pattern.Pattern.AtWhen(x, y, pattern.Width, pattern.Height) -} - -// Bounds satisfies the image.Image interface. -func (pattern WrappedPattern) Bounds () (rectangle image.Rectangle) { - rectangle.Min = image.Point { -1e9, -1e9 } - rectangle.Max = image.Point { 1e9, 1e9 } - return -} - -// ColorModel satisfies the image.Image interface. -func (pattern WrappedPattern) ColorModel () (model color.Model) { - return color.RGBAModel -} From 7e51dc5e5a16b4dd9a2d4980c0c9a1e738862e64 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sat, 25 Feb 2023 23:04:51 -0500 Subject: [PATCH 09/21] Documented artist package --- artist/patterns/border.go | 28 ++++++++++++++++++++++++++++ artist/patterns/doc.go | 3 +++ 2 files changed, 31 insertions(+) create mode 100644 artist/patterns/doc.go diff --git a/artist/patterns/border.go b/artist/patterns/border.go index 0b23370..2723aa3 100644 --- a/artist/patterns/border.go +++ b/artist/patterns/border.go @@ -4,11 +4,39 @@ import "image" import "git.tebibyte.media/sashakoshka/tomo/canvas" 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 destination's 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 { canvas.Canvas artist.Inset } +// Draw draws the border pattern onto the destination canvas within the clipping +// bounds. func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) { bounds := clip.Canon().Intersect(destination.Bounds()) if bounds.Empty() { return } diff --git a/artist/patterns/doc.go b/artist/patterns/doc.go new file mode 100644 index 0000000..c36fb42 --- /dev/null +++ b/artist/patterns/doc.go @@ -0,0 +1,3 @@ +// Package patterns provides a basic set of types that satisfy the +// artist.Pattern interface. +package patterns From 2859dc33135859504a2eb300416006e366408770 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 26 Feb 2023 00:44:44 -0500 Subject: [PATCH 10/21] Cleaned out the old theme code and moved padding and margins to theme --- artist/patterns/uniform.go | 13 ++ config/config.go | 31 ----- theme/default.go | 206 +++------------------------- theme/defaultpatterns.go | 272 +------------------------------------ theme/theme.go | 67 ++------- theme/util.go | 16 --- theme/wrapped.go | 68 ++++++++++ 7 files changed, 112 insertions(+), 561 deletions(-) delete mode 100644 theme/util.go create mode 100644 theme/wrapped.go diff --git a/artist/patterns/uniform.go b/artist/patterns/uniform.go index 0aedbca..c31aad4 100644 --- a/artist/patterns/uniform.go +++ b/artist/patterns/uniform.go @@ -12,3 +12,16 @@ type Uniform color.RGBA func (pattern Uniform) Draw (destination canvas.Canvas, clip image.Rectangle) { shapes.FillColorRectangle(destination, color.RGBA(pattern), clip) } + +// Uhex creates a new Uniform pattern from an RGBA integer value. +func Uhex (color uint32) (uniform Uniform) { + return Uniform(hex(color)) +} + +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 +} diff --git a/config/config.go b/config/config.go index 2937e0c..a7e2a66 100644 --- a/config/config.go +++ b/config/config.go @@ -2,15 +2,6 @@ package config // Config can return global configuration parameters. type Config interface { - // Padding returns the amount of internal padding elements should have. - // An element's inner content (such as text) should be inset by this - // amount, in addition to the inset returned by the pattern of its - // background. - Padding () int - - // Margin returns how much space should be put in between elements. - Margin () int - // HandleWidth returns how large grab handles should typically be. This // is important for accessibility reasons. HandleWidth () int @@ -26,15 +17,6 @@ type Config interface { // Default specifies default configuration values. type Default struct { } -// Padding returns the default padding value. -func (Default) Padding () int { - return 7 -} - -// Margin returns the default margin value. -func (Default) Margin () int { - return 8 -} // HandleWidth returns the default handle width value. func (Default) HandleWidth () int { @@ -56,19 +38,6 @@ type Wrapped struct { Config } -// Padding returns the amount of internal padding elements should have. -// An element's inner content (such as text) should be inset by this -// amount, in addition to the inset returned by the pattern of its -// background. -func (wrapped Wrapped) Padding () int { - return wrapped.ensure().Padding() -} - -// Margin returns how much space should be put in between elements. -func (wrapped Wrapped) Margin () int { - return wrapped.ensure().Margin() -} - // HandleWidth returns how large grab handles should typically be. This // is important for accessibility reasons. func (wrapped Wrapped) HandleWidth () int { diff --git a/theme/default.go b/theme/default.go index e211037..4cdebcb 100644 --- a/theme/default.go +++ b/theme/default.go @@ -5,6 +5,7 @@ import "golang.org/x/image/font" import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/defaultfont" +import "git.tebibyte.media/sashakoshka/tomo/artist/patterns" // Default is the default theme. type Default struct { } @@ -38,204 +39,37 @@ func (Default) Pattern ( ) artist.Pattern { switch pattern { case PatternAccent: - return accentPattern + return patterns.Uhex(0xFF8800FF) case PatternBackground: - return backgroundPattern + return patterns.Uhex(0x000000FF) case PatternForeground: - if state.Disabled || c == C("basic", "spacer") { - return weakForegroundPattern - } else { - return foregroundPattern - } - case PatternDead: - return deadPattern - case PatternRaised: - if c == C("basic", "listEntry") { - if state.Focused { - if state.On { - return focusedOnListEntryPattern - } else { - return focusedListEntryPattern - } - } else { - if state.On { - return onListEntryPattern - } else { - return listEntryPattern - } - } - } else { - if state.Focused { - return selectedRaisedPattern - } else { - return raisedPattern - } - } - case PatternSunken: - if c == C("basic", "list") { - if state.Focused { - return focusedListPattern - } else { - return listPattern - } - } else if c == C("basic", "textBox") { - if state.Disabled { - return disabledInputPattern - } else { - if state.Focused { - return selectedInputPattern - } else { - return inputPattern - } - } - } else { - if state.Focused { - return focusedSunkenPattern - } else { - return sunkenPattern - } - } - case PatternPinboard: - if state.Focused { - return focusedTexturedSunkenPattern - } else { - return texturedSunkenPattern - } - case PatternButton: - if state.Disabled { - return disabledButtonPattern - } else { - if c == C("fun", "sharpKey") { - if state.Pressed { - return pressedDarkButtonPattern - } else { - return darkButtonPattern - } - } else if c == C("fun", "flatKey") { - if state.Pressed { - return pressedButtonPattern - } else { - return buttonPattern - } - } else { - if state.Pressed || state.On && c == C("basic", "checkbox") { - if state.Focused { - return pressedSelectedButtonPattern - } else { - return pressedButtonPattern - } - } else { - if state.Focused { - return selectedButtonPattern - } else { - return buttonPattern - } - } - } - } - case PatternInput: - if state.Disabled { - return disabledInputPattern - } else { - if state.Focused { - return selectedInputPattern - } else { - return inputPattern - } - } - case PatternGutter: - if c == C("basic", "sliderVertical") || c == C("basic", "sliderHorizontal") { - if state.Disabled { - return disabledThinScrollGutterPattern - } else { - return thinScrollGutterPattern - } - } else { - if state.Disabled { - return disabledScrollGutterPattern - } else { - return scrollGutterPattern - } - } - case PatternHandle: - if state.Disabled { - return disabledScrollBarPattern - } else { - if state.Focused { - if state.Pressed { - return pressedSelectedScrollBarPattern - } else { - return selectedScrollBarPattern - } - } else { - if state.Pressed { - return pressedScrollBarPattern - } else { - return scrollBarPattern - } - } - } - default: - return uhex(0) + return patterns.Uhex(0xFFFFFFFF) + // case PatternDead: + // case PatternRaised: + // case PatternSunken: + // case PatternPinboard: + // case PatternButton: + // case PatternInput: + // case PatternGutter: + // case PatternHandle: + default: return patterns.Uhex(0x888888FF) } } -// Inset returns the default inset value for the given pattern. -func (Default) Inset (pattern Pattern, c Case) Inset { - switch pattern { - case PatternRaised: - if c == C("basic", "listEntry") { - return Inset { 4, 6, 4, 6 } - } else { - return Inset { 2, 2, 2, 2 } - } - - case PatternSunken: - if c == C("basic", "list") { - return Inset { 2, 1, 2, 1 } - } else if c == C("basic", "progressBar") { - return Inset { 2, 1, 1, 2 } - } else { - return Inset { 2, 2, 2, 2 } - } +// Padding returns the default padding value for the given pattern. +func (Default) Padding (pattern Pattern, c Case) artist.Inset { + return artist.Inset { 4, 4, 4, 4} +} - case PatternPinboard: - return Inset { 2, 2, 2, 2 } - - case PatternInput, PatternButton, PatternHandle: - return Inset { 2, 2, 2, 2} - - default: return Inset { } - } +// Margin returns the default margin value for the given pattern. +func (Default) Margin (id Pattern, c Case) image.Point { + return image.Pt(4, 4) } // Hints returns rendering optimization hints for a particular pattern. // These are optional, but following them may result in improved // performance. func (Default) Hints (pattern Pattern, c Case) (hints Hints) { - switch pattern { - case PatternRaised: - if c == C("basic", "listEntry") { - hints.StaticInset = Inset { 0, 1, 0, 1 } - } else { - hints.StaticInset = Inset { 3, 3, 3, 3 } - } - - case PatternSunken: - if c == C("basic", "list") { - hints.StaticInset = Inset { 2, 1, 2, 1 } - } else { - hints.StaticInset = Inset { 3, 3, 3, 3 } - } - - case - PatternPinboard, - PatternInput, - PatternButton, - PatternHandle: - - hints.StaticInset = Inset { 3, 3, 3, 3 } - } return } diff --git a/theme/defaultpatterns.go b/theme/defaultpatterns.go index 82afd47..2ec48b5 100644 --- a/theme/defaultpatterns.go +++ b/theme/defaultpatterns.go @@ -1,272 +1,4 @@ package theme -import "image/color" -import "git.tebibyte.media/sashakoshka/tomo/artist" - -// var backgroundPattern = artist.Gradient { - // First: uhex(0xFF0000FF), - // Second: uhex(0x00FF00FF), -// } -var accentPattern = artist.NewUniform(hex(0x408090FF)) -var backgroundPattern = artist.NewUniform(color.Gray16 { 0xAAAA }) -var foregroundPattern = artist.NewUniform(color.Gray16 { 0x0000 }) -var weakForegroundPattern = artist.NewUniform(color.Gray16 { 0x4444 }) -var strokePattern = artist.NewUniform(color.Gray16 { 0x0000 }) - -var sunkenPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0x3b534eFF)), - artist.NewUniform(hex(0x97a09cFF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) }) -var focusedSunkenPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { Weight: 1, Pattern: accentPattern }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) }) - -var texturedSunkenPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0x3b534eFF)), - artist.NewUniform(hex(0x97a09cFF)), - }, - }, - - artist.Stroke { Pattern: artist.Noisy { - Low: artist.NewUniform(hex(0x97a09cFF)), - High: artist.NewUniform(hex(0x6e8079FF)), - }}) -var focusedTexturedSunkenPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { Weight: 1, Pattern: accentPattern }, - artist.Stroke { Pattern: artist.Noisy { - Low: artist.NewUniform(hex(0x97a09cFF)), - High: artist.NewUniform(hex(0x6e8079FF)), - }}) - -var raisedPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xDBDBDBFF)), - artist.NewUniform(hex(0x383C3AFF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0xAAAAAAFF)) }) - -var selectedRaisedPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xDBDBDBFF)), - artist.NewUniform(hex(0x383C3AFF)), - }, - }, - artist.Stroke { Weight: 1, Pattern: accentPattern }, - artist.Stroke { Pattern: artist.NewUniform(hex(0xAAAAAAFF)) }) - -var deadPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) }) - -var buttonPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xCCD5D2FF)), - artist.NewUniform(hex(0x4B5B59FF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) -var selectedButtonPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xCCD5D2FF)), - artist.NewUniform(hex(0x4B5B59FF)), - }, - }, - artist.Stroke { Weight: 1, Pattern: accentPattern }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) -var pressedButtonPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0x4B5B59FF)), - artist.NewUniform(hex(0x8D9894FF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) -var pressedSelectedButtonPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0x4B5B59FF)), - artist.NewUniform(hex(0x8D9894FF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) -var disabledButtonPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: weakForegroundPattern }, - artist.Stroke { Pattern: backgroundPattern }) - -var darkButtonPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xaebdb9FF)), - artist.NewUniform(hex(0x3b4947FF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x6b7a75FF)) }) -var pressedDarkButtonPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0x3b4947FF)), - artist.NewUniform(hex(0x6b7a75FF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x6b7a75FF)) }) - -var inputPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0x89925AFF)), - artist.NewUniform(hex(0xD2CB9AFF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0xD2CB9AFF)) }) -var selectedInputPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { Weight: 1, Pattern: accentPattern }, - artist.Stroke { Pattern: artist.NewUniform(hex(0xD2CB9AFF)) }) -var disabledInputPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: weakForegroundPattern }, - artist.Stroke { Pattern: backgroundPattern }) - -var listPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - uhex(0x383C3AFF), - uhex(0x999C99FF), - }, - }, - artist.Stroke { Pattern: uhex(0x999C99FF) }) - -var focusedListPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { Weight: 1, Pattern: accentPattern }, - artist.Stroke { Pattern: uhex(0x999C99FF) }) - -var listEntryPattern = artist.Padded { - Stroke: uhex(0x383C3AFF), - Fill: uhex(0x999C99FF), - Sides: []int { 0, 0, 0, 1 }, -} - -var onListEntryPattern = artist.Padded { - Stroke: uhex(0x383C3AFF), - Fill: uhex(0x6e8079FF), - Sides: []int { 0, 0, 0, 1 }, -} - -var focusedListEntryPattern = artist.Padded { - Stroke: accentPattern, - Fill: uhex(0x999C99FF), - Sides: []int { 0, 1, 0, 1 }, -} - -var focusedOnListEntryPattern = artist.Padded { - Stroke: accentPattern, - Fill: uhex(0x6e8079FF), - Sides: []int { 0, 1, 0, 1 }, -} - -var scrollGutterPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0x3b534eFF)), - artist.NewUniform(hex(0x6e8079FF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x6e8079FF)) }) -var thinScrollGutterPattern = artist.Padded { - Fill: scrollGutterPattern, - Stroke: sunkenPattern, - Sides: []int{ 6, 6, 6, 6 }, -} -var disabledScrollGutterPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: weakForegroundPattern }, - artist.Stroke { Pattern: backgroundPattern }) -var disabledThinScrollGutterPattern = artist.Padded { - Fill: disabledScrollGutterPattern, - Stroke: disabledButtonPattern, - Sides: []int{ 6, 6, 6, 6}, -} -var scrollBarPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xCCD5D2FF)), - artist.NewUniform(hex(0x4B5B59FF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) -var selectedScrollBarPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xCCD5D2FF)), - artist.NewUniform(hex(0x4B5B59FF)), - }, - }, - artist.Stroke { Weight: 1, Pattern: accentPattern }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) -var pressedScrollBarPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xCCD5D2FF)), - artist.NewUniform(hex(0x4B5B59FF)), - }, - }, - artist.Stroke { Weight: 1, Pattern: artist.NewUniform(hex(0x8D9894FF)) }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x7f8c89FF)) }) -var pressedSelectedScrollBarPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xCCD5D2FF)), - artist.NewUniform(hex(0x4B5B59FF)), - }, - }, - artist.Stroke { Weight: 1, Pattern: accentPattern }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x7f8c89FF)) }) -var disabledScrollBarPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: weakForegroundPattern }, - artist.Stroke { Pattern: backgroundPattern }) +// import "image/color" +// import "git.tebibyte.media/sashakoshka/tomo/artist" diff --git a/theme/theme.go b/theme/theme.go index e88e028..f506035 100644 --- a/theme/theme.go +++ b/theme/theme.go @@ -63,7 +63,7 @@ type Hints struct { // StaticInset defines an inset rectangular area in the middle of the // pattern that does not change between PatternStates. If the inset is // zero on all sides, this hint does not apply. - StaticInset Inset + StaticInset artist.Inset // Uniform specifies a singular color for the entire pattern. If the // alpha channel is zero, this hint does not apply. @@ -82,9 +82,14 @@ type Theme interface { // and state. Pattern (Pattern, PatternState, Case) artist.Pattern - // Inset returns the area on all sides of a given pattern that is not - // meant to be drawn on. - Inset (Pattern, Case) Inset + // Padding returns how much space should be between the bounds of a + // pattern whatever an element draws inside of it. + Padding (Pattern, Case) artist.Inset + + // Margin returns the left/right (x) and top/bottom (y) margins that + // should be put between any self-contained objects drawn within this + // pattern (if applicable). + Margin (Pattern, Case) image.Point // Sink returns a vector that should be added to an element's inner // content when it is pressed down (if applicable) to simulate a 3D @@ -96,57 +101,3 @@ type Theme interface { // performance. Hints (Pattern, Case) Hints } - -// Wrapped wraps any theme and injects a case into it automatically so that it -// doesn't need to be specified for each query. Additionally, if the underlying -// theme is nil, it just uses the default theme instead. -type Wrapped struct { - Theme - Case -} - -// FontFace returns the proper font for a given style and size. -func (wrapped Wrapped) FontFace (style FontStyle, size FontSize) font.Face { - real := wrapped.ensure() - return real.FontFace(style, size, wrapped.Case) -} - -// Icon returns an appropriate icon given an icon name. -func (wrapped Wrapped) Icon (name string, size IconSize) canvas.Image { - real := wrapped.ensure() - return real.Icon(name, size, wrapped.Case) -} - -// Pattern returns an appropriate pattern given a pattern name and state. -func (wrapped Wrapped) Pattern (id Pattern, state PatternState) artist.Pattern { - real := wrapped.ensure() - return real.Pattern(id, state, wrapped.Case) -} - -// Inset returns the area on all sides of a given pattern that is not meant to -// be drawn on. -func (wrapped Wrapped) Inset (id Pattern) Inset { - real := wrapped.ensure() - return real.Inset(id, wrapped.Case) -} - -// Sink returns a vector that should be added to an element's inner content when -// it is pressed down (if applicable) to simulate a 3D sinking effect. -func (wrapped Wrapped) Sink (id Pattern) image.Point { - real := wrapped.ensure() - return real.Sink(id, wrapped.Case) -} - -// Hints returns rendering optimization hints for a particular pattern. -// These are optional, but following them may result in improved -// performance. -func (wrapped Wrapped) Hints (id Pattern) Hints { - real := wrapped.ensure() - return real.Hints(id, wrapped.Case) -} - -func (wrapped Wrapped) ensure () (real Theme) { - real = wrapped.Theme - if real == nil { real = Default { } } - return -} diff --git a/theme/util.go b/theme/util.go deleted file mode 100644 index 2e9723e..0000000 --- a/theme/util.go +++ /dev/null @@ -1,16 +0,0 @@ -package theme - -import "image/color" -import "git.tebibyte.media/sashakoshka/tomo/artist" - -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 -} - -func uhex (color uint32) (pattern artist.Pattern) { - return artist.NewUniform(hex(color)) -} diff --git a/theme/wrapped.go b/theme/wrapped.go new file mode 100644 index 0000000..947481b --- /dev/null +++ b/theme/wrapped.go @@ -0,0 +1,68 @@ +package theme + +import "image" +import "golang.org/x/image/font" +import "git.tebibyte.media/sashakoshka/tomo/artist" +import "git.tebibyte.media/sashakoshka/tomo/canvas" + +// Wrapped wraps any theme and injects a case into it automatically so that it +// doesn't need to be specified for each query. Additionally, if the underlying +// theme is nil, it just uses the default theme instead. +type Wrapped struct { + Theme + Case +} + +// FontFace returns the proper font for a given style and size. +func (wrapped Wrapped) FontFace (style FontStyle, size FontSize) font.Face { + real := wrapped.ensure() + return real.FontFace(style, size, wrapped.Case) +} + +// Icon returns an appropriate icon given an icon name. +func (wrapped Wrapped) Icon (name string, size IconSize) canvas.Image { + real := wrapped.ensure() + return real.Icon(name, size, wrapped.Case) +} + +// Pattern returns an appropriate pattern given a pattern name and state. +func (wrapped Wrapped) Pattern (id Pattern, state PatternState) artist.Pattern { + real := wrapped.ensure() + return real.Pattern(id, state, wrapped.Case) +} + +// Padding returns how much space should be between the bounds of a +// pattern whatever an element draws inside of it. +func (wrapped Wrapped) Padding (id Pattern) artist.Inset { + real := wrapped.ensure() + return real.Padding(id, wrapped.Case) +} + +// Margin returns the left/right (x) and top/bottom (y) margins that +// should be put between any self-contained objects drawn within this +// pattern (if applicable). +func (wrapped Wrapped) Margin (id Pattern) image.Point { + real := wrapped.ensure() + return real.Margin(id, wrapped.Case) +} + +// Sink returns a vector that should be added to an element's inner content when +// it is pressed down (if applicable) to simulate a 3D sinking effect. +func (wrapped Wrapped) Sink (id Pattern) image.Point { + real := wrapped.ensure() + return real.Sink(id, wrapped.Case) +} + +// Hints returns rendering optimization hints for a particular pattern. +// These are optional, but following them may result in improved +// performance. +func (wrapped Wrapped) Hints (id Pattern) Hints { + real := wrapped.ensure() + return real.Hints(id, wrapped.Case) +} + +func (wrapped Wrapped) ensure () (real Theme) { + real = wrapped.Theme + if real == nil { real = Default { } } + return +} From 241c297626edd992f051b03e1c60bd1fb3001184 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 26 Feb 2023 14:27:38 -0500 Subject: [PATCH 11/21] whee back in busineess --- artist/color.go | 12 ++ artist/patterns/uniform.go | 11 +- artist/shapes/ellipse.go | 40 +++-- artist/shapes/plot.go | 15 +- artist/shapes/rectangle.go | 43 +++-- elements/testing/artist.go | 334 ++++++++----------------------------- elements/testing/mouse.go | 30 ++-- examples/artist/main.go | 4 +- textdraw/drawer.go | 12 +- 9 files changed, 151 insertions(+), 350 deletions(-) create mode 100644 artist/color.go diff --git a/artist/color.go b/artist/color.go new file mode 100644 index 0000000..1729447 --- /dev/null +++ b/artist/color.go @@ -0,0 +1,12 @@ +package artist + +import "image/color" + +// 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 +} diff --git a/artist/patterns/uniform.go b/artist/patterns/uniform.go index c31aad4..8efb8be 100644 --- a/artist/patterns/uniform.go +++ b/artist/patterns/uniform.go @@ -3,6 +3,7 @@ package patterns import "image" import "image/color" import "git.tebibyte.media/sashakoshka/tomo/canvas" +import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" // Uniform is a pattern that draws a solid color. @@ -15,13 +16,5 @@ func (pattern Uniform) Draw (destination canvas.Canvas, clip image.Rectangle) { // Uhex creates a new Uniform pattern from an RGBA integer value. func Uhex (color uint32) (uniform Uniform) { - return Uniform(hex(color)) -} - -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 + return Uniform(artist.Hex(color)) } diff --git a/artist/shapes/ellipse.go b/artist/shapes/ellipse.go index 3acc2e2..0e5f0c5 100644 --- a/artist/shapes/ellipse.go +++ b/artist/shapes/ellipse.go @@ -5,23 +5,23 @@ import "image" import "image/color" import "git.tebibyte.media/sashakoshka/tomo/canvas" -// FillEllipse draws the content of one canvas onto another, clipped by an -// ellipse stretched to the bounds of the source canvas. The offset point -// defines where the origin point of the source canvas is positioned in relation -// to the origin point of the destination canvas. To prevent the entire source -// canvas's bounds from being used, it must be cut with canvas.Cut(). +// 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 canvas.Canvas, source canvas.Canvas, - offset image.Point, ) ( updatedRegion image.Rectangle, ) { dstData, dstStride := destination.Buffer() srcData, srcStride := source.Buffer() - bounds := source.Bounds().Intersect(destination.Bounds()).Canon() - realBounds := source.Bounds() + offset := source.Bounds().Min.Sub(destination.Bounds().Min) + bounds := source.Bounds().Sub(offset).Intersect(destination.Bounds()) + realBounds := destination.Bounds() if bounds.Empty() { return } updatedRegion = bounds @@ -30,22 +30,17 @@ func FillEllipse ( for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ { if inEllipse(point, realBounds) { offsetPoint := point.Add(offset) - dstIndex := offsetPoint.X + (offsetPoint.Y) * dstStride - srcIndex := point.X + point.Y * srcStride + dstIndex := point.X + point.Y * dstStride + srcIndex := offsetPoint.X + offsetPoint.Y * srcStride dstData[dstIndex] = srcData[srcIndex] } }} return } -// StrokeEllipse is similar to FillEllipse, but it draws an elliptical inset -// outline of the source canvas onto the destination canvas. To prevent the -// entire source canvas's bounds from being used, it must be cut with -// canvas.Cut(). func StrokeEllipse ( destination canvas.Canvas, source canvas.Canvas, - offset image.Point, weight int, ) { if weight < 1 { return } @@ -53,7 +48,10 @@ func StrokeEllipse ( dstData, dstStride := destination.Buffer() srcData, srcStride := source.Buffer() - bounds := source.Bounds().Inset(weight - 1) + bounds := destination.Bounds().Inset(weight - 1) + offset := source.Bounds().Min.Sub(destination.Bounds().Min) + realBounds := destination.Bounds() + if bounds.Empty() { return } context := ellipsePlottingContext { plottingContext: plottingContext { @@ -63,9 +61,9 @@ func StrokeEllipse ( srcStride: srcStride, weight: weight, offset: offset, - bounds: bounds.Intersect(destination.Bounds()), + bounds: realBounds, }, - radii: image.Pt(bounds.Dx() / 2 - 1, bounds.Dy() / 2 - 1), + radii: image.Pt(bounds.Dx() / 2, bounds.Dy() / 2), } context.center = bounds.Min.Add(context.radii) context.plotEllipse() @@ -205,7 +203,7 @@ func StrokeColorEllipse ( if weight < 1 { return } dstData, dstStride := destination.Buffer() - bounds = bounds.Inset(weight - 1) + insetBounds := bounds.Inset(weight - 1) context := ellipsePlottingContext { plottingContext: plottingContext { @@ -215,9 +213,9 @@ func StrokeColorEllipse ( weight: weight, bounds: bounds.Intersect(destination.Bounds()), }, - radii: image.Pt(bounds.Dx() / 2 - 1, bounds.Dy() / 2 - 1), + radii: image.Pt(insetBounds.Dx() / 2, insetBounds.Dy() / 2), } - context.center = bounds.Min.Add(context.radii) + context.center = insetBounds.Min.Add(context.radii) context.plotEllipse() return } diff --git a/artist/shapes/plot.go b/artist/shapes/plot.go index b629bd9..6749768 100644 --- a/artist/shapes/plot.go +++ b/artist/shapes/plot.go @@ -16,32 +16,31 @@ type plottingContext struct { bounds image.Rectangle } -func (context plottingContext) square (center image.Point) 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). - Add(context.offset). Intersect(context.bounds) } func (context plottingContext) plotColor (center image.Point) { square := context.square(center) - for y := square.Min.Y; y < square.Min.Y; y ++ { - for x := square.Min.X; x < square.Min.X; x ++ { + 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.Min.Y; y ++ { - for x := square.Min.X; x < square.Min.X; x ++ { + 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 + x + context.offset.X + + (y + context.offset.Y) * context.dstStride dstIndex := x + y * context.dstStride context.dstData[dstIndex] = context.srcData [srcIndex] }} diff --git a/artist/shapes/rectangle.go b/artist/shapes/rectangle.go index 9e6f86f..8912ade 100644 --- a/artist/shapes/rectangle.go +++ b/artist/shapes/rectangle.go @@ -7,51 +7,44 @@ import "git.tebibyte.media/sashakoshka/tomo/shatter" // TODO: return updatedRegion for all routines in this package -// FillRectangle draws the content of one canvas onto another. The offset point -// defines where the origin point of the source canvas is positioned in relation -// to the origin point of the destination canvas. To prevent the entire source -// canvas from being drawn, it must be cut with canvas.Cut(). func FillRectangle ( destination canvas.Canvas, source canvas.Canvas, - offset image.Point, ) ( updatedRegion image.Rectangle, ) { dstData, dstStride := destination.Buffer() srcData, srcStride := source.Buffer() - sourceBounds := - source.Bounds().Canon(). - Intersect(destination.Bounds().Sub(offset)) - if sourceBounds.Empty() { return } + offset := source.Bounds().Min.Sub(destination.Bounds().Min) + bounds := source.Bounds().Sub(offset).Intersect(destination.Bounds()) + if bounds.Empty() { return } + updatedRegion = bounds - updatedRegion = sourceBounds.Add(offset) - for y := sourceBounds.Min.Y; y < sourceBounds.Max.Y; y ++ { - for x := sourceBounds.Min.X; x < sourceBounds.Max.X; x ++ { - dstData[x + offset.X + (y + offset.Y) * dstStride] = - srcData[x + y * srcStride] + 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 ++ { + offsetPoint := point.Add(offset) + dstIndex := point.X + point.Y * dstStride + srcIndex := offsetPoint.X + offsetPoint.Y * srcStride + dstData[dstIndex] = srcData[srcIndex] }} return } -// StrokeRectangle is similar to FillRectangle, but it draws an inset outline of -// the source canvas onto the destination canvas. To prevent the entire source -// canvas's bounds from being used, it must be cut with canvas.Cut(). func StrokeRectangle ( destination canvas.Canvas, source canvas.Canvas, - offset image.Point, weight int, ) { - bounds := source.Bounds() + bounds := destination.Bounds() insetBounds := bounds.Inset(weight) if insetBounds.Empty() { - FillRectangle(destination, source, offset) + FillRectangle(destination, source) return } - FillRectangleShatter(destination, source, offset, insetBounds) + FillRectangleShatter(destination, source, insetBounds) } // FillRectangleShatter is like FillRectangle, but it does not draw in areas @@ -59,12 +52,14 @@ func StrokeRectangle ( func FillRectangleShatter ( destination canvas.Canvas, source canvas.Canvas, - offset image.Point, rocks ...image.Rectangle, ) { - tiles := shatter.Shatter(source.Bounds().Sub(offset), rocks...) + tiles := shatter.Shatter(destination.Bounds(), rocks...) + offset := source.Bounds().Min.Sub(destination.Bounds().Min) for _, tile := range tiles { - FillRectangle(destination, canvas.Cut(source, tile), offset) + FillRectangle ( + canvas.Cut(destination, tile), + canvas.Cut(source, tile.Add(offset))) } } diff --git a/elements/testing/artist.go b/elements/testing/artist.go index 7db6f3e..d3b5475 100644 --- a/elements/testing/artist.go +++ b/elements/testing/artist.go @@ -4,11 +4,14 @@ import "fmt" import "time" import "image" import "image/color" +import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/shatter" import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "git.tebibyte.media/sashakoshka/tomo/defaultfont" import "git.tebibyte.media/sashakoshka/tomo/elements/core" +import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" +import "git.tebibyte.media/sashakoshka/tomo/artist/patterns" // Artist is an element that displays shapes and patterns drawn by the artist // package in order to test it. @@ -21,103 +24,52 @@ type Artist struct { func NewArtist () (element *Artist) { element = &Artist { } element.Core, element.core = core.NewCore(element.draw) - element.core.SetMinimumSize(240, 360) + element.core.SetMinimumSize(240, 240) return } func (element *Artist) draw () { bounds := element.Bounds() - artist.FillRectangle(element.core, artist.NewUniform(hex(0)), bounds) + patterns.Uhex(0x000000FF).Draw(element.core, bounds) drawStart := time.Now() - // 0, 0 - artist.FillRectangle ( - element.core, - artist.Beveled { - artist.NewUniform(hex(0xFF0000FF)), - artist.NewUniform(hex(0x0000FFFF)), - }, - element.cellAt(0, 0)) - - // 1, 0 - artist.StrokeRectangle ( - element.core, - artist.NewUniform(hex(0x00FF00FF)), 3, - element.cellAt(1, 0)) - - // 2, 0 - artist.FillRectangle ( - element.core, - artist.NewMultiBordered ( - artist.Stroke { Pattern: uhex(0xFF0000FF), Weight: 1 }, - artist.Stroke { Pattern: uhex(0x888800FF), Weight: 2 }, - artist.Stroke { Pattern: uhex(0x00FF00FF), Weight: 3 }, - artist.Stroke { Pattern: uhex(0x008888FF), Weight: 4 }, - artist.Stroke { Pattern: uhex(0x0000FFFF), Weight: 5 }, - ), - element.cellAt(2, 0)) - - // 3, 0 - artist.FillRectangle ( - element.core, - artist.Bordered { - Stroke: artist.Stroke { Pattern: uhex(0x0000FFFF), Weight: 5 }, - Fill: uhex(0xFF0000FF), - }, - element.cellAt(3, 0)) + // 0, 0 - 3, 0 + for x := 0; x < 4; x ++ { + element.colorLines(x + 1, element.cellAt(x, 0).Bounds()) + } // 4, 0 - artist.FillRectangle ( - element.core, - artist.Padded { - Stroke: uhex(0xFFFFFFFF), - Fill: uhex(0x666666FF), - Sides: []int { 4, 13, 2, 0 }, - }, - element.cellAt(4, 0)) + c40 := element.cellAt(4, 0) + shapes.StrokeColorRectangle(c40, artist.Hex(0x888888FF), c40.Bounds(), 1) + shapes.ColorLine ( + c40, artist.Hex(0xFF0000FF), 1, + c40.Bounds().Min, c40.Bounds().Max) - // 0, 1 - 3, 1 - for x := 0; x < 4; x ++ { - artist.FillRectangle ( - element.core, - artist.Striped { - First: artist.Stroke { Pattern: uhex(0xFF8800FF), Weight: 7 }, - Second: artist.Stroke { Pattern: uhex(0x0088FFFF), Weight: 2 }, - Orientation: artist.Orientation(x), - - }, - element.cellAt(x, 1)) - } + // 0, 1 + c01 := element.cellAt(0, 1) + shapes.StrokeColorRectangle(c01, artist.Hex(0x888888FF), c01.Bounds(), 1) + shapes.FillColorEllipse(element.core, artist.Hex(0x00FF00FF), c01.Bounds()) - // 0, 2 - 3, 2 - for x := 0; x < 4; x ++ { - element.lines(x + 1, element.cellAt(x, 2)) - } - - // 0, 3 - artist.StrokeRectangle ( - element.core, uhex(0x888888FF), 1, - element.cellAt(0, 3)) - artist.FillEllipse(element.core, uhex(0x00FF00FF), element.cellAt(0, 3)) - - // 1, 3 - 3, 3 + // 1, 1 - 3, 1 for x := 1; x < 4; x ++ { - artist.StrokeRectangle ( - element.core,uhex(0x888888FF), 1, - element.cellAt(x, 3)) - artist.StrokeEllipse ( + c := element.cellAt(x, 1) + shapes.StrokeColorRectangle ( + element.core, artist.Hex(0x888888FF), + c.Bounds(), 1) + shapes.StrokeColorEllipse ( element.core, - []artist.Pattern { - uhex(0xFF0000FF), - uhex(0x00FF00FF), - uhex(0xFF00FFFF), + []color.RGBA { + artist.Hex(0xFF0000FF), + artist.Hex(0x00FF00FF), + artist.Hex(0xFF00FFFF), } [x - 1], - x, element.cellAt(x, 3)) + c.Bounds(), x) } - // 4, 3 - shatterPos := element.cellAt(4, 3).Min + // 4, 1 + c41 := element.cellAt(4, 1) + shatterPos := c41.Bounds().Min rocks := []image.Rectangle { image.Rect(3, 12, 13, 23).Add(shatterPos), // image.Rect(30, 10, 40, 23).Add(shatterPos), @@ -125,159 +77,36 @@ func (element *Artist) draw () { image.Rect(30, -10, 40, 43).Add(shatterPos), image.Rect(80, 30, 90, 45).Add(shatterPos), } - tiles := shatter.Shatter(element.cellAt(4, 3), rocks...) - for _, tile := range tiles { - artist.FillRectangle ( - element.core, - artist.Bordered { - Fill: uhex(0x888888FF), - Stroke: artist.Stroke { - Pattern: artist.Beveled { - uhex(0xCCCCCCFF), - uhex(0x444444FF), - }, - Weight: 1, - }, - }, - tile) + tiles := shatter.Shatter(c41.Bounds(), rocks...) + for index, tile := range tiles { + artist.DrawBounds ( + element.core, tile, + []artist.Pattern { + patterns.Uhex(0xFF0000FF), + patterns.Uhex(0x00FF00FF), + patterns.Uhex(0xFF00FFFF), + patterns.Uhex(0xFFFF00FF), + patterns.Uhex(0x00FFFFFF), + } [index % 5], tile) } - // 0, 4 - 3, 4 - for x := 0; x < 4; x ++ { - artist.FillEllipse ( - element.core, - artist.Split { - First: uhex(0xFF0000FF), - Second: uhex(0x0000FFFF), - Orientation: artist.Orientation(x), - }, - element.cellAt(x, 4)) - } + // 0, 2 + c02 := element.cellAt(0, 2) + shapes.StrokeColorRectangle(c02, artist.Hex(0x888888FF), c02.Bounds(), 1) + shapes.FillEllipse(c02, c41) + + // 1, 2 + c12 := element.cellAt(1, 2) + shapes.StrokeColorRectangle(c12, artist.Hex(0x888888FF), c12.Bounds(), 1) + shapes.StrokeEllipse(c12, c41, 5) - // 0, 5 - artist.FillRectangle ( - element.core, - artist.QuadBeveled { - uhex(0x880000FF), - uhex(0x00FF00FF), - uhex(0x0000FFFF), - uhex(0xFF00FFFF), - }, - element.cellAt(0, 5)) - - // 1, 5 - artist.FillRectangle ( - element.core, - artist.Checkered { - First: artist.QuadBeveled { - uhex(0x880000FF), - uhex(0x00FF00FF), - uhex(0x0000FFFF), - uhex(0xFF00FFFF), - }, - Second: artist.Striped { - First: artist.Stroke { Pattern: uhex(0xFF8800FF), Weight: 1 }, - Second: artist.Stroke { Pattern: uhex(0x0088FFFF), Weight: 1 }, - Orientation: artist.OrientationVertical, - }, - CellWidth: 32, - CellHeight: 16, - }, - element.cellAt(1, 5)) - - // 2, 5 - artist.FillRectangle ( - element.core, - artist.Dotted { - Foreground: uhex(0x00FF00FF), - Background: artist.Checkered { - First: uhex(0x444444FF), - Second: uhex(0x888888FF), - CellWidth: 16, - CellHeight: 16, - }, - Size: 8, - Spacing: 16, - }, - element.cellAt(2, 5)) - - // 3, 5 - artist.FillRectangle ( - element.core, - artist.Tiled { - Pattern: artist.QuadBeveled { - uhex(0x880000FF), - uhex(0x00FF00FF), - uhex(0x0000FFFF), - uhex(0xFF00FFFF), - }, - CellWidth: 17, - CellHeight: 23, - }, - element.cellAt(3, 5)) - - // 0, 6 - 3, 6 - for x := 0; x < 4; x ++ { - artist.FillRectangle ( - element.core, - artist.Gradient { - First: uhex(0xFF0000FF), - Second: uhex(0x0000FFFF), - Orientation: artist.Orientation(x), - }, - element.cellAt(x, 6)) - } + // 2, 2 + c22 := element.cellAt(2, 2) + shapes.FillRectangle(c22, c41) - // 0, 7 - artist.FillEllipse ( - element.core, - artist.EllipticallyBordered { - Fill: artist.Gradient { - First: uhex(0x00FF00FF), - Second: uhex(0x0000FFFF), - Orientation: artist.OrientationVertical, - }, - Stroke: artist.Stroke { Pattern: uhex(0x00FF00), Weight: 5 }, - }, - element.cellAt(0, 7)) - - // 1, 7 - artist.FillRectangle ( - element.core, - artist.Noisy { - Low: uhex(0x000000FF), - High: uhex(0xFFFFFFFF), - Seed: 0, - }, - element.cellAt(1, 7), - ) - - // 2, 7 - artist.FillRectangle ( - element.core, - artist.Noisy { - Low: uhex(0x000000FF), - High: artist.Gradient { - First: uhex(0x000000FF), - Second: uhex(0xFFFFFFFF), - Orientation: artist.OrientationVertical, - }, - Seed: 0, - }, - element.cellAt(2, 7), - ) - - // 3, 7 - artist.FillRectangle ( - element.core, - artist.Noisy { - Low: uhex(0x000000FF), - High: uhex(0xFFFFFFFF), - Seed: 0, - Harsh: true, - }, - element.cellAt(3, 7), - ) + // 3, 2 + c32 := element.cellAt(3, 2) + shapes.StrokeRectangle(c32, c41, 5) // how long did that take to render? drawTime := time.Since(drawStart) @@ -288,68 +117,51 @@ func (element *Artist) draw () { drawTime.Milliseconds(), drawTime.Microseconds()))) textDrawer.Draw ( - element.core, uhex(0xFFFFFFFF), + element.core, artist.Hex(0xFFFFFFFF), image.Pt(bounds.Min.X + 8, bounds.Max.Y - 24)) } -func (element *Artist) lines (weight int, bounds image.Rectangle) { +func (element *Artist) colorLines (weight int, bounds image.Rectangle) { bounds = bounds.Inset(4) - c := uhex(0xFFFFFFFF) - artist.Line(element.core, c, weight, bounds.Min, bounds.Max) - artist.Line ( + c := artist.Hex(0xFFFFFFFF) + shapes.ColorLine(element.core, c, weight, bounds.Min, bounds.Max) + shapes.ColorLine ( element.core, c, weight, image.Pt(bounds.Max.X, bounds.Min.Y), image.Pt(bounds.Min.X, bounds.Max.Y)) - artist.Line ( + shapes.ColorLine ( element.core, c, weight, image.Pt(bounds.Max.X, bounds.Min.Y + 16), image.Pt(bounds.Min.X, bounds.Max.Y - 16)) - artist.Line ( + shapes.ColorLine ( element.core, c, weight, image.Pt(bounds.Min.X, bounds.Min.Y + 16), image.Pt(bounds.Max.X, bounds.Max.Y - 16)) - artist.Line ( + shapes.ColorLine ( element.core, c, weight, image.Pt(bounds.Min.X + 20, bounds.Min.Y), image.Pt(bounds.Max.X - 20, bounds.Max.Y)) - artist.Line ( + shapes.ColorLine ( element.core, c, weight, image.Pt(bounds.Max.X - 20, bounds.Min.Y), image.Pt(bounds.Min.X + 20, bounds.Max.Y)) - artist.Line ( + shapes.ColorLine ( element.core, c, weight, image.Pt(bounds.Min.X, bounds.Min.Y + bounds.Dy() / 2), image.Pt(bounds.Max.X, bounds.Min.Y + bounds.Dy() / 2)) - artist.Line ( + shapes.ColorLine ( element.core, c, weight, image.Pt(bounds.Min.X + bounds.Dx() / 2, bounds.Min.Y), image.Pt(bounds.Min.X + bounds.Dx() / 2, bounds.Max.Y)) } -func (element *Artist) cellAt (x, y int) (image.Rectangle) { +func (element *Artist) cellAt (x, y int) (canvas.Canvas) { bounds := element.Bounds() cellBounds := image.Rectangle { } cellBounds.Min = bounds.Min cellBounds.Max.X = bounds.Min.X + bounds.Dx() / 5 - cellBounds.Max.Y = bounds.Min.Y + (bounds.Dy() - 48) / 8 - return cellBounds.Add (image.Pt ( + cellBounds.Max.Y = bounds.Min.Y + (bounds.Dy() - 48) / 4 + return canvas.Cut (element.core, cellBounds.Add (image.Pt ( x * cellBounds.Dx(), - y * cellBounds.Dy())) -} - -func hex (n uint32) (c color.RGBA) { - c.A = uint8(n) - c.B = uint8(n >> 8) - c.G = uint8(n >> 16) - c.R = uint8(n >> 24) - return -} - -func uhex (n uint32) (artist.Pattern) { - return artist.NewUniform (color.RGBA { - A: uint8(n), - B: uint8(n >> 8), - G: uint8(n >> 16), - R: uint8(n >> 24), - }) + y * cellBounds.Dy()))) } diff --git a/elements/testing/mouse.go b/elements/testing/mouse.go index 63695c0..57eeb67 100644 --- a/elements/testing/mouse.go +++ b/elements/testing/mouse.go @@ -1,11 +1,11 @@ package testing import "image" -import "image/color" import "git.tebibyte.media/sashakoshka/tomo/input" -import "git.tebibyte.media/sashakoshka/tomo/config" import "git.tebibyte.media/sashakoshka/tomo/theme" +import "git.tebibyte.media/sashakoshka/tomo/config" import "git.tebibyte.media/sashakoshka/tomo/artist" +import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" import "git.tebibyte.media/sashakoshka/tomo/elements/core" // Mouse is an element capable of testing mouse input. When the mouse is clicked @@ -14,7 +14,6 @@ type Mouse struct { *core.Core core core.CoreControl drawing bool - color artist.Pattern lastMousePos image.Point config config.Config @@ -27,7 +26,6 @@ func NewMouse () (element *Mouse) { element = &Mouse { c: theme.C("testing", "mouse") } element.Core, element.core = core.NewCore(element.draw) element.core.SetMinimumSize(32, 32) - element.color = artist.NewUniform(color.Black) return } @@ -55,17 +53,17 @@ func (element *Mouse) draw () { theme.PatternAccent, theme.PatternState { }, element.c) - artist.FillRectangle(element.core, pattern, bounds) - artist.StrokeRectangle ( + pattern.Draw(element.core, bounds) + shapes.StrokeColorRectangle ( element.core, - artist.NewUniform(color.Black), 1, - bounds) - artist.Line ( - element.core, artist.NewUniform(color.White), 1, + artist.Hex(0x000000FF), + bounds, 1) + shapes.ColorLine ( + element.core, artist.Hex(0xFFFFFFFF), 1, bounds.Min.Add(image.Pt(1, 1)), bounds.Min.Add(image.Pt(bounds.Dx() - 2, bounds.Dy() - 2))) - artist.Line ( - element.core, artist.NewUniform(color.White), 1, + shapes.ColorLine ( + element.core, artist.Hex(0xFFFFFFFF), 1, bounds.Min.Add(image.Pt(1, bounds.Dy() - 2)), bounds.Min.Add(image.Pt(bounds.Dx() - 2, 1))) } @@ -78,8 +76,8 @@ func (element *Mouse) HandleMouseDown (x, y int, button input.Button) { func (element *Mouse) HandleMouseUp (x, y int, button input.Button) { element.drawing = false mousePos := image.Pt(x, y) - element.core.DamageRegion (artist.Line ( - element.core, element.color, 1, + element.core.DamageRegion (shapes.ColorLine ( + element.core, artist.Hex(0x000000FF), 1, element.lastMousePos, mousePos)) element.lastMousePos = mousePos } @@ -87,8 +85,8 @@ func (element *Mouse) HandleMouseUp (x, y int, button input.Button) { func (element *Mouse) HandleMouseMove (x, y int) { if !element.drawing { return } mousePos := image.Pt(x, y) - element.core.DamageRegion (artist.Line ( - element.core, element.color, 1, + element.core.DamageRegion (shapes.ColorLine ( + element.core, artist.Hex(0x000000FF), 1, element.lastMousePos, mousePos)) element.lastMousePos = mousePos } diff --git a/examples/artist/main.go b/examples/artist/main.go index b09932b..3b4517f 100644 --- a/examples/artist/main.go +++ b/examples/artist/main.go @@ -11,12 +11,12 @@ func main () { } func run () { - window, _ := tomo.NewWindow(128, 128) + window, _ := tomo.NewWindow(480, 360) window.SetTitle("Draw Test") window.Adopt(testing.NewArtist()) window.OnClose(tomo.Stop) window.Show() go func () { - http.ListenAndServe("localhost:6060", nil) + http.ListenAndServe("localhost:9090", nil) } () } diff --git a/textdraw/drawer.go b/textdraw/drawer.go index 09fe749..3ef4011 100644 --- a/textdraw/drawer.go +++ b/textdraw/drawer.go @@ -3,9 +3,9 @@ package textdraw import "image" import "unicode" import "image/draw" +import "image/color" import "golang.org/x/image/math/fixed" import "git.tebibyte.media/sashakoshka/tomo/canvas" -import "git.tebibyte.media/sashakoshka/tomo/artist" // 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. @@ -14,17 +14,11 @@ type Drawer struct { TypeSetter } // Draw draws the drawer's text onto the specified canvas at the given offset. func (drawer Drawer) Draw ( destination canvas.Canvas, - source artist.Pattern, + color color.RGBA, offset image.Point, ) ( updatedRegion image.Rectangle, ) { - wrappedSource := artist.WrappedPattern { - Pattern: source, - Width: 0, - Height: 0, // TODO: choose a better width and height - } - drawer.For (func ( index int, char rune, @@ -46,7 +40,7 @@ func (drawer Drawer) Draw ( draw.DrawMask ( destination, destinationRectangle, - wrappedSource, image.Point { }, + image.NewUniform(color), image.Point { }, mask, maskPoint, draw.Over) From cda2d1f0ae1d720be3f596d950029ac84535a8cb Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 26 Feb 2023 22:20:17 -0500 Subject: [PATCH 12/21] Default elements compile --- artist/inset.go | 10 ++++++ artist/pattern.go | 31 ++++++++++++++----- canvas/canvas.go | 15 +++++++++ elements/basic/button.go | 25 +++++++-------- elements/basic/checkbox.go | 15 ++++----- elements/basic/container.go | 22 ++++++++----- elements/basic/image.go | 10 +++--- elements/basic/label.go | 15 ++++----- elements/basic/list.go | 24 +++++++-------- elements/basic/listentry.go | 18 +++++------ elements/basic/progressbar.go | 24 +++++++-------- elements/basic/scrollcontainer.go | 26 ++++++++-------- elements/basic/slider.go | 8 ++--- elements/basic/spacer.go | 14 ++++----- elements/basic/switch.go | 17 ++++++----- elements/basic/textbox.go | 51 ++++++++++++++++--------------- elements/fun/clock.go | 19 ++++++------ elements/fun/piano.go | 25 +++++++-------- elements/testing/artist.go | 2 +- elements/testing/mouse.go | 8 ++--- examples/raycaster/raycaster.go | 17 ++++++----- theme/default.go | 24 +++++++-------- theme/state.go | 16 +++++----- theme/theme.go | 28 +++++++++-------- theme/wrapped.go | 9 +++++- 25 files changed, 268 insertions(+), 205 deletions(-) diff --git a/artist/inset.go b/artist/inset.go index d552eda..196b29b 100644 --- a/artist/inset.go +++ b/artist/inset.go @@ -48,3 +48,13 @@ func (inset Inset) Inverse () (prime Inset) { 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] +} diff --git a/artist/pattern.go b/artist/pattern.go index 2a41d41..ce8a683 100644 --- a/artist/pattern.go +++ b/artist/pattern.go @@ -2,6 +2,7 @@ package artist import "image" import "git.tebibyte.media/sashakoshka/tomo/canvas" +import "git.tebibyte.media/sashakoshka/tomo/shatter" // Pattern is capable of drawing to a canvas within the bounds of a given // clipping rectangle. @@ -20,24 +21,38 @@ func Draw ( destination canvas.Canvas, source Pattern, clips ...image.Rectangle, +) ( + updatedRegion image.Rectangle, ) { for _, clip := range clips { source.Draw(destination, clip) + updatedRegion = updatedRegion.Union(clip) } + return } -// DrawBounds is like Draw, but lets you specify an overall bounding rectangle -// for the pattern. The destination is cut to this rectangle. +// DrawBounds lets you specify an overall bounding rectangle for drawing a +// pattern. The destination is cut to this rectangle. func DrawBounds ( destination canvas.Canvas, - bounds image.Rectangle, source Pattern, - clips ...image.Rectangle, + bounds image.Rectangle, +) ( + updatedRegion image.Rectangle, ) { - cut := canvas.Cut(destination, bounds) - for _, clip := range clips { - source.Draw(cut, clip) - } + return Draw(canvas.Cut(destination, bounds), source, bounds) +} + +// DrawShatter is like an inverse of Draw, drawing nothing in the areas +// specified in "rocks". +func DrawShatter ( + destination canvas.Canvas, + source Pattern, + rocks ...image.Rectangle, +) ( + updatedRegion image.Rectangle, +) { + return Draw(destination, source, shatter.Shatter(destination.Bounds(), rocks...)...) } // AllocateSample returns a new canvas containing the result of a pattern. The diff --git a/canvas/canvas.go b/canvas/canvas.go index 436dcd1..1661b9c 100644 --- a/canvas/canvas.go +++ b/canvas/canvas.go @@ -34,6 +34,21 @@ func NewBasicCanvas (width, height int) (canvas BasicCanvas) { 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 diff --git a/elements/basic/button.go b/elements/basic/button.go index 69ed715..e11a827 100644 --- a/elements/basic/button.go +++ b/elements/basic/button.go @@ -6,6 +6,7 @@ import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" import "git.tebibyte.media/sashakoshka/tomo/artist" +import "git.tebibyte.media/sashakoshka/tomo/shatter" import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "git.tebibyte.media/sashakoshka/tomo/elements/core" @@ -121,7 +122,8 @@ func (element *Button) SetConfig (new config.Config) { func (element *Button) updateMinimumSize () { textBounds := element.drawer.LayoutBounds() - minimumSize := textBounds.Inset(-element.config.Padding()) + padding := element.theme.Padding(theme.PatternButton) + minimumSize := padding.Inverse().Apply(textBounds) element.core.SetMinimumSize(minimumSize.Dx(), minimumSize.Dy()) } @@ -138,8 +140,8 @@ func (element *Button) drawAndPush (partial bool) { } } -func (element *Button) state () theme.PatternState { - return theme.PatternState { +func (element *Button) state () theme.State { + return theme.State { Disabled: !element.Enabled(), Focused: element.Focused(), Pressed: element.pressed, @@ -152,20 +154,20 @@ func (element *Button) drawBackground (partial bool) []image.Rectangle { pattern := element.theme.Pattern(theme.PatternButton, state) static := element.theme.Hints(theme.PatternButton).StaticInset - if partial && static != (theme.Inset { }) { - return artist.FillRectangleShatter ( - element.core, pattern, bounds, static.Apply(bounds)) + if partial && static != (artist.Inset { }) { + tiles := shatter.Shatter(bounds, static.Apply(bounds)) + artist.Draw(element.core, pattern, tiles...) + return tiles } else { - return []image.Rectangle { - artist.FillRectangle(element.core, pattern, bounds), - } + pattern.Draw(element.core, bounds) + return []image.Rectangle { bounds } } } func (element *Button) drawText (partial bool) image.Rectangle { state := element.state() bounds := element.Bounds() - foreground := element.theme.Pattern(theme.PatternForeground, state) + foreground := element.theme.Color(theme.ColorForeground, state) sink := element.theme.Sink(theme.PatternButton) textBounds := element.drawer.LayoutBounds() @@ -183,8 +185,7 @@ func (element *Button) drawText (partial bool) image.Rectangle { if partial { pattern := element.theme.Pattern(theme.PatternButton, state) - artist.FillRectangleClip ( - element.core, pattern, bounds, region) + pattern.Draw(element.core, region) } element.drawer.Draw(element.core, foreground, offset) diff --git a/elements/basic/checkbox.go b/elements/basic/checkbox.go index 89eaabc..14690bb 100644 --- a/elements/basic/checkbox.go +++ b/elements/basic/checkbox.go @@ -4,7 +4,6 @@ import "image" import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" -import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "git.tebibyte.media/sashakoshka/tomo/elements/core" @@ -146,8 +145,9 @@ func (element *Checkbox) updateMinimumSize () { if element.text == "" { element.core.SetMinimumSize(textBounds.Dy(), textBounds.Dy()) } else { + margin := element.theme.Margin(theme.PatternBackground) element.core.SetMinimumSize ( - textBounds.Dy() + element.config.Padding() + textBounds.Dx(), + textBounds.Dy() + margin.X + textBounds.Dx(), textBounds.Dy()) } } @@ -163,7 +163,7 @@ func (element *Checkbox) draw () { bounds := element.Bounds() boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min) - state := theme.PatternState { + state := theme.State { Disabled: !element.Enabled(), Focused: element.Focused(), Pressed: element.pressed, @@ -172,19 +172,20 @@ func (element *Checkbox) draw () { backgroundPattern := element.theme.Pattern ( theme.PatternBackground, state) - artist.FillRectangle(element.core, backgroundPattern, bounds) + backgroundPattern.Draw(element.core, bounds) pattern := element.theme.Pattern(theme.PatternButton, state) - artist.FillRectangle(element.core, pattern, boxBounds) + pattern.Draw(element.core, boxBounds) textBounds := element.drawer.LayoutBounds() + margin := element.theme.Margin(theme.PatternBackground) offset := bounds.Min.Add(image.Point { - X: bounds.Dy() + element.config.Padding(), + X: bounds.Dy() + margin.X, }) offset.Y -= textBounds.Min.Y offset.X -= textBounds.Min.X - foreground := element.theme.Pattern(theme.PatternForeground, state) + foreground := element.theme.Color(theme.ColorForeground, state) element.drawer.Draw(element.core, foreground, offset) } diff --git a/elements/basic/container.go b/elements/basic/container.go index b4bb5b0..0347def 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -226,9 +226,9 @@ func (element *Container) redoAll () { } pattern := element.theme.Pattern ( theme.PatternBackground, - theme.PatternState { }) - artist.FillRectangleShatter ( - element.core, pattern, element.Bounds(), rocks...) + theme.State { }) + artist.DrawShatter ( + element.core, pattern, rocks...) // cut our canvas up and give peices to child elements for _, entry := range element.children { @@ -311,9 +311,11 @@ func (element *Container) HandleKeyUp (key input.Key, modifiers input.Modifiers) } func (element *Container) FlexibleHeightFor (width int) (height int) { + margin := element.theme.Margin(theme.PatternBackground) + // TODO: have layouts take in x and y margins return element.layout.FlexibleHeightFor ( element.children, - element.config.Margin(), width) + margin.X, width) } func (element *Container) OnFlexibleHeightChange (callback func ()) { @@ -515,16 +517,20 @@ func (element *Container) childFocusRequestCallback ( } func (element *Container) updateMinimumSize () { - width, height := element.layout.MinimumSize ( - element.children, element.config.Margin()) + margin := element.theme.Margin(theme.PatternBackground) + // TODO: have layouts take in x and y margins + width, height := element.layout.MinimumSize(element.children, margin.X) if element.flexible { height = element.layout.FlexibleHeightFor ( - element.children, element.config.Margin(), width) + element.children, + margin.X, width) } element.core.SetMinimumSize(width, height) } func (element *Container) doLayout () { + margin := element.theme.Margin(theme.PatternBackground) + // TODO: have layouts take in x and y margins element.layout.Arrange ( - element.children, element.config.Margin(), element.Bounds()) + element.children, margin.X, element.Bounds()) } diff --git a/elements/basic/image.go b/elements/basic/image.go index 46482a3..2e5d4fd 100644 --- a/elements/basic/image.go +++ b/elements/basic/image.go @@ -1,17 +1,18 @@ package basicElements import "image" -import "git.tebibyte.media/sashakoshka/tomo/artist" +import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/elements/core" +import "git.tebibyte.media/sashakoshka/tomo/artist/patterns" type Image struct { *core.Core core core.CoreControl - buffer artist.Pattern + buffer canvas.Canvas } func NewImage (image image.Image) (element *Image) { - element = &Image { buffer: artist.NewTexture(image) } + element = &Image { buffer: canvas.FromImage(image) } element.Core, element.core = core.NewCore(element.draw) bounds := image.Bounds() element.core.SetMinimumSize(bounds.Dx(), bounds.Dy()) @@ -19,5 +20,6 @@ func NewImage (image image.Image) (element *Image) { } func (element *Image) draw () { - artist.FillRectangle(element.core, element.buffer, element.Bounds()) + (patterns.Texture { Canvas: element.buffer }). + Draw(element.core, element.Bounds()) } diff --git a/elements/basic/label.go b/elements/basic/label.go index e82fea9..2674715 100644 --- a/elements/basic/label.go +++ b/elements/basic/label.go @@ -2,7 +2,6 @@ package basicElements import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" -import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "git.tebibyte.media/sashakoshka/tomo/elements/core" @@ -137,7 +136,9 @@ func (element *Label) SetConfig (new config.Config) { func (element *Label) updateMinimumSize () { if element.wrap { em := element.drawer.Em().Round() - if em < 1 { em = element.config.Padding() } + if em < 1 { + em = element.theme.Padding(theme.PatternBackground)[0] + } element.core.SetMinimumSize ( em, element.drawer.LineHeight().Round()) if element.onFlexibleHeightChange != nil { @@ -154,13 +155,13 @@ func (element *Label) draw () { pattern := element.theme.Pattern ( theme.PatternBackground, - theme.PatternState { }) - artist.FillRectangle(element.core, pattern, bounds) + theme.State { }) + pattern.Draw(element.core, bounds) textBounds := element.drawer.LayoutBounds() - foreground := element.theme.Pattern ( - theme.PatternForeground, - theme.PatternState { }) + foreground := element.theme.Color ( + theme.ColorForeground, + theme.State { }) element.drawer.Draw(element.core, foreground, bounds.Min.Sub(textBounds.Min)) } diff --git a/elements/basic/list.go b/elements/basic/list.go index 7fdaa87..0f4c009 100644 --- a/elements/basic/list.go +++ b/elements/basic/list.go @@ -221,8 +221,8 @@ func (element *List) ScrollAxes () (horizontal, vertical bool) { } func (element *List) scrollViewportHeight () (height int) { - inset := element.theme.Inset(theme.PatternSunken) - return element.Bounds().Dy() - inset[0] - inset[2] + padding := element.theme.Padding(theme.PatternSunken) + return element.Bounds().Dy() - padding[0] - padding[2] } func (element *List) maxScrollHeight () (height int) { @@ -355,8 +355,8 @@ func (element *List) Select (index int) { } func (element *List) selectUnderMouse (x, y int) (updated bool) { - inset := element.theme.Inset(theme.PatternSunken) - bounds := inset.Apply(element.Bounds()) + padding := element.theme.Padding(theme.PatternSunken) + bounds := padding.Apply(element.Bounds()) mousePoint := image.Pt(x, y) dot := image.Pt ( bounds.Min.X, @@ -398,8 +398,8 @@ func (element *List) changeSelectionBy (delta int) (updated bool) { func (element *List) resizeEntryToFit (entry ListEntry) (resized ListEntry) { bounds := element.Bounds() - inset := element.theme.Inset(theme.PatternSunken) - entry.Resize(inset.Apply(bounds).Dx()) + padding := element.theme.Padding(theme.PatternSunken) + entry.Resize(padding.Apply(bounds).Dx()) return entry } @@ -425,17 +425,17 @@ func (element *List) updateMinimumSize () { minimumHeight = element.contentHeight } - inset := element.theme.Inset(theme.PatternSunken) - minimumHeight += inset[0] + inset[2] + padding := element.theme.Padding(theme.PatternSunken) + minimumHeight += padding[0] + padding[2] element.core.SetMinimumSize(minimumWidth, minimumHeight) } func (element *List) draw () { bounds := element.Bounds() - inset := element.theme.Inset(theme.PatternSunken) - innerBounds := inset.Apply(bounds) - state := theme.PatternState { + padding := element.theme.Padding(theme.PatternSunken) + innerBounds := padding.Apply(bounds) + state := theme.State { Disabled: !element.Enabled(), Focused: element.Focused(), } @@ -460,6 +460,6 @@ func (element *List) draw () { innerBounds.Dx(), element.contentHeight, ).Add(innerBounds.Min).Intersect(innerBounds) pattern := element.theme.Pattern(theme.PatternSunken, state) - artist.FillRectangleShatter ( + artist.DrawShatter ( element.core, pattern, bounds, covered) } diff --git a/elements/basic/listentry.go b/elements/basic/listentry.go index 439ce90..1a690cd 100644 --- a/elements/basic/listentry.go +++ b/elements/basic/listentry.go @@ -47,8 +47,8 @@ func (entry *ListEntry) SetConfig (new config.Config) { } func (entry *ListEntry) updateBounds () { - inset := entry.theme.Inset(theme.PatternRaised) - entry.bounds = inset.Inverse().Apply(entry.drawer.LayoutBounds()) + padding := entry.theme.Padding(theme.PatternRaised) + entry.bounds = padding.Inverse().Apply(entry.drawer.LayoutBounds()) entry.bounds = entry.bounds.Sub(entry.bounds.Min) entry.minimumWidth = entry.bounds.Dx() entry.bounds.Max.X = entry.width @@ -62,23 +62,21 @@ func (entry *ListEntry) Draw ( ) ( updatedRegion image.Rectangle, ) { - state := theme.PatternState { + state := theme.State { Focused: focused, On: on, } pattern := entry.theme.Pattern (theme.PatternRaised, state) - inset := entry.theme.Inset(theme.PatternRaised) - artist.FillRectangle ( - destination, - pattern, - entry.Bounds().Add(offset)) + padding := entry.theme.Padding(theme.PatternRaised) + bounds := entry.Bounds().Add(offset) + artist.DrawBounds(destination, pattern, bounds) - foreground := entry.theme.Pattern (theme.PatternForeground, state) + foreground := entry.theme.Color (theme.ColorForeground, state) return entry.drawer.Draw ( destination, foreground, - offset.Add(image.Pt(inset[3], inset[0])). + offset.Add(image.Pt(padding[3], padding[0])). Sub(entry.drawer.LayoutBounds().Min)) } diff --git a/elements/basic/progressbar.go b/elements/basic/progressbar.go index 626b215..3b197c7 100644 --- a/elements/basic/progressbar.go +++ b/elements/basic/progressbar.go @@ -3,7 +3,7 @@ package basicElements import "image" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" -import "git.tebibyte.media/sashakoshka/tomo/artist" +import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" import "git.tebibyte.media/sashakoshka/tomo/elements/core" // ProgressBar displays a visual indication of how far along a task is. @@ -52,9 +52,10 @@ func (element *ProgressBar) SetConfig (new config.Config) { } func (element (ProgressBar)) updateMinimumSize() { + padding := element.theme.Padding(theme.PatternSunken) element.core.SetMinimumSize ( - element.config.Padding() * 2, - element.config.Padding() * 2) + padding[3] + padding[1], + padding[0] + padding[2]) } func (element *ProgressBar) redo () { @@ -67,18 +68,15 @@ func (element *ProgressBar) redo () { func (element *ProgressBar) draw () { bounds := element.Bounds() - pattern := element.theme.Pattern ( - theme.PatternSunken, - theme.PatternState { }) - inset := element.theme.Inset(theme.PatternSunken) - artist.FillRectangle(element.core, pattern, bounds) - bounds = inset.Apply(bounds) + pattern := element.theme.Pattern(theme.PatternSunken, theme.State { }) + padding := element.theme.Padding(theme.PatternSunken) + pattern.Draw(element.core, bounds) + bounds = padding.Apply(bounds) meterBounds := image.Rect ( bounds.Min.X, bounds.Min.Y, bounds.Min.X + int(float64(bounds.Dx()) * element.progress), bounds.Max.Y) - accent := element.theme.Pattern ( - theme.PatternAccent, - theme.PatternState { }) - artist.FillRectangle(element.core, accent, meterBounds) + // TODO: maybe dont use the accent color here... + accent := element.theme.Color(theme.ColorAccent, theme.State { }) + shapes.FillColorRectangle(element.core, accent, meterBounds) } diff --git a/elements/basic/scrollcontainer.go b/elements/basic/scrollcontainer.go index 719eff2..c686878 100644 --- a/elements/basic/scrollcontainer.go +++ b/elements/basic/scrollcontainer.go @@ -302,7 +302,7 @@ func (element *ScrollContainer) OnFocusMotionRequest ( } func (element *ScrollContainer) childDamageCallback (region canvas.Canvas) { - element.core.DamageRegion(artist.Paste(element.core, region, image.Point { })) + element.core.DamageRegion(region.Bounds()) } func (element *ScrollContainer) childFocusRequestCallback () (granted bool) { @@ -352,8 +352,8 @@ func (element *ScrollContainer) recalculate () { horizontal := &element.horizontal vertical := &element.vertical - gutterInsetHorizontal := horizontal.theme.Inset(theme.PatternGutter) - gutterInsetVertical := vertical.theme.Inset(theme.PatternGutter) + gutterInsetHorizontal := horizontal.theme.Padding(theme.PatternGutter) + gutterInsetVertical := vertical.theme.Padding(theme.PatternGutter) bounds := element.Bounds() thicknessHorizontal := @@ -438,8 +438,8 @@ func (element *ScrollContainer) recalculate () { func (element *ScrollContainer) draw () { deadPattern := element.theme.Pattern ( - theme.PatternDead, theme.PatternState { }) - artist.FillRectangle ( + theme.PatternDead, theme.State { }) + artist.DrawBounds ( element.core, deadPattern, image.Rect ( element.vertical.gutter.Min.X, @@ -451,27 +451,27 @@ func (element *ScrollContainer) draw () { } func (element *ScrollContainer) drawHorizontalBar () { - state := theme.PatternState { + state := theme.State { Disabled: !element.horizontal.enabled, Pressed: element.horizontal.dragging, } gutterPattern := element.horizontal.theme.Pattern(theme.PatternGutter, state) - artist.FillRectangle(element.core, gutterPattern, element.horizontal.gutter) + artist.DrawBounds(element.core, gutterPattern, element.horizontal.gutter) handlePattern := element.horizontal.theme.Pattern(theme.PatternHandle, state) - artist.FillRectangle(element.core, handlePattern, element.horizontal.bar) + artist.DrawBounds(element.core, handlePattern, element.horizontal.bar) } func (element *ScrollContainer) drawVerticalBar () { - state := theme.PatternState { + state := theme.State { Disabled: !element.vertical.enabled, Pressed: element.vertical.dragging, } gutterPattern := element.vertical.theme.Pattern(theme.PatternGutter, state) - artist.FillRectangle(element.core, gutterPattern, element.vertical.gutter) + artist.DrawBounds(element.core, gutterPattern, element.vertical.gutter) handlePattern := element.vertical.theme.Pattern(theme.PatternHandle, state) - artist.FillRectangle(element.core, handlePattern, element.vertical.bar) + artist.DrawBounds(element.core, handlePattern, element.vertical.bar) } func (element *ScrollContainer) dragHorizontalBar (mousePosition image.Point) { @@ -493,8 +493,8 @@ func (element *ScrollContainer) dragVerticalBar (mousePosition image.Point) { } func (element *ScrollContainer) updateMinimumSize () { - gutterInsetHorizontal := element.horizontal.theme.Inset(theme.PatternGutter) - gutterInsetVertical := element.vertical.theme.Inset(theme.PatternGutter) + gutterInsetHorizontal := element.horizontal.theme.Padding(theme.PatternGutter) + gutterInsetVertical := element.vertical.theme.Padding(theme.PatternGutter) thicknessHorizontal := element.config.HandleWidth() + diff --git a/elements/basic/slider.go b/elements/basic/slider.go index 63121bb..bf6efaa 100644 --- a/elements/basic/slider.go +++ b/elements/basic/slider.go @@ -173,7 +173,7 @@ func (element *Slider) redo () { func (element *Slider) draw () { bounds := element.Bounds() - element.track = element.theme.Inset(theme.PatternGutter).Apply(bounds) + element.track = element.theme.Padding(theme.PatternGutter).Apply(bounds) if element.vertical { barSize := element.track.Dx() element.bar = image.Rect(0, 0, barSize, barSize).Add(bounds.Min) @@ -190,16 +190,16 @@ func (element *Slider) draw () { element.bar = element.bar.Add(image.Pt(int(barOffset), 0)) } - state := theme.PatternState { + state := theme.State { Focused: element.Focused(), Disabled: !element.Enabled(), Pressed: element.dragging, } - artist.FillRectangle ( + artist.DrawBounds ( element.core, element.theme.Pattern(theme.PatternGutter, state), bounds) - artist.FillRectangle ( + artist.DrawBounds ( element.core, element.theme.Pattern(theme.PatternHandle, state), element.bar) diff --git a/elements/basic/spacer.go b/elements/basic/spacer.go index cb77206..297ad71 100644 --- a/elements/basic/spacer.go +++ b/elements/basic/spacer.go @@ -2,7 +2,7 @@ package basicElements import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" -import "git.tebibyte.media/sashakoshka/tomo/artist" +import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" import "git.tebibyte.media/sashakoshka/tomo/elements/core" // Spacer can be used to put space between two elements.. @@ -61,14 +61,14 @@ func (element *Spacer) draw () { bounds := element.Bounds() if element.line { - pattern := element.theme.Pattern ( - theme.PatternForeground, - theme.PatternState { }) - artist.FillRectangle(element.core, pattern, bounds) + color := element.theme.Color ( + theme.ColorForeground, + theme.State { }) + shapes.FillColorRectangle(element.core, color, bounds) } else { pattern := element.theme.Pattern ( theme.PatternBackground, - theme.PatternState { }) - artist.FillRectangle(element.core, pattern, bounds) + theme.State { }) + pattern.Draw(element.core, bounds) } } diff --git a/elements/basic/switch.go b/elements/basic/switch.go index 1c06b80..4eeb9ad 100644 --- a/elements/basic/switch.go +++ b/elements/basic/switch.go @@ -149,7 +149,7 @@ func (element *Switch) updateMinimumSize () { } else { element.core.SetMinimumSize ( lineHeight * 2 + - element.config.Padding() + + element.theme.Margin(theme.PatternBackground).X + textBounds.Dx(), lineHeight) } @@ -160,14 +160,14 @@ func (element *Switch) draw () { 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) - state := theme.PatternState { + state := theme.State { Disabled: !element.Enabled(), Focused: element.Focused(), Pressed: element.pressed, } backgroundPattern := element.theme.Pattern ( theme.PatternBackground, state) - artist.FillRectangle (element.core, backgroundPattern, bounds) + backgroundPattern.Draw(element.core, bounds) if element.checked { handleBounds.Min.X += bounds.Dy() @@ -185,21 +185,22 @@ func (element *Switch) draw () { gutterPattern := element.theme.Pattern ( theme.PatternGutter, state) - artist.FillRectangle(element.core, gutterPattern, gutterBounds) + artist.DrawBounds(element.core, gutterPattern, gutterBounds) handlePattern := element.theme.Pattern ( theme.PatternHandle, state) - artist.FillRectangle(element.core, handlePattern, handleBounds) + artist.DrawBounds(element.core, handlePattern, handleBounds) textBounds := element.drawer.LayoutBounds() offset := bounds.Min.Add(image.Point { - X: bounds.Dy() * 2 + element.config.Padding(), + X: bounds.Dy() * 2 + + element.theme.Margin(theme.PatternBackground).X, }) offset.Y -= textBounds.Min.Y offset.X -= textBounds.Min.X - foreground := element.theme.Pattern ( - theme.PatternForeground, state) + foreground := element.theme.Color ( + theme.ColorForeground, state) element.drawer.Draw(element.core, foreground, offset) } diff --git a/elements/basic/textbox.go b/elements/basic/textbox.go index bcc004b..7d7649a 100644 --- a/elements/basic/textbox.go +++ b/elements/basic/textbox.go @@ -9,6 +9,7 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "git.tebibyte.media/sashakoshka/tomo/textmanip" import "git.tebibyte.media/sashakoshka/tomo/fixedutil" +import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" import "git.tebibyte.media/sashakoshka/tomo/elements/core" // TextBox is a single-line text input. @@ -92,9 +93,10 @@ func (element *TextBox) HandleMouseMove (x, y int) { } func (element *TextBox) atPosition (position image.Point) int { + padding := element.theme.Padding(theme.PatternSunken) offset := element.Bounds().Min.Add (image.Pt ( - element.config.Padding() - element.scroll, - element.config.Padding())) + padding[artist.SideLeft] - element.scroll, + padding[artist.SideTop])) textBoundsMin := element.valueDrawer.LayoutBounds().Min return element.valueDrawer.AtPosition ( fixedutil.Pt(position.Sub(offset).Add(textBoundsMin))) @@ -251,7 +253,8 @@ func (element *TextBox) ScrollViewportBounds () (bounds image.Rectangle) { } func (element *TextBox) scrollViewportWidth () (width int) { - return element.Bounds().Inset(element.config.Padding()).Dx() + padding := element.theme.Padding(theme.PatternSunken) + return padding.Apply(element.Bounds()).Dx() } // ScrollTo scrolls the viewport to the specified point relative to @@ -290,7 +293,8 @@ func (element *TextBox) runOnChange () { func (element *TextBox) scrollToCursor () { if !element.core.HasImage() { return } - bounds := element.Bounds().Inset(element.config.Padding()) + padding := element.theme.Padding(theme.PatternSunken) + bounds := padding.Apply(element.Bounds()) bounds = bounds.Sub(bounds.Min) bounds.Max.X -= element.valueDrawer.Em().Round() cursorPosition := fixedutil.RoundPt ( @@ -329,11 +333,11 @@ func (element *TextBox) SetConfig (new config.Config) { func (element *TextBox) updateMinimumSize () { textBounds := element.placeholderDrawer.LayoutBounds() + padding := element.theme.Padding(theme.PatternSunken) element.core.SetMinimumSize ( - textBounds.Dx() + - element.config.Padding() * 2, - element.placeholderDrawer.LineHeight().Round() + - element.config.Padding() * 2) + padding.Horizontal() + textBounds.Dx(), + padding.Vertical() + + element.placeholderDrawer.LineHeight().Round()) } func (element *TextBox) redo () { @@ -346,30 +350,29 @@ func (element *TextBox) redo () { func (element *TextBox) draw () { bounds := element.Bounds() - state := theme.PatternState { + state := theme.State { Disabled: !element.Enabled(), Focused: element.Focused(), } pattern := element.theme.Pattern(theme.PatternSunken, state) - inset := element.theme.Inset(theme.PatternSunken) - innerCanvas := canvas.Cut(element.core, inset.Apply(bounds)) - artist.FillRectangle(element.core, pattern, bounds) + padding := element.theme.Padding(theme.PatternSunken) + innerCanvas := canvas.Cut(element.core, padding.Apply(bounds)) + pattern.Draw(element.core, bounds) offset := bounds.Min.Add (image.Point { - X: element.config.Padding() - element.scroll, - Y: element.config.Padding(), + X: -element.scroll, + Y: 0, }) if element.Focused() && !element.dot.Empty() { // draw selection bounds - accent := element.theme.Pattern ( - theme.PatternAccent, state) + accent := element.theme.Color(theme.ColorAccent, state) canon := element.dot.Canon() foff := fixedutil.Pt(offset) start := element.valueDrawer.PositionAt(canon.Start).Add(foff) end := element.valueDrawer.PositionAt(canon.End).Add(foff) end.Y += element.valueDrawer.LineHeight() - artist.FillRectangle ( + shapes.FillColorRectangle ( innerCanvas, accent, image.Rectangle { @@ -381,9 +384,9 @@ func (element *TextBox) draw () { if len(element.text) == 0 { // draw placeholder textBounds := element.placeholderDrawer.LayoutBounds() - foreground := element.theme.Pattern ( - theme.PatternForeground, - theme.PatternState { Disabled: true }) + foreground := element.theme.Color ( + theme.ColorForeground, + theme.State { Disabled: true }) element.placeholderDrawer.Draw ( innerCanvas, foreground, @@ -391,8 +394,7 @@ func (element *TextBox) draw () { } else { // draw input value textBounds := element.valueDrawer.LayoutBounds() - foreground := element.theme.Pattern ( - theme.PatternForeground, state) + foreground := element.theme.Color(theme.ColorForeground, state) element.valueDrawer.Draw ( innerCanvas, foreground, @@ -401,11 +403,10 @@ func (element *TextBox) draw () { if element.Focused() && element.dot.Empty() { // draw cursor - foreground := element.theme.Pattern ( - theme.PatternForeground, state) + foreground := element.theme.Color(theme.ColorForeground, state) cursorPosition := fixedutil.RoundPt ( element.valueDrawer.PositionAt(element.dot.End)) - artist.Line ( + shapes.ColorLine ( innerCanvas, foreground, 1, cursorPosition.Add(offset), diff --git a/elements/fun/clock.go b/elements/fun/clock.go index 066af12..00861ab 100644 --- a/elements/fun/clock.go +++ b/elements/fun/clock.go @@ -3,9 +3,10 @@ package fun import "time" import "math" import "image" +import "image/color" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" -import "git.tebibyte.media/sashakoshka/tomo/artist" +import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" import "git.tebibyte.media/sashakoshka/tomo/elements/core" // AnalogClock can display the time of day in an analog format. @@ -58,15 +59,15 @@ func (element *AnalogClock) redo () { func (element *AnalogClock) draw () { bounds := element.Bounds() - state := theme.PatternState { } + state := theme.State { } pattern := element.theme.Pattern(theme.PatternSunken, state) - inset := element.theme.Inset(theme.PatternSunken) - artist.FillRectangle(element.core, pattern, bounds) + padding := element.theme.Padding(theme.PatternSunken) + pattern.Draw(element.core, bounds) - bounds = inset.Apply(bounds) + bounds = padding.Apply(bounds) - foreground := element.theme.Pattern(theme.PatternForeground, state) - accent := element.theme.Pattern(theme.PatternAccent, state) + foreground := element.theme.Color(theme.ColorForeground, state) + accent := element.theme.Color(theme.ColorAccent, state) for hour := 0; hour < 12; hour ++ { element.radialLine ( @@ -93,7 +94,7 @@ func (element *AnalogClock) FlexibleHeightFor (width int) (height int) { func (element *AnalogClock) OnFlexibleHeightChange (func ()) { } func (element *AnalogClock) radialLine ( - source artist.Pattern, + source color.RGBA, inner float64, outer float64, radian float64, @@ -107,5 +108,5 @@ func (element *AnalogClock) radialLine ( max := element.Bounds().Min.Add(image.Pt ( int(math.Cos(radian) * outer * width + width), int(math.Sin(radian) * outer * height + height))) - artist.Line(element.core, source, 1, min, max) + shapes.ColorLine(element.core, source, 1, min, max) } diff --git a/elements/fun/piano.go b/elements/fun/piano.go index e116c1d..a11a3c2 100644 --- a/elements/fun/piano.go +++ b/elements/fun/piano.go @@ -218,10 +218,11 @@ func (element *Piano) SetConfig (new config.Config) { } func (element *Piano) updateMinimumSize () { - inset := element.theme.Inset(theme.PatternSunken) + padding := element.theme.Padding(theme.PatternSunken) element.core.SetMinimumSize ( - pianoKeyWidth * 7 * element.countOctaves() + inset[1] + inset[3], - 64 + inset[0] + inset[2]) + pianoKeyWidth * 7 * element.countOctaves() + + padding[1] + padding[3], + 64 + padding[0] + padding[2]) } func (element *Piano) countOctaves () int { @@ -247,8 +248,8 @@ func (element *Piano) recalculate () { element.flatKeys = make([]pianoKey, element.countFlats()) element.sharpKeys = make([]pianoKey, element.countSharps()) - inset := element.theme.Inset(theme.PatternPinboard) - bounds := inset.Apply(element.Bounds()) + padding := element.theme.Padding(theme.PatternPinboard) + bounds := padding.Apply(element.Bounds()) dot := bounds.Min note := element.low.Note(0) @@ -280,7 +281,7 @@ func (element *Piano) recalculate () { } func (element *Piano) draw () { - state := theme.PatternState { + state := theme.State { Focused: element.Focused(), Disabled: !element.Enabled(), } @@ -303,28 +304,28 @@ func (element *Piano) draw () { } pattern := element.theme.Pattern(theme.PatternPinboard, state) - artist.FillRectangleShatter ( - element.core, pattern, element.Bounds(), element.contentBounds) + artist.DrawShatter ( + element.core, pattern, element.contentBounds) } func (element *Piano) drawFlat ( bounds image.Rectangle, pressed bool, - state theme.PatternState, + state theme.State, ) { state.Pressed = pressed pattern := element.theme.Theme.Pattern ( theme.PatternButton, state, theme.C("fun", "flatKey")) - artist.FillRectangle(element.core, pattern, bounds) + artist.DrawBounds(element.core, pattern, bounds) } func (element *Piano) drawSharp ( bounds image.Rectangle, pressed bool, - state theme.PatternState, + state theme.State, ) { state.Pressed = pressed pattern := element.theme.Theme.Pattern ( theme.PatternButton, state, theme.C("fun", "sharpKey")) - artist.FillRectangle(element.core, pattern, bounds) + artist.DrawBounds(element.core, pattern, bounds) } diff --git a/elements/testing/artist.go b/elements/testing/artist.go index d3b5475..57022d1 100644 --- a/elements/testing/artist.go +++ b/elements/testing/artist.go @@ -80,7 +80,7 @@ func (element *Artist) draw () { tiles := shatter.Shatter(c41.Bounds(), rocks...) for index, tile := range tiles { artist.DrawBounds ( - element.core, tile, + element.core, []artist.Pattern { patterns.Uhex(0xFF0000FF), patterns.Uhex(0x00FF00FF), diff --git a/elements/testing/mouse.go b/elements/testing/mouse.go index 57eeb67..194518a 100644 --- a/elements/testing/mouse.go +++ b/elements/testing/mouse.go @@ -49,11 +49,11 @@ func (element *Mouse) redo () { func (element *Mouse) draw () { bounds := element.Bounds() - pattern := element.theme.Pattern ( - theme.PatternAccent, - theme.PatternState { }, + accent := element.theme.Color ( + theme.ColorAccent, + theme.State { }, element.c) - pattern.Draw(element.core, bounds) + shapes.FillColorRectangle(element.core, accent, bounds) shapes.StrokeColorRectangle ( element.core, artist.Hex(0x000000FF), diff --git a/examples/raycaster/raycaster.go b/examples/raycaster/raycaster.go index f4bfa7b..3ccd703 100644 --- a/examples/raycaster/raycaster.go +++ b/examples/raycaster/raycaster.go @@ -7,6 +7,7 @@ import "image/color" import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/config" import "git.tebibyte.media/sashakoshka/tomo/artist" +import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" import "git.tebibyte.media/sashakoshka/tomo/elements/core" type ControlState struct { @@ -202,9 +203,9 @@ func (element *Raycaster) drawMinimap () { if cell > 0 { cellColor = color.RGBA { 0xFF, 0xFF, 0xFF, 0xFF } } - artist.FillRectangle ( + shapes.FillColorRectangle ( element.core, - artist.NewUniform(cellColor), + cellColor, cellBounds.Inset(1)) }} @@ -217,18 +218,18 @@ func (element *Raycaster) drawMinimap () { hitPt := hit.Mul(float64(scale)).Point().Add(bounds.Min) playerBounds := image.Rectangle { playerPt, playerPt }.Inset(scale / -8) - artist.FillEllipse ( + shapes.FillColorEllipse ( element.core, - artist.Uhex(0xFFFFFFFF), + artist.Hex(0xFFFFFFFF), playerBounds) - artist.Line ( + shapes.ColorLine ( element.core, - artist.Uhex(0xFFFFFFFF), 1, + artist.Hex(0xFFFFFFFF), 1, playerPt, playerAnglePt) - artist.Line ( + shapes.ColorLine ( element.core, - artist.Uhex(0x00FF00FF), 1, + artist.Hex(0x00FF00FF), 1, playerPt, hitPt) } diff --git a/theme/default.go b/theme/default.go index 4cdebcb..da0e868 100644 --- a/theme/default.go +++ b/theme/default.go @@ -1,6 +1,7 @@ package theme import "image" +import "image/color" import "golang.org/x/image/font" import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/canvas" @@ -32,18 +33,9 @@ func (Default) Icon (string, IconSize, Case) canvas.Image { // Pattern returns a pattern from the default theme corresponding to the given // pattern ID. -func (Default) Pattern ( - pattern Pattern, - state PatternState, - c Case, -) artist.Pattern { - switch pattern { - case PatternAccent: - return patterns.Uhex(0xFF8800FF) - case PatternBackground: - return patterns.Uhex(0x000000FF) - case PatternForeground: - return patterns.Uhex(0xFFFFFFFF) +func (Default) Pattern (id Pattern, state State, c Case) artist.Pattern { + switch id { + case PatternBackground: return patterns.Uhex(0x000000FF) // case PatternDead: // case PatternRaised: // case PatternSunken: @@ -56,6 +48,14 @@ func (Default) Pattern ( } } +func (Default) Color (id Color, state State, c Case) color.RGBA { + switch id { + case ColorAccent: return artist.Hex(0xFF8800FF) + case ColorForeground: return artist.Hex(0xFFFFFFFF) + default: return artist.Hex(0x888888FF) + } +} + // Padding returns the default padding value for the given pattern. func (Default) Padding (pattern Pattern, c Case) artist.Inset { return artist.Inset { 4, 4, 4, 4} diff --git a/theme/state.go b/theme/state.go index cd46e75..57f96a4 100644 --- a/theme/state.go +++ b/theme/state.go @@ -8,7 +8,7 @@ package theme // specific elements. type Case struct { Namespace, Element string } -// C can be used as shorthand to generate a case struct as used in PatternState. +// C can be used as shorthand to generate a case struct as used in State. func C (namespace, element string) (c Case) { return Case { Namespace: namespace, @@ -16,14 +16,14 @@ func C (namespace, element string) (c Case) { } } -// PatternState lists parameters which can change the appearance of some -// patterns. For example, passing a PatternState with Selected set to true may -// result in a pattern that has a colored border within it. -type PatternState struct { +// State lists parameters which can change the appearance of some patterns and +// colors. For example, passing a State with Selected set to true may result in +// a pattern that has a colored border within it. +type State struct { // On should be set to true if the element that is using this pattern is - // in some sort of "on" state, such as if a checkbox is checked or a - // switch is toggled on. This is only necessary if the element in - // question is capable of being toggled. + // in some sort of selected or "on" state, such as if a checkbox is + // checked or a switch is toggled on. This is only necessary if the + // element in question is capable of being toggled or selected. On bool // Focused should be set to true if the element that is using this diff --git a/theme/theme.go b/theme/theme.go index f506035..b5ca38b 100644 --- a/theme/theme.go +++ b/theme/theme.go @@ -18,17 +18,9 @@ const ( // This allows custom elements to follow themes, even those that do not // explicitly support them. type Pattern int; const ( - // PatternAccent is the accent color of the theme. It is safe to assume - // that this is, by default, a solid color. - PatternAccent Pattern = iota - - // PatternBackground is the background color of the theme. It is safe to - // assume that this is, by default, a solid color. - PatternBackground - - // PatternForeground is the foreground text color of the theme. It is - // safe to assume that this is, by default, a solid color. - PatternForeground + // PatternBackground is the window background of the theme. It appears + // in things like containers and behind text. + PatternBackground Pattern = iota // PatternDead is a pattern that is displayed on a "dead area" where no // controls exist, but there still must be some indication of visual @@ -57,6 +49,14 @@ type Pattern int; const ( PatternHandle ) +type Color int; const ( + // ColorAccent is the accent color of the theme. + ColorAccent Color = iota + + // ColorForeground is the text/icon color of the theme. + ColorForeground +) + // Hints specifies rendering hints for a particular pattern. Elements can take // these into account in order to gain extra performance. type Hints struct { @@ -80,7 +80,11 @@ type Theme interface { // Pattern returns an appropriate pattern given a pattern name, case, // and state. - Pattern (Pattern, PatternState, Case) artist.Pattern + Pattern (Pattern, State, Case) artist.Pattern + + // Color returns an appropriate pattern given a color name, case, and + // state. + Color (Color, State, Case) color.RGBA // Padding returns how much space should be between the bounds of a // pattern whatever an element draws inside of it. diff --git a/theme/wrapped.go b/theme/wrapped.go index 947481b..731c6fc 100644 --- a/theme/wrapped.go +++ b/theme/wrapped.go @@ -1,6 +1,7 @@ package theme import "image" +import "image/color" import "golang.org/x/image/font" import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/canvas" @@ -26,11 +27,17 @@ func (wrapped Wrapped) Icon (name string, size IconSize) canvas.Image { } // Pattern returns an appropriate pattern given a pattern name and state. -func (wrapped Wrapped) Pattern (id Pattern, state PatternState) artist.Pattern { +func (wrapped Wrapped) Pattern (id Pattern, state State) artist.Pattern { real := wrapped.ensure() return real.Pattern(id, state, wrapped.Case) } +// Color returns an appropriate color given a color name and state. +func (wrapped Wrapped) Color (id Color, state State) color.RGBA { + real := wrapped.ensure() + return real.Color(id, state, wrapped.Case) +} + // Padding returns how much space should be between the bounds of a // pattern whatever an element draws inside of it. func (wrapped Wrapped) Padding (id Pattern) artist.Inset { From 26787d86704d3f4903142c39c3da4d245b0e02dc Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 26 Feb 2023 22:48:14 -0500 Subject: [PATCH 13/21] Fixed TextBox --- elements/basic/textbox.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elements/basic/textbox.go b/elements/basic/textbox.go index 7d7649a..885cdae 100644 --- a/elements/basic/textbox.go +++ b/elements/basic/textbox.go @@ -360,8 +360,8 @@ func (element *TextBox) draw () { pattern.Draw(element.core, bounds) offset := bounds.Min.Add (image.Point { - X: -element.scroll, - Y: 0, + X: padding[artist.SideLeft] - element.scroll, + Y: padding[artist.SideTop], }) if element.Focused() && !element.dot.Empty() { From 449922851f401c89feeb643a29672418bec2b329 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 26 Feb 2023 22:56:20 -0500 Subject: [PATCH 14/21] Fix list not drawing background --- artist/pattern.go | 3 ++- elements/basic/list.go | 2 +- elements/basic/listentry.go | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/artist/pattern.go b/artist/pattern.go index ce8a683..558bbf6 100644 --- a/artist/pattern.go +++ b/artist/pattern.go @@ -52,7 +52,8 @@ func DrawShatter ( ) ( updatedRegion image.Rectangle, ) { - return Draw(destination, source, shatter.Shatter(destination.Bounds(), rocks...)...) + tiles := shatter.Shatter(destination.Bounds(), rocks...) + return Draw(destination, source, tiles...) } // AllocateSample returns a new canvas containing the result of a pattern. The diff --git a/elements/basic/list.go b/elements/basic/list.go index 0f4c009..f6386b1 100644 --- a/elements/basic/list.go +++ b/elements/basic/list.go @@ -461,5 +461,5 @@ func (element *List) draw () { ).Add(innerBounds.Min).Intersect(innerBounds) pattern := element.theme.Pattern(theme.PatternSunken, state) artist.DrawShatter ( - element.core, pattern, bounds, covered) + element.core, pattern, covered) } diff --git a/elements/basic/listentry.go b/elements/basic/listentry.go index 1a690cd..cad33df 100644 --- a/elements/basic/listentry.go +++ b/elements/basic/listentry.go @@ -76,7 +76,7 @@ func (entry *ListEntry) Draw ( return entry.drawer.Draw ( destination, foreground, - offset.Add(image.Pt(padding[3], padding[0])). + offset.Add(image.Pt(padding[artist.SideLeft], padding[artist.SideTop])). Sub(entry.drawer.LayoutBounds().Min)) } From de10cde63033dd482b8e1ad0eefc6c87dc0802a5 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 27 Feb 2023 12:48:44 -0500 Subject: [PATCH 15/21] Add image textures to theme --- artist/patterns/border.go | 4 +- artist/patterns/texture.go | 7 +-- elements/basic/textbox.go | 12 ++--- theme/assets/wintergreen.png | Bin 0 -> 2289 bytes theme/default.go | 92 +++++++++++++++++++++++++++++------ 5 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 theme/assets/wintergreen.png diff --git a/artist/patterns/border.go b/artist/patterns/border.go index 2723aa3..3202a0b 100644 --- a/artist/patterns/border.go +++ b/artist/patterns/border.go @@ -44,9 +44,7 @@ func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) { srcSections := nonasect(pattern.Bounds(), pattern.Inset) srcTextures := [9]Texture { } for index, section := range srcSections { - srcTextures[index] = Texture { - Canvas: canvas.Cut(pattern, section), - } + srcTextures[index].Canvas = canvas.Cut(pattern, section) } dstSections := nonasect(destination.Bounds(), pattern.Inset) diff --git a/artist/patterns/texture.go b/artist/patterns/texture.go index 5a97b8b..3c0b374 100644 --- a/artist/patterns/texture.go +++ b/artist/patterns/texture.go @@ -12,7 +12,8 @@ type Texture struct { // Draw tiles the pattern's canvas within the clipping bounds. The minimum // points of the pattern's canvas and the destination canvas will be lined up. func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) { - bounds := clip.Canon().Intersect(destination.Bounds()) + realBounds := destination.Bounds() + bounds := clip.Canon().Intersect(realBounds) if bounds.Empty() { return } dstData, dstStride := destination.Buffer() @@ -23,8 +24,8 @@ func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) { for x := bounds.Min.X; x < bounds.Max.X; x ++ { dstIndex := x + y * dstStride srcIndex := - wrap(x - bounds.Min.X, srcBounds.Min.X, srcBounds.Max.X) + - wrap(x - bounds.Min.Y, srcBounds.Min.Y, srcBounds.Max.Y) * srcStride + wrap(x - realBounds.Min.X, srcBounds.Min.X, srcBounds.Max.X) + + wrap(y - realBounds.Min.Y, srcBounds.Min.Y, srcBounds.Max.Y) * srcStride dstData[dstIndex] = srcData[srcIndex] }} } diff --git a/elements/basic/textbox.go b/elements/basic/textbox.go index 885cdae..1f99460 100644 --- a/elements/basic/textbox.go +++ b/elements/basic/textbox.go @@ -93,7 +93,7 @@ func (element *TextBox) HandleMouseMove (x, y int) { } func (element *TextBox) atPosition (position image.Point) int { - padding := element.theme.Padding(theme.PatternSunken) + padding := element.theme.Padding(theme.PatternInput) offset := element.Bounds().Min.Add (image.Pt ( padding[artist.SideLeft] - element.scroll, padding[artist.SideTop])) @@ -253,7 +253,7 @@ func (element *TextBox) ScrollViewportBounds () (bounds image.Rectangle) { } func (element *TextBox) scrollViewportWidth () (width int) { - padding := element.theme.Padding(theme.PatternSunken) + padding := element.theme.Padding(theme.PatternInput) return padding.Apply(element.Bounds()).Dx() } @@ -293,7 +293,7 @@ func (element *TextBox) runOnChange () { func (element *TextBox) scrollToCursor () { if !element.core.HasImage() { return } - padding := element.theme.Padding(theme.PatternSunken) + padding := element.theme.Padding(theme.PatternInput) bounds := padding.Apply(element.Bounds()) bounds = bounds.Sub(bounds.Min) bounds.Max.X -= element.valueDrawer.Em().Round() @@ -333,7 +333,7 @@ func (element *TextBox) SetConfig (new config.Config) { func (element *TextBox) updateMinimumSize () { textBounds := element.placeholderDrawer.LayoutBounds() - padding := element.theme.Padding(theme.PatternSunken) + padding := element.theme.Padding(theme.PatternInput) element.core.SetMinimumSize ( padding.Horizontal() + textBounds.Dx(), padding.Vertical() + @@ -354,8 +354,8 @@ func (element *TextBox) draw () { Disabled: !element.Enabled(), Focused: element.Focused(), } - pattern := element.theme.Pattern(theme.PatternSunken, state) - padding := element.theme.Padding(theme.PatternSunken) + pattern := element.theme.Pattern(theme.PatternInput, state) + padding := element.theme.Padding(theme.PatternInput) innerCanvas := canvas.Cut(element.core, padding.Apply(bounds)) pattern.Draw(element.core, bounds) diff --git a/theme/assets/wintergreen.png b/theme/assets/wintergreen.png new file mode 100644 index 0000000000000000000000000000000000000000..2abe477b4319a2a1733bda505866a42d1e6e6c3a GIT binary patch literal 2289 zcmYjTd03M97XE#JewYZPj$43GP1((Bj@zgSg5pSvNok{LCY4(jX62qD1f`a_v}x8P zrlu{@WLap7pG$?4shLSGCElr|Xwq>l4ZY0$>;7?`_dVx%-uFGv`Rn9xH~AVZvRVWH zFj~i9Z_%MpS5hn*07$Z-Vwrw3}f6G*4*F?S!knh65PyZ^R5 zc6rT?Pjx}$_Pq@)bE~#k_!Ul^=?ryJ?#%sj!VQ3c(b)q-;&IU7a>iX!^p1DiiR=K^q25VC+s%(5c&t6~hexEa<)I@$c>=@iB4RPYV z97Tr>gzvJqTf%m8TCsdJ$qbj2=k*lO5){(g`Yl|4Y*>)L{%k+(4z|n(+wqD&=S)-Y zweM0sky4`YiORB_4ZgS-Q?iv$m~zzWanRa)ukcP%T%2jP_X*d*xO>UpcGlFlwfDf6 zwmMe3^%X5rRgO;8MR(z}4Xjsz&-C|ILd|QmBO+PZkfTL_{$>fuGgm>N=X&{_=yr@3 zWG`{`l<2#Wmj8!ZxUw=@<=SdQ%Ic!Fv8q!NJFB=P33u`L)Q6H^h(teoE@`QLi;DO! zT0@)*SD8wZr%6r)$z0=QyvFb~vc;|*D4c0lHfz9La{Ricj~O;{Ku!|bBmd@Gl>%v|ephDbuXjFVd`XuAr>&KbhN7@oWXc!1L2v;=os{vg zIe3`yNqnErqBzAl{5YXv6hC%Vh#$QyHQ#A5wNDlVsDyEd^L@CEHnZ9K^-N@hono+_ zy6h0^Lx-N_c|%KJ-*}Y}lj%TEZx?!SI*uORd%TcmH(4|9K?vBE_4i#AI7G7+wnHg= zMTQq4f=!5!Q54w}#ddnZV!8qdQv$5J1fRrNdmq2m2i=QT4MTqA@McbtZ{wS0%&2!J z3Be2#nKx~mV6e{=4+X}W!k`CRB4{BF*0W2fE!afJ0~Ykx1l1(Z6T#fU5!Fo} zQ@LB#4V1u^K$BXa&w0nfBe9eU$T*bwR?GgiJr7sx6o_GKepWHoCQiDz#<*{z0(pC?P< z$~O_CK$tiTgkk1MXrzIt*YzA7%{&gFNnPg|(>$nJq7<1&z?Q>^p%<+YrQ|MTZ$aHEuP;h*e&>km!p1q7<# z?}p&~_<4)LfVrU^8wRp4W69rxp)psDTOrc1{KwG`=0mIfN~{g@_g?e34}91w@p=L>@f7yX4j=n)g@6rx8P(}-4k zsa~P?uZ&Nmt2BswGwd*}*CU`~4wmB;3D{~a1$BVzdK4+aU-aVn!5s!o$MF|UkI3;4 z#K?PhrgHCa-54XXQJc;zZV8y^#FQ;Ee(@aN<6D8!2v<-)EA^)K)Nu(!&qF}adc@RZ zP7j5SK!`MZjgZ#GV&C^bJ<=wjf?+$FFi~Z!BMfig?8p1V@;B!dnA>;@RP`Rz^!01g znLnPuq|Y^m!*XTw+!<{WSv9RGo$Wb)oj8`1BxP8=4~CtfeK#pc;|S^N2SXZl+7J`M zX?F4A^+Mj#qZP3e*ss$A20XG{0(wOousrft9Mt0^S{0qpTTE6p(|A&r|^=;5jLGW-O30fY~*h~{sVN!s!{{_fEv4P7+$peG`I?PtYm%?O&AoU zsZ223ttB$pRMdZXq4XxK{)Vg|r#yWm9L6eeWMpx{{@3RA7sU?Va6JBxpG_;*p#tGe zE18L^1RZ(n51h}Jp?l^Rx}kG#&2{N1;mDVFqMQs?pUGT=U&;lm@EwNnTT9-W+n}od zXkQ9Wyx4?_2#;q#wQF2UL7#d05v1rIf%DnW6dIae=r_+@lBgn&pmB!-qtu|zGmU}h zVs(4C*$n;e=4UK*k9UWzp{q=!?qdQ}bsLT>on9!$e}|GTRJqD&eoJ+~3s~o~i7oTu G9r-UUI0GdB literal 0 HcmV?d00001 diff --git a/theme/default.go b/theme/default.go index da0e868..ae9f482 100644 --- a/theme/default.go +++ b/theme/default.go @@ -1,6 +1,9 @@ package theme import "image" +import "bytes" +import _ "embed" +import _ "image/png" import "image/color" import "golang.org/x/image/font" import "git.tebibyte.media/sashakoshka/tomo/artist" @@ -8,6 +11,47 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/defaultfont" import "git.tebibyte.media/sashakoshka/tomo/artist/patterns" +//go:embed assets/wintergreen.png +var defaultAtlasBytes []byte +var defaultAtlas canvas.Canvas +var defaultTextures [8][10]artist.Pattern + +func atlasCell (col, row int, border artist.Inset) { + bounds := image.Rect(0, 0, 16, 16).Add(image.Pt(col, row).Mul(16)) + defaultTextures[col][row] = patterns.Border { + Canvas: canvas.Cut(defaultAtlas, bounds), + Inset: border, + } +} + +func atlasCol (col int, border artist.Inset) { + for index, _ := range defaultTextures[col] { + atlasCell(col, index, border) + } +} + +func init () { + defaultAtlasImage, _, _ := image.Decode(bytes.NewReader(defaultAtlasBytes)) + defaultAtlas = canvas.FromImage(defaultAtlasImage) + + // PatternDead + atlasCol(0, artist.Inset { }) + // PatternRaised + atlasCol(1, artist.Inset { 6, 6, 6, 6 }) + // PatternSunken + atlasCol(2, artist.Inset { 4, 4, 4, 4 }) + // PatternPinboard + atlasCol(3, artist.Inset { 2, 2, 2, 2 }) + // PatternButton + atlasCol(4, artist.Inset { 6, 6, 6, 6 }) + // PatternInput + atlasCol(5, artist.Inset { 4, 4, 4, 4 }) + // PatternGutter + atlasCol(6, artist.Inset { 4, 4, 4, 4 }) + // PatternHandle + atlasCol(7, artist.Inset { 6, 6, 6, 6 }) +} + // Default is the default theme. type Default struct { } @@ -34,36 +78,52 @@ func (Default) Icon (string, IconSize, Case) canvas.Image { // Pattern returns a pattern from the default theme corresponding to the given // pattern ID. func (Default) Pattern (id Pattern, state State, c Case) artist.Pattern { + offset := 0; switch { + case state.Disabled: offset = 1 + case state.Focused && state.Pressed: offset = 6 + case state.Focused && state.On: offset = 7 + case state.Invalid && state.Pressed: offset = 8 + case state.Invalid && state.On: offset = 9 + case state.Invalid: offset = 5 + case state.Focused: offset = 4 + case state.Pressed: offset = 2 + case state.On: offset = 3 + } + switch id { - case PatternBackground: return patterns.Uhex(0x000000FF) - // case PatternDead: - // case PatternRaised: - // case PatternSunken: - // case PatternPinboard: - // case PatternButton: - // case PatternInput: - // case PatternGutter: - // case PatternHandle: - default: return patterns.Uhex(0x888888FF) + case PatternBackground: return patterns.Uhex(0xaaaaaaFF) + case PatternDead: return defaultTextures[0][offset] + case PatternRaised: return defaultTextures[1][offset] + case PatternSunken: return defaultTextures[2][offset] + case PatternPinboard: return defaultTextures[3][offset] + case PatternButton: return defaultTextures[4][offset] + case PatternInput: return defaultTextures[5][offset] + case PatternGutter: return defaultTextures[6][offset] + case PatternHandle: return defaultTextures[7][offset] + default: return patterns.Uhex(0xFF00FFFF) } } func (Default) Color (id Color, state State, c Case) color.RGBA { - switch id { - case ColorAccent: return artist.Hex(0xFF8800FF) - case ColorForeground: return artist.Hex(0xFFFFFFFF) - default: return artist.Hex(0x888888FF) + if state.Disabled { + return artist.Hex(0x444444FF) + } else { + switch id { + case ColorAccent: return artist.Hex(0x408090FF) + case ColorForeground: return artist.Hex(0x000000FF) + default: return artist.Hex(0x888888FF) + } } } // Padding returns the default padding value for the given pattern. func (Default) Padding (pattern Pattern, c Case) artist.Inset { - return artist.Inset { 4, 4, 4, 4} + return artist.Inset { 8, 8, 8, 8 } } // Margin returns the default margin value for the given pattern. func (Default) Margin (id Pattern, c Case) image.Point { - return image.Pt(4, 4) + return image.Pt(8, 8) } // Hints returns rendering optimization hints for a particular pattern. From 8dd506a0075e2d2770fdc49bc417aaf5d0456429 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 27 Feb 2023 16:38:33 -0500 Subject: [PATCH 16/21] Textures now render properly --- artist/patterns/texture.go | 15 +++++++++------ elements/testing/artist.go | 21 +++++++++++++++++++++ theme/default.go | 6 +++--- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/artist/patterns/texture.go b/artist/patterns/texture.go index 3c0b374..897417e 100644 --- a/artist/patterns/texture.go +++ b/artist/patterns/texture.go @@ -19,13 +19,16 @@ func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) { dstData, dstStride := destination.Buffer() srcData, srcStride := pattern.Buffer() srcBounds := pattern.Bounds() - - for y := bounds.Min.Y; y < bounds.Max.Y; y ++ { - for x := bounds.Min.X; x < bounds.Max.X; x ++ { - dstIndex := x + y * dstStride + + 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 ++ { + srcPoint := point.Sub(realBounds.Min).Add(srcBounds.Min) + + dstIndex := point.X + point.Y * dstStride srcIndex := - wrap(x - realBounds.Min.X, srcBounds.Min.X, srcBounds.Max.X) + - wrap(y - realBounds.Min.Y, srcBounds.Min.Y, srcBounds.Max.Y) * srcStride + wrap(srcPoint.X, srcBounds.Min.X, srcBounds.Max.X) + + wrap(srcPoint.Y, srcBounds.Min.Y, srcBounds.Max.Y) * srcStride dstData[dstIndex] = srcData[srcIndex] }} } diff --git a/elements/testing/artist.go b/elements/testing/artist.go index 57022d1..9277041 100644 --- a/elements/testing/artist.go +++ b/elements/testing/artist.go @@ -108,6 +108,16 @@ func (element *Artist) draw () { c32 := element.cellAt(3, 2) shapes.StrokeRectangle(c32, c41, 5) + // 4, 2 + c42 := element.cellAt(4, 2) + + // 0, 3 + c03 := element.cellAt(0, 3) + patterns.Border { + Canvas: element.thingy(c42), + Inset: artist.Inset { 8, 8, 8, 8 }, + }.Draw(c03, c03.Bounds()) + // how long did that take to render? drawTime := time.Since(drawStart) textDrawer := textdraw.Drawer { } @@ -165,3 +175,14 @@ func (element *Artist) cellAt (x, y int) (canvas.Canvas) { x * cellBounds.Dx(), y * cellBounds.Dy()))) } + +func (element *Artist) thingy (destination canvas.Canvas) (result canvas.Canvas) { + bounds := destination.Bounds() + bounds = image.Rect(0, 0, 32, 32).Add(bounds.Min) + shapes.FillColorRectangle(destination, artist.Hex(0x440000FF), bounds) + shapes.StrokeColorRectangle(destination, artist.Hex(0xFF0000FF), bounds, 1) + shapes.StrokeColorRectangle(destination, artist.Hex(0x004400FF), bounds.Inset(4), 1) + shapes.FillColorRectangle(destination, artist.Hex(0x004444FF), bounds.Inset(12)) + shapes.StrokeColorRectangle(destination, artist.Hex(0x888888FF), bounds.Inset(8), 1) + return canvas.Cut(destination, bounds) +} diff --git a/theme/default.go b/theme/default.go index ae9f482..96a3d86 100644 --- a/theme/default.go +++ b/theme/default.go @@ -37,19 +37,19 @@ func init () { // PatternDead atlasCol(0, artist.Inset { }) // PatternRaised - atlasCol(1, artist.Inset { 6, 6, 6, 6 }) + atlasCol(1, artist.Inset { 6, 6, 6, 6 }) // broken // PatternSunken atlasCol(2, artist.Inset { 4, 4, 4, 4 }) // PatternPinboard atlasCol(3, artist.Inset { 2, 2, 2, 2 }) // PatternButton - atlasCol(4, artist.Inset { 6, 6, 6, 6 }) + atlasCol(4, artist.Inset { 6, 6, 6, 6 }) // broken // PatternInput atlasCol(5, artist.Inset { 4, 4, 4, 4 }) // PatternGutter atlasCol(6, artist.Inset { 4, 4, 4, 4 }) // PatternHandle - atlasCol(7, artist.Inset { 6, 6, 6, 6 }) + atlasCol(7, artist.Inset { 6, 6, 6, 6 }) // broken } // Default is the default theme. From ee45b2fa606425613f80590320f86a3acfadea50 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 27 Feb 2023 17:00:28 -0500 Subject: [PATCH 17/21] Theming tweaks and rendering fixes --- elements/basic/checkbox.go | 3 ++- elements/basic/listentry.go | 2 +- elements/testing/artist.go | 7 +++++++ theme/assets/wintergreen.png | Bin 2289 -> 2299 bytes theme/default.go | 13 +++++++++++-- 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/elements/basic/checkbox.go b/elements/basic/checkbox.go index 14690bb..ab2217e 100644 --- a/elements/basic/checkbox.go +++ b/elements/basic/checkbox.go @@ -4,6 +4,7 @@ import "image" import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" +import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/textdraw" import "git.tebibyte.media/sashakoshka/tomo/elements/core" @@ -175,7 +176,7 @@ func (element *Checkbox) draw () { backgroundPattern.Draw(element.core, bounds) pattern := element.theme.Pattern(theme.PatternButton, state) - pattern.Draw(element.core, boxBounds) + artist.DrawBounds(element.core, pattern, boxBounds) textBounds := element.drawer.LayoutBounds() margin := element.theme.Margin(theme.PatternBackground) diff --git a/elements/basic/listentry.go b/elements/basic/listentry.go index cad33df..d1020c2 100644 --- a/elements/basic/listentry.go +++ b/elements/basic/listentry.go @@ -67,7 +67,7 @@ func (entry *ListEntry) Draw ( On: on, } - pattern := entry.theme.Pattern (theme.PatternRaised, state) + pattern := entry.theme.Pattern(theme.PatternRaised, state) padding := entry.theme.Padding(theme.PatternRaised) bounds := entry.Bounds().Add(offset) artist.DrawBounds(destination, pattern, bounds) diff --git a/elements/testing/artist.go b/elements/testing/artist.go index 9277041..bb42bfa 100644 --- a/elements/testing/artist.go +++ b/elements/testing/artist.go @@ -118,6 +118,13 @@ func (element *Artist) draw () { Inset: artist.Inset { 8, 8, 8, 8 }, }.Draw(c03, c03.Bounds()) + // 1, 3 + c13 := element.cellAt(1, 3) + patterns.Border { + Canvas: element.thingy(c42), + Inset: artist.Inset { 8, 8, 8, 8 }, + }.Draw(c13, c13.Bounds().Inset(10)) + // how long did that take to render? drawTime := time.Since(drawStart) textDrawer := textdraw.Drawer { } diff --git a/theme/assets/wintergreen.png b/theme/assets/wintergreen.png index 2abe477b4319a2a1733bda505866a42d1e6e6c3a..ea9871c51ee1691f327607243be0a615584d9712 100644 GIT binary patch delta 2060 zcmaixc~p}58pnTt`Qj2Uj%#U%X+~2`ikVA*K;Twxl`~d)MJp{U$5JbCd0#ZM$x5bm z%1TUgv{G9%mkQ`+W@<=fsA+{cnW846xRN^FbI-Z|-si8+InVi?@AG`We;a9ygh!EB z9}a8l(f1#=!+E)6L(}~ArLveOD?iQUog`Ws>QEt^D~B z9%&H*CmfhF$~f3TZp?SI+17oRcQ@X|T^FCfLHC-o#c@8nua-}hw-NHW_|@#Vve?dY zJyv*zK)gsUvdIR8WB@SW;!@A@Z1@j<%J496CL;L$O=4|NVhB=pb;h_h{`PS zK;2sX6TM-lO}>k|EM4SjYC~0R^O;P}J>kZCLWA zaI0f4bcz-2>|WFrJ2gcPVl>=L5mK!G+RX4i4^tbUkoJnuy{y#5K}hNGh~n$~2&c&L z`+f%dznIbPq}wo^`|@z$*1rQiECKUp3}9^R#DYEu^m5MmO~AIdBP72{u%g*%M-sL{ zm4`{|Ftf0q-fL3tP0|RAE#Ee}qIuAI@SWC<0eOz#3iOTbnL@U`Oz8n)_%N7cGyvhl z1q?JzAIu=esKe?0=^*Fs6mlrCjfNx`8v{)Xn4XZHl+i+5ttsHu))nd;8x({hCemph z4)h6b+7|_!aOv_IMRV&DW^OG$!cdk;R*C(3{wqDwbW&j`SU)fI3oaS1T7<>M%>;pg zjDtMs#5fpKQw~$B!{RHDnz4?3c>amcXPk(_}MC;O$j-?DL(y8nS-> zNMF^CKYT;}2t7B8BO5Wi%wy*xG6tO;QM!TX*_Lx_bdDf1SGRPGwSd@1WW&)iVxV>P zP`ZQdHw9CM^PYR6c6dzgSZ)1DznPKw!#KS`PSa`gz*P@nT^M@|2uaF^23?Z?Q$1@6Ekh7Vc%|$Q6swK2}}2jadgU%eKG{@7(igK z_O2yRWKz&rl61t#*Js${67+jwesh?4$gVI-Jof@ukvTJ;I|TNVcHuaROlor22`k&z zvn>OdFmon+b^zP+4Src0h_F50)#YB_L_*z;=_xYbkRA>Kl;#R}qdXVz7t4g{0`sOQ zrG*oDO&Q*)fxQZ7)n@DSHQ6h(TOsZL-Z{|9cNfqU56*TOC>Kc`*8^a<@7i z`&dHkvaSdq%7F$`kbL_Y$tAj@Cpl7*Bh^>>=$yxq#=l%#iKlgG&*c2g#?RJD_h zD^iom=FEC*gXP+1W9V=}0zEKt?~qerHLJACOL`lUEoZ6(qk2NVGMzX%CN;HDs{~cX z)wLvbBCo3$w@8bPk&v)8LC`LDoz^s1P))eX=J`SXHg}6~Uyt#)kiR}F6#B?g9$F$A zs8nx1ns@(G8iU;2T{1;w6{mBMgm|DaDv`R2^zhnR5u?;hh4~y!kmiI4BO@hD9%^W@ zR3~bQgO2-&J(s+sRiFI9fOMJ8T|y{(9-{H0BE3y+1rO?v@f4U1z}ZhKz7-=`xy?@5 zIXB}jePZ_U&%vqzP*skownUe)rJZh&<~8NTyc;ih49wEMB2t%dJmy|FSaKu1LQ){k>@qho+>-Cl?rZZ~(Z!d%YBxnf5a zYO%-T1EczYj5T%q##$d#CGFQKCS3KO2d$pS-YEfnKNs#UeWS&?x4^>&?bm1y_tsF7 zBlGU){ko;6X&F^xi`=Emc^L36d_-ahnrUwuwN5(O~ZA}#{ozE&< zn|`o>@VF-hjmT*Hh$;p{92L delta 2050 zcmV+d2>ti_5%Cd_cznxh~L6rs0TJ*#!My#JdXf6)*`P+Pq__S>z} zbR3c}1h&OE7JgG5WAQA8qAyyPl7K3?8XL;27p3)kO)_-E$vUnJ$C`Uh)s~hEP zHNe;FW$t@3SkItZG+@*316|u+VlXZnL^(7jBr6;i40et}#{eO8$e^eobIoX#A;HB^NwP(-_{klQbRLcfzr2)b({pW$Mq2DtY zQwC9l96yZ64?T=RO^ebqaDIpzI6s62IUFS7Adx4fXW%%*4IGEiAjv_fcy-UGP#lC% z5&U_*ghmh$O)yX#A_l|Y5Go-{*+53_L85$+K|a93rsYZt z=+lU5HGd34WfnFgW-r^O_n|)uP!0O$0D!76)XxS0kX0~%*V48C0Qv!dIniX({lmy= z*zen~0H{wt#zO#E{WHKX+XBNxKaIi|$%8&{Vq;nv2k`S?2Y{akI{^HI>63ZjE@Z?9 zGLnfh4&eJ>2Y~N`3;=0c-~;HV0MI`H2-BFZAAeO7&IiKvm&Nl(H!%S~{|uo24y@?R z`@;D^0cfU5fqx7E1mOCk0M-W^06-5cH8H(Hyx~5Y2i!EK>sQsd^8t7LW$|qqp8%kL z2GD<7RJFu$n} zXn!&f=v;t2NX~_OYl@LzF7&PbJP%V@hw-)o0COQ9fXo4SZODB>4`f6SWUd~_h#tsX zJ&+MS$N+$NQyVt=C#oh)0(h(`)0S!%~2T@3uN59>|1n)zW2LvDyUPl*h3IP2R z0NdGS22g;K*ONE|cYi%IgZ>%7Z5Pa0-?J+O)CX?6Aj}AUW&qc}0^sU{-2hM@900KO zK{i0G#ylVZs1Itpfe8Rye^4LL?ZQa5&Y#l*2?eUDwZC*VZ}R~9ZLvKM!S&AoZo6R4 zI-hO9ArIVkfg2wn0N1|);PPNM0OY{|0GkKd0AF^aG66syKz~04aQ#6Z(CxxVjt@S# zF1URs)dP^Oq#iJ#=RzImK`9(yFcHX*OAhIr$x$6RptP5qXsIthqupa>O-~fQlgKU7vx=?1X3z1_a+W&bypz8wY zL2_MS+af;LzrM)DD7d(4gko50{}J;b^|~jL;-a30G5x^N_zjf zJc!f>Wo~`IBK1L;ySTDQeXt(@^56h~&4X-!NPSRd*9Vc~MA{$b!RY_M0i^3l2R!VTTdpARaNl=)j29BPHUyA? zp9#UqEq@du@_w=&p!bLC0pCaIdZ5J8+Gj{{gIc+dfL9NkPk__|=My0Hzy%@j>wzmm z;MD_fCvbH=;OoG+Q3o>i1=M~$1ahMe$aDy({T>2w9XN%+nhu;oU`+?k3BigEoD_n% z4!ncFa9*9G-ywNMWbKXU+n z#DWwP`vn36WWW$1FzbLJ&_P7IJOaM`b^=%jE+K$*;1U8@2R=gpHBDg38J#sI1hWnp z0;p*M_N%J)e&%G0M>y^2w)xf3<1j6aotu9#v zU}w0K0I3JgCqU|f^9hi8;1dK;Rhq5`6n}x*d;g!W2k800CLMUkzJTEf$P6985s+`# z7ciWIGD8P&4$3zarAY!*2TmtIb>MUYR0rNq05zGR1JwTi(gBv<7tncrcwfNo`gWKDRR8ed)ua8;V#7bpU#%FH4Fi_uO3WM6PT0kSVRp8(kxe1gE2w+~3{3sV#U z-~RvF7mOQq;2q}!40}{&=m51pdOpB#4$2H2z&R-2usKZ~2M|CkQ^0d||GIJhBAA^-pY07*qoM6N<$f?+|VrvLx| diff --git a/theme/default.go b/theme/default.go index 96a3d86..e953f7e 100644 --- a/theme/default.go +++ b/theme/default.go @@ -117,8 +117,17 @@ func (Default) Color (id Color, state State, c Case) color.RGBA { } // Padding returns the default padding value for the given pattern. -func (Default) Padding (pattern Pattern, c Case) artist.Inset { - return artist.Inset { 8, 8, 8, 8 } +func (Default) Padding (id Pattern, c Case) artist.Inset { + switch id { + case PatternSunken: + if c == C("basic", "list") { + return artist.Inset { 2, 2, 2, 2 } + } else { + return artist.Inset { 8, 8, 8, 8 } + } + case PatternGutter: return artist.Inset { } + default: return artist.Inset { 8, 8, 8, 8 } + } } // Margin returns the default margin value for the given pattern. From b1d15fb4ec62df40e0db3607d436e683208fac2a Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 28 Feb 2023 00:17:05 -0500 Subject: [PATCH 18/21] this piano is DOPE and PHAT and WAY COOL --- elements/basic/spacer.go | 21 +++++++++++---- elements/fun/piano.go | 6 ++--- theme/assets/wintergreen.png | Bin 2299 -> 3088 bytes theme/default.go | 48 ++++++++++++++++++++++++++++++----- theme/theme.go | 3 +++ 5 files changed, 64 insertions(+), 14 deletions(-) diff --git a/elements/basic/spacer.go b/elements/basic/spacer.go index 297ad71..4d710b3 100644 --- a/elements/basic/spacer.go +++ b/elements/basic/spacer.go @@ -2,7 +2,6 @@ package basicElements import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" -import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" import "git.tebibyte.media/sashakoshka/tomo/elements/core" // Spacer can be used to put space between two elements.. @@ -22,7 +21,7 @@ func NewSpacer (line bool) (element *Spacer) { element = &Spacer { line: line } element.theme.Case = theme.C("basic", "spacer") element.Core, element.core = core.NewCore(element.draw) - element.core.SetMinimumSize(1, 1) + element.updateMinimumSize() return } @@ -30,6 +29,7 @@ func NewSpacer (line bool) (element *Spacer) { func (element *Spacer) SetLine (line bool) { if element.line == line { return } element.line = line + element.updateMinimumSize() if element.core.HasImage() { element.draw() element.core.DamageAll() @@ -50,6 +50,17 @@ func (element *Spacer) SetConfig (new config.Config) { element.redo() } +func (element *Spacer) updateMinimumSize () { + if element.line { + padding := element.theme.Padding(theme.PatternLine) + element.core.SetMinimumSize ( + padding.Horizontal(), + padding.Vertical()) + } else { + element.core.SetMinimumSize(1, 1) + } +} + func (element *Spacer) redo () { if !element.core.HasImage() { element.draw() @@ -61,10 +72,10 @@ func (element *Spacer) draw () { bounds := element.Bounds() if element.line { - color := element.theme.Color ( - theme.ColorForeground, + pattern := element.theme.Pattern ( + theme.PatternLine, theme.State { }) - shapes.FillColorRectangle(element.core, color, bounds) + pattern.Draw(element.core, bounds) } else { pattern := element.theme.Pattern ( theme.PatternBackground, diff --git a/elements/fun/piano.go b/elements/fun/piano.go index a11a3c2..4cba308 100644 --- a/elements/fun/piano.go +++ b/elements/fun/piano.go @@ -218,11 +218,11 @@ func (element *Piano) SetConfig (new config.Config) { } func (element *Piano) updateMinimumSize () { - padding := element.theme.Padding(theme.PatternSunken) + padding := element.theme.Padding(theme.PatternPinboard) element.core.SetMinimumSize ( pianoKeyWidth * 7 * element.countOctaves() + - padding[1] + padding[3], - 64 + padding[0] + padding[2]) + padding.Horizontal(), + 64 + padding.Vertical()) } func (element *Piano) countOctaves () int { diff --git a/theme/assets/wintergreen.png b/theme/assets/wintergreen.png index ea9871c51ee1691f327607243be0a615584d9712..bcee114d0044737c8439a19aa0ec37e907a8630b 100644 GIT binary patch delta 3034 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 delta 2239 zcmZuzdpOg58~<+8elzSh8CEot4(q9&$00ei&9=y)rcrt-yhaD5@+dl(!|ykeN@bm& zbTCm-p#wQowqBADDh*LhN(}Kb$35fOdp-ZX*ZaqP-Jk2aug~?lKlgqAaZ9KgW9t(F zUr!_ATp|F#$lJ@^Uzbr`NC`LqFh|$sY&mPa2_Zy!yK^@l`S@uooOzXQXqvSuzairJ z(l0Yt&meEf?t;Ki)!IV=m*Uvxh6v4JmIkuCGY^lLt$u4>c++3@>TE3k@DVo;V)l{) zH-=-=x?NB0k7uW54TconRhcKKN+@gIZ)YtmqMq^yUsmpsrr2CmeKYtGwSI$ZUH0ek z3ko4pWY%@*Wu>!+%T!*_Cr0=^OvS%yI|FtUoZuGklHW$)n0+(G$@|;rwOMx7TRI;I zA4F4J@r10^`0I|;VC9KPl--HJ;2$^~>GuC&D+t`*A-~o9L;@?v@LNbJT{G zsiopB^4tY31k=Kwla!OmUAwyMp z{h1W^hmy4qB@D9zOU!1!gMk)=NG@Vb*bVRIYdggbkJL0QTB;Ea-tixBGeiIBat-RQ87qE1jOQqhV`QnV7^jGwPzL-IQuCnp$jP@xIm> z&4gBgznHBUkZA`ppr5=o@pPLjj4n_C9|9BX8X$Qxi;X4{0ircQZBO@3`8jsR(}R#L zOeDs{1gIOq)VTa)19P*p6$8B4xMV2Z8U;ZJMLs3OfgbU#MYq=blp=Qh28wX;?a_{*q>UO0=?NDUd*uV8QfIn?#cf_s`VT1WC%Gxof(U>aQEv+nd`1 zVd4g>rs?gprZNyKxn?k~pxC?!>Iu}2GuQ2ySKUqo^(pbd9#THG9@l3{j9HR05#2^a zj~?Z$QPXkYD?fpw$%APHrbwIx@bb(#`t|;9HJ#Wy+*15Yayy%S;_TQU9a!?QQIo|2biOuZI2T{H-pOr7cZshBP`i$Uk1TP@#dPQuVlADK%926T9KB#~3xmbQUJYs11ioK_mX^vET z%N+bz9DzaXu8*buYAxt}HJ-3TR{sp;Y@@Eo;70QaF@ITHgnah>O61;h-`O;xw$W=q zA}@fEFU32ClCaNYbjh37u)wgqm|RF0y9*)c3Z*zN&`oKkLnho}4=)UcSN5|8LhM9L)c z#HifVdPpTMF)1yl4ILDA?b>Ghp?llI))B=?wTpf=8vyGr#5nsO;S-%N%18cvd@ zZ zVFi<#tAEDKt*aKUkMM5fm6ZVSGhLOrq@bo;iNF{!>c&t|> zj7ampPmq3K1j7vMu7j`VgH@H+^}JY7Y>YpAZ9UqI%cP@s-^H+`gyzpPGb5vbW!0L4 z8*DYVwZvS>!Z~AyjWpO}(Sn^lpn*Gi?B)t@R3-1#%Ozd&)d7vZbdf_0`f&ycah1Q* zU|k#GL4(%oOxuUQG2+59@9Y1up{s5WroiXD2I~O=u58g0+I{p!`weva9GmsSgsw?i zPo5Ya!<O5%OKkIjD@$ lfzRuKqps3CZS`Jk>c%GRX>e>2GIG=nen? diff --git a/theme/default.go b/theme/default.go index e953f7e..f7f6410 100644 --- a/theme/default.go +++ b/theme/default.go @@ -14,7 +14,7 @@ import "git.tebibyte.media/sashakoshka/tomo/artist/patterns" //go:embed assets/wintergreen.png var defaultAtlasBytes []byte var defaultAtlas canvas.Canvas -var defaultTextures [8][10]artist.Pattern +var defaultTextures [13][10]artist.Pattern func atlasCell (col, row int, border artist.Inset) { bounds := image.Rect(0, 0, 16, 16).Add(image.Pt(col, row).Mul(16)) @@ -37,19 +37,30 @@ func init () { // PatternDead atlasCol(0, artist.Inset { }) // PatternRaised - atlasCol(1, artist.Inset { 6, 6, 6, 6 }) // broken + atlasCol(1, artist.Inset { 6, 6, 6, 6 }) // PatternSunken atlasCol(2, artist.Inset { 4, 4, 4, 4 }) // PatternPinboard atlasCol(3, artist.Inset { 2, 2, 2, 2 }) // PatternButton - atlasCol(4, artist.Inset { 6, 6, 6, 6 }) // broken + atlasCol(4, artist.Inset { 6, 6, 6, 6 }) // PatternInput atlasCol(5, artist.Inset { 4, 4, 4, 4 }) // PatternGutter atlasCol(6, artist.Inset { 4, 4, 4, 4 }) // PatternHandle - atlasCol(7, artist.Inset { 6, 6, 6, 6 }) // broken + atlasCol(7, artist.Inset { 6, 6, 6, 6 }) + // PatternLine + atlasCol(8, artist.Inset { 1, 1, 1, 1 }) + + // PatternButton: basic.checkbox + atlasCol(9, artist.Inset { 3, 3, 3, 3 }) + // PatternRaised: basic.listEntry + atlasCol(10, artist.Inset { 3, 3, 3, 3 }) + // PatternRaised: fun.flatKey + atlasCol(11, artist.Inset { 3, 3, 5, 3 }) + // PatternRaised: fun.sharpKey + atlasCol(12, artist.Inset { 3, 3, 4, 3 }) } // Default is the default theme. @@ -93,13 +104,25 @@ func (Default) Pattern (id Pattern, state State, c Case) artist.Pattern { switch id { case PatternBackground: return patterns.Uhex(0xaaaaaaFF) case PatternDead: return defaultTextures[0][offset] - case PatternRaised: return defaultTextures[1][offset] + case PatternRaised: + if c == C("basic", "listEntry") { + return defaultTextures[10][offset] + } else { + return defaultTextures[1][offset] + } case PatternSunken: return defaultTextures[2][offset] case PatternPinboard: return defaultTextures[3][offset] - case PatternButton: return defaultTextures[4][offset] + case PatternButton: + switch c { + case C("basic", "checkbox"): return defaultTextures[9][offset] + case C("fun", "flatKey"): return defaultTextures[11][offset] + case C("fun", "sharpKey"): return defaultTextures[12][offset] + default: return defaultTextures[4][offset] + } case PatternInput: return defaultTextures[5][offset] case PatternGutter: return defaultTextures[6][offset] case PatternHandle: return defaultTextures[7][offset] + case PatternLine: return defaultTextures[8][offset] default: return patterns.Uhex(0xFF00FFFF) } } @@ -119,13 +142,26 @@ func (Default) Color (id Color, state State, c Case) color.RGBA { // Padding returns the default padding value for the given pattern. func (Default) Padding (id Pattern, c Case) artist.Inset { switch id { + case PatternRaised: + if c == C("basic", "listEntry") { + return artist.Inset { 4, 4, 4, 4 } + } else { + return artist.Inset { 8, 8, 8, 8 } + } case PatternSunken: if c == C("basic", "list") { + return artist.Inset { 3, 3, 3, 3 } + } else { + return artist.Inset { 8, 8, 8, 8 } + } + case PatternPinboard: + if c == C("fun", "piano") { return artist.Inset { 2, 2, 2, 2 } } else { return artist.Inset { 8, 8, 8, 8 } } case PatternGutter: return artist.Inset { } + case PatternLine: return artist.Inset { 1, 1, 1, 1 } default: return artist.Inset { 8, 8, 8, 8 } } } diff --git a/theme/theme.go b/theme/theme.go index b5ca38b..64a70f5 100644 --- a/theme/theme.go +++ b/theme/theme.go @@ -47,6 +47,9 @@ type Pattern int; const ( // PatternHandle is a handle that slides along a gutter. PatternHandle + + // PatternLine is an engraved line that separates things. + PatternLine ) type Color int; const ( From 829f1525b8f4927b45c9e82cf3f2c7311c13f3c7 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 28 Feb 2023 17:15:20 -0500 Subject: [PATCH 19/21] Not even gonna bother writing a good name --- theme/assets/wintergreen.png | Bin 3088 -> 2938 bytes theme/default.go | 21 ++++++++++----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/theme/assets/wintergreen.png b/theme/assets/wintergreen.png index bcee114d0044737c8439a19aa0ec37e907a8630b..ea67ed8bb08ef8eb8e8414fdd528e129ef496747 100644 GIT binary patch delta 2862 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 F{{eZMd*1*6 delta 3013 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 Date: Tue, 28 Feb 2023 19:00:34 -0500 Subject: [PATCH 20/21] Made the progress bar look nicer --- elements/basic/progressbar.go | 14 +++++++------- theme/assets/wintergreen.png | Bin 2938 -> 1964 bytes theme/default.go | 8 +++++++- theme/theme.go | 3 +++ 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/elements/basic/progressbar.go b/elements/basic/progressbar.go index 3b197c7..8e5ce32 100644 --- a/elements/basic/progressbar.go +++ b/elements/basic/progressbar.go @@ -3,7 +3,7 @@ package basicElements import "image" import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/config" -import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" +import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/elements/core" // ProgressBar displays a visual indication of how far along a task is. @@ -52,10 +52,11 @@ func (element *ProgressBar) SetConfig (new config.Config) { } func (element (ProgressBar)) updateMinimumSize() { - padding := element.theme.Padding(theme.PatternSunken) + padding := element.theme.Padding(theme.PatternSunken) + innerPadding := element.theme.Padding(theme.PatternMercury) element.core.SetMinimumSize ( - padding[3] + padding[1], - padding[0] + padding[2]) + padding.Horizontal() + innerPadding.Horizontal(), + padding.Vertical() + innerPadding.Vertical()) } func (element *ProgressBar) redo () { @@ -76,7 +77,6 @@ func (element *ProgressBar) draw () { bounds.Min.X, bounds.Min.Y, bounds.Min.X + int(float64(bounds.Dx()) * element.progress), bounds.Max.Y) - // TODO: maybe dont use the accent color here... - accent := element.theme.Color(theme.ColorAccent, theme.State { }) - shapes.FillColorRectangle(element.core, accent, meterBounds) + mercury := element.theme.Pattern(theme.PatternMercury, theme.State { }) + artist.DrawBounds(element.core, mercury, meterBounds) } diff --git a/theme/assets/wintergreen.png b/theme/assets/wintergreen.png index ea67ed8bb08ef8eb8e8414fdd528e129ef496747..23657895556988aacd24123ed3d2b695f32950dc 100644 GIT binary patch delta 1911 zcmZvdeLU3J9>;%!F`C6sYsS*v_BVH}hf>VSjbvy1iZ&5LHwrOYyD6zj2u<^wX0!=i zETI&Vhe?K3d48Nth+TH_xE|-m8hIGT7#bS(r|tfC|2XG+&i6dLKJRnB-*Z;=ib11W zG}mvn@Z0bJ0BAWKcJM&02rWq(s%UL2O#c)$uE|C&$hFcM|BU>zw?CBw0M5Y40seR7 z;M{%8N4Ca-9F2_9XplQ0|aPp*{97q&NdePchrGQvXE-##Dq z`u*{|g%0Lk$?U}^b-FJ72m&9hm!WgS7$mAy28fC8Cpk|nTG|1IC#>2OiG4EdTwmnm z4qtKm;UP!%SskCFQ6<{{&d9HK_Jkno=Q4x9Vq){z)2~d=MaO%a>05)WEAf=|I>P(M z_zO01o%{qBLh%>f+G9^LTdN49J&w+B=Q zY7}(4oPp!Pg%Q0iYrmt{ANVJ5o2iB(r=BBo zE??c{Jt?o|Xyu)>f?}fYfi-eFC}ym&ra$po2|ZMi^V(e5r4)Vr8vJ2Z?6@a1{*Ac2 z`}7Fnp!*g#!@j9=L}+XoZTH1G3-icy$xum0-CQ)%_DNH*s>K1R-?OFoPKCh4v<(;^ z-dv+gBX@#(lUYx-BmJzRu;Tah?F-NU+m3JHJ7ylH>Z?_ia4rQ4jbDS31A=|jj3(0} zFGeS5Opco--j{J}X=sN6!@fxTpjoW)u>Epf&@Q^(%#=TC;ZNL$YDI#3*)l~@=Io%y zu^8WB`&YO-o3cf?qnKk$+|SLb8ld450=ZJuf-jlJS$i_BQEfh%QV0f^wPnE3CZYJ7E8yb8jktZ=<+Q|lnRQ-_*5&SI&Nq4&nl$i1nt z>}aZKY3Z7F)~I>na-ZrE>OeNF?+OAGQga^|s~c2~LXOf%4c;=>P&fo=nAkW`ZGgB{ z4CB6W0p>xpzo7eXSU(i@?%%M(AhP8nY^b#W_z2raOIi~E{|9y=*j*ikvTQm|oRE>Q z1QsJ5e*~dsh|thLk3|cOZzxc=eu=BDd##ugO>Ij%_Zr-`tCxutr=Oc-C4HnkLgisy zM!|D)9;*sb6k+OoGWbR@kcMq=Yq5jW?3HVQ6Y_F9=+~#CwvdCp9PL&FbjSoQ7gQ_s z6ggF6nt%y`@Ok6zF5I4K=@8;QdwvE`bpNP{rWlqTcxFhCk<77#G*uGXah0B^liZXM z=7ej@4m-_G0?h&6<1hvEu(yVWv(X$Z*o($<40GX+N4)iGmgjSCGHLraZwkrQ^eKWV zyi-L&rVfTbW^zyldz5zKFpMknDZ;`0C7Gad>p}?3L=GuQLuK~Vpe*pqoWY}QGr>`m zjDY%Z3Qui`m^u`_DYMv1%zd-$3$%^I48IFO$6wtZ>pQ)v^Z=})p%AIqmpyyH{-5Y@ zHq+=JW`s%BBpaV9!zOrr0K0|0FQRqA2dP2Dr@YB=H44$QZ-q`LYONlU?oRy^>TzBA z8Q(IPzXkisLu(P9^SC|91@wc{c~G8$^ca-@@Lbb>>cEAwH!y}gw}Kj6kUIK_0#1&u K4kZWZoWB6yw4jFo delta 2893 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 diff --git a/theme/default.go b/theme/default.go index 834d285..1141309 100644 --- a/theme/default.go +++ b/theme/default.go @@ -14,7 +14,7 @@ import "git.tebibyte.media/sashakoshka/tomo/artist/patterns" //go:embed assets/wintergreen.png var defaultAtlasBytes []byte var defaultAtlas canvas.Canvas -var defaultTextures [13][9]artist.Pattern +var defaultTextures [14][9]artist.Pattern func atlasCell (col, row int, border artist.Inset) { bounds := image.Rect(0, 0, 16, 16).Add(image.Pt(col, row).Mul(16)) @@ -52,6 +52,8 @@ func init () { atlasCol(7, artist.Inset { 6, 6, 6, 6 }) // PatternLine atlasCol(8, artist.Inset { 1, 1, 1, 1 }) + // PatternMercury + atlasCol(13, artist.Inset { 2, 2, 2, 2 }) // PatternButton: basic.checkbox atlasCol(9, artist.Inset { 3, 3, 3, 3 }) @@ -122,6 +124,7 @@ func (Default) Pattern (id Pattern, state State, c Case) artist.Pattern { case PatternGutter: return defaultTextures[6][offset] case PatternHandle: return defaultTextures[7][offset] case PatternLine: return defaultTextures[8][offset] + case PatternMercury: return defaultTextures[13][offset] default: return patterns.Uhex(0xFF00FFFF) } } @@ -150,6 +153,8 @@ func (Default) Padding (id Pattern, c Case) artist.Inset { case PatternSunken: if c == C("basic", "list") { return artist.Inset { 4, 0, 3, 0 } + } else if c == C("basic", "progressBar") { + return artist.Inset { 2, 1, 1, 2 } } else { return artist.Inset { 8, 8, 8, 8 } } @@ -161,6 +166,7 @@ func (Default) Padding (id Pattern, c Case) artist.Inset { } case PatternGutter: return artist.Inset { } case PatternLine: return artist.Inset { 1, 1, 1, 1 } + case PatternMercury: return artist.Inset { 5, 5, 5, 5 } default: return artist.Inset { 8, 8, 8, 8 } } } diff --git a/theme/theme.go b/theme/theme.go index 64a70f5..840c599 100644 --- a/theme/theme.go +++ b/theme/theme.go @@ -50,6 +50,9 @@ type Pattern int; const ( // PatternLine is an engraved line that separates things. PatternLine + + // PatternMercury is a fill pattern for progress bars, meters, etc. + PatternMercury ) type Color int; const ( From 1f2e8aa6770f0eb1106ba3869eb5eb80766a2ef9 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 1 Mar 2023 13:06:34 -0500 Subject: [PATCH 21/21] Some final theme tweaks --- theme/assets/wintergreen.png | Bin 1964 -> 1948 bytes theme/default.go | 2 +- theme/defaultpatterns.go | 4 ---- 3 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 theme/defaultpatterns.go diff --git a/theme/assets/wintergreen.png b/theme/assets/wintergreen.png index 23657895556988aacd24123ed3d2b695f32950dc..154a6d8b81561fa518b8abe5ee99d4eafcdc1916 100644 GIT binary patch delta 1843 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 P0j#;Z9d#``z~KBBW2clb diff --git a/theme/default.go b/theme/default.go index 1141309..061db58 100644 --- a/theme/default.go +++ b/theme/default.go @@ -49,7 +49,7 @@ func init () { // PatternGutter atlasCol(6, artist.Inset { 7, 7, 7, 7 }) // PatternHandle - atlasCol(7, artist.Inset { 6, 6, 6, 6 }) + atlasCol(7, artist.Inset { 3, 3, 3, 3 }) // PatternLine atlasCol(8, artist.Inset { 1, 1, 1, 1 }) // PatternMercury diff --git a/theme/defaultpatterns.go b/theme/defaultpatterns.go deleted file mode 100644 index 2ec48b5..0000000 --- a/theme/defaultpatterns.go +++ /dev/null @@ -1,4 +0,0 @@ -package theme - -// import "image/color" -// import "git.tebibyte.media/sashakoshka/tomo/artist"