Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dc377c36a5 | |||
| 2b99a98a8e | |||
| d1b62f5560 | |||
| 85fe5ac65b | |||
| 14fc0ba372 | |||
| 9f4e8a539a | |||
| 1cb3be8de8 | |||
| 32e58ce63d | |||
| 4dbd86cec3 | |||
| 573212fe7d | |||
| ae5c177484 | |||
| facd85ef21 | |||
| ec967fe4f8 | |||
| 9efeaef8a8 | |||
| d6baf82a94 |
12
backend.go
12
backend.go
@@ -4,18 +4,26 @@ import "sort"
|
||||
import "image"
|
||||
import "errors"
|
||||
|
||||
// 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 {
|
||||
// These methods create new objects. The backend must reject any object
|
||||
// that was not made by it.
|
||||
NewWindow (image.Rectangle) (MainWindow, error)
|
||||
NewBox () Box
|
||||
NewTextBox () TextBox
|
||||
NewCanvasBox () CanvasBox
|
||||
NewContainerBox () ContainerBox
|
||||
|
||||
// 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 ())
|
||||
}
|
||||
|
||||
@@ -8,24 +8,24 @@ import "image/color"
|
||||
|
||||
// Cap represents a stroke cap type.
|
||||
type Cap int; const (
|
||||
CapButt Cap = iota
|
||||
CapRound
|
||||
CapSquare
|
||||
CapButt Cap = iota // Square cap that ends at the point
|
||||
CapRound // Round cap that surrounds the point
|
||||
CapSquare // square cap that surrounds 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
|
||||
@@ -44,9 +44,8 @@ type Pen interface {
|
||||
StrokeWeight (int) // how thick the stroke is
|
||||
StrokeAlign (StrokeAlign) // where the stroke is drawn
|
||||
|
||||
// set the stroke/fill to a solid color
|
||||
Stroke (color.Color)
|
||||
Fill (color.Color)
|
||||
Stroke (color.Color) // Sets the stroke to a solid color
|
||||
Fill (color.Color) // Sets the fill to a solid color
|
||||
}
|
||||
|
||||
// Canvas is an image that supports drawing paths.
|
||||
@@ -62,6 +61,7 @@ type Canvas interface {
|
||||
|
||||
// Drawer is an object that can draw to a canvas.
|
||||
type Drawer interface {
|
||||
// Draw draws to the given canvas.
|
||||
Draw (Canvas)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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.
|
||||
|
||||
188
object.go
188
object.go
@@ -3,13 +3,12 @@ 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"
|
||||
|
||||
type Gap image.Point
|
||||
|
||||
// Side represents one side of a rectangle.
|
||||
type Side int; const (
|
||||
SideTop Side = iota
|
||||
@@ -18,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:
|
||||
@@ -86,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
|
||||
@@ -108,24 +111,66 @@ type Object interface {
|
||||
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)
|
||||
// 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 (int, int)
|
||||
// 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
|
||||
@@ -144,7 +189,13 @@ type Box interface {
|
||||
// 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 ()
|
||||
}
|
||||
|
||||
@@ -152,58 +203,151 @@ type CanvasBox interface {
|
||||
// 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
|
||||
SetTextColor (color.Color)
|
||||
SetFace (font.Face)
|
||||
SetHAlign (Align)
|
||||
SetVAlign (Align)
|
||||
|
||||
// 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)
|
||||
// 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 (Gap)
|
||||
Add (Object)
|
||||
Delete (Object)
|
||||
Insert (child Object, before Object)
|
||||
Clear ()
|
||||
Length () int
|
||||
At (int) Object
|
||||
SetLayout (Layout)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Layout can be given to a ContainerBox to arrange child objects.
|
||||
// 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 {
|
||||
Arrange (image.Rectangle, Gap, []Box)
|
||||
// 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 (sizes []image.Image)
|
||||
NewMenu (image.Rectangle) (Window, error)
|
||||
NewModal (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)
|
||||
// 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)
|
||||
}
|
||||
|
||||
248
text/text.go
Normal file
248
text/text.go
Normal 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
|
||||
}
|
||||
20
tomo.go
20
tomo.go
@@ -1,8 +1,10 @@
|
||||
package tomo
|
||||
|
||||
import "sync"
|
||||
import "image"
|
||||
import "errors"
|
||||
|
||||
var backendLock sync.Mutex
|
||||
var backend Backend
|
||||
|
||||
// Run initializes a backend, runs the specified callback function, and runs the
|
||||
@@ -17,7 +19,10 @@ func Run (callback func ()) error {
|
||||
|
||||
back, err := Initialize()
|
||||
if err != nil { return err }
|
||||
|
||||
backendLock.Lock()
|
||||
backend = back
|
||||
backendLock.Unlock()
|
||||
|
||||
callback()
|
||||
return backend.Run()
|
||||
@@ -32,29 +37,44 @@ 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)
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
Reference in New Issue
Block a user