app

package
v0.0.0-...-492a67e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 11, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ZoomMin = -3
	ZoomMax = +1
)

ZoomMin / ZoomMax — 5 levels total, all clamped here.

View Source
const (
	AttrBold   = 1 << 0
	AttrFaint  = 1 << 1
	AttrItalic = 1 << 2
	AttrUnder  = 1 << 3
	AttrStrike = 1 << 4
)

Attr bits for a cell.

Variables

View Source
var (
	CorkDark    = RGB{82, 52, 32}
	CorkMid     = RGB{122, 84, 56}
	CorkLight   = RGB{172, 126, 76}
	CorkRust    = RGB{146, 80, 44}  // warmer reddish-brown
	CorkWarm    = RGB{198, 148, 88} // honey highlight
	CorkPore    = RGB{58, 36, 22}   // rare pores / holes
	CorkBlotch  = RGB{94, 64, 40}   // cork blotches
	CorkPatchFg = RGB{110, 74, 48}  // bigger brown "patch" fg (with `▒` density)

	PinRed   = RGB{230, 60, 60}
	PinHi    = RGB{255, 152, 152}
	PinDark  = RGB{140, 26, 26}
	StringRd = RGB{210, 44, 44}
	StringHi = RGB{245, 100, 100} // brighter red for the pulled-end tip
	ShadowBG = RGB{34, 22, 14}
	ShadowMi = RGB{52, 34, 22}
	Footer   = RGB{154, 123, 90}
	DimText  = RGB{180, 150, 110}
	Flash    = RGB{240, 232, 210} // slightly warm, not pure white
	DarkTint = RGB{18, 12, 8}     // for darken/vignette passes

	// SelBorder — legacy default (first entry of SelBorderChoices).
	// Per-note colors are preferred: Note.BorderColor picks an index.
	SelBorder = SelBorderChoices[0].Color
)
View Source
var Bayer4x4 = [4][4]int{
	{0, 8, 2, 10},
	{12, 4, 14, 6},
	{3, 11, 1, 9},
	{15, 7, 13, 5},
}

Bayer 4x4 ordered-dither matrix (0..15).

View Source
var BlotchChars = []rune{'░', '▒'}

Extremely rare cork blotches (imperfections).

View Source
var FiberChars = []rune{'.', '·', ',', '\'', '˙'}

Chars used for the note's own paper-fiber flecks.

View Source
var HalftoneLadder = []rune{' ', '░', '▒', '▓', '█'}
View Source
var PoreChars = []rune{'∘', '°', '◦', '○'}

Rare bigger cork pores / pinholes.

View Source
var SelBorderChoices = []BorderChoice{
	{"warm white", RGB{245, 238, 220}},
	{"cool white", RGB{230, 240, 250}},
	{"bright", RGB{255, 255, 255}},
	{"cyan", RGB{100, 220, 235}},
	{"gold", RGB{220, 180, 80}},
	{"mint", RGB{170, 235, 200}},
	{"lavender", RGB{200, 180, 240}},
	{"amber", RGB{240, 200, 100}},
	{"teal", RGB{80, 200, 180}},
}

SelBorderChoices — the 9 border colors available for notes. Keys 1-9 map to these in order; `c` cycles through them.

View Source
var ShadowChars = []rune{'▒', '░', '▓', '▞', '▚'}

Chars used inside the drop shadow for texture.

View Source
var StarChars = []rune{
	'.', ',', '·', '\'', ':', ';', '`', '~', '˙',
	'ˏ', 'ˎ', '‚', '„', '•',
}

Chars used to texture the cork surface — main pool. More variety gives the cork a busier, more ASCII-art feel.

View Source
var TintOrder = []string{
	"yellow", "pink", "blue", "green", "purple",
	"orange", "teal", "cream", "coral",
}

TintOrder — 1-9 in order.

View Source
var Tints = map[string]Tint{
	"yellow": {
		Name:  "yellow",
		Paper: RGB{246, 220, 120}, Ink: RGB{250, 230, 145},
		Fiber: RGB{204, 176, 85}, Tape: RGB{220, 195, 110}, Edge: RGB{255, 240, 170},
	},
	"pink": {
		Name:  "pink",
		Paper: RGB{245, 175, 200}, Ink: RGB{250, 190, 210},
		Fiber: RGB{205, 130, 160}, Tape: RGB{220, 150, 180}, Edge: RGB{255, 200, 220},
	},
	"blue": {
		Name:  "blue",
		Paper: RGB{175, 210, 240}, Ink: RGB{190, 220, 248},
		Fiber: RGB{130, 170, 205}, Tape: RGB{150, 185, 220}, Edge: RGB{200, 225, 255},
	},
	"green": {
		Name:  "green",
		Paper: RGB{180, 230, 180}, Ink: RGB{200, 240, 200},
		Fiber: RGB{130, 190, 130}, Tape: RGB{155, 210, 155}, Edge: RGB{210, 245, 210},
	},
	"purple": {
		Name:  "purple",
		Paper: RGB{210, 180, 240}, Ink: RGB{225, 195, 248},
		Fiber: RGB{165, 130, 205}, Tape: RGB{190, 160, 225}, Edge: RGB{230, 205, 255},
	},
	"orange": {
		Name:  "orange",
		Paper: RGB{245, 200, 140}, Ink: RGB{250, 210, 155},
		Fiber: RGB{205, 155, 95}, Tape: RGB{222, 178, 120}, Edge: RGB{255, 220, 175},
	},
	"teal": {
		Name:  "teal",
		Paper: RGB{170, 230, 220}, Ink: RGB{190, 240, 230},
		Fiber: RGB{125, 185, 180}, Tape: RGB{150, 210, 200}, Edge: RGB{200, 245, 235},
	},
	"cream": {
		Name:  "cream",
		Paper: RGB{235, 225, 205}, Ink: RGB{245, 235, 215},
		Fiber: RGB{195, 185, 160}, Tape: RGB{215, 205, 185}, Edge: RGB{250, 245, 225},
	},
	"coral": {
		Name:  "coral",
		Paper: RGB{245, 180, 165}, Ink: RGB{250, 195, 180},
		Fiber: RGB{210, 135, 120}, Tape: RGB{225, 155, 140}, Edge: RGB{255, 205, 190},
	},
}

Functions

func DataPath

func DataPath() (string, error)

func DitherShade

func DitherShade(x, y int, density float32) rune

DitherShade returns a halftone char for a given (x,y) and density 0..1.

func HalftoneChar

func HalftoneChar(level int) rune

HalftoneChar is a simple level lookup (0..4).

func ModeAttr

func ModeAttr(_ TextStyleMode) uint8

ModeAttr returns attribute bits to OR into cell renders. Unicode-math modes provide no ANSI attributes of their own (the visual "boldness" comes from the codepoint itself), so this always returns 0.

func Run

func Run()

Run is the package's main entry point. cmd/redthread/main.go calls it.

func SaveWorkspace

func SaveWorkspace(w *Workspace) error

SaveWorkspace writes the workspace as a v4 file (workspace envelope).

func ScreenDeltaToWorld

func ScreenDeltaToWorld(d, zoom int) int

ScreenDeltaToWorld converts a 1-cell screen delta into a world delta, ensuring a non-zero input always moves at least 1 world cell.

func ScreenX

func ScreenX(worldX, zoom int) int

ScreenX / ScreenY convert a world cell to screen (no bob applied).

func ScreenY

func ScreenY(worldY, zoom int) int

func SpliceOverlay

func SpliceOverlay(bg, overlay string, x, y int) string

SpliceOverlay splices `overlay` onto `bg` at visual cell position (x, y). Both strings are newline-separated; ANSI CSI escapes are preserved. Style state active at the cut point is re-emitted before the suffix so chars just past the overlay keep their original styling (otherwise they'd render in terminal default, often appearing white).

func StyleText

func StyleText(s string, mode TextStyleMode) string

StyleText maps ASCII letters/digits to the mode's Unicode variant.

func StyleViewText

func StyleViewText(s string, mode TextStyleMode) string

StyleViewText applies styleRune to a pre-rendered string while passing through ANSI CSI escape sequences untouched. Useful when post-processing the output of a third-party widget (e.g. bubbles/textarea) so the visible glyphs reflect the board's font without breaking cursor highlighting.

func WorldX

func WorldX(screenX, zoom int) int

WorldX / WorldY convert a screen cell coordinate to the equivalent world coordinate given the zoom scale.

func WorldY

func WorldY(screenY, zoom int) int

Types

type Board

type Board struct {
	Name           string        `json:"name"`
	GrainSeed      int64         `json:"grainSeed,omitempty"`
	Notes          []*Note       `json:"notes"`
	Strings        []*StringConn `json:"strings,omitempty"`
	Selected       string        `json:"-"`
	TextMode       TextStyleMode `json:"textMode,omitempty"`
	Zoom           int           `json:"zoom,omitempty"`
	HighlightColor int           `json:"highlightColor,omitempty"`
}

func (*Board) ApplyGlobalBorder

func (b *Board) ApplyGlobalBorder()

ApplyGlobalBorder syncs the package-level SelBorder with this board's HighlightColor. Call on load and whenever HighlightColor changes.

func (*Board) Connect

func (b *Board) Connect(fromID, toID string) *StringConn

func (*Board) ConnectToWall

func (b *Board) ConnectToWall(fromID string, worldX, worldY int) *StringConn

ConnectToWall creates a string from a note to a WORLD-coord wall-pin. The caller is responsible for converting a mouse click (screen) to world before calling this.

func (*Board) Cycle

func (b *Board) Cycle(direction int)

func (*Board) Delete

func (b *Board) Delete(id string)

func (*Board) DeleteStringAt

func (b *Board) DeleteStringAt(i int) bool

func (*Board) DeleteStringsTouching

func (b *Board) DeleteStringsTouching(id string) int

func (*Board) FindNote

func (b *Board) FindNote(id string) *Note

func (*Board) HitTopmost

func (b *Board) HitTopmost(cx, cy int) int

func (*Board) NewNote

func (b *Board) NewNote(screenW, screenH int) *Note

NewNote places a note at screen-centre converted to world coords, so it appears centred at whatever zoom level the user is on.

func (*Board) Raise

func (b *Board) Raise(i int)

func (*Board) Select

func (b *Board) Select(id string)

func (*Board) Selection

func (b *Board) Selection() *Note

func (*Board) StringsTouching

func (b *Board) StringsTouching(id string) []int

type BorderChoice

type BorderChoice struct {
	Name  string
	Color RGB
}

Weighted shade pool — duplicates bias the random pick toward common mid tones, with occasional brighter/rustier accents. BorderChoice pairs a display name with an RGB for the border palette.

type Canvas

type Canvas struct {
	W, H int
	// contains filtered or unexported fields
}

func NewCanvas

func NewCanvas(w, h int) *Canvas

func (*Canvas) BlankRect

func (c *Canvas) BlankRect(x, y, w, h int)

BlankRect clears a rectangle back to transparent.

func (*Canvas) BlitAt

func (c *Canvas) BlitAt(src *Canvas, dx, dy int)

BlitAt copies every cell from `src` into this canvas at offset (dx, dy). Cells that fall outside this canvas are clipped. Source-empty cells (Rune == 0 && Fg == nil) overwrite the destination just the same — the caller is expected to pass a freshly-allocated dst canvas if it wants the off-screen area to read as terminal default.

func (*Canvas) Clear

func (c *Canvas) Clear()

func (*Canvas) Dim

func (c *Canvas) Dim(f float32)

Dim applies a multiplier to every cell's foreground color, preserving transparency on bare cells. Used for blurring the backdrop during edit.

func (*Canvas) FillRect

func (c *Canvas) FillRect(x, y, w, h int, r rune, fg *RGB, attr uint8)

FillRect writes a single rune + style across a rectangular area, overwriting whatever was there (useful for drop shadows, clears, etc).

func (*Canvas) Get

func (c *Canvas) Get(x, y int) Cell

func (*Canvas) Inside

func (c *Canvas) Inside(x, y int) bool

func (*Canvas) Serialize

func (c *Canvas) Serialize() string

Serialize walks the grid, coalescing adjacent cells that share style. Returns a string containing ANSI SGR + text, suitable for Bubble Tea's View() return value.

func (*Canvas) Set

func (c *Canvas) Set(x, y int, cell Cell)

func (*Canvas) SetBlank

func (c *Canvas) SetBlank(x, y int)

SetBlank clears a cell back to transparent (erases cork stars etc).

func (*Canvas) SetRune

func (c *Canvas) SetRune(x, y int, r rune, fg RGB, attr uint8)

SetRune writes just rune+fg+attr without allocation.

func (*Canvas) WriteText

func (c *Canvas) WriteText(x, y int, s string, fg RGB, attr uint8)

WriteText writes a single-line string cell-by-cell starting at (x, y), wrapping handled by the caller.

type Cell

type Cell struct {
	Rune rune
	Fg   *RGB  // nil = terminal default fg (so a space is fully transparent)
	Attr uint8 // Attr* bits
}

func (Cell) IsEmpty

func (c Cell) IsEmpty() bool

IsEmpty returns true if the cell is the fully-transparent default.

type Editor

type Editor struct {
	Ta     textarea.Model
	NoteID string
}

func NewEditor

func NewEditor(n *Note, w, h int) Editor

func (*Editor) Resize

func (e *Editor) Resize(w, h int)

func (*Editor) Split

func (e *Editor) Split() (string, string)

Split returns the live (title, body) from the textarea content. First non-empty line is the title; the rest (skipping one separator blank) is the body.

func (*Editor) Update

func (e *Editor) Update(msg tea.Msg) tea.Cmd

func (Editor) View

func (e Editor) View(w, h int, n *Note, stars []Star, textMode TextStyleMode, board *Board) string

View composes: blurred backdrop → ornate canvas frame at target rect → textarea spliced inside the frame → footer.

type FontMenu

type FontMenu struct {
	Cursor    int           // index into TextModes
	SavedMode TextStyleMode // mode at time of opening; restored on Esc
}

FontMenu is the stateful popup. nil on model = closed.

func NewFontMenu

func NewFontMenu(current TextStyleMode) *FontMenu

func (*FontMenu) Move

func (m *FontMenu) Move(delta int)

func (*FontMenu) Selected

func (m *FontMenu) Selected() TextStyleMode

type Mode

type Mode int
const (
	ModeBoard Mode = iota
	ModeEdit
)

type Note

type Note struct {
	ID      string    `json:"id"`
	Title   string    `json:"title"`
	Body    string    `json:"body"`
	X       int       `json:"x"` // WORLD coord
	Y       int       `json:"y"` // WORLD coord
	Tint    string    `json:"tint"`
	Created time.Time `json:"created"`
	Updated time.Time `json:"updated"`

	// Runtime-only:
	Bob    float32 `json:"-"` // SCREEN-cell vertical bob
	BobX   float32 `json:"-"` // SCREEN-cell lateral wobble
	Lifted bool    `json:"-"`
	Flash  float32 `json:"-"`
}

func (*Note) EffectiveX

func (n *Note) EffectiveX(zoom int) int

EffectiveX: SCREEN X = world→screen projection + bob.

func (*Note) EffectiveY

func (n *Note) EffectiveY(zoom int) int

EffectiveY: SCREEN Y = world→screen projection + bob.

func (*Note) Hit

func (n *Note) Hit(cx, cy, zoom int) bool

Hit tests a screen-cell (cx, cy) against the note's current screen rect.

func (*Note) PinPos

func (n *Note) PinPos(zoom int) (int, int)

PinPos — screen position of the pin (top-center) including bob.

func (*Note) ScreenRect

func (n *Note) ScreenRect(zoom int) Rect

ScreenRect — drawing rect (no bob, stable for shadow/hit-test).

type NoteDims

type NoteDims struct{ W, H int }

func DimsFor

func DimsFor(zoom int) NoteDims

DimsFor returns rendered note size at a zoom level.

type PinOverride

type PinOverride struct {
	NoteID string
	X, Y   int
}

PinOverride lets callers substitute the screen position used for a specific note's string endpoints — used during the zoom-to-edit transition so strings follow the morphing rect instead of the original pin location.

type PullState

type PullState struct {
	Active bool
	FromID string // note ID the string starts at

	TargetX int
	TargetY int

	VX float32
	VY float32
}

func (*PullState) SetTarget

func (p *PullState) SetTarget(x, y int)

func (*PullState) Start

func (p *PullState) Start(fromID string, fromX, fromY int)

func (*PullState) Stop

func (p *PullState) Stop()

func (*PullState) Tick

func (p *PullState) Tick()

type RGB

type RGB struct{ R, G, B uint8 }

RGB is a 24-bit color.

func Lerp

func Lerp(a, b RGB, t float32) RGB

Lerp blends two colors linearly (0 → a, 1 → b).

func (RGB) Brighter

func (c RGB) Brighter(f float32) RGB

func (RGB) Brightness

func (c RGB) Brightness() float32

Brightness — Rec 601 luma, used by the monochrome dither pass.

func (RGB) Dimmed

func (c RGB) Dimmed(f float32) RGB

func (RGB) Hex

func (c RGB) Hex() string

func (RGB) SGR

func (c RGB) SGR() string

type Rect

type Rect struct{ X, Y, W, H int }

func FontMenuRect

func FontMenuRect(w, h int) Rect

func RectFromNote

func RectFromNote(n *Note, zoom int) Rect

RectFromNote builds the source rect for a note at the board's zoom (SCREEN coords, no bob — transitions should start from the stable resting position, not mid-bounce).

func TargetRect

func TargetRect(w, h int) Rect

TargetRect returns the centered edit-card rect for a given canvas size.

type Saver

type Saver struct {
	// contains filtered or unexported fields
}

func NewSaver

func NewSaver(w *Workspace, delay time.Duration) *Saver

func (*Saver) Flush

func (s *Saver) Flush() error

func (*Saver) Touch

func (s *Saver) Touch()

type Star

type Star struct {
	X, Y  int
	R     rune
	Color RGB
}

func GenStars

func GenStars(w, h int) []Star

GenStars returns a stable cork-texture for (w,h) using the default seed. For per-board grain, use GenStarsForBoard.

func GenStarsForBoard

func GenStarsForBoard(w, h int, grainSeed int64) []Star

GenStarsForBoard returns a cork-texture stable across resizes-of-equal-size AND distinct per `grainSeed`. Row 0 is reserved for the tab bar — no stars are placed there.

type StringConn

type StringConn struct {
	A       StringEnd `json:"a"`
	B       StringEnd `json:"b"`
	Tight   bool      `json:"tight"`
	InFront bool      `json:"front,omitempty"`

	// legacy v2 fields (loaded, never written)
	FromID string `json:"from,omitempty"`
	ToID   string `json:"to,omitempty"`
}

func (*StringConn) InvolvesNote

func (s *StringConn) InvolvesNote(id string) bool

type StringEnd

type StringEnd struct {
	NoteID string `json:"note,omitempty"`
	X      int    `json:"x,omitempty"` // WORLD coord (for wall pins)
	Y      int    `json:"y,omitempty"`
}

func (*StringEnd) Pos

func (e *StringEnd) Pos(b *Board) (int, int, bool)

Pos returns an endpoint's SCREEN position (for drawing).

type TextStyleMode

type TextStyleMode int
const (
	TextPlain   TextStyleMode = iota // plain
	TextBold                         // 𝗕𝗼𝗹𝗱 — math sans-serif bold
	TextItalic                       // 𝘐𝘵𝘢𝘭𝘪𝘤 — math sans-serif italic
	TextBoldIt                       // 𝘽𝙤𝙡𝙙 𝙄𝙩 — math sans-serif bold italic
	TextScript                       // 𝓢𝓬𝓻𝓲𝓹𝓽 — math bold script
	TextFraktur                      // 𝕱𝖗𝖆𝖐𝖙𝖚𝖗 — math bold fraktur
	TextDouble                       // 𝔻𝕠𝕦𝕓𝕝𝕖 — math double-struck
	TextMono                         // 𝙼𝚘𝚗𝚘 — math monospace
)

func (TextStyleMode) Name

func (m TextStyleMode) Name() string

func (TextStyleMode) Sample

func (m TextStyleMode) Sample() string

type Tint

type Tint struct {
	Name  string
	Paper RGB // main ink / border color
	Ink   RGB // body text color (slightly lighter or darker for contrast)
	Fiber RGB // paper-fiber dot color
	Tape  RGB // tape strip color (tape on top of the note)
	Edge  RGB // edge highlight when selected
}

A Tint is a named color set for a sticky-note look.

func GetTint

func GetTint(name string) Tint

type Transition

type Transition struct {
	Mode     TransitionMode
	Start    time.Time
	Source   Rect
	NoteID   string
	Duration time.Duration
}

func NewTransitionIn

func NewTransitionIn(n *Note, zoom int) *Transition

func NewTransitionOut

func NewTransitionOut(n *Note, zoom int) *Transition

func (*Transition) Done

func (t *Transition) Done(now time.Time) bool

func (*Transition) Progress

func (t *Transition) Progress(now time.Time) float32

Progress: 0 = at source, 1 = at target. In runs 0→1; Out runs 1→0.

type TransitionMode

type TransitionMode int
const (
	TransitionIn TransitionMode = iota
	TransitionOut
)

type Workspace

type Workspace struct {
	Boards    []*Board `json:"boards"`
	ActiveIdx int      `json:"activeIdx,omitempty"`
}

Workspace is a list of named cork boards the user can cycle between. One is "active"; everything in the running model points at that one.

func LoadWorkspace

func LoadWorkspace() (*Workspace, error)

LoadWorkspace reads notes.json, migrating older schemas on the way in. Returns (nil, nil) when the file simply doesn't exist yet.

func (*Workspace) ActiveBoard

func (w *Workspace) ActiveBoard() *Board

ActiveBoard returns the currently-focused board, never nil while the workspace has at least one board.

func (*Workspace) AddBoard

func (w *Workspace) AddBoard(name string) *Board

AddBoard appends a fresh board with a unique grain seed and makes it active. Returns the new board.

func (*Workspace) CycleActive

func (w *Workspace) CycleActive(delta int)

CycleActive moves the active index by `delta`, wrapping around.

func (*Workspace) DeleteBoard

func (w *Workspace) DeleteBoard(i int) bool

DeleteBoard removes the board at index i. Refuses to delete the last remaining board.

func (*Workspace) MoveActive

func (w *Workspace) MoveActive(delta int) bool

MoveActive shifts the active board by `delta` positions in the workspace order (negative = move left, positive = move right). Wraps around. The active index follows the moved board so the tab bar's `●` stays on it. Returns true if a swap happened.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL