360 lines
13 KiB
Go
360 lines
13 KiB
Go
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"
|
|
import "git.tebibyte.media/tomo/tomo/canvas"
|
|
|
|
// Side represents one side of a rectangle.
|
|
type Side int; const (
|
|
SideTop Side = iota
|
|
SideRight
|
|
SideBottom
|
|
SideLeft
|
|
)
|
|
|
|
// Inset represents a rectangle inset that can have a different value for each
|
|
// side.
|
|
type Inset [4]int
|
|
|
|
// I allows you to create an inset in a CSS-ish way:
|
|
//
|
|
// - One argument: all sides are set to this value
|
|
// - Two arguments: the top and bottom sides are set to the first value, and
|
|
// the left and right sides are set to the second value.
|
|
// - Three arguments: the top side is set by the first value, the left and
|
|
// right sides are set by the second vaue, and the bottom side is set by the
|
|
// third value.
|
|
// - Four arguments: each value corresponds to a side.
|
|
//
|
|
// This function will panic if an argument count that isn't one of these is
|
|
// given.
|
|
func I (sides ...int) Inset {
|
|
switch len(sides) {
|
|
case 1: return Inset { sides[0], sides[0], sides[0], sides[0] }
|
|
case 2: return Inset { sides[0], sides[1], sides[0], sides[1] }
|
|
case 3: return Inset { sides[0], sides[1], sides[2], sides[1] }
|
|
case 4: return Inset { sides[0], sides[1], sides[2], sides[3] }
|
|
default: panic("I: illegal argument count.")
|
|
}
|
|
}
|
|
|
|
// Apply returns the given rectangle, shrunk on all four sides by the given
|
|
// inset. If a measurment of the inset is negative, that side will instead be
|
|
// expanded outward. If the rectangle's dimensions cannot be reduced any
|
|
// further, an empty rectangle near its center will be returned.
|
|
func (inset Inset) Apply (bigger image.Rectangle) (smaller image.Rectangle) {
|
|
smaller = bigger
|
|
if smaller.Dx() < inset[3] + inset[1] {
|
|
smaller.Min.X = (smaller.Min.X + smaller.Max.X) / 2
|
|
smaller.Max.X = smaller.Min.X
|
|
} else {
|
|
smaller.Min.X += inset[3]
|
|
smaller.Max.X -= inset[1]
|
|
}
|
|
|
|
if smaller.Dy() < inset[0] + inset[2] {
|
|
smaller.Min.Y = (smaller.Min.Y + smaller.Max.Y) / 2
|
|
smaller.Max.Y = smaller.Min.Y
|
|
} else {
|
|
smaller.Min.Y += inset[0]
|
|
smaller.Max.Y -= inset[2]
|
|
}
|
|
return
|
|
}
|
|
|
|
// Inverse returns a negated version of the inset.
|
|
func (inset Inset) Inverse () (prime Inset) {
|
|
return Inset {
|
|
inset[0] * -1,
|
|
inset[1] * -1,
|
|
inset[2] * -1,
|
|
inset[3] * -1,
|
|
}
|
|
}
|
|
|
|
// Horizontal returns the sum of SideRight and SideLeft.
|
|
func (inset Inset) Horizontal () int {
|
|
return inset[SideRight] + inset[SideLeft]
|
|
}
|
|
|
|
// Vertical returns the sum of SideTop and SideBottom.
|
|
func (inset Inset) Vertical () int {
|
|
return inset[SideTop] + inset[SideBottom]
|
|
}
|
|
|
|
// Border represents a single border of a box.
|
|
type Border struct {
|
|
Width Inset
|
|
Color [4]color.Color
|
|
}
|
|
|
|
// Align lists basic alignment types.
|
|
type Align int; const (
|
|
AlignStart Align = iota // similar to left-aligned text
|
|
AlignMiddle // similar to center-aligned text
|
|
AlignEnd // similar to right-aligned text
|
|
AlignEven // similar to justified text
|
|
)
|
|
|
|
// Object is any obscreen object. Each object must be linked to a box, even if
|
|
// it is that box.
|
|
type Object interface {
|
|
GetBox () Box
|
|
}
|
|
|
|
// Box is a basic styled box.
|
|
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 () 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 sets the padding between the Box's innermost Border and
|
|
// its content.
|
|
SetPadding (Inset)
|
|
|
|
// 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 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 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
|
|
OnDNDLeave (func ()) event.Cookie
|
|
OnDNDDrop (func (data.Data)) event.Cookie
|
|
OnMouseEnter (func ()) event.Cookie
|
|
OnMouseLeave (func ()) event.Cookie
|
|
OnMouseMove (func ()) event.Cookie
|
|
OnMouseDown (func (input.Button)) event.Cookie
|
|
OnMouseUp (func (input.Button)) event.Cookie
|
|
OnScroll (func (deltaX, deltaY float64)) event.Cookie
|
|
OnKeyDown (func (key input.Key, numberPad bool)) event.Cookie
|
|
OnKeyUp (func (key input.Key, numberPad bool)) event.Cookie
|
|
}
|
|
|
|
// 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.
|
|
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 ()
|
|
}
|
|
|
|
// ContentBox is an abstract box that has some kind of content. Its only purpose
|
|
// is to be embedded into TextBox and ContainerBox.
|
|
type ContentBox interface {
|
|
Box
|
|
|
|
// 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 window.
|
|
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
|
|
}
|
|
|
|
// TextBox is a box that contains text content.
|
|
type TextBox interface {
|
|
ContentBox
|
|
|
|
// SetText sets the text content of the Box.
|
|
SetText (string)
|
|
// SetTextColor sets the text color.
|
|
SetTextColor (color.Color)
|
|
// 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
|
|
// according to a layout rule.
|
|
type ContainerBox interface {
|
|
ContentBox
|
|
|
|
// SetPropagateEvents specifies whether or not child Objects will
|
|
// receive user input events. It is true by default. If it is false, all
|
|
// user input that would otherwise be directed to a child Box is
|
|
// directed to this Box.
|
|
SetPropagateEvents (bool)
|
|
// SetGap sets the gap between child Objects.
|
|
SetGap (image.Point)
|
|
// Add appends a child Object.
|
|
Add (Object)
|
|
// Delete removes a child Object, if it is a child of this Box.
|
|
Delete (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)
|
|
}
|
|
|
|
// 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 wether 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
|
|
}
|
|
|
|
// 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 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 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 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)
|
|
// 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)
|
|
// Show shows the window.
|
|
Show ()
|
|
// Hide hides the window.
|
|
Hide ()
|
|
// 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)
|
|
}
|