Pad/internal/ui/element.go
Greg Pomerantz fa43bf2402 Add ShapeText/DrawShapedText: single shaper call, zero-call drawing
- New shaper.go: ShapeText() returns ShapedText with measured width +
  stored PathSpec + BitmapsCall, DrawShapedText() draws with zero shaper calls
- drawStatusBar: ShapeText for filename + right icons, positions from widths
- drawBottomBar: ShapeText for cursor/bytepos/wrap, positions by alignment
- No separate measureText helper: ShapeText.Width provides measurements
- StatusBar uses IconLeft/IconRow string slices for icon configuration
- BottomBar uses BarAlignment enum for item positioning
2026-05-12 12:06:28 -04:00

370 lines
8.5 KiB
Go

package ui
import "gioui.org/unit"
// Region defines a screen area in device-independent pixels (Dp).
// All element positions and sizes use Dp for device independence.
type Region struct {
X, Y Dp
W, H Dp
}
// Element is the base interface for all UI elements.
type Element interface {
Region() Region
Visible() bool
}
// TextAlign specifies horizontal text alignment.
type TextAlign int
const (
AlignStart TextAlign = iota
AlignCenter
AlignEnd
)
// InputType specifies the type of user input event.
type InputType int
const (
Tap InputType = iota
DoubleTap
LongPress
Scroll
KeyDown
KeyUp
)
// InputEvent represents a user input event routed to an element.
type InputEvent struct {
ElementID string
Type InputType
Data any
}
// ConfigEvent represents a window configuration change (resize, orientation).
// Width and Height are in device-independent pixels (Dp).
type ConfigEvent struct {
Width Dp
Height Dp
}
// Label displays static text.
type Label struct {
id string
region Region
visible bool
Text string
Align TextAlign
FontSize unit.Sp // 0 = theme default
Color Color // 0 = theme default
Bold bool
}
func (l Label) Region() Region { return l.region }
func (l Label) Visible() bool { return l.visible }
func (l Label) ID() string { return l.id }
// NewLabel creates a visible Label element.
func NewLabel(text string, fontSize unit.Sp, region Region) Label {
return Label{
region: region,
visible: true,
Text: text,
FontSize: fontSize,
}
}
// TextField accepts text input or displays multiline text.
type TextField struct {
id string
region Region
visible bool
Value string
Placeholder string
Focused bool
Multiline bool
ScrollOffset Dp
VisibleLines []Line
WordWrap bool
WrapWidth Dp
}
func (tf TextField) Region() Region { return tf.region }
func (tf TextField) Visible() bool { return tf.visible }
func (tf TextField) ID() string { return tf.id }
// Line represents a single line of text in a multiline TextField.
type Line struct {
Text string
LineNumber int // 1-indexed, for display
}
// ListView displays a scrollable list of items.
type ListView struct {
id string
region Region
visible bool
Items []ListItem
ScrollOffset int
Selected int
}
func (lv ListView) Region() Region { return lv.region }
func (lv ListView) Visible() bool { return lv.visible }
func (lv ListView) ID() string { return lv.id }
// ListItem is a single entry in a ListView.
type ListItem struct {
Text string
Subtext string
Selected bool
}
// AlphaIndex displays an alphabetical index for quick navigation.
type AlphaIndex struct {
id string
region Region
visible bool
Letters []string
ActiveLetter string
}
func (ai AlphaIndex) Region() Region { return ai.region }
func (ai AlphaIndex) Visible() bool { return ai.visible }
func (ai AlphaIndex) ID() string { return ai.id }
// NewAlphaIndex creates a visible AlphaIndex element.
func NewAlphaIndex(region Region, letters []string) AlphaIndex {
return AlphaIndex{
region: region,
visible: true,
Letters: letters,
}
}
// Button is an interactive button element.
type Button struct {
id string
region Region
visible bool
Text string
Enabled bool
Primary bool
}
func (b Button) Region() Region { return b.region }
func (b Button) Visible() bool { return b.visible }
func (b Button) ID() string { return b.id }
// NewButton creates a visible Button element.
func NewButton(text string, enabled bool, primary bool, region Region) Button {
return Button{
region: region,
visible: true,
Text: text,
Enabled: enabled,
Primary: primary,
}
}
// SearchBar displays an in-editor search interface.
type SearchBar struct {
id string
region Region
visible bool
Query string
Match int
Total int
Forward bool
}
func (sb SearchBar) Region() Region { return sb.region }
func (sb SearchBar) Visible() bool { return sb.visible }
func (sb SearchBar) ID() string { return sb.id }
// NewSearchBar creates a visible SearchBar element.
func NewSearchBar(region Region, query string, match, total int, forward bool) SearchBar {
return SearchBar{
region: region,
visible: true,
Query: query,
Match: match,
Total: total,
Forward: forward,
}
}
// Cursor displays the text cursor and optional selection highlight.
type Cursor struct {
id string
region Region
visible bool
Line int
Column int
Blinking bool
Selection *Selection
}
func (c Cursor) Region() Region { return c.region }
func (c Cursor) Visible() bool { return c.visible }
func (c Cursor) ID() string { return c.id }
// Selection represents a text selection range.
type Selection struct {
StartLine, StartCol int
EndLine, EndCol int
}
// MergeHunk displays a conflict resolution hunk.
type MergeHunk struct {
id string
region Region
visible bool
HunkNumber int
TotalHunks int
LineRange string
ContextLines []string
OurLines []string
TheirLines []string
Resolution HunkResolution
}
func (mh MergeHunk) Region() Region { return mh.region }
func (mh MergeHunk) Visible() bool { return mh.visible }
func (mh MergeHunk) ID() string { return mh.id }
// HunkResolution represents the resolution state of a merge hunk.
type HunkResolution int
const (
Unresolved HunkResolution = iota
KeepOurs
KeepTheirs
MergeBoth
)
// StatusBar displays the top bar with filename, action icons, and conflict indicator.
// IconLeft contains icon names drawn left-aligned before the filename.
// IconRow contains icon names drawn right-aligned after the filename.
type StatusBar struct {
id string
region Region
visible bool
Filename string
IconLeft []string // icon names for left side (e.g., ["cut"])
IconRow []string // icon names for right side (e.g., ["conflict", "search"])
ConflictIcon bool
Search bool
}
func (sb StatusBar) Region() Region { return sb.region }
func (sb StatusBar) Visible() bool { return sb.visible }
func (sb StatusBar) ID() string { return sb.id }
// NewStatusBar creates a visible StatusBar element.
func NewStatusBar(region Region, filename string, filenameExp bool, cutCopy, copy, paste, conflictIcon, search bool) StatusBar {
return StatusBar{
region: region,
visible: true,
Filename: filename,
IconLeft: []string{"cut"},
IconRow: []string{"search"},
ConflictIcon: conflictIcon,
Search: search,
}
}
// BarAlignment specifies how a bar item is positioned within its bar.
type BarAlignment int
const (
BarLeft BarAlignment = iota
BarCenter
BarRight
)
// BottomBar displays the bottom bar with cursor position, byte position, and word wrap toggle.
type BottomBar struct {
id string
region Region
visible bool
CursorPos string
CursorAlign BarAlignment
BytePos string
BytePosAlign BarAlignment
WordWrap bool
WrapAlign BarAlignment
}
func (bb BottomBar) Region() Region { return bb.region }
func (bb BottomBar) Visible() bool { return bb.visible }
func (bb BottomBar) ID() string { return bb.id }
// NewBottomBar creates a visible BottomBar element.
func NewBottomBar(region Region, cursorPos, bytePos string, wordWrap bool) BottomBar {
return BottomBar{
region: region,
visible: true,
CursorPos: cursorPos,
CursorAlign: BarLeft,
BytePos: bytePos,
BytePosAlign: BarCenter,
WordWrap: wordWrap,
WrapAlign: BarRight,
}
}
// Toast displays a temporary notification.
type Toast struct {
id string
region Region
visible bool
Text string
Timeout int // milliseconds
}
func (t Toast) Region() Region { return t.region }
func (t Toast) Visible() bool { return t.visible }
func (t Toast) ID() string { return t.id }
// NewToast creates a visible Toast element.
func NewToast(region Region, text string, timeout int) Toast {
return Toast{
region: region,
visible: true,
Text: text,
Timeout: timeout,
}
}
// Spacer adds vertical or horizontal space.
type Spacer struct {
id string
region Region
visible bool
}
func (s Spacer) Region() Region { return s.region }
func (s Spacer) Visible() bool { return s.visible }
func (s Spacer) ID() string { return s.id }
// NewSpacer creates a visible Spacer element.
func NewSpacer(height Dp) Spacer {
return Spacer{
region: Region{H: height},
visible: true,
}
}
// Color is an RGBA color.
type Color struct {
R, G, B, A uint8
}
// Theme holds styling defaults for the UI.
type Theme struct {
FontSize unit.Sp // Gio's shaper requires unit.Sp for font sizes
}