Compare commits

...

70 Commits

Author SHA1 Message Date
Sasha Koshka a9fc52c55b Change clip behaviors to sub behaviors
Remedies #4
2024-05-15 01:37:21 -04:00
Sasha Koshka e745e80f09 Add more information to README.md 2024-05-14 12:43:40 -04:00
Sasha Koshka 748996c789 Be more descriptive with what a path is 2024-05-14 12:34:42 -04:00
Sasha Koshka d4aac7e26c Set CapRound as the default cap style 2024-05-14 12:32:44 -04:00
Sasha Koshka 3d645b8064 Tweak doc comments for canvas.Pen 2024-05-14 12:32:09 -04:00
Sasha Koshka bde1a2bc34 Remove wierd spacing from box interface definitions
Seemed like a good idea at the time
2024-05-14 12:20:44 -04:00
Sasha Koshka d43ba6b1af Replace Window's Show/Hide methods with Visible/SetVisible 2024-05-14 12:18:21 -04:00
Sasha Koshka 096c1b5e1b Add Visible() and SetVisible() to Box 2024-05-14 12:17:14 -04:00
Sasha Koshka c44ff4a34a Change ContainerBox.Delete() to Remove()
Remedy #3
2024-05-14 12:11:00 -04:00
Sasha Koshka dc02f4dfb4 Remedy #1 2024-05-14 12:09:34 -04:00
Sasha Koshka 7c30b2bac0 Add requirement for ContainerBox.Add to remove a previously
parented object first
2024-05-14 12:03:10 -04:00
Sasha Koshka 24264bbc91 Fix capitalization errors with previous commit 2024-05-14 11:58:48 -04:00
Sasha Koshka f167af3281 Describe the canvas used by CanvasBox in detail 2024-05-14 11:57:48 -04:00
Sasha Koshka 9a5b4ee7e8 Add home icon 2024-05-05 02:53:07 -04:00
Sasha Koshka 3e561d7bf0 Fix wording of theme.Theme doc comment 2024-05-03 13:46:50 -04:00
Sasha Koshka b96e8f744f Namespace icon IDs 2024-05-03 13:21:35 -04:00
Sasha Koshka 9aa6f2900e Added application role icons 2024-05-03 13:10:44 -04:00
Sasha Koshka a3fc0ce66d Remove ApplicationIcon from Theme
Since Icon can now take in arbitrary strings, we can just have it
serve application icons as well.
2024-05-03 13:08:14 -04:00
Sasha Koshka 65bf341514 Icon IDs are now strings 2024-05-03 13:07:27 -04:00
Sasha Koshka 042f2f0131 Completely remove plugin system
Plugins may be moved to Nasin
2024-04-30 13:12:34 -04:00
Sasha Koshka 4fd5e54e42 Add editorconfig 2024-04-29 16:23:46 -04:00
Sasha Koshka 9a9e546b37 Remove application framework stuff 2024-04-29 00:39:17 -04:00
Sasha Koshka a2a5c16e38 Changed the semantics of ContentBounds 2023-09-14 14:47:33 -04:00
Sasha Koshka 28cd889254 Removed tiler for now, needs to be rethought a bit 2023-09-08 20:57:15 -04:00
Sasha Koshka e682fdd9d8 Add icon for switch 2023-09-08 20:57:00 -04:00
Sasha Koshka 9719391e5d NewApplicationWindow returns MainWindow nows 2023-09-08 16:29:03 -04:00
Sasha Koshka 8a531986eb What??? 2023-09-07 18:25:35 -04:00
Sasha Koshka c3c6ff61f5 Add Tiler interface 2023-09-05 18:14:36 -04:00
Sasha Koshka 89f7bf47ce Add ability to create an undecorated window 2023-09-05 13:21:59 -04:00
Sasha Koshka bebd58dac1 Added event capturing to containers 2023-09-05 13:10:35 -04:00
Sasha Koshka f9a85fd949 Added filesystems for application data 2023-09-04 13:48:03 -04:00
Sasha Koshka 6ac653adb6 Made ownership of textures more explicit 2023-09-04 12:21:17 -04:00
Sasha Koshka 7b28419432 Texture must now implement Image 2023-09-04 12:07:29 -04:00
Sasha Koshka 57e6a9ff21 lolll whoops 2023-09-04 02:56:00 -04:00
Sasha Koshka a06f94e41b Added support for defining applications as objects 2023-09-04 02:26:21 -04:00
Sasha Koshka 4d157756eb Added a String method to a bunch of stuff 2023-09-04 01:47:03 -04:00
Sasha Koshka 63a67e40d1 Added a Bounds() method to Texture 2023-09-04 01:28:04 -04:00
Sasha Koshka b629b4eb4e Cleared up wording around theme textures 2023-08-29 15:52:07 -04:00
Sasha Koshka 7510047ef3 Fix package name errors in theme 2023-08-24 16:30:10 -04:00
Sasha Koshka fdea479ee7 Textures have been moved to the canvas module 2023-08-24 01:01:40 -04:00
Sasha Koshka dc31de0ecb Textures are now actually used 2023-08-23 18:04:54 -04:00
Sasha Koshka a8d5a64837 Added SetTexture on Object 2023-08-21 21:52:51 -04:00
Sasha Koshka 50697e4369 All icons now return textures 2023-08-20 18:41:46 -04:00
Sasha Koshka 3614979e35 Add a function to protect textures 2023-08-20 18:33:20 -04:00
Sasha Koshka 5f5b928528 Icons now use textures 2023-08-20 18:28:30 -04:00
Sasha Koshka b62b846745 AhhhahsdjashdMerge branch 'main' of git.tebibyte.media:tomo/tomo 2023-08-20 17:58:45 -04:00
Sasha Koshka 35c6e167be Added a texture interface 2023-08-20 17:54:06 -04:00
Sasha Koshka 6ced7d1372 Fixed syntax errors 2023-08-12 00:56:13 -04:00
Sasha Koshka e259f122c7 Added an icon API 2023-08-12 00:51:16 -04:00
Sasha Koshka 8e25397a05 Theme roles now have a variant field 2023-08-10 17:49:22 -04:00
Sasha Koshka 2884604fdd Renamed Object.Box to GetBox to resolve naming conflicts 2023-08-10 17:48:40 -04:00
Sasha Koshka f99d9e0d2a Upgrade x/image 2023-08-09 20:55:46 -04:00
Sasha Koshka c785fb461c Colors now operate more sensibly 2023-08-09 15:13:19 -04:00
Sasha Koshka 487471d7a9 Add colors 2023-08-09 12:08:17 -04:00
Sasha Koshka 2f5421a5c9 Add Role constructor 2023-08-08 03:00:41 -04:00
Sasha Koshka d0f7047fcf Fix package import error in theme 2023-08-07 21:57:50 -04:00
Sasha Koshka e63ebdb89e Add MultiCookie to make theming easier 2023-08-07 21:56:28 -04:00
Sasha Koshka 9d40ab654a Clarfied wording in Role.Object 2023-08-07 21:51:05 -04:00
Sasha Koshka 522ff64fd3 Added a theme package 2023-08-07 21:49:11 -04:00
Sasha Koshka e14bd81c04 Add SetDotColor method 2023-08-07 21:09:58 -04:00
Sasha Koshka dc377c36a5 Add function keys up to F24 2023-08-02 01:37:46 -04:00
Sasha Koshka 2b99a98a8e Add a ton more doc comments 2023-08-02 01:34:07 -04:00
Sasha Koshka d1b62f5560 Add dot manipulation to TextBox 2023-07-20 00:14:15 -04:00
Sasha Koshka 85fe5ac65b Added text manipulation 2023-07-19 23:58:27 -04:00
Sasha Koshka 14fc0ba372 Add more doc comments 2023-07-18 21:49:36 -04:00
Sasha Koshka 9f4e8a539a The Do function is now thread safe 2023-07-16 01:06:24 -04:00
Sasha Koshka 1cb3be8de8 Added a global Do function 2023-07-16 00:33:44 -04:00
Sasha Koshka 32e58ce63d Container event propagation can be disabled 2023-07-13 12:53:08 -04:00
Sasha Koshka 4dbd86cec3 ContainerBox can now be aligned as well 2023-07-13 03:00:50 -04:00
Sasha Koshka 573212fe7d Add support for text wrapping 2023-07-12 18:56:04 -04:00
17 changed files with 1008 additions and 150 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 8
charset = utf-8
[*.md]
indent_style = space
indent_size = 2

View File

@ -1,6 +1,8 @@
# tomo
WIP rewrite of tomo.
[![Go Reference](https://pkg.go.dev/badge/git.tebibyte.media/tomo/tomo.svg)](https://pkg.go.dev/git.tebibyte.media/tomo/tomo)
This module will serve as a wafer-thin collection of interfaces and glue code so
that plugins will be an actual viable concept.
Tomo is a lightweight GUI toolkit written in pure Go. This repository defines
the API that other components of the toolkit agree on. In order to use Tomo in
an application, use [Nasin](https://git.tebibyte.media/tomo/nasin), which builds
an application framework on top of Tomo.

View File

@ -3,19 +3,39 @@ package tomo
import "sort"
import "image"
import "errors"
import "git.tebibyte.media/tomo/tomo/canvas"
// Backend is any Tomo implementation. Backends handle window creation, layout,
// rendering, and events so that there can be as many platform-specific
// optimizations as possible.
type Backend interface {
NewWindow (image.Rectangle) (MainWindow, error)
// These methods create new objects. The backend must reject any object
// that was not made by it.
NewBox () Box
NewTextBox () TextBox
NewCanvasBox () CanvasBox
NewContainerBox () ContainerBox
// NewWindow creates a normal MainWindow and returns it.
NewWindow (image.Rectangle) (MainWindow, 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)
// 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
// Run runs the event loop until Stop() is called, or the backend
// experiences a fatal error.
Run () error
// Stop must unblock run.
Run () error
Stop ()
// Do performs a callback function in the main thread as soon as
// Do performs a callback function in the event loop thread as soon as
// possible. This method must be safe to call concurrently.
Do (func ())
}

View File

@ -8,45 +8,60 @@ import "image/color"
// Cap represents a stroke cap type.
type Cap int; const (
CapButt Cap = iota
CapRound
CapSquare
CapRound Cap = iota // Round cap that surrounds the point
CapSquare // Square cap that surrounds the point
CapButt // Square cap that ends at the point
)
// Joint represents a stroke joint type.
type Joint int; const (
JointRount Joint = iota
JointSharp
JointMiter
JointRount Joint = iota // Rounded joint
JointSharp // Sharp joint
JointMiter // Clipped/beveled joint
)
// StrokeAlign determines whether a stroke is drawn inside, outside, or on a
// path.
type StrokeAlign int; const (
StrokeAlignCenter StrokeAlign = iota
StrokeAlignInner
StrokeAlignOuter
StrokeAlignCenter StrokeAlign = iota // Centered on the path
StrokeAlignInner // Inset into the path
StrokeAlignOuter // Outset around the path
)
// Pen represents a drawing context that is linked to a canvas. Each canvas can
// have multiple pens associated with it, each maintaining their own drawing
// context.
// state.
type Pen interface {
// Rectangle draws a rectangle
// Rectangle draws a rectangle.
Rectangle (image.Rectangle)
// Path draws a path
// Path draws a path, which is a series of connected points.
Path (points ...image.Point)
Closed (bool) // if the path is closed
Cap (Cap) // line cap stype
Joint (Joint) // line joint style
StrokeWeight (int) // how thick the stroke is
StrokeAlign (StrokeAlign) // where the stroke is drawn
// StrokeWeight sets how thick the stroke is. The default value is zero.
StrokeWeight (int)
// SetClosed sets whether the path is a closed shape, or has an open
// side. This only applies if the stroke weight is greater than zero.
Closed (bool)
// Cap sets how the ends of the stroke look. This only applies if the
// stroke weight is greater than zero, and if the path is not closed.
// The default value is CapRound.
Cap (Cap)
// Joint sets how bend points in the stroke look. This only applies if
// the stroke weight is greater than zero. The default value is
// JointRound.
Joint (Joint)
// StrokeAlign sets where the stroke is drawn in relation to the path.
// This only applies if the stroke weight is greater than zero. The
// default value is StrokeAlignCenter.
StrokeAlign (StrokeAlign)
// set the stroke/fill to a solid color
// Stroke sets the stroke to a solid color.
Stroke (color.Color)
Fill (color.Color)
// Fill sets the fill to a solid color.
Fill (color.Color)
// Texture overlaps a texture onto the fill color.
Texture (Texture)
}
// Canvas is an image that supports drawing paths.
@ -56,12 +71,15 @@ type Canvas interface {
// Pen returns a new pen for this canvas.
Pen () Pen
// Clip returns a new canvas that points to a specific area of this one.
Clip (image.Rectangle) Canvas
// SubCanvas returns a returns a Canvas representing the portion of this
// Canvas visible and drawable through a rectangle. The returned value
// shares pixels with the original Canvas.
SubCanvas (image.Rectangle) Canvas
}
// Drawer is an object that can draw to a canvas.
type Drawer interface {
// Draw draws to the given canvas.
Draw (Canvas)
}

21
canvas/texture.go Normal file
View File

@ -0,0 +1,21 @@
package canvas
import "io"
import "image"
// Texture is a handle that points to a 2D raster image managed by the backend.
type Texture interface {
image.Image
// SubTexture returns a returns a Texture representing the portion of
// this Texture visible through a rectangle. The returned value shares
// pixels with the original Texture.
SubTexture (image.Rectangle) Texture
}
// TextureCloser is a texture that can be closed. Anything that receives a
// TextureCloser must close it after use.
type TextureCloser interface {
Texture
io.Closer
}

View File

@ -27,7 +27,10 @@ func (mime Mime) String () string {
return mime.Type + "/" + mime.Subtype
}
// MimePlain returns the MIME type of plain text.
func MimePlain () Mime { return Mime { "text", "plain" } }
// MimeFile returns the MIME type of a file path/URI.
func MimeFile () Mime { return Mime { "text", "uri-list" } }
type byteReadCloser struct { *bytes.Reader }

View File

@ -45,6 +45,10 @@ func (broadcaster *Broadcaster[L]) ensure () {
}
}
// NoCookie is a cookie that does nothing when closed.
type NoCookie struct { }
func (NoCookie) Close () { }
type cookie[L any] struct {
id int
broadcaster *Broadcaster[L]
@ -65,3 +69,17 @@ func (broadcaster *FuncBroadcaster) Broadcast () {
listener()
}
}
type multiCookie []Cookie
// MultiCookie creates a single cookie that, when closed, closes a list of other
// cookies.
func MultiCookie (cookies ...Cookie) Cookie {
return multiCookie(cookies)
}
func (cookies multiCookie) Close () {
for _, cookie := range cookies {
cookie.Close()
}
}

2
go.mod
View File

@ -2,4 +2,4 @@ module git.tebibyte.media/tomo/tomo
go 1.20
require golang.org/x/image v0.8.0
require golang.org/x/image v0.11.0

3
go.sum
View File

@ -3,6 +3,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
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=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -26,6 +28,7 @@ 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=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

View File

@ -59,6 +59,18 @@ const (
KeyF10 Key = 138
KeyF11 Key = 139
KeyF12 Key = 140
KeyF13 Key = 141
KeyF14 Key = 142
KeyF15 Key = 143
KeyF16 Key = 144
KeyF17 Key = 145
KeyF18 Key = 146
KeyF19 Key = 147
KeyF20 Key = 148
KeyF21 Key = 149
KeyF22 Key = 150
KeyF23 Key = 151
KeyF24 Key = 152
)
// Button represents a mouse button.

243
object.go
View File

@ -3,6 +3,7 @@ package tomo
import "image"
import "image/color"
import "golang.org/x/image/font"
import "git.tebibyte.media/tomo/tomo/text"
import "git.tebibyte.media/tomo/tomo/data"
import "git.tebibyte.media/tomo/tomo/event"
import "git.tebibyte.media/tomo/tomo/input"
@ -16,6 +17,8 @@ type Side int; const (
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:
@ -84,11 +87,13 @@ 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
@ -96,35 +101,86 @@ type Align int; const (
AlignEven // similar to justified text
)
// Object is any obscreen object. Each object must be linked to a box, even if
// Object is any onscreen object. Each object must be linked to a box, even if
// it is that box.
type Object interface {
Box () Box
GetBox () Box
}
// Box is a basic styled box.
type Box interface {
Object
Window () Window
Bounds () image.Rectangle
InnerBounds () image.Rectangle
MinimumSize () image.Point
SetBounds (image.Rectangle)
SetColor (color.Color)
SetBorder (...Border)
// Window returns the Window this Box is a part of.
Window () Window
// Bounds returns the outer bounding rectangle of the Box relative to
// the Window.
Bounds () image.Rectangle
// InnerBounds returns the inner bounding rectangle of the box. It is
// the value of Bounds inset by the Box's border and padding.
InnerBounds () image.Rectangle
// MinimumSize returns the minimum width and height this Box's bounds
// can be set to. This will return the value of whichever of these is
// greater:
// - The size as set by SetMinimumSize
// - The size taken up by the Box's border and padding. If there is
// internal content that does not overflow, the size of that is also
// taken into account here.
MinimumSize () image.Point
// SetBounds sets the bounding rectangle of this Box relative to the
// Window.
SetBounds (image.Rectangle)
// SetColor sets the background color of this Box.
SetColor (color.Color)
// SetTexture sets a repeating background texture. If the texture is
// transparent, it will be overlayed atop the color specified by
// SetColor().
SetTexture (canvas.Texture)
// SetBorder sets the Border(s) of the box. The first Border will be the
// most outset, and the last Border will be the most inset.
SetBorder (...Border)
// SetMinimumSize sets the minimum width and height of the box, as
// described in MinimumSize.
SetMinimumSize (image.Point)
SetPadding (Inset)
// 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
SetDNDData (data.Data)
// 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
// picked up.
SetDNDData (data.Data)
// SetDNDAccept sets the type of data that can be dropped onto this Box.
// If this is nil (which is the default), this Box will reject all
// drops.
SetDNDAccept (...data.Mime)
SetFocused (bool)
// SetFocused sets whether or not this Box has keyboard focus. If set to
// true, this method will steal focus away from whichever Object
// currently has focus.
SetFocused (bool)
// SetFocusable sets whether or not this Box can receive keyboard focus.
// If set to false and the Box is already focused. the focus is removed.
SetFocusable (bool)
Focused () bool
Modifiers () input.Modifiers
// Focused returns whether or not this Box has keyboard focus.
Focused () bool
// Modifiers returns which modifier keys on the keyboard are currently
// being held down.
Modifiers () input.Modifiers
// MousePosition returns the position of the mouse pointer relative to
// the Window.
MousePosition () image.Point
// These are event subscription functions that allow callbacks to be
// connected to particular events. Multiple callbacks may be connected
// to the same event at once. Callbacks can be removed by closing the
// returned cookie.
OnFocusEnter (func ()) event.Cookie
OnFocusLeave (func ()) event.Cookie
OnDNDEnter (func ()) event.Cookie
@ -143,7 +199,18 @@ type Box interface {
// CanvasBox is a box that can be drawn to.
type CanvasBox interface {
Box
SetDrawer (canvas.Drawer)
// SetDrawer sets the Drawer that will be called upon to draw the Box's
// content when it is invalidated. The Canvas passed to the drawer will
// have these properties:
// - It will have the same origin (0, 0) as the window which contains
// the CanvasBox
// - The Canvas bounds will describe the portion of the CanvasBox
// visible on screen
SetDrawer (canvas.Drawer)
// Invalidate causes the Box's area to be redrawn at the end of the
// event cycle, even if it wouldn't be otherwise.
Invalidate ()
}
@ -151,9 +218,22 @@ type CanvasBox interface {
// is to be embedded into TextBox and ContainerBox.
type ContentBox interface {
Box
SetOverflow (horizontal, vertical bool)
ContentBounds () image.Rectangle
ScrollTo (image.Point)
// SetOverflow sets whether or not the Box's content overflows
// horizontally and vertically. Overflowing content is clipped to the
// bounds of the Box inset by all Borders (but not padding).
SetOverflow (horizontal, vertical bool)
// SetAlign sets how the Box's content is distributed horizontally and
// vertically.
SetAlign (x, y Align)
// ContentBounds returns the bounds of the inner content of the Box
// relative to the Box's InnerBounds.
ContentBounds () image.Rectangle
// 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)
// OnContentBoundsChange specifies a function to be called when the
// Box's ContentBounds or InnerBounds changes.
OnContentBoundsChange (func ()) event.Cookie
}
@ -161,59 +241,136 @@ type ContentBox interface {
type TextBox interface {
ContentBox
SetText (string)
// SetText sets the text content of the Box.
SetText (string)
// SetTextColor sets the text color.
SetTextColor (color.Color)
SetFace (font.Face)
SetHAlign (Align)
SetVAlign (Align)
// SetFace sets the font face text is rendered in.
SetFace (font.Face)
// SetWrap sets whether or not the text wraps.
SetWrap (bool)
// SetSelectable sets whether or not the text content can be
// highlighted/selected.
SetSelectable (bool)
// SetDotColor sets the highlight color of selected text.
SetDotColor (color.Color)
// Select sets the text cursor or selection.
Select (text.Dot)
// Dot returns the text cursor or selection.
Dot () text.Dot
// OnDotChange specifies a function to be called when the text cursor or
// selection changes.
OnDotChange (func ()) event.Cookie
}
// ContentBox is a box that can contain child objects. It arranges them
// ContentBox is a box that can contain child Objects. It arranges them
// according to a layout rule.
type ContainerBox interface {
ContentBox
SetGap (image.Point)
Add (Object)
Delete (Object)
Insert (child Object, before Object)
Clear ()
Length () int
At (int) Object
// SetGap sets the gap between child Objects.
SetGap (image.Point)
// Add appends a child Object. If the object is already a child of
// another object, it will be removed from that object first.
Add (Object)
// Remove removes a child Object, if it is a child of this Box.
Remove (Object)
// Insert inserts a child Object before a specified Object. If the
// before Object is nil or is not contained within this Box, the
// inserted Object is appended.
Insert (child Object, before Object)
// Clear removes all child Objects.
Clear ()
// Length returns the amount of child Objects.
Length () int
// At returns the child Object at the specified index.
At (int) Object
// SetLayout sets the layout of this Box. Child Objects will be
// positioned according to it.
SetLayout (Layout)
// These methods control whether certain user input events get
// propagated to child Objects. If set to true, the relevant events will
// be sent to this container. If set to false (which is the default),
// the events will be sent to the appropriate child Object.
CaptureDND (bool)
CaptureMouse (bool)
CaptureScroll (bool)
CaptureKeyboard (bool)
}
// LayoutHints are passed to a layout to tell it how to arrange child boxes.
type LayoutHints struct {
// Bounds is the bounding rectangle that children should be placed
// within. Any padding values are already applied.
Bounds image.Rectangle
// OverflowX and OverflowY control whether child Boxes may be positioned
// outside of Bounds.
OverflowX bool
OverflowY bool
// AlignX and AlignY control how child Boxes are aligned horizontally
// and vertically. The effect of this may vary depending on the Layout.
AlignX Align
AlignY Align
// Gap controls the amount of horizontal and vertical spacing in-between
// child Boxes.
Gap image.Point
}
// Layout can be given to a ContainerBox to arrange child objects.
// A Layout can be given to a ContainerBox to arrange child objects.
type Layout interface {
// MinimumSize returns the minimum width and height of
// LayoutHints.Bounds needed to properly lay out all child Boxes.
MinimumSize (LayoutHints, []Box) image.Point
Arrange (LayoutHints, []Box)
// Arrange arranges child boxes according to the given LayoutHints.
Arrange (LayoutHints, []Box)
}
// Window is an operating system window. It can contain one object.
type Window interface {
SetRoot (Object)
// 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.
SetRoot (Object)
// SetTitle sets the title of the window.
SetTitle (string)
SetIcon (sizes []image.Image)
NewMenu (image.Rectangle) (Window, error)
// SetIcon sets the icon of the window. When multiple icon sizes are
// provided, the best fitting one is chosen for display.
SetIcon (... image.Image)
// 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)
// 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)
// NewModal creates a new modal window that blocks all input to this
// window until it is closed.
NewModal (image.Rectangle) (Window, error)
Widget () (Window, error)
Copy (data.Data)
Paste (callback func (data.Data, error), accept ...data.Mime)
Show ()
Hide ()
Close ()
OnClose (func ()) event.Cookie
// Copy copies data to the clipboard.
Copy (data.Data)
// Paste reads data from the clipboard. When the data is available or an
// error has occurred, the provided function will be called.
Paste (callback func (data.Data, error), accept ...data.Mime)
// SetVisible sets whether or not this window is visible. Windows are
// invisible by default.
SetVisible (bool)
// Visible returns whether or not this window is visible.
Visible () bool
// Close closes the window.
Close ()
// 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,55 +0,0 @@
package tomo
import "os"
import "plugin"
import "path/filepath"
var pluginPaths []string
func loadPlugins () {
for _, dir := range pluginPaths {
if dir != "" {
loadPluginsFrom(dir)
}
}
}
func loadPluginsFrom (dir string) {
entries, err := os.ReadDir(dir)
// its no big deal if one of the dirs doesn't exist
if err != nil { return }
for _, entry := range entries {
if entry.IsDir() { continue }
if filepath.Ext(entry.Name()) != ".so" { continue }
pluginPath := filepath.Join(dir, entry.Name())
loadPlugin(pluginPath)
}
}
func loadPlugin (path string) {
die := func (reason string) {
println("tomo: could not load plugin at", path + ":", reason)
}
plugin, err := plugin.Open(path)
if err != nil {
die(err.Error())
return
}
// check for and obtain basic plugin functions
name, ok := extract[func () string](plugin, "Name")
if !ok { die("does not implement Name() string"); return }
_, ok = extract[func () string](plugin, "Description")
if !ok { die("does not implement Description() string"); return }
println("tomo: loaded plugin", name())
}
func extract[T any] (plugin *plugin.Plugin, name string) (value T, ok bool) {
symbol, err := plugin.Lookup(name)
if err != nil { return }
value, ok = symbol.(T)
return
}

248
text/text.go Normal file
View File

@ -0,0 +1,248 @@
package text
import "unicode"
// Dot represents a cursor or text selection. It has a start and end position,
// referring to where the user began and ended the selection respectively.
type Dot struct { Start, End int }
// EmptyDot returns a zero-width dot at the specified position.
func EmptyDot (position int) Dot {
return Dot { position, position }
}
// Canon places the lesser value at the start, and the greater value at the end.
// Note that a canonized dot does not in all cases correspond directly to the
// original, because there is a semantic value to the start and end positions.
func (dot Dot) Canon () Dot {
if dot.Start > dot.End {
return Dot { dot.End, dot.Start }
} else {
return dot
}
}
// Empty returns whether or not the
func (dot Dot) Empty () bool {
return dot.Start == dot.End
}
// Add shifts the dot to the right by the specified amount.
func (dot Dot) Add (delta int) Dot {
return Dot {
dot.Start + delta,
dot.End + delta,
}
}
// Sub shifts the dot to the left by the specified amount.
func (dot Dot) Sub (delta int) Dot {
return Dot {
dot.Start - delta,
dot.End - delta,
}
}
// Constrain constrains the dot's start and end from zero to length (inclusive).
func (dot Dot) Constrain (length int) Dot {
if dot.Start < 0 { dot.Start = 0 }
if dot.Start > length { dot.Start = length }
if dot.End < 0 { dot.End = 0 }
if dot.End > length { dot.End = length }
return dot
}
// Width returns how many runes the dot spans.
func (dot Dot) Width () int {
dot = dot.Canon()
return dot.End - dot.Start
}
// Slice returns the subset of text that the dot covers.
func (dot Dot) Slice (text []rune) []rune {
dot = dot.Canon().Constrain(len(text))
return text[dot.Start:dot.End]
}
// WordToLeft returns how far away to the left the next word boundary is from a
// given position.
func WordToLeft (text []rune, position int) (length int) {
if position < 1 { return }
if position > len(text) { position = len(text) }
index := position - 1
for index >= 0 && unicode.IsSpace(text[index]) {
length ++
index --
}
for index >= 0 && !unicode.IsSpace(text[index]) {
length ++
index --
}
return
}
// WordToRight returns how far away to the right the next word boundary is from
// a given position.
func WordToRight (text []rune, position int) (length int) {
if position < 0 { return }
if position > len(text) { position = len(text) }
index := position
for index < len(text) && unicode.IsSpace(text[index]) {
length ++
index ++
}
for index < len(text) && !unicode.IsSpace(text[index]) {
length ++
index ++
}
return
}
// WordAround returns a dot that surrounds the word at the specified position.
func WordAround (text []rune, position int) (around Dot) {
return Dot {
position - WordToLeft(text, position),
position + WordToRight(text, position),
}
}
// Backspace deletes the rune to the left of the dot. If word is true, it
// deletes up until the next word boundary on the left. If the dot is non-empty,
// it deletes the text inside of the dot.
func Backspace (text []rune, dot Dot, word bool) (result []rune, moved Dot) {
dot = dot.Constrain(len(text))
if dot.Empty() {
distance := 1
if word {
distance = WordToLeft(text, dot.End)
}
result = append (
result,
text[:dot.Sub(distance).Constrain(len(text)).End]...)
result = append(result, text[dot.End:]...)
moved = EmptyDot(dot.Sub(distance).Start)
return
} else {
return Delete(text, dot, word)
}
}
// Delete deletes the rune to the right of the dot. If word is true, it deletes
// up until the next word boundary on the right. If the dot is non-empty, it
// deletes the text inside of the dot.
func Delete (text []rune, dot Dot, word bool) (result []rune, moved Dot) {
dot = dot.Constrain(len(text))
if dot.Empty() {
distance := 1
if word {
distance = WordToRight(text, dot.End)
}
result = append(result, text[:dot.End]...)
result = append (
result,
text[dot.Add(distance).Constrain(len(text)).End:]...)
moved = dot
return
} else {
dot = dot.Canon()
result = append(result, text[:dot.Start]...)
result = append(result, text[dot.End:]...)
moved = EmptyDot(dot.Start)
return
}
}
// Lift removes the section of text inside of the dot, and returns a copy of it.
func Lift (text []rune, dot Dot) (result []rune, moved Dot, lifted []rune) {
dot = dot.Constrain(len(text))
if dot.Empty() {
moved = dot
return
}
dot = dot.Canon()
lifted = make([]rune, dot.Width())
copy(lifted, dot.Slice(text))
result = append(result, text[:dot.Start]...)
result = append(result, text[dot.End:]...)
moved = EmptyDot(dot.Start)
return
}
// Type inserts one of more runes into the text at the dot position. If the dot
// is non-empty, it replaces the text inside of the dot with the new runes.
func Type (text []rune, dot Dot, characters ...rune) (result []rune, moved Dot) {
dot = dot.Constrain(len(text))
if dot.Empty() {
result = append(result, text[:dot.End]...)
result = append(result, characters...)
if dot.End < len(text) {
result = append(result, text[dot.End:]...)
}
moved = EmptyDot(dot.Add(len(characters)).End)
return
} else {
dot = dot.Canon()
result = append(result, text[:dot.Start]...)
result = append(result, characters...)
result = append(result, text[dot.End:]...)
moved = EmptyDot(dot.Add(len(characters)).Start)
return
}
}
// MoveLeft moves the dot left one rune. If word is true, it moves the dot to
// the next word boundary on the left.
func MoveLeft (text []rune, dot Dot, word bool) (moved Dot) {
dot = dot.Canon().Constrain(len(text))
distance := 0
if dot.Empty() {
distance = 1
}
if word {
distance = WordToLeft(text, dot.Start)
}
moved = EmptyDot(dot.Sub(distance).Start)
return
}
// MoveRight moves the dot right one rune. If word is true, it moves the dot to
// the next word boundary on the right.
func MoveRight (text []rune, dot Dot, word bool) (moved Dot) {
dot = dot.Canon().Constrain(len(text))
distance := 0
if dot.Empty() {
distance = 1
}
if word {
distance = WordToRight(text, dot.End)
}
moved = EmptyDot(dot.Add(distance).End)
return
}
// SelectLeft moves the end of the dot left one rune. If word is true, it moves
// the end of the dot to the next word boundary on the left.
func SelectLeft (text []rune, dot Dot, word bool) (moved Dot) {
dot = dot.Constrain(len(text))
distance := 1
if word {
distance = WordToLeft(text, dot.End)
}
dot.End -= distance
return dot
}
// SelectRight moves the end of the dot right one rune. If word is true, it
// moves the end of the dot to the next word boundary on the right.
func SelectRight (text []rune, dot Dot, word bool) (moved Dot) {
dot = dot.Constrain(len(text))
distance := 1
if word {
distance = WordToRight(text, dot.End)
}
dot.End += distance
return dot
}

278
theme/icon.go Normal file
View File

@ -0,0 +1,278 @@
package theme
import "git.tebibyte.media/tomo/tomo/data"
import "git.tebibyte.media/tomo/tomo/canvas"
// IconSize represents the size of an icon.
type IconSize int; const (
IconSizeSmall IconSize = iota;
IconSizeMedium
IconSizeLarge
)
// String satisfies the fmt.Stringer interface.
func (size IconSize) String () string {
switch size {
case IconSizeSmall: return "small"
case IconSizeMedium: return "medium"
case IconSizeLarge: return "large"
default: return "unknown"
}
}
// Icon represents an icon ID.
type Icon string
// A list of standard icon IDs.
const (
IconUnknown Icon = ""
// objects: files
IconFile Icon = "File" // generic
IconDirectory Icon = "Directory"
IconDirectoryFull Icon = "DirectoryFull"
// objects: places
IconPlaceHome Icon = "PlaceHome"
IconPlaceDownloads Icon = "PlaceDownloads"
IconPlacePhotos Icon = "PlacePhotos"
IconPlaceBooks Icon = "PlaceBooks"
IconPlaceDocuments Icon = "PlaceDocuments"
IconPlaceRepositories Icon = "PlaceRepositories"
IconPlaceMusic Icon = "PlaceMusic"
IconPlaceArchives Icon = "PlaceArchives"
IconPlaceFonts Icon = "PlaceFonts"
IconPlaceBinaries Icon = "PlaceBinaries"
IconPlaceVideos Icon = "PlaceVideos"
IconPlace3DObjects Icon = "Place3DObjects"
IconPlaceHistory Icon = "PlaceHistory"
IconPlacePreferences Icon = "PlacePreferences"
// objects: storage
IconStorage Icon = "Storage" // generic
IconStorageMagneticTape Icon = "StorageMagneticTape"
IconStorageFloppyDisk Icon = "StorageFloppyDisk"
IconStorageHardDisk Icon = "StorageHardDisk"
IconStorageSolidStateDisk Icon = "StorageSolidState"
IconStorageFlashStick Icon = "StorageFlashStick"
IconStorageFlashCard Icon = "StorageFlashCard"
IconStorageROM Icon = "StorageROM"
IconStorageRAM Icon = "StorageRAM"
IconStorageCD Icon = "StorageCD"
IconStorageDVD Icon = "StorageDVD"
// objects: applications
// Keep these in sync with nasin.ApplicationRole!
IconApplication Icon = "Application" // generic
IconApplicationWebBrowser Icon = "ApplicationWebBrowser"
IconApplicationMesssanger Icon = "ApplicationMesssanger"
IconApplicationPhone Icon = "ApplicationPhone"
IconApplicationMail Icon = "ApplicationMail"
IconApplicationTerminalEmulator Icon = "ApplicationTerminalEmulator"
IconApplicationFileBrowser Icon = "ApplicationFileBrowser"
IconApplicationTextEditor Icon = "ApplicationTextEditor"
IconApplicationDocumentViewer Icon = "ApplicationDocumentViewer"
IconApplicationWordProcessor Icon = "ApplicationWordProcessor"
IconApplicationSpreadsheet Icon = "ApplicationSpreadsheet"
IconApplicationSlideshow Icon = "ApplicationSlideshow"
IconApplicationCalculator Icon = "ApplicationCalculator"
IconApplicationPreferences Icon = "ApplicationPreferences"
IconApplicationProcessManager Icon = "ApplicationProcessManager"
IconApplicationSystemInformation Icon = "ApplicationSystemInformation"
IconApplicationManual Icon = "ApplicationManual"
IconApplicationCamera Icon = "ApplicationCamera"
IconApplicationImageViewer Icon = "ApplicationImageViewer"
IconApplicationMediaPlayer Icon = "ApplicationMediaPlayer"
IconApplicationImageEditor Icon = "ApplicationImageEditor"
IconApplicationAudioEditor Icon = "ApplicationAudioEditor"
IconApplicationVideoEditor Icon = "ApplicationVideoEditor"
IconApplicationClock Icon = "ApplicationClock"
IconApplicationCalendar Icon = "ApplicationCalendar"
IconApplicationChecklist Icon = "ApplicationChecklist"
// objects: networks
IconNetwork Icon = "Network" // generic
IconNetworkLocal Icon = "NetworkLocal"
IconNetworkInternet Icon = "NetworkInternet"
IconNetworkEthernet Icon = "NetworkEthernet"
IconNetworkWireless Icon = "NetworkWireless"
IconNetworkCell Icon = "NetworkCell"
IconNetworkBluetooth Icon = "NetworkBluetooth"
IconNetworkRadio Icon = "NetworkRadio"
// objects: devices
IconDevice Icon = "Device" // generic
IconDeviceRouter Icon = "DeviceRouter"
IconDeviceServer Icon = "DeviceServer"
IconDeviceDesktop Icon = "DeviceDesktop"
IconDeviceLaptop Icon = "DeviceLaptop"
IconDeviceTablet Icon = "DeviceTablet"
IconDevicePhone Icon = "DevicePhone"
IconDeviceWatch Icon = "DeviceWatch"
IconDeviceCamera Icon = "DeviceCamera"
// objects: peripherals
IconPeripheral Icon = "Peripheral" // generic
IconPeripheralKeyboard Icon = "PeripheralKeyboard"
IconPeripheralMouse Icon = "PeripheralMouse"
IconPeripheralMonitor Icon = "PeripheralMonitor"
IconPeripheralWebcam Icon = "PeripheralWebcam"
IconPeripheralMicrophone Icon = "PeripheralMicrophone"
IconPeripheralSpeaker Icon = "PeripheralSpeaker"
IconPeripheralPenTablet Icon = "PeripheralPenTablet"
IconPeripheralTrackpad Icon = "PeripheralTrackpad"
IconPeripheralController Icon = "PeripheralController"
// objects: i/o
IconPort Icon = "Port" // generic
IconPortEthernet Icon = "PortEthernet"
IconPortUSB Icon = "PortUSB"
IconPortParallel Icon = "PortParallel"
IconPortSerial Icon = "PortSerial"
IconPortPS2 Icon = "PortPS2"
IconPortDisplay Icon = "PortDisplay"
IconPortCGA Icon = "PortCGA"
IconPortVGA Icon = "PortVGA"
IconPortHDMI Icon = "PortHDMI"
IconPortDisplayPort Icon = "PortDisplayPort"
IconPortInfrared Icon = "PortInfrared"
// actions: files
IconActionOpen Icon = "ActionOpen"
IconActionOpenIn Icon = "ActionOpenIn"
IconActionSave Icon = "ActionSave"
IconActionSaveAs Icon = "ActionSaveAs"
IconActionPrint Icon = "ActionPrint"
IconActionNew Icon = "ActionNew"
IconActionNewDirectory Icon = "ActionNewDirectory"
IconActionDelete Icon = "ActionDelete"
IconActionRename Icon = "ActionRename"
IconActionGetInformation Icon = "ActionGetInformation"
IconActionChangePermissions Icon = "ActionChangePermissions"
IconActionRevert Icon = "ActionRevert"
// actions: list management
IconActionAdd Icon = "ActionAdd"
IconActionRemove Icon = "ActionRemove"
IconActionAddBookmark Icon = "ActionAddBookmark"
IconActionRemoveBookmark Icon = "ActionRemoveBookmark"
IconActionAddFavorite Icon = "ActionAddFavorite"
IconActionRemoveFavorite Icon = "ActionRemoveFavorite"
// actions: media
IconActionPlay Icon = "ActionPlay"
IconActionPause Icon = "ActionPause"
IconActionStop Icon = "ActionStop"
IconActionFastForward Icon = "ActionFastForward"
IconActionRewind Icon = "ActionRewind"
IconActionToBeginning Icon = "ActionToBeginning"
IconActionToEnd Icon = "ActionToEnd"
IconActionRecord Icon = "ActionRecord"
IconActionVolumeUp Icon = "ActionVolumeUp"
IconActionVolumeDown Icon = "ActionVolumeDown"
IconActionMute Icon = "ActionMute"
// actions: editing
IconActionUndo Icon = "ActionUndo"
IconActionRedo Icon = "ActionRedo"
IconActionCut Icon = "ActionCut"
IconActionCopy Icon = "ActionCopy"
IconActionPaste Icon = "ActionPaste"
IconActionFind Icon = "ActionFind"
IconActionReplace Icon = "ActionReplace"
IconActionSelectAll Icon = "ActionSelectAll"
IconActionSelectNone Icon = "ActionSelectNone"
IconActionIncrement Icon = "ActionIncrement"
IconActionDecrement Icon = "ActionDecrement"
// actions: window management
IconActionClose Icon = "ActionClose"
IconActionQuit Icon = "ActionQuit"
IconActionIconify Icon = "ActionIconify"
IconActionShade Icon = "ActionShade"
IconActionMaximize Icon = "ActionMaximize"
IconActionFullScreen Icon = "ActionFullScreen"
IconActionRestore Icon = "ActionRestore"
// actions: view
IconActionExpand Icon = "ActionExpand"
IconActionContract Icon = "ActionContract"
IconActionBack Icon = "ActionBack"
IconActionForward Icon = "ActionForward"
IconActionUp Icon = "ActionUp"
IconActionDown Icon = "ActionDown"
IconActionReload Icon = "ActionReload"
IconActionZoomIn Icon = "ActionZoomIn"
IconActionZoomOut Icon = "ActionZoomOut"
IconActionZoomReset Icon = "ActionZoomReset"
IconActionMove Icon = "ActionMove"
IconActionResize Icon = "ActionResize"
IconActionGoTo Icon = "ActionGoTo"
// actions: tools
IconActionTransform Icon = "ActionTransform"
IconActionTranslate Icon = "ActionTranslate"
IconActionRotate Icon = "ActionRotate"
IconActionScale Icon = "ActionScale"
IconActionWarp Icon = "ActionWarp"
IconActionCornerPin Icon = "ActionCornerPin"
IconActionSelectRectangle Icon = "ActionSelectRectangle"
IconActionSelectEllipse Icon = "ActionSelectEllipse"
IconActionSelectLasso Icon = "ActionSelectLasso"
IconActionSelectGeometric Icon = "ActionSelectGeometric"
IconActionSelectAuto Icon = "ActionSelectAuto"
IconActionCrop Icon = "ActionCrop"
IconActionFill Icon = "ActionFill"
IconActionGradient Icon = "ActionGradient"
IconActionPencil Icon = "ActionPencil"
IconActionBrush Icon = "ActionBrush"
IconActionEraser Icon = "ActionEraser"
IconActionText Icon = "ActionText"
IconActionEyedropper Icon = "ActionEyedropper"
// status: dialog
IconStatusInformation Icon = "StatusInformation"
IconStatusQuestion Icon = "StatusQuestion"
IconStatusWarning Icon = "StatusWarning"
IconStatusError Icon = "StatusError"
IconStatusCancel Icon = "StatusCancel"
IconStatusOkay Icon = "StatusOkay"
// status: network
IconStatusCellSignal0 Icon = "StatusCellSignal0"
IconStatusCellSignal1 Icon = "StatusCellSignal1"
IconStatusCellSignal2 Icon = "StatusCellSignal2"
IconStatusCellSignal3 Icon = "StatusCellSignal3"
IconStatusWirelessSignal0 Icon = "StatusWirelessSignal0"
IconStatusWirelessSignal1 Icon = "StatusWirelessSignal1"
IconStatusWirelessSignal2 Icon = "StatusWirelessSignal2"
IconStatusWirelessSignal3 Icon = "StatusWirelessSignal3"
// status: power
IconStatusBattery0 Icon = "StatusBattery0"
IconStatusBattery1 Icon = "StatusBattery1"
IconStatusBattery2 Icon = "StatusBattery2"
IconStatusBattery3 Icon = "StatusBattery3"
IconStatusBrightness0 Icon = "StatusBrightness0"
IconStatusBrightness1 Icon = "StatusBrightness1"
IconStatusBrightness2 Icon = "StatusBrightness2"
IconStatusBrightness3 Icon = "StatusBrightness3"
// status: media
IconStatusVolume0 Icon = "StatusVolume0"
IconStatusVolume1 Icon = "StatusVolume1"
IconStatusVolume2 Icon = "StatusVolume2"
IconStatusVolume3 Icon = "StatusVolume3"
)
// Texture returns a texture of the corresponding icon ID.
func (id Icon) Texture (size IconSize) canvas.Texture {
if current == nil { return nil }
return current.Icon(id, size)
}
// MimeIcon returns an icon corresponding to a MIME type.
func MimeIcon (mime data.Mime, size IconSize) canvas.Texture {
if current == nil { return nil }
return current.MimeIcon(mime, size)
}

109
theme/theme.go Normal file
View File

@ -0,0 +1,109 @@
package theme
import "fmt"
import "git.tebibyte.media/tomo/tomo"
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 {
// Package is an optional namespace field. If specified, it should be
// the package name or module name the object is from.
Package string
// Object specifies what type of object it is. For example:
// - TextInput
// - Table
// - Label
// - Dial
// This should correspond directly to the type name of the object.
Object string
// Variant is an optional field to be used when an object has one or
// more soft variants under one type. For example, an object "Slider"
// may have variations "horizontal" and "vertical".
Variant string
}
// String satisfies the fmt.Stringer interface.
// It follows the format of:
// Package.Object[Variant]
func (r Role) String () string {
return fmt.Sprintf("%s.%s[%s]", r.Package, r.Object, r.Variant)
}
// R is shorthand for creating a Role structure.
func R (pack, object, variant string) Role {
return Role { Package: pack, Object: object, Variant: variant }
}
// Color represents a color ID.
type Color int; const (
ColorBackground Color = iota
ColorForeground
ColorRaised
ColorSunken
ColorAccent
)
// String satisfies the fmt.Stringer interface.
func (c Color) String () string {
switch c {
case ColorBackground: return "background"
case ColorForeground: return "foreground"
case ColorRaised: return "raised"
case ColorSunken: return "sunken"
case ColorAccent: return "accent"
default: return "unknown"
}
}
// RGBA satisfies the color.Color interface.
func (id Color) RGBA () (r, g, b, a uint32) {
if current == nil { return }
return current.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 (tomo.Object, Role) 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 current Theme
// SetTheme sets the theme.
func SetTheme (theme Theme) {
current = theme
}
// 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 tomo.Object, role Role) event.Cookie {
if current == nil { return event.NoCookie { } }
return current.Apply(object, role)
}

38
tomo.go
View File

@ -1,23 +1,27 @@
package tomo
import "sync"
import "image"
import "errors"
import "git.tebibyte.media/tomo/tomo/canvas"
var backendLock sync.Mutex
var backend Backend
// Run initializes a backend, runs the specified callback function, and runs the
// event loop in that order. This function blocks until Stop is called, or the
// backend experiences a fatal error.
func Run (callback func ()) error {
loadPlugins()
if backend != nil {
return errors.New("there is already a backend running")
}
back, err := Initialize()
if err != nil { return err }
backendLock.Lock()
backend = back
backendLock.Unlock()
callback()
return backend.Run()
@ -32,30 +36,60 @@ func assertBackend () {
func Stop () {
assertBackend()
backend.Stop()
backendLock.Lock()
backend = nil
backendLock.Unlock()
}
// Do performs a callback function in the event loop thread as soon as possible.
func Do (callback func ()) {
backendLock.Lock()
if backend != nil { backend.Do(callback) }
backendLock.Unlock()
}
// NewWindow creates and returns a window within the specified bounds on screen.
func NewWindow (bounds image.Rectangle) (MainWindow, error) {
assertBackend()
return backend.NewWindow(bounds)
}
// 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) {
assertBackend()
return backend.NewPlainWindow(bounds)
}
// NewBox creates and returns a basic Box.
func NewBox () Box {
assertBackend()
return backend.NewBox()
}
// NewTextBox creates and returns a Box that can display text.
func NewTextBox () TextBox {
assertBackend()
return backend.NewTextBox()
}
// NewCanvasBox creates and returns a Box that can display custom graphics.
func NewCanvasBox () CanvasBox {
assertBackend()
return backend.NewCanvasBox()
}
// NewContainerBox creates and returns a Box that can contain other boxes.
func NewContainerBox () ContainerBox {
assertBackend()
return backend.NewContainerBox()
}
// NewTexture creates a new texture from an image. When no longer in use, it
// must be freed using Close().
func NewTexture (source image.Image) canvas.TextureCloser {
assertBackend()
return backend.NewTexture(source)
}

22
unix.go
View File

@ -1,22 +0,0 @@
//go:build linux || darwin || freebsd
package tomo
import "os"
import "strings"
import "path/filepath"
func init () {
pathVariable := os.Getenv("TOMO_PLUGIN_PATH")
pluginPaths = strings.Split(pathVariable, ":")
pluginPaths = append (
pluginPaths,
"/usr/lib/tomo/plugins",
"/usr/local/lib/tomo/plugins")
homeDir, err := os.UserHomeDir()
if err == nil {
pluginPaths = append (
pluginPaths,
filepath.Join(homeDir, ".local/lib/tomo/plugins"))
}
}