Compare commits

..

27 Commits

Author SHA1 Message Date
d35b0532a1 Speaks english for 21 years and still has no idea how to spell 2024-06-07 19:22:13 -04:00
dffe766328 Rename theme.go to style.go 2024-06-07 19:19:35 -04:00
c1d77028b4 Add a note that reccomended sizes arent always respected 2024-06-07 19:15:33 -04:00
83b2c3c665 Add system for reccomending widths/heights
Remedy #6
2024-06-07 19:13:22 -04:00
89307e0dd6 Slight wording fix 2024-06-07 19:06:18 -04:00
5d9c584743 Document that windows are supposed to be transparent
Remedy #16
2024-06-07 19:05:38 -04:00
fd7d2b7ead Add Window.SetBounds
Remedy #17
2024-06-07 18:59:43 -04:00
ca4d3e62c4 Remedy #10 2024-06-07 18:57:16 -04:00
d587e39506 Separate icon functionality out of Style and into Icons 2024-06-07 18:27:26 -04:00
f0c092be8a Break some scalars, vectors, and enums into a separate file 2024-06-07 18:21:15 -04:00
06f45c80e2 Change Theme to Style 2024-06-07 18:19:43 -04:00
71171d762c Add a "SetResizable" behavior to Window 2024-06-07 14:11:36 -04:00
341eea5b5d Remove references to MainWindow 2024-06-07 00:56:29 -04:00
453953bbcd Unify Window and MainWindow 2024-06-07 00:54:14 -04:00
31ce286bc7 Window icons are set using textures 2024-06-06 20:15:00 -04:00
26cf1f4a88 Roles are stored in Boxes now 2024-06-03 19:57:20 -04:00
6ef1f7ba78 Add OnSizeChange to SurfaceBox
Remedy #15
2024-06-03 19:37:34 -04:00
d7093be696 Update go.sum 2024-06-03 19:33:07 -04:00
64bf8f3b2d NewCanvas actually returns a Canvas 2024-06-03 19:32:47 -04:00
50cbac17a9 CanvasCloser is now actually a valid type 2024-06-03 19:32:06 -04:00
d096d40c57 Add a way for applications to request canvases
Makes fix to #14 useful
2024-06-03 19:29:41 -04:00
34358da6eb Add canvas.CanvasCloser
Remedy #14
2024-06-03 19:28:03 -04:00
be1b9d77e8 Instruct themes to return nil icons if the icon doesn't exist 2024-05-28 22:01:56 -04:00
d71ac02748 Add unchecked checkbox icon 2024-05-27 15:10:11 -04:00
2bbf85ebd8 Add more missing icons 2024-05-27 04:02:22 -04:00
320535e7f3 Remove Box.Visible, box.SetVisible
It was a bad idea. Remedy #12.
2024-05-26 17:42:11 -04:00
775fb84e9b Add back some missing icons 2024-05-26 17:42:04 -04:00
8 changed files with 257 additions and 179 deletions

View File

@@ -17,18 +17,33 @@ type Backend interface {
NewSurfaceBox () (SurfaceBox, error)
NewContainerBox () ContainerBox
// NewWindow creates a normal MainWindow and returns it.
NewWindow (image.Rectangle) (MainWindow, error)
// NewWindow creates a normal Window and returns it.
NewWindow (image.Rectangle) (Window, error)
// NewPlainWindow creates an undecorated window that does not appear in
// window lists and returns it. This is intended for making things like
// panels, docks, etc.
NewPlainWindow (image.Rectangle) (MainWindow, error)
NewPlainWindow (image.Rectangle) (Window, error)
// NewTexture creates a new texture from an image. The backend must
// reject any texture that was not made by it.
NewTexture (image.Image) canvas.TextureCloser
// NewCanvas creates a new canvas with the specified bounds. The backend
// must reject any canvas that was not made by it.
NewCanvas (image.Rectangle) canvas.CanvasCloser
// SetStyle sets the style that will be used on objects. The backend is
// in charge of applying the style to objects. When this method is
// called, it must propagate a StyleChange event to all boxes it is
// keeping track of.
SetStyle (Style)
// SetIcons sets the icon set that icons will be pulled from. When this
// method is called, it must propagate an IconChange event to all boxes
// it is keeping track of.
SetIcons (Icons)
// Run runs the event loop until Stop() is called, or the backend
// experiences a fatal error.
Run () error

View File

@@ -2,6 +2,7 @@
// primitives.
package canvas
import "io"
import "image"
import "image/draw"
import "image/color"
@@ -77,6 +78,13 @@ type Canvas interface {
SubCanvas (image.Rectangle) Canvas
}
// CanvasCloser is a canvas that can be closed. Anything that receives a
// CanvasCloser must close it after use.
type CanvasCloser interface {
Canvas
io.Closer
}
// Drawer can draw to a canvas.
type Drawer interface {
// Draw draws to the given canvas.

3
go.sum
View File

@@ -1,8 +1,6 @@
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg=
golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM=
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -27,7 +25,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=

81
icon.go
View File

@@ -35,19 +35,9 @@ const (
IconCallStart Icon = "CallStart"
IconCallStop Icon = "CallStop"
IconContactNew Icon = "ContactNew"
// actions: file
IconFileNew Icon = "FileNew"
IconDirectoryNew Icon = "DirectoryNew"
IconFileOpen Icon = "FileOpen"
IconFileOpenRecent Icon = "FileOpenRecent"
IconFilePageSetup Icon = "FilePageSetup"
IconFilePrint Icon = "FilePrint"
IconFilePrintPreview Icon = "FilePrintPreview"
IconFileProperties Icon = "FileProperties"
IconFileRevert Icon = "FileRevert"
IconFileSave Icon = "FileSave"
IconFileSaveAs Icon = "FileSaveAs"
IconFileSend Icon = "FileSend"
// actions: dialog
IconDialogOkay Icon = "DialogOkay"
IconDialogCancel Icon = "DialogCancel"
// actions: edit
IconEditClear Icon = "EditClear"
IconEditCopy Icon = "EditCopy"
@@ -59,6 +49,21 @@ const (
IconEditRedo Icon = "EditRedo"
IconEditSelectAll Icon = "EditSelectAll"
IconEditUndo Icon = "EditUndo"
// actions: file
IconFileNew Icon = "FileNew"
IconDirectoryNew Icon = "DirectoryNew"
IconFileOpen Icon = "FileOpen"
IconFileOpenRecent Icon = "FileOpenRecent"
IconFilePageSetup Icon = "FilePageSetup"
IconFilePrint Icon = "FilePrint"
IconFilePrintPreview Icon = "FilePrintPreview"
IconFilePermissions Icon = "FilePermissions"
IconFileProperties Icon = "FileProperties"
IconFileRename Icon = "FileRename"
IconFileRevert Icon = "FileRevert"
IconFileSave Icon = "FileSave"
IconFileSaveAs Icon = "FileSaveAs"
IconFileSend Icon = "FileSend"
// actions: format
IconFormatIndentLess Icon = "FormatIndentLess"
IconFormatIndentMore Icon = "FormatIndentMore"
@@ -73,7 +78,6 @@ const (
IconFormatTextUnderline Icon = "FormatTextUnderline"
IconFormatTextStrikethrough Icon = "FormatTextStrikethrough"
// actions: go
IconGo Icon = "Go"
IconGoBottom Icon = "GoBottom"
IconGoDown Icon = "GoDown"
IconGoFirst Icon = "GoFirst"
@@ -134,6 +138,10 @@ const (
IconSystemShutdown Icon = "SystemShutdown"
// actions: tools
IconToolsCheckSpelling Icon = "ToolsCheckSpelling"
// actions: value
IconValueIncrement Icon = "ValueIncrement"
IconValueDecrement Icon = "ValueDecrement"
IconValueReset Icon = "ValueReset"
// actions: view
IconViewFullscreen Icon = "ViewFullscreen"
IconViewRefresh Icon = "ViewRefresh"
@@ -192,7 +200,6 @@ const (
IconApplicationsSystem Icon = "ApplicationsSystem"
IconApplicationsUtilities Icon = "ApplicationsUtilities"
// categories: preferences
IconPreferences Icon = "Preferences"
IconPreferencesDesktop Icon = "PreferencesDesktop"
IconPreferencesPeripherals Icon = "PreferencesPeripherals"
@@ -224,6 +231,7 @@ const (
IconPowerBattery Icon = "PowerBattery"
// devices: storage
IconStorageHardDisk Icon = "StorageHardDisk"
IconStorageFloppyDisk Icon = "StorageFloppyDisk"
IconStorageSolidState Icon = "StorageSolidState"
IconStorageOptical Icon = "StorageOptical"
IconStorageFlashStick Icon = "StorageFlashStick"
@@ -277,6 +285,9 @@ const (
IconPlaceHistory Icon = "PlaceHistory"
IconPlacePreferences Icon = "PlacePreferences"
// status: checkbox
IconCheckboxChecked Icon = "CheckboxChecked"
IconCheckboxUnchecked Icon = "CheckboxUnchecked"
// status: appointments
IconAppointmentMissed Icon = "AppointmentMissed"
IconAppointmentSoon Icon = "AppointmentSoon"
@@ -371,12 +382,44 @@ const (
// Texture returns a texture of the corresponding icon ID.
func (id Icon) Texture (size IconSize) canvas.Texture {
if theme == nil { return nil }
return theme.Icon(id, size)
if icons == nil { return nil }
return icons.Icon(id, size)
}
// MimeIcon returns an icon corresponding to a MIME type.
func MimeIcon (mime data.Mime, size IconSize) canvas.Texture {
if theme == nil { return nil }
return theme.MimeIcon(mime, size)
if icons == nil { return nil }
return icons.MimeIcon(mime, size)
}
// Icons holds a set of icon textures.
type Icons interface {
// A word on textures:
//
// Because textures can be linked to some resource that is outside of
// the control of Go's garbage collector, methods of Icons must not
// allocate new copies of a texture each time they are called. It is
// fine to lazily load textures and save them for later use, but the
// same texture must never be allocated multiple times as this could
// cause a memory leak.
//
// As such, textures returned by these methods must be protected.
// Icon returns a texture of the corresponding icon ID. If there is no
// suitable option, it should return nil.
Icon (Icon, IconSize) canvas.Texture
// MimeIcon returns a texture of an icon corresponding to a MIME type.
// If there is no suitable specific option, it should return a more
// generic icon or a plain file icon.
MimeIcon (data.Mime, IconSize) canvas.Texture
}
var icons Icons
// SetIcons sets the icon set.
func SetIcons (icns Icons) {
assertBackend()
icons = icns
backend.SetIcons(icns)
}

163
object.go
View File

@@ -9,98 +9,6 @@ import "git.tebibyte.media/tomo/tomo/event"
import "git.tebibyte.media/tomo/tomo/input"
import "git.tebibyte.media/tomo/tomo/canvas"
// Side represents one side of a rectangle.
type Side int; const (
SideTop Side = iota
SideRight
SideBottom
SideLeft
)
// Inset represents a rectangle inset that can have a different value for each
// side.
type Inset [4]int
// I allows you to create an inset in a CSS-ish way:
//
// - One argument: all sides are set to this value
// - Two arguments: the top and bottom sides are set to the first value, and
// the left and right sides are set to the second value.
// - Three arguments: the top side is set by the first value, the left and
// right sides are set by the second vaue, and the bottom side is set by the
// third value.
// - Four arguments: each value corresponds to a side.
//
// This function will panic if an argument count that isn't one of these is
// given.
func I (sides ...int) Inset {
switch len(sides) {
case 1: return Inset { sides[0], sides[0], sides[0], sides[0] }
case 2: return Inset { sides[0], sides[1], sides[0], sides[1] }
case 3: return Inset { sides[0], sides[1], sides[2], sides[1] }
case 4: return Inset { sides[0], sides[1], sides[2], sides[3] }
default: panic("I: illegal argument count.")
}
}
// Apply returns the given rectangle, shrunk on all four sides by the given
// inset. If a measurment of the inset is negative, that side will instead be
// expanded outward. If the rectangle's dimensions cannot be reduced any
// further, an empty rectangle near its center will be returned.
func (inset Inset) Apply (bigger image.Rectangle) (smaller image.Rectangle) {
smaller = bigger
if smaller.Dx() < inset[3] + inset[1] {
smaller.Min.X = (smaller.Min.X + smaller.Max.X) / 2
smaller.Max.X = smaller.Min.X
} else {
smaller.Min.X += inset[3]
smaller.Max.X -= inset[1]
}
if smaller.Dy() < inset[0] + inset[2] {
smaller.Min.Y = (smaller.Min.Y + smaller.Max.Y) / 2
smaller.Max.Y = smaller.Min.Y
} else {
smaller.Min.Y += inset[0]
smaller.Max.Y -= inset[2]
}
return
}
// Inverse returns a negated version of the inset.
func (inset Inset) Inverse () (prime Inset) {
return Inset {
inset[0] * -1,
inset[1] * -1,
inset[2] * -1,
inset[3] * -1,
}
}
// Horizontal returns the sum of SideRight and SideLeft.
func (inset Inset) Horizontal () int {
return inset[SideRight] + inset[SideLeft]
}
// Vertical returns the sum of SideTop and SideBottom.
func (inset Inset) Vertical () int {
return inset[SideTop] + inset[SideBottom]
}
// Border represents a single border of a box.
type Border struct {
Width Inset
Color [4]color.Color
}
// Align lists basic alignment types.
type Align int; const (
AlignStart Align = iota // similar to left-aligned text
AlignMiddle // similar to center-aligned text
AlignEnd // similar to right-aligned text
AlignEven // similar to justified text
)
// Object is any onscreen object that is linked to a box (or is that box).
// Unlike the Box interface and associated interfaces, Object implementations
// may originate from anywhere.
@@ -144,6 +52,8 @@ type Box interface {
// internal content that does not overflow, the size of that is also
// taken into account here.
MinimumSize () image.Point
// Role returns this Box's role as set by SetRole.
Role () Role
// SetBounds sets the bounding rectangle of this Box relative to the
// Window.
SetBounds (image.Rectangle)
@@ -168,12 +78,9 @@ type Box interface {
// SetPadding sets the padding between the Box's innermost Border and
// its content.
SetPadding (Inset)
// SetVisible sets whether or not this box is visible. If invisible,
// it will not be drawn and no space will be created for it in the
// layout. Boxes are visible by default.
SetVisible (bool)
// Visible returns whether or not this box is visible.
Visible () bool
// SetRole sets what role this Box takes on. It is used to apply styling
// from a theme.
SetRole (Role)
// SetDNDData sets the data that will be picked up if this Box is
// dragged. If this is nil (which is the default), this Box will not be
@@ -217,6 +124,8 @@ type Box interface {
OnScroll (func (deltaX, deltaY float64)) event.Cookie
OnKeyDown (func (key input.Key, numberPad bool)) event.Cookie
OnKeyUp (func (key input.Key, numberPad bool)) event.Cookie
OnStyleChange (func ()) event.Cookie
OnIconsChange (func ()) event.Cookie
}
// CanvasBox is a box that can be drawn to.
@@ -250,9 +159,16 @@ type SurfaceBox interface {
// expected type was not found.
Surface () any
// Invalidate causes the Box's area to be redrawn at the end of the
// event cycle, even if it wouldn't be otherwise.
// Invalidate causes the data within the surface to be pushed to the
// screen at the end of the event cycle, even if it wouldn't be
// otherwise.
Invalidate ()
// OnSizeChange specifies a function to be called when the size of the
// Box is changed, or the surface is re-allocated. The application must
// call Surface() each time this event fires in order to not draw to a
// closed surface.
OnSizeChange (func ())
}
// ContentBox is a box that has some kind of content.
@@ -269,6 +185,14 @@ type ContentBox interface {
// ContentBounds returns the bounds of the inner content of the Box
// relative to the Box's InnerBounds.
ContentBounds () image.Rectangle
// RecommendedHeight returns the recommended height for a given width,
// if supported by the current content or layout. Otherwise, it should
// just return the minimum height.
RecommendedHeight(width int) int
// RecommendedWidth returns the recommended width for a given height, if
// supported by the current content or layout. Otherwise, it should just
// return the minimum width.
RecommendedWidth(height int) int
// ScrollTo shifts the origin of the Box's content to the origin of the
// Box's InnerBounds, offset by the given point.
ScrollTo (image.Point)
@@ -365,9 +289,21 @@ type Layout interface {
MinimumSize (LayoutHints, []Box) image.Point
// Arrange arranges child boxes according to the given LayoutHints.
Arrange (LayoutHints, []Box)
// RecommendedHeight returns the recommended height for a given width,
// if supported. Otherwise, it should just return the minimum height.
// The result of this behavior may or may not be respected, depending on
// the situation.
RecommendedHeight (width int) int
// RecommendedWidth returns the recommended width for a given height, if
// supported. Otherwise, it should just return the minimum width. The
// result of this behavior may or may not be respected, depending on the
// situation.
RecommendedWidth (height int) int
}
// Window is an operating system window. It can contain one object.
// Window is an operating system window. It can contain one object. Windows
// themselves are completely transparent, and become opaque once an opaque
// object is added as their root.
type Window interface {
// SetRoot sets the root child of the window. There can only be one at
// a time, and setting it will remove the current child if there is one.
@@ -376,12 +312,24 @@ type Window interface {
SetTitle (string)
// SetIcon sets the icon of the window. When multiple icon sizes are
// provided, the best fitting one is chosen for display.
SetIcon (... image.Image)
SetIcon (... canvas.Texture)
// SetResizable sets whether the window can be resized by the user in
// the X and Y directions. If one or both axes are false, the ones that
// are will be shrunk to the window's minimum size.
SetResizable (x, y bool)
// SetBounds sets this window's bounds. This may or may not have any
// effect on the window's position on screen depending on the platform.
SetBounds (image.Rectangle)
// Widget returns a window representing a smaller iconified form of this
// window. How exactly this window is used depends on the platform.
// Subsequent calls to this method on the same window will return the
// same window object.
Widget () (Window, error)
// NewChild creates a new window that is semantically a child of this
// window. It does not actually reside within this window, but it may be
// linked to it via some other means. This is intended for things like
// toolboxes and tear-off menus.
NewChild (image.Rectangle) (Window, error)
// NewMenu creates a new menu window. This window is undecorated and
// will close once the user clicks outside of it.
NewMenu (image.Rectangle) (Window, error)
@@ -403,14 +351,3 @@ type Window interface {
// OnClose specifies a function to be called when the window is closed.
OnClose (func ()) event.Cookie
}
// MainWindow is a top-level operating system window.
type MainWindow interface {
Window
// NewChild creates a new window that is semantically a child of this
// window. It does not actually reside within this window, but it may be
// linked to it via some other means. This is intended for things like
// toolboxes and tear-off menus.
NewChild (image.Rectangle) (Window, error)
}

View File

@@ -1,9 +1,7 @@
package tomo
import "fmt"
import "git.tebibyte.media/tomo/tomo/data"
import "git.tebibyte.media/tomo/tomo/event"
import "git.tebibyte.media/tomo/tomo/canvas"
// Role describes the role of an object.
type Role struct {
@@ -60,49 +58,26 @@ func (c Color) String () string {
// RGBA satisfies the color.Color interface.
func (id Color) RGBA () (r, g, b, a uint32) {
if theme == nil { return }
return theme.RGBA(id)
if style == nil { return }
return style.RGBA(id)
}
// Theme can apply a visual style to different objects.
type Theme interface {
// A word on textures:
//
// Because textures can be linked to some resource that is outside of
// the control of Go's garbage collector, methods of Theme must not
// allocate new copies of a texture each time they are called. It is
// fine to lazily load textures and save them for later use, but the
// same texture must never be allocated multiple times as this could
// cause a memory leak.
//
// As such, textures returned by these methods must be protected.
// Apply applies the theme to the given object, according to the given
// role. This may register event listeners with the given object;
// closing the returned cookie must remove them.
Apply (Object, Role) event.Cookie
// Style can apply a visual style to different objects.
type Style interface {
// Apply applies the theme to the given object, according to its role.
// This may register event listeners with the given object; closing the
// returned cookie must remove them.
Apply (Object) event.Cookie
// RGBA returns the RGBA values of the corresponding color ID.
RGBA (Color) (r, g, b, a uint32)
// Icon returns a texture of the corresponding icon ID.
Icon (Icon, IconSize) canvas.Texture
// MimeIcon returns an icon corresponding to a MIME type.
MimeIcon (data.Mime, IconSize) canvas.Texture
}
var theme Theme
var style Style
// SetTheme sets the theme.
func SetTheme (them Theme) {
theme = them
}
// Apply applies the current theme to the given object, according to the given
// role. This may register event listeners with the given object; closing the
// returned cookie will remove them.
func Apply (object Object, role Role) event.Cookie {
if theme == nil { return event.NoCookie { } }
return theme.Apply(object, role)
// SetStyle sets the style.
func SetStyle (sty Style) {
assertBackend()
style = sty
backend.SetStyle(sty)
}

11
tomo.go
View File

@@ -50,7 +50,7 @@ func Do (callback func ()) {
}
// NewWindow creates and returns a window within the specified bounds on screen.
func NewWindow (bounds image.Rectangle) (MainWindow, error) {
func NewWindow (bounds image.Rectangle) (Window, error) {
assertBackend()
return backend.NewWindow(bounds)
}
@@ -58,7 +58,7 @@ func NewWindow (bounds image.Rectangle) (MainWindow, error) {
// NewPlainWindow is like NewWindow, but it creates an undecorated window that
// does not appear in window lists. It is intended for creating things like
// docks, panels, etc.
func NewPlainWindow (bounds image.Rectangle) (MainWindow, error) {
func NewPlainWindow (bounds image.Rectangle) (Window, error) {
assertBackend()
return backend.NewPlainWindow(bounds)
}
@@ -93,3 +93,10 @@ func NewTexture (source image.Image) canvas.TextureCloser {
assertBackend()
return backend.NewTexture(source)
}
// NewCanvas creates a new canvas with the specified bounds. When no longer in
// use, it must be freed using Close().
func NewCanvas (bounds image.Rectangle) canvas.CanvasCloser {
assertBackend()
return backend.NewCanvas(bounds)
}

96
unit.go Normal file
View File

@@ -0,0 +1,96 @@
package tomo
import "image"
import "image/color"
// Side represents one side of a rectangle.
type Side int; const (
SideTop Side = iota
SideRight
SideBottom
SideLeft
)
// Inset represents a rectangle inset that can have a different value for each
// side.
type Inset [4]int
// I allows you to create an inset in a CSS-ish way:
//
// - One argument: all sides are set to this value
// - Two arguments: the top and bottom sides are set to the first value, and
// the left and right sides are set to the second value.
// - Three arguments: the top side is set by the first value, the left and
// right sides are set by the second vaue, and the bottom side is set by the
// third value.
// - Four arguments: each value corresponds to a side.
//
// This function will panic if an argument count that isn't one of these is
// given.
func I (sides ...int) Inset {
switch len(sides) {
case 1: return Inset { sides[0], sides[0], sides[0], sides[0] }
case 2: return Inset { sides[0], sides[1], sides[0], sides[1] }
case 3: return Inset { sides[0], sides[1], sides[2], sides[1] }
case 4: return Inset { sides[0], sides[1], sides[2], sides[3] }
default: panic("I: illegal argument count.")
}
}
// Apply returns the given rectangle, shrunk on all four sides by the given
// inset. If a measurment of the inset is negative, that side will instead be
// expanded outward. If the rectangle's dimensions cannot be reduced any
// further, an empty rectangle near its center will be returned.
func (inset Inset) Apply (bigger image.Rectangle) (smaller image.Rectangle) {
smaller = bigger
if smaller.Dx() < inset[3] + inset[1] {
smaller.Min.X = (smaller.Min.X + smaller.Max.X) / 2
smaller.Max.X = smaller.Min.X
} else {
smaller.Min.X += inset[3]
smaller.Max.X -= inset[1]
}
if smaller.Dy() < inset[0] + inset[2] {
smaller.Min.Y = (smaller.Min.Y + smaller.Max.Y) / 2
smaller.Max.Y = smaller.Min.Y
} else {
smaller.Min.Y += inset[0]
smaller.Max.Y -= inset[2]
}
return
}
// Inverse returns a negated version of the inset.
func (inset Inset) Inverse () (prime Inset) {
return Inset {
inset[0] * -1,
inset[1] * -1,
inset[2] * -1,
inset[3] * -1,
}
}
// Horizontal returns the sum of SideRight and SideLeft.
func (inset Inset) Horizontal () int {
return inset[SideRight] + inset[SideLeft]
}
// Vertical returns the sum of SideTop and SideBottom.
func (inset Inset) Vertical () int {
return inset[SideTop] + inset[SideBottom]
}
// Border represents a single border of a box.
type Border struct {
Width Inset
Color [4]color.Color
}
// Align lists basic alignment types.
type Align int; const (
AlignStart Align = iota // similar to left-aligned text
AlignMiddle // similar to center-aligned text
AlignEnd // similar to right-aligned text
AlignEven // similar to justified text
)