Compare commits

...

24 Commits

Author SHA1 Message Date
484ead0f76 More doc comment improvements 2024-09-12 00:21:44 -04:00
018f93cbf9 Add String method to IconSize 2024-09-12 00:02:08 -04:00
8657730c25 Forgot one 2024-09-11 23:56:12 -04:00
9d63e27ab6 Many doc comment fixes 2024-09-11 23:52:36 -04:00
8a3f41c7db The "pressed" tag now references config.ButtonChordInteract 2024-09-11 23:15:10 -04:00
987bb613dd Change the buttons in config to button chords 2024-09-11 22:56:46 -04:00
7bf62c25fe Fix misspelling in config doc comment 2024-09-11 22:42:35 -04:00
763e5db3fc Config now stores common keybinds 2024-09-11 22:41:30 -04:00
dcdb411c0e Modifier keys are now stored using bit flags
Or-ing together constants is cleaner than a struct literal with
booleans
2024-09-11 22:40:19 -04:00
efbdaef390 Add utilities for key/mouse chords to input 2024-09-11 18:44:26 -04:00
8546f11471 Add config package
Closes #6
2024-09-11 01:49:25 -04:00
46f4c50381 Document the window kinds 2024-09-11 01:06:54 -04:00
b88e32fa49 Introduce the concept of window kinds 2024-09-11 00:54:45 -04:00
8f43fa310c Add new child window constructors
Closes #29
2024-09-11 00:21:57 -04:00
20c7e0fdb1 Add FuncCookie
Closes #27
2024-08-20 22:55:31 -04:00
d8a8ad7e0a Re-organize event.go 2024-08-20 22:55:20 -04:00
3feba5f811 event.Cookie is now just io.Closer
Closes #26
2024-08-20 22:50:51 -04:00
75c654d4ae Add error to Window.Close 2024-08-20 22:42:31 -04:00
da38e5411f Add TryClose event to Window
Closes #24
2024-08-20 22:41:41 -04:00
d47b525e42 Add documentation for AttrKind 2024-08-20 21:29:12 -04:00
862e08edf1 Add AttrCursor 2024-08-20 21:25:54 -04:00
47b2231acd Add list of cursor shapes
Progress on #25
2024-08-20 21:22:19 -04:00
b05e1f5d50 Fixed IconListContract being identical to IconListExpand 2024-08-18 16:16:40 -04:00
608a898be3 Fix deadlock in Stop 2024-08-16 17:40:39 -04:00
13 changed files with 453 additions and 224 deletions

View File

@@ -4,15 +4,15 @@ import "image"
import "image/color"
import "git.tebibyte.media/tomo/tomo/canvas"
// Attr modifies one thing about a box's style.
// Attr modifies one thing about a Box's style.
type Attr interface {
// Equals returns true if both attributes can reasonably be declared
// equal.
// Equals returns true if both Attrs can reasonably be declared equal.
Equals (Attr) bool
Kind () AttrKind
attr ()
}
// AttrKind enumerates all attribute kinds. Each Attr- type has one of these.
type AttrKind int; const (
AttrKindColor AttrKind = iota
AttrKindTexture
@@ -28,95 +28,102 @@ type AttrKind int; const (
AttrKindAlign
AttrKindOverflow
AttrKindLayout
AttrKindCursor
)
// AttrColor sets the background color of a box.
// AttrColor sets the background color of a Box.
type AttrColor struct { color.Color }
// AttrTexture sets the texture of a box to a texture.
// AttrTexture sets the texture of a Box.
type AttrTexture struct { canvas.Texture }
// AttrTextureMode sets the rendering mode of a box's texture.
// AttrTextureMode sets the rendering mode of a Box's texture.
type AttrTextureMode TextureMode
// AttrBorder sets the border of a box.
// AttrBorder sets the Border of a Box.
type AttrBorder []Border
// AttrMinimumSize sets the minimum size of a box.
// AttrMinimumSize sets the minimum size of a Box.
type AttrMinimumSize image.Point
// AttrPadding sets the inner padding of a box.
// AttrPadding sets the inner padding of a Box.
type AttrPadding Inset
// AttrGap sets the gap between child boxes, if the box is a ContainerBox.
// AttrGap sets the gap between child Boxes, if the Box is a ContainerBox.
type AttrGap image.Point
// AttrTextColor sets the text color, if the box is a TextBox.
// AttrTextColor sets the text color, if the Box is a TextBox.
type AttrTextColor struct { color.Color }
// AttrDotColor sets the text selection color, if the box is a TextBox.
// AttrDotColor sets the text selection color, if the Box is a TextBox.
type AttrDotColor struct { color.Color }
// AttrFace sets the type face, if the box is a TextBox.
// AttrFace sets the type face, if the Box is a TextBox.
type AttrFace Face
// AttrWrap sets if the text wraps, if the box is a TextBox.
// AttrWrap sets whether or not the text wraps, if the Box is a TextBox.
type AttrWrap bool
// AttrAlign sets the alignment, if the box is a ContentBox.
// AttrAlign sets the layout alignment, if the Box is a ContentBox.
type AttrAlign struct { X, Y Align }
// AttrOverflow sets the overflow, if the box is a ContentBox.
// AttrOverflow sets the overflow, if the Box is a ContentBox.
type AttrOverflow struct { X, Y bool }
// AttrLayout sets the layout, if the box is a ContentBox.
// AttrLayout sets the Layout, if the Box is a ContentBox.
type AttrLayout struct { Layout }
// AttrCursor sets the mouse cursor shape.
type AttrCursor Cursor
// AColor is a convenience constructor for the color attribute.
// AColor is a convenience constructor for AttrColor.
func AColor (col color.Color) AttrColor {
return AttrColor { Color: col }
}
// ATexture is a convenience constructor for the texture attribute.
// ATexture is a convenience constructor for AttrTexture.
func ATexture (texture canvas.Texture) AttrTexture {
return AttrTexture { Texture: texture }
}
// ATextureMode is a convenience constructor for the texture mode attribute.
// ATextureMode is a convenience constructor for AttrTextureMode.
func ATextureMode (mode TextureMode) AttrTextureMode {
return AttrTextureMode(mode)
}
// ABorder is a convenience constructor for the border attribute.
// ABorder is a convenience constructor for AttrBorder.
func ABorder (borders ...Border) AttrBorder {
return AttrBorder(borders)
}
// AMinimumSize is a convenience constructor for the minimum size attribute.
// AMinimumSize is a convenience constructor for AttrMinimumSize.
func AMinimumSize (x, y int) AttrMinimumSize {
return AttrMinimumSize(image.Pt(x, y))
}
// APadding is a convenience constructor for the padding attribute.
// APadding is a convenience constructor for AttrPadding.
func APadding (sides ...int) AttrPadding {
return AttrPadding(I(sides...))
}
// AGap is a convenience constructor for the gap attribute.
// AGap is a convenience constructor for AttrGap.
func AGap (x, y int) AttrGap {
return AttrGap(image.Pt(x, y))
}
// ATextColor is a convenience constructor for the text color attribute.
// ATextColor is a convenience constructor for AttrTextColor.
func ATextColor (col color.Color) AttrTextColor {
return AttrTextColor { Color: col }
}
// ADotColor is a convenience constructor for the dot color attribute.
// ADotColor is a convenience constructor for AttrDotColor.
func ADotColor (col color.Color) AttrDotColor {
return AttrDotColor { Color: col }
}
// AFace is a convenience constructor for the face attribute.
// AFace is a convenience constructor for AttrFace.
func AFace (face Face) AttrFace {
return AttrFace(face)
}
// AWrap is a convenience constructor for the wrap attribute.
// AWrap is a convenience constructor for AttrWrap.
func AWrap (wrap bool) AttrWrap {
return AttrWrap(wrap)
}
// AAlign is a convenience constructor for the align attribute.
// AAlign is a convenience constructor for AttrAlign.
func AAlign (x, y Align) AttrAlign {
return AttrAlign { X: x, Y: y }
}
// AOverflow is a convenience constructor for the overflow attribute.
// AOverflow is a convenience constructor for AttrOverflow.
func AOverflow (x, y bool) AttrOverflow {
return AttrOverflow { X: x, Y: y }
}
// ALayout is a convenience constructor for the overflow attribute.
// ALayout is a convenience constructor for AttrLayout.
func ALayout (layout Layout) AttrLayout {
return AttrLayout { Layout: layout }
}
// ACursor is a convenience constructor for AttrCursor.
func ACursor (cursor Cursor) AttrCursor {
return AttrCursor(cursor)
}
// Equals returns true if both attributes can reasonably be declared equal.
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrColor) Equals (other Attr) bool {
if other, ok := other.(AttrColor); ok {
return this == other
@@ -124,7 +131,7 @@ func (this AttrColor) Equals (other Attr) bool {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrTexture) Equals (other Attr) bool {
if other, ok := other.(AttrTexture); ok {
return this == other
@@ -132,7 +139,7 @@ func (this AttrTexture) Equals (other Attr) bool {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrTextureMode) Equals (other Attr) bool {
if other, ok := other.(AttrTextureMode); ok {
return this == other
@@ -140,7 +147,7 @@ func (this AttrTextureMode) Equals (other Attr) bool {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrBorder) Equals (other Attr) bool {
if other, ok := other.(AttrBorder); ok {
if len(this) != len(other) { return false }
@@ -154,7 +161,7 @@ func (this AttrBorder) Equals (other Attr) bool {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrMinimumSize) Equals (other Attr) bool {
if other, ok := other.(AttrMinimumSize); ok {
return this == other
@@ -162,7 +169,7 @@ func (this AttrMinimumSize) Equals (other Attr) bool {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrPadding) Equals (other Attr) bool {
if other, ok := other.(AttrPadding); ok {
return this == other
@@ -170,7 +177,7 @@ func (this AttrPadding) Equals (other Attr) bool {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrGap) Equals (other Attr) bool {
if other, ok := other.(AttrGap); ok {
return this == other
@@ -178,7 +185,7 @@ func (this AttrGap) Equals (other Attr) bool {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrTextColor) Equals (other Attr) bool {
if other, ok := other.(AttrTextColor); ok {
return this == other
@@ -186,7 +193,7 @@ func (this AttrTextColor) Equals (other Attr) bool {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrDotColor) Equals (other Attr) bool {
if other, ok := other.(AttrDotColor); ok {
return this == other
@@ -194,7 +201,7 @@ func (this AttrDotColor) Equals (other Attr) bool {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrFace) Equals (other Attr) bool {
if other, ok := other.(AttrFace); ok {
return this == other
@@ -202,7 +209,7 @@ func (this AttrFace) Equals (other Attr) bool {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrWrap) Equals (other Attr) bool {
if other, ok := other.(AttrWrap); ok {
return this == other
@@ -210,7 +217,7 @@ func (this AttrWrap) Equals (other Attr) bool {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrAlign) Equals (other Attr) bool {
if other, ok := other.(AttrAlign); ok {
return this == other
@@ -218,7 +225,7 @@ func (this AttrAlign) Equals (other Attr) bool {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrOverflow) Equals (other Attr) bool {
if other, ok := other.(AttrOverflow); ok {
return this == other
@@ -226,13 +233,21 @@ func (this AttrOverflow) Equals (other Attr) bool {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrLayout) Equals (other Attr) bool {
// for some goofy reason if we try to compare two AttrLayouts we get a
// fucking runtime error????? its probably for the best anyways because
// two layouts cannot "reasonably" be declared equal
return false
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrCursor) Equals (other Attr) bool {
if other, ok := other.(AttrCursor); ok {
return this == other
} else {
return false
}
}
func (AttrColor) Kind () AttrKind { return AttrKindColor }
func (AttrTexture) Kind () AttrKind { return AttrKindTexture }
@@ -248,6 +263,7 @@ func (AttrWrap) Kind () AttrKind { return AttrKindWrap }
func (AttrAlign) Kind () AttrKind { return AttrKindAlign }
func (AttrOverflow) Kind () AttrKind { return AttrKindOverflow }
func (AttrLayout) Kind () AttrKind { return AttrKindLayout }
func (AttrCursor) Kind () AttrKind { return AttrKindCursor }
func (AttrColor) attr () { }
func (AttrTexture) attr () { }
@@ -263,3 +279,4 @@ func (AttrWrap) attr () { }
func (AttrAlign) attr () { }
func (AttrOverflow) attr () { }
func (AttrLayout) attr () { }
func (AttrCursor) attr () { }

View File

@@ -9,7 +9,7 @@ import "git.tebibyte.media/tomo/tomo/canvas"
// rendering, and events so that there can be as many platform-specific
// optimizations as possible.
type Backend interface {
// These methods create new objects. The backend must reject any object
// These methods create new Objects. The backend must reject any Object
// that was not made by it.
NewBox () Box
NewTextBox () TextBox
@@ -18,44 +18,39 @@ type Backend interface {
NewContainerBox () ContainerBox
// NewWindow creates a normal Window and returns it.
NewWindow (image.Rectangle) (Window, error)
NewWindow (WindowKind, 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) (Window, error)
// NewTexture creates a new texture from an image. The backend must
// reject any texture that was not made by it.
// NewTexture creates a new canvs.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 creates a new canvas.Canvas with the specified bounds. The
// backend must reject any canvas that was not made by it.
NewCanvas (image.Rectangle) canvas.CanvasCloser
// ColorRGBA returns the RGBA of a color according to the current style,
// as specified in color.Color.RGBA. It may be rendered invalid if the
// visual style changes, but the backend must send a StyleChange event
// visual style changes, but the Backend must send a StyleChange event
// to all managed boxes when this happens.
ColorRGBA (id Color) (r, g, b, a uint32)
// IconTexture returns the texture of an icon. It may be closed and
// therefore rendered invalid if the icon set changes, but the backend
// must send an IconSetChange event to all managed boxes when this
// happens.
// IconTexture returns the canvas.Texture of an Icon. It may be closed
// and therefore rendered invalid if the icon set changes, but the
// Backend must send an IconSetChange event to all managed boxes when
// this happens.
IconTexture (Icon, IconSize) canvas.Texture
// MimeIconTexture returns the texture of an icon corresponding to the
// specified MIME type. It may be closed and therefore rendered invalid
// if the icon set changes, but the backend must send an IconSetChange
// event to all managed boxes when this happens.
// MimeIconTexture returns the canvas.Texture of an icon corresponding
// to the specified MIME type. It may be closed and therefore rendered
// invalid if the icon set changes, but the Backend must send an
// IconSetChange event to all managed boxes when this happens.
MimeIconTexture (data.Mime, IconSize) canvas.Texture
// Run runs the event loop until Stop is called, or the backend
// Run runs the event loop until Stop is called, or the Backend
// experiences a fatal error.
Run () error
// Stop must unblock run. This behavior may only be called from within
// Stop must unblock Run. This behavior may only be called from within
// tomo.Stop.
Stop ()
@@ -71,8 +66,8 @@ func assertBackend () {
if backend == nil { panic("nil backend") }
}
// SetBackend sets the backend that functions in this package will call upon.
// This function will panic if there is already a backend running.
// SetBackend sets the Backend that functions in this package will call upon.
// This function will panic if there is already a Backend running.
func SetBackend (back Backend) {
backendLock.Lock()
defer backendLock.Unlock()

View File

@@ -33,7 +33,7 @@ type StrokeAlign int; const (
// have multiple pens associated with it, each maintaining their own drawing
// state.
type Pen interface {
// Rectangle draws a rectangle.
// Rectangle draws an image.Rectangle.
Rectangle (image.Rectangle)
// Path draws a path, which is a series of connected points.
@@ -69,7 +69,7 @@ type Pen interface {
type Canvas interface {
draw.Image
// Pen returns a new pen for this canvas.
// Pen returns a new pen for this Canvas.
Pen () Pen
// SubCanvas returns a returns a Canvas representing the portion of this
@@ -87,11 +87,11 @@ type CanvasCloser interface {
// Drawer can draw to a canvas.
type Drawer interface {
// Draw draws to the given canvas.
// Draw draws to the given Canvas.
Draw (Canvas)
}
// PushCanvas is a canvas that can push a region of itself to the screen (or
// PushCanvas is a Canvas that can push a region of itself to the screen (or
// some other destination).
type PushCanvas interface {
Canvas

View File

@@ -2,7 +2,10 @@ package canvas
import "image"
// Shatter takes in a bounding rectangle, and several rectangles to be
// TODO look into other options besides returning a slice, as that causes memory
// allocations.
// Shatter takes in a bounding rectangle, and several image.Rectangles to be
// subtracted from it. It returns a slice of rectangles that tile together to
// make up the difference between them. This is intended to be used for figuring
// out which areas of a container box's background are covered by other boxes so

View File

@@ -3,7 +3,7 @@ package canvas
import "io"
import "image"
// Texture is a handle that points to a 2D raster image managed by the backend.
// Texture is a handle that points to a 2D raster image.
type Texture interface {
image.Image
@@ -13,7 +13,7 @@ type Texture interface {
SubTexture (image.Rectangle) Texture
}
// TextureCloser is a texture that can be closed. Anything that receives a
// TextureCloser is a Texture that can be closed. Anything that receives a
// TextureCloser must close it after use.
type TextureCloser interface {
Texture

44
config/config.go Normal file
View File

@@ -0,0 +1,44 @@
// Package config stores common configuration parameters. They are unmanaged,
// and do not broadcast events when changed. Thus, they must be queried each
// time they are used, whether that be by backends, applications, or objects.
// They are intended to be set by things like application frameworks. Values set
// by other things are subject to being overridden.
package config
import "time"
import "git.tebibyte.media/tomo/tomo/input"
// DoubleClickDelay is the maximum amount of time that can pass between two
// consecutive clicks for them to be considered a double-click.
var DoubleClickDelay time.Duration = time.Second
// ScrollSpeed is how many units (pixels at a scale of 1.0) the content of a
// ContentBox should be moved in response to a scroll delta of 1.0.
var ScrollSpeed int = 16
// KeyChordConfirm is used to activate focused objects, and commonly to signal
// to the application that the user wishes to submit a value they have entered
// using the keyboard into an input field.
var KeyChordConfirm = input.KC(input.KeyEnter, input.ModNone)
// KeyChordClose is used to break out of some mode or state, such as a modal
// dialog.
var KeyChordClose = input.KC(input.KeyEscape, input.ModNone)
// KeyChordFocusNext is used to advance the input focus to the next focusable
// object.
var KeyChordFocusNext = input.KC(input.KeyTab, input.ModNone)
// KeyChordFocusPrevious is used to advance the input focus to the previous
// focusable object.
var KeyChordFocusPrevious = input.KC(input.KeyTab, input.ModShift)
// ButtonChordInteract is used to select, activate, and drag objects.
var ButtonChordInteract = input.BC(input.ButtonLeft, input.ModNone)
// ButtonChordContextMenu is used to open a context menu on an object.
var ButtonChordContextMenu = input.BC(input.ButtonRight, input.ModNone)
// ButtonChordPan is used to move the content of a content object relative to
// its inner bounds.
var ButtonChordPan = input.BC(input.ButtonMiddle, input.ModNone)

35
cursor.go Normal file
View File

@@ -0,0 +1,35 @@
package tomo
// Cursor represents a mouse cursor shape.
type Cursor string
// A list of standard cursor shapes. This is based off of the XDG Cursor
// Conventions Specification
// (https://www.freedesktop.org/wiki/Specifications/cursor-spec/).
const (
CursorUnknown Cursor = ""
CursorDefault Cursor = "Default"
CursorText Cursor = "Text"
CursorPointer Cursor = "Pointer"
CursorHelp Cursor = "Help"
CursorProgress Cursor = "Progress"
CursorWait Cursor = "Wait"
CursorCopy Cursor = "Copy"
CursorAlias Cursor = "Alias"
CursorNoDrop Cursor = "NoDrop"
CursorNotAllowed Cursor = "NotAllowed"
CursorAllScroll Cursor = "AllScroll"
CursorRowResize Cursor = "RowResize"
CursorColResize Cursor = "ColResize"
CursorEResize Cursor = "EResize"
CursorNEResize Cursor = "NEResize"
CursorNWResize Cursor = "NWResize"
CursorNResize Cursor = "NResize"
CursorSEResize Cursor = "SEResize"
CursorSWResize Cursor = "SWResize"
CursorSResize Cursor = "SResize"
CursorWResize Cursor = "WResize"
CursorVerticalText Cursor = "VerticalText"
CursorCrosshair Cursor = "Crosshair"
CursorCell Cursor = "Cell"
)

View File

@@ -2,11 +2,36 @@
// handlers.
package event
// A cookie is returned when you add an event handler so you can remove it
// later if you so choose.
type Cookie interface {
// Close removes the event handler this cookie is for.
Close ()
import "io"
import "errors"
// Cookie is returned when you add an event handler so you can remove it later
// if you so choose. When the Close behavior is called, the handler must be
// removed, even if an error is returned.
type Cookie io.Closer
// FuncCookie is a cookie that calls a function (itself) when closed.
type FuncCookie func () error
func (cookie FuncCookie) Close () error { return cookie () }
// NoCookie is a cookie that does nothing when closed.
type NoCookie struct { }
func (NoCookie) Close () error { return nil }
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 () error {
errs := make([]error, len(cookies))
for index, cookie := range cookies {
errs[index] = cookie.Close()
}
return errors.Join(errs...)
}
// Broadcaster manages event listeners.
@@ -16,7 +41,7 @@ type Broadcaster[L any] struct {
}
// Connect adds a new listener to the broadcaster and returns a corresponding
// cookie.
// Cookie.
func (broadcaster *Broadcaster[L]) Connect (listener L) Cookie {
broadcaster.ensure()
@@ -31,9 +56,9 @@ func (broadcaster *Broadcaster[L]) Listeners () map[int] L {
return broadcaster.listeners
}
func (broadcaster *Broadcaster[L]) newCookie () cookie[L] {
func (broadcaster *Broadcaster[L]) newCookie () broadcasterCookie[L] {
broadcaster.lastID ++
return cookie[L] {
return broadcasterCookie[L] {
id: broadcaster.lastID,
broadcaster: broadcaster,
}
@@ -45,20 +70,17 @@ 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 {
type broadcasterCookie[L any] struct {
id int
broadcaster *Broadcaster[L]
}
func (cookie cookie[L]) Close () {
func (cookie broadcasterCookie[L]) Close () error {
delete(cookie.broadcaster.listeners, cookie.id)
return nil
}
// FuncBroadcaster is a broadcaster that manages functions with no arguments.
// FuncBroadcaster is a Broadcaster that manages functions with no arguments.
type FuncBroadcaster struct {
Broadcaster[func ()]
}
@@ -69,17 +91,3 @@ 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()
}
}

32
icon.go
View File

@@ -1,22 +1,32 @@
package tomo
import "fmt"
import "git.tebibyte.media/tomo/tomo/data"
import "git.tebibyte.media/tomo/tomo/canvas"
// IconSize represents the size of an icon.
// IconSize represents the size of an Icon.
type IconSize int; const (
IconSizeSmall IconSize = iota;
IconSizeMedium
IconSizeLarge
)
func (size IconSize) String () string {
switch size {
case IconSizeSmall: return "IconSizeSmall"
case IconSizeMedium: return "IconSizeMedium"
case IconSizeLarge: return "IconSizeLarge"
default: return fmt.Sprintf("IconSize(%d)", size)
}
}
// Icon represents an icon ID.
type Icon string
// A list of standard icon IDs. This is roughly based off of the XDG Icon Naming
// Specification (https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html).
const (
// IconUnknown should be a blank space the size of a regular icon.
// IconUnknown should be a blank space the size of a regular Icon.
IconUnknown Icon = ""
// actions
@@ -93,7 +103,7 @@ const (
IconListRemove Icon = "ListRemove"
IconListChoose Icon = "ListChoose"
IconListExpand Icon = "ListExpand"
IconListContract Icon = "ListExpand"
IconListContract Icon = "ListContract"
// actions: mail
IconMailForward Icon = "MailForward"
IconMailMarkImportant Icon = "MailMarkImportant"
@@ -371,19 +381,19 @@ const (
IconWeatherStorm Icon = "WeatherStorm"
)
// Texture returns a texture of the corresponding icon ID. It may be closed and
// therefore rendered invalid if the icon set changes, so it is necessary to
// subscribe to the IconSetChange event in order to get a new icon texture when
// this happens.
// Texture returns a canvas.Texture of the corresponding icon ID. It may be
// closed and therefore rendered invalid if the icon set changes, so it is
// necessary to subscribe to the IconSetChange event in order to get a new icon
// texture when this happens.
func (id Icon) Texture (size IconSize) canvas.Texture {
assertBackend()
return backend.IconTexture(id, size)
}
// MimeIconTexture returns an icon corresponding to a MIME type. It may be
// closed and therefore rendered invalid if the icon set changes, so it is
// necessary to subscribe to the IconSetChange event in order to get a new icon
// texture when this happens.
// MimeIconTexture returns a canvas.Texture of the icon corresponding to a MIME
// type. It may be closed and therefore rendered invalid if the icon set
// changes, so it is necessary to subscribe to the IconSetChange event in order
// to get a new icon texture when this happens.
func MimeIconTexture (mime data.Mime, size IconSize) canvas.Texture {
assertBackend()
return backend.MimeIconTexture(mime, size)

View File

@@ -107,11 +107,80 @@ func (key Key) Printable () (printable bool) {
// Modifiers lists what modifier keys are being pressed. These should be used
// instead of attempting to track the state of the modifier keys, because there
// is no guarantee that one press event will be coupled with one release event.
type Modifiers struct {
Shift bool
Control bool
Alt bool
Meta bool
Super bool
Hyper bool
type Modifiers uint; const (
ModNone Modifiers = 0
ModShift Modifiers = 1 << iota
ModControl
ModAlt
ModMeta
ModSuper
ModHyper
)
// Shift returns whether the list of modifiers includes the shift key.
func (modifiers Modifiers) Shift () bool {
return modifiers & ModShift != ModNone
}
// Control returns whether the list of modifiers includes the control key.
func (modifiers Modifiers) Control () bool {
return modifiers & ModControl != ModNone
}
// Alt returns whether the list of modifiers includes the alt key.
func (modifiers Modifiers) Alt () bool {
return modifiers & ModAlt != ModNone
}
// Meta returns whether the list of modifiers includes the meta key.
func (modifiers Modifiers) Meta () bool {
return modifiers & ModAlt != ModNone
}
// Super returns whether the list of modifiers includes the super key.
func (modifiers Modifiers) Super () bool {
return modifiers & ModSuper != ModNone
}
// Hyper returns whether the list of modifiers includes the hyper key.
func (modifiers Modifiers) Hyper () bool {
return modifiers & ModHyper != ModNone
}
// KeyChord combines a keyboard key with Modifiers.
type KeyChord struct {
Key Key
Modifiers Modifiers
}
// KC is a convenience constructor for a KeyChord.
func KC (key Key, modifiers Modifiers) KeyChord {
return KeyChord {
Key: key,
Modifiers: modifiers,
}
}
// Pressed returns true if the given Key Modifiers match the KeyChord.
func (chord KeyChord) Pressed (key Key, modifiers Modifiers) bool {
return chord == KC(key, modifiers)
}
// ButtonChord combines a mouse button with a number of modifiers.
type ButtonChord struct {
Button Button
Modifiers Modifiers
}
// BC is a convenience constructor for a ButtonChord.
func BC (button Button, modifiers Modifiers) ButtonChord {
return ButtonChord {
Button: button,
Modifiers: modifiers,
}
}
// Pressed returns true if the given Button and Modifiers match the ButtonChord.
func (chord ButtonChord) Pressed (button Button, modifiers Modifiers) bool {
return chord == BC(button, modifiers)
}

190
object.go
View File

@@ -14,7 +14,7 @@ type Object interface {
GetBox () Box
}
// ContentObject is an object that has some kind of content.
// ContentObject is an Object that contains some kind of content.
type ContentObject interface {
Object
@@ -29,30 +29,31 @@ type ContentObject interface {
OnContentBoundsChange (func ()) event.Cookie
}
// Box is a basic styled box. Implementations of Box, as well as any interface
// that embed Box, may only originate from a Backend.
// Box is a basic box with no content. Implementations of Box, as well as any
// interface that embed Box, may only originate from a Backend.
type Box interface {
Object
// 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 returns the outer bounding image.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 returns the inner bounding image.Rectangle of the box. It
// is the value of Bounds inset by the Box's border and padding.
InnerBounds () image.Rectangle
// Role returns this Box's role as set by SetRole.
Role () Role
// SetRole sets what role this Box takes on. It is used to apply styling
// from a theme.
// SetRole sets what role this Box takes on. It is used by the Backend
// for applying styling.
SetRole (Role)
// Tag returns whether or not a named tag exists. These are used for
// applying styling, among other things. There are some special tags
// that are only and always extant during certain user input states:
// - hovered: The mouse pointer is within the box
// - focused: The box has keyboard focus
// - pressed: The box is being pressed by the left mouse button
// Tag returns whether or not a named tag exists. These are used by the
// Backend for applying styling, among other things. There are some
// special tags that are only and always extant during certain user
// input states:
// - hovered: The mouse pointer is within the Box
// - focused: The Box has keyboard focus
// - pressed: The Box is being pressed by config.ButtonChordInteract
Tag (string) bool
// SetTag adds or removes a named tag.
SetTag (string, bool)
@@ -68,9 +69,9 @@ type Box interface {
// 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 sets the types of data which can be dropped onto this
// Box. If none are specified (which is the default), this Box will
// reject all drops.
SetDNDAccept (...data.Mime)
// SetFocused sets whether or not this Box has keyboard focus. If set to
// true, this method will steal focus away from whichever Object
@@ -83,7 +84,7 @@ type Box interface {
// These are event subscription behaviors 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.
// returned event.Cookie.
OnFocusEnter (func () ) event.Cookie
OnFocusLeave (func () ) event.Cookie
OnStyleChange (func () ) event.Cookie
@@ -98,8 +99,13 @@ type Box interface {
// to the Box which is most directly affected by them, and then to all
// of its parents from the bottom-up. Returning true from the callback
// will cause the propagation to stop immediately, thereby "catching"
// the event. Generally, if the event was successfully handled, the
// callbacks ought to return true.
// the event.
//
// Generally, if the event was successfully handled, the callbacks ought
// to return true. Additionally, when subscribing to an event that is
// often paired with a second one (for example, KeyDown and KeyUp), it
// is good practice to subscribe to both and return true/false under the
// same circumstances.
OnMouseMove (func () bool) event.Cookie
OnButtonDown (func (button input.Button) bool) event.Cookie
OnButtonUp (func (button input.Button) bool) event.Cookie
@@ -108,46 +114,50 @@ type Box interface {
OnKeyUp (func (key input.Key, numberPad bool) bool) event.Cookie
}
// CanvasBox is a box that can be drawn to.
// CanvasBox is a Box that can be drawn to.
type CanvasBox interface {
Box
// 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 sets the canvas.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.
// - Its Bounds will describe the portion of the CanvasBox visible on
// screen. Therefore, it should not be used to determine the
// position of anything drawn within it. The Bounds of the CanvasBox
// should be used for this purpose.
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 causes the CanvasBox's area to be redrawn at the end of
// the event cycle, even if it wouldn't otherwise be. This will call the
// canvas.Drawer specified by SetDrawer.
Invalidate ()
}
// SurfaceBox is a box that can be drawn to via a hardware accelerated (or
// SurfaceBox is a Box that can be drawn to via a hardware accelerated (or
// platform-specific) graphics context.
type SurfaceBox interface {
Box
// Surface returns the underlying graphics context. The result must be
// asserted to the expected type or passed through a type switch before
// use. The exact type returned here depends on the backend being used,
// use. The exact type returned here depends on the Backend being used,
// and it is up to the application author to ensure that the application
// and backend agree on it. Applications should fail gracefully if the
// expected type was not found.
// and Backend agree on it. Applications should fail gracefully if the
// expected type was not found. If the surface has been destroyed by the
// Backend, it will return nil.
Surface () any
// 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.
// screen at the end of the event cycle, even if it wouldn't otherwise
// be.
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.
// Box is changed, the surface is re-allocated, or the surface is
// destroyed. The application must call Surface() each time this event
// fires in order to not draw onto a closed surface.
OnSizeChange (func ())
}
@@ -159,7 +169,7 @@ type ContentBox interface {
// 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.
// Box's InnerBounds, offset by the given image.Point.
ScrollTo (image.Point)
// OnContentBoundsChange specifies a function to be called when the
@@ -191,8 +201,8 @@ type TextBox interface {
type ContainerBox interface {
ContentBox
// Add appends a child Object. If the object is already a child of
// another object, it will be removed from that object first.
// 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)
@@ -219,7 +229,7 @@ type ContainerBox interface {
// types.
}
// LayoutHints are passed to a layout to tell it how to arrange child boxes.
// 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.
@@ -242,7 +252,7 @@ type Layout interface {
// MinimumSize returns the minimum width and height of
// LayoutHints.Bounds needed to properly lay out all child Boxes.
MinimumSize (LayoutHints, BoxQuerier) image.Point
// Arrange arranges child boxes according to the given LayoutHints.
// Arrange arranges child Boxes according to the given LayoutHints.
Arrange (LayoutHints, BoxArranger)
// RecommendedHeight returns the recommended height for a given width,
// if supported. Otherwise, it should just return the minimum height.
@@ -258,18 +268,18 @@ type Layout interface {
// BoxQuerier allows the attributes of a ContainerBox's children to be queried.
type BoxQuerier interface {
// Len returns the amount of boxes.
// Len returns the amount of Boxes.
Len () int
// MinimumSize returns the minimum size of a box.
// MinimumSize returns the minimum size of a Box.
MinimumSize (index int) image.Point
// RecommendedWidth returns the recommended width for a given height for
// a box, if supported. Otherwise, it should just return the minimum
// width of that box. The result of this behavior may or may not be
// a Box, if supported. Otherwise, it should just return the minimum
// width of that Box. The result of this behavior may or may not be
// respected, depending on the situation.
RecommendedWidth (index int, height int) int
// RecommendedHeight returns the recommended height for a given width
// for a box, if supported. Otherwise, it should just return the minimum
// width of that box. The result of this behavireor may or may not be
// for a Box, if supported. Otherwise, it should just return the minimum
// width of that Box. The result of this behavior may or may not be
// respected, depending on the situation.
RecommendedHeight (index int, width int) int
}
@@ -279,46 +289,65 @@ type BoxQuerier interface {
type BoxArranger interface {
BoxQuerier
// SetBounds sets the bounds of a box.
// SetBounds sets the bounds of a Box.
SetBounds (index int, bounds image.Rectangle)
}
// 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.
// WindowKind specifies a Window's kind, which determines how it is displayed
// and managed by the operating system.
type WindowKind string; const (
// Normal is a normal Window.
WindowKindNormal WindowKind = "Normal"
// Plain is an undecorated Window that does not appear in window lists.
// It is intended for things like docks, panels, etc.
WindowKindPlain WindowKind = "Plain"
// Utility is a small Window for toolboxes, command palletes, etc. It is
// usually given special styling and management by the OS.
WindowKindUtility WindowKind = "Utility"
// Toolbar is a small Window for menus and panes which have been "torn
// off" from their main Window or position. It is usually given special
// styling and management by the OS.
WindowKindToolbar WindowKind = "Toolbar"
// Menu is an undecorated Window for drop down menus, context menus,
// etc. It closes once the user interacts outside of it.
WindowKindMenu WindowKind = "Menu"
// Modal, while open, blocks all user input from reaching its parent
// window, forcing the user's attention. It is usually given special
// styling and management by the OS. Note that in some environments it
// will not be given window controls, so it should contain some "Close"
// or "Cancel" button.
WindowKindModal WindowKind = "Modal"
)
// Window is an operating system Window. It can directly contain one object,
// which is usually a container. Windows themselves are completely transparent,
// and become opaque once an opaque object is added as their root.
type Window interface {
// Bounds returns the bounds of the window including its frame, if
// Bounds returns the bounds of the Window including its frame, if
// possible. This means that the top-left point of the bounds will be
// either zero or negative.
Bounds () image.Rectangle
// InnerBounds returns the inner bounds of the window, not including its
// InnerBounds returns the inner bounds of the Window, not including its
// frame. This means that the top-left point of the bounds will be zero.
InnerBounds () image.Rectangle
// SetRoot sets the root child of the window. There can only be one at
// 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 sets the title of the Window.
SetTitle (string)
// SetIcon sets the icon of the window.
// SetIcon sets the icon of the Window.
SetIcon (Icon)
// SetResizable sets whether the window can be resized by the user in
// 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.
// 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 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)
// 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)
// NewModal creates a new modal window that blocks all input to this
// window until it is closed.
NewModal (image.Rectangle) (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.
NewChild (WindowKind, image.Rectangle) (Window, error)
// Modifiers returns which modifier keys on the keyboard are currently
// being held down.
Modifiers () input.Modifiers
@@ -335,8 +364,13 @@ type Window interface {
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.
// Close closes the window. This does not trigger the TryClose event.
Close () error
// OnTryClose specifies a function to be called when the user attempts
// to close the Window. If any registered handlers returns false, the
// Window will not be closed. This can be used to display some sort of
// "Unsaved changes" warning to the user.
OnTryClose (func () bool) event.Cookie
// OnClose specifies a function to be called when the Window is closed.
OnClose (func ()) event.Cookie
}

42
tomo.go
View File

@@ -1,15 +1,37 @@
package tomo
import "sync"
import "image"
import "git.tebibyte.media/tomo/tomo/canvas"
// Stop stops the currently running backend.
// TODO this really sucks. It might be a good idea to have Do be the entry point
// for every off-thread call, and Stop should just call backend.Stop within
// backend.Do. This is because Do is a queue and is not vulnerable to recursive
// locking.
var stopping bool
var stoppingLock sync.Mutex
func isStopping () bool {
stoppingLock.Lock()
defer stoppingLock.Unlock()
return stopping
}
func setStopping (is bool) {
stoppingLock.Lock()
defer stoppingLock.Unlock()
stopping = is
}
// Stop stops the currently running Backend.
func Stop () {
if isStopping() { return }
setStopping(true)
backendLock.Lock()
defer backendLock.Unlock()
if backend == nil { return }
backend.Stop()
backend = nil
setStopping(false)
}
// Do performs a callback function in the event loop thread as soon as possible.
@@ -19,18 +41,10 @@ func Do (callback func ()) {
if backend != nil { backend.Do(callback) }
}
// NewWindow creates and returns a window within the specified bounds on screen.
func NewWindow (bounds image.Rectangle) (Window, error) {
// NewWindow creates and returns a Window within the specified bounds on screen.
func NewWindow (kind WindowKind, bounds image.Rectangle) (Window, 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) (Window, error) {
assertBackend()
return backend.NewPlainWindow(bounds)
return backend.NewWindow(kind, bounds)
}
// NewBox creates and returns a basic Box.
@@ -57,8 +71,8 @@ func NewContainerBox () ContainerBox {
return backend.NewContainerBox()
}
// NewTexture creates a new texture from an image. When no longer in use, it
// must be freed using Close().
// NewTexture creates a new canvas.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)

18
unit.go
View File

@@ -52,9 +52,9 @@ func (inset Inset) String () string {
return fmt.Sprintf("%d %d %d %d", inset[0], inset[1], inset[2], inset[3])
}
// 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
// Apply returns the given image.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
@@ -76,7 +76,7 @@ func (inset Inset) Apply (bigger image.Rectangle) (smaller image.Rectangle) {
return
}
// Inverse returns a negated version of the inset.
// Inverse returns a negated version of the Inset.
func (inset Inset) Inverse () (prime Inset) {
return Inset {
inset[0] * -1,
@@ -96,7 +96,7 @@ func (inset Inset) Vertical () int {
return inset[SideTop] + inset[SideBottom]
}
// Border represents a single border of a box.
// Border represents a single border of a Box.
type Border struct {
Width Inset
Color [4]color.Color
@@ -169,18 +169,18 @@ func (face Face) String () string {
face.Font, face.Size, face.Weight, face.Italic, face.Slant)
}
// Role describes the role of an object.
// 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:
// 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.
// This should correspond directly to the type name of the Object.
Object string
}
@@ -190,7 +190,7 @@ func (r Role) String () string {
return fmt.Sprintf("%s.%s", r.Package, r.Object)
}
// R is shorthand for creating a role structure.
// R is a convenience constructor for Role.
func R (pack, object string) Role {
return Role { Package: pack, Object: object }
}