Changelog
All notable changes to the oat-latte framework are listed here, newest first.
v0.3.0
layout.VGap / layout.HGap — fixed-size inert spacers · layout package file split
Added
-
layout.VGap— a fixed-height inert spacer for use inVBox. UnlikeVFill, it always occupies exactlynrows and does not participate in flex distribution.layout.NewVGap(n int) *VGap— constructor.n < 0is clamped to0.MeasurereturnsSize{Width: 0, Height: n}.Renderis a no-op.
-
layout.HGap— a fixed-width inert spacer for use inHBox. Always occupies exactlyncolumns.layout.NewHGap(n int) *HGap— constructor.n < 0is clamped to0.MeasurereturnsSize{Width: n, Height: 0}.Renderis a no-op.
Changed
-
layoutpackage file split — the monolithiclayout/layout.go(1 092 lines) has been split into focused per-type files. All types remain inpackage layout; the public API is completely unchanged.File Contents layout/box.goVBox,HBox,childSlot, alignment helpers,clamp,toOatInsets,containsFocuslayout/grid.goGrid,GridChildlayout/stack.goStacklayout/border.goBorderlayout/padding.goPaddinglayout/spacer.goFlexSpacer,VFill,HFill,FlexChild,AlignChild,VGap,HGaplayout/scroll.goScrollView(unchanged) -
Example apps — all occurrences of
layout.NewVFill().WithMaxSize(n)andlayout.NewHFill().WithMaxSize(n)used as fixed gaps have been replaced withlayout.NewVGap(n)/layout.NewHGap(n)in all five example apps (tasklist,notes,kanban,people,showcase).
Notes
VFill.WithMaxSizeandHFill.WithMaxSizeare not removed. They remain the correct tool for capped flex spacers (gaps that grow up toncells but yield gracefully when space is scarce).VGap/HGapare the correct tool for guaranteed fixed gaps.
v0.2.9
layout.ScrollView — vertically scrollable layout container
Added
-
layout.ScrollView— a layout container that clips a single child to a viewport and allows the user to scroll vertically to reveal content that exceeds the visible height.layout.NewScrollView(child oat.Component) *ScrollView— standalone constructor.(*VBox).AsScrollView() *ScrollView— convenience builder; wraps the VBox in a ScrollView in one call.(*HBox).AsScrollView() *ScrollView— same for HBox (horizontal content, vertical scroll).(*ScrollView).WithScrollBar(show bool, anchor ...oat.Anchor) *ScrollView— enables an optional single-column gutter bar.oat.AnchorRight(default) places it on the right edge;oat.AnchorLefton the left. Bar colours are driven by the theme (Muted→ track│,AccentFG → thumb█) and can be overridden withWithTrackColor/WithThumbColor.(*ScrollView).WithTrackColor(c latte.Color) *ScrollView— overrides the track colour for this ScrollView. SurvivesSetTheme; passlatte.ColorDefaultto revert to theme-driven behaviour.(*ScrollView).WithThumbColor(c latte.Color) *ScrollView— overrides the thumb colour. Same semantics asWithTrackColor.(*ScrollView).WithID(id string) *ScrollView— sets a stable component identifier.ScrollOffset() int,ContentHeight() int,ScrollTo(off int)— programmatic scroll interface (oat.Scrollable).
-
Focus model —
ScrollViewis always present in the Tab cycle.HandleKeyreturnsfalsefor scroll keys when content fits the viewport so arrows fall through to inter-widget cycling. When content overflows it consumes↑/↓(±1 row),PgUp/PgDn(±viewport), andHome/End(jump to extremes). -
ApplyTheme— propagates the active theme to the child and mapst.Muted.FG→ track colour,t.Accent.FG→ thumb colour. -
Implements
oat.Layout(Children()/AddChild) so theme propagation and the focus collector recurse into the child tree automatically.
Fixed
-
Buffer.Sub— separateoriginX/originYfromclip— PreviouslyBufferused a singleclipfield for both coordinate translation and write-guarding. Passing a sub-region with a negative Y offset (asScrollViewdoes to shift content upward by the scroll offset) caused the clip origin to move above the viewport boundary, allowing scrolled-off content to bleed into neighbouring panels.Buffernow has two independent internal fields:originX/originY— coordinate translation:(x, y)→(originX+x, originY+y)in screen space. Can be negative/above the viewport.clip— screen-space write guard: the intersection of the requested region's absolute bounding box with the parent's clip. Always stays within the parent.
The public API (
Sub,SetCell,DrawText, etc.) is unchanged. Custom widgets that callbuf.Sub(region)continue to work exactly as before. -
ScrollView.Render— re-measure child unconstrainedly —HBox.RendercallsRenderon flex children without a precedingMeasurecall in the same frame whenVAlignFillis in effect. This causedsv.contentHto reflect the constrained height, makingHandleKeyfall through to focus cycling instead of scrolling. Fixed by re-measuring the child withMaxHeight: -1at the start ofScrollView.Render. -
Constraint.Shrink— preserve-1(unconstrained) —Shrinkproduced-3(and clamped to0) whenMaxHeight == -1, silently converting an unconstrained axis into a zero-height constraint. Fixed to leave-1unchanged on both axes. -
ScrollView— removedFocusGuard/IsFocusable()— The focus tree is collected at startup before anyMeasure/Renderpass, so bothcontentHandviewportHare0at that point. The previousIsFocusable()always returnedfalse, permanently excludingScrollViewfrom Tab cycling.HandleKeyreturningfalsewhen content fits the viewport is sufficient — noFocusGuardneeded.
Notes
- Prefer
Border(ScrollView(VBox))overScrollView(Border(VBox))— the former keeps the border chrome fixed while only the content scrolls. In the reverse pattern the entire Border (including its title row) scrolls away. - Avoid
VFill/FlexChildinside a ScrollView that is expected to overflow — they collapse to zero height in the unconstrained measure pass. Use fixed-height children instead.
v0.2.8
Cross-axis alignment · RoundedCorner theme flag · callerStyle pattern
Added
-
oat.HAlign— horizontal-axis alignment type for widgets inside aVBox:HAlignFill(0, default) — fill the full allocated width; zero-value, all existing code unchanged.HAlignLeft— shrink to natural width, pin to the left.HAlignCenter— shrink to natural width, centre horizontally.HAlignRight— shrink to natural width, pin to the right.
-
oat.VAlign— vertical-axis alignment type for widgets inside anHBox:VAlignFill(0, default) — fill the full allocated height; zero-value, all existing code unchanged.VAlignTop— shrink to natural height, pin to the top.VAlignMiddle— shrink to natural height, centre vertically.VAlignBottom— shrink to natural height, pin to the bottom.
-
oat.AlignProviderinterface — satisfied automatically by any component that embedsBaseComponent:type AlignProvider interface {
GetHAlign() HAlign
GetVAlign() VAlign
} -
BaseComponent.HAlign/BaseComponent.VAlignfields — carry the alignment preference for a widget. Zero values mean "use parent box default" (fill/stretch). -
BaseComponent.GetHAlign()/GetVAlign()— implementAlignProvider. -
Concrete
WithHAlign/WithVAlignfluent builders on every built-in widget —Text,Title,Button,CheckBox,EditText,List,ComponentList,Label,ProgressBar,StatusBar,Divider. Each returns the concrete widget type so alignment can be set inline without a type assertion:saveBtn := widget.NewButton("Save", fn).WithHAlign(oat.HAlignRight)
cancelBtn := widget.NewButton("Cancel", fn).WithHAlign(oat.HAlignLeft)
lbl := widget.NewText("note").WithVAlign(oat.VAlignBottom)Each method is variadic (
a ...oat.HAlign) — calling with no argument resets toHAlignFill/VAlignFill. Internally they delegate toBaseComponent.HAlign/BaseComponent.VAlign; custom widgets that embedBaseComponentbut have not yet added their own builders can set the field directly:myWidget.BaseComponent.HAlign = oat.HAlignRight.NotificationManagerandDialogare intentionally excluded — they are never placed directly in aVBox/HBox. -
VBox.WithHAlign(a ...oat.HAlign) *VBox— sets a box-wide default horizontal alignment for all children that do not declare their own. -
HBox.WithVAlign(a ...oat.VAlign) *HBox— sets a box-wide default vertical alignment for all children that do not declare their own. -
layout.AlignChild— a wrapper component that attaches explicit alignment overrides to any child. Takes precedence over both the child's ownBaseComponentalignment and the box-wide default.// Constructor:
func NewAlignChild(child oat.Component, h oat.HAlign, v oat.VAlign) *AlignChild
// Usage:
vbox := layout.NewVBox(
layout.NewAlignChild(saveBtn, oat.HAlignRight, oat.VAlignFill),
layout.NewAlignChild(cancelBtn, oat.HAlignLeft, oat.VAlignFill),
)AlignChildimplementsoat.Layout(viaChildren()), so theme propagation and focus collection recurse into the wrapped component automatically. It is not aFlexSpacer— wrap it withAddFlexChildorNewFlexChildwhen flex behaviour is also needed. -
latte.Theme.RoundedCorner bool— new field onTheme. All five built-in themes setRoundedCorner: true. WhenApplyThemeis called,ButtonandBorderwidgets automatically adopt arc corners (╭─╮/╰─╯) without any per-widget configuration. -
(Theme).WithRoundedCorner(bool) Theme— builder method to setRoundedCorneron a derived theme. Returns a newThemeby value; the originals are never mutated.
Changed (non-breaking)
-
VBox.Render— resolves each child's effectiveHAlignbefore assigning its region. WhenHAlignFill, behaviour is identical to before (unchanged code path). For non-fill values the child is re-measured and positioned within its row. -
HBox.Render— same forVAlign. -
Border.WithRoundedCorner— completely reworked. Previously mutatedStyle.Borderat call time and panicked on incompatible styles. Now:- Stores the intent in an internal
roundedCorner boolfield; does not mutateStyle.Border. - The effective corner shape is resolved at render time:
BorderSingle ↔ BorderRoundedis toggled based on the field; incompatible styles (BorderDouble,BorderThick,BorderDashed) are silently left unchanged — no panic. - Once called, the explicit preference overrides
theme.RoundedCorneron all futureApplyThemecalls.WithRoundedCorner(false)opts out even whentheme.RoundedCorneristrue. ApplyThemesyncsroundedCornerfromt.RoundedCorneronly whenWithRoundedCornerhas never been called on this instance (bidirectional — both enabling and disabling work correctly on theme switch).
- Stores the intent in an internal
-
Button.WithRoundedCorner— same rework asBorder:- Stores intent in
roundedCorner bool/roundedCornerSet boolfields; does not mutateStyle.Border. - Incompatible styles are silently ignored — no panic.
ApplyThemesyncs fromt.RoundedCornerwhen!roundedCornerSet.fmtimport removed fromwidget/button.go(was only used for the now-deleted panic message).
- Stores intent in
-
callerStylepattern — all built-in widgets that exposeWithStylenow store the caller's original intent in acallerStylefield and use that as theMergebase inApplyTheme. This prevents stale colours from a previous theme accumulating inw.Styleand blocking a new theme from fully replacing them onSetThemecalls after the first.
Resolution order (per child in each box)
AlignChildwrapper — highest priority.AlignProvideron the child itself (e.g.BaseComponent.HAlign/VAlignset to a non-fill value).- Box-wide default (
WithHAlign/WithVAlign). HAlignFill/VAlignFill— full-stretch fallback; identical to the pre-v0.2.8 behaviour.
v0.2.7
(v0.2.6 was skipped — these changes were shipped directly as v0.2.7.)
ComponentList widget · ThemeLight colour fixes · theme-aware accent colours · Buffer background inheritance
Fixed
ThemeLightcolour palette — several tokens produced illegible or visually incorrect output:ListSelectedhad no background colour → addedLightBgSelected = RGB(219, 228, 253)(light cobalt tint).Header/Footerused the canvas background (LightBg) → addedLightBgHeader = RGB(232, 229, 224)(slightly darker warm gray) so header and footer regions are visually distinct.LightBgScrimwas too close to the canvas colour, making dialogs nearly invisible → darkened fromRGB(224, 220, 215)toRGB(180, 175, 168).LightMutedfailed WCAG AA contrast against the light background → darkened from#8e8e9atoRGB(110, 110, 125)(#6e6e7d).
- All four true-color themes (
ThemeDark,ThemeLight,ThemeDracula,ThemeNord) —Panel.BGwasColorDefault(unfilled), causing panel backgrounds to appear as terminal-black instead of the theme canvas colour. Each theme now has an explicitPanel.BGmatching its canvas background. StatusBar— hardcodedColorBrightCyanfor key-hint brackets →ApplyThemenow readst.Accent.FGand stores it asaccentColor; bracket labels are rendered in the active theme's accent colour. Falls back toColorBrightCyanforThemeDefault(ANSI-16).ComponentList— hardcodedColorBrightCyanfor the>cursor glyph → same fix; cursor colour is now driven byt.Accent.FGviaaccentColor.Bufferbackground inheritance — the root cause of all "terminal-black bleed-through" bugs on light (and custom) themes. tcell has no transparency: everySetContentcall withtcell.ColorDefaultpaints terminal black over any background the parent already drew. Fixed by adding abg latte.Colorfield toBuffer:Fill/FillBGrecordstyle.BGintob.bgwhen it is a concrete colour (the canvas pre-fill establishes the theme background for the whole tree).Subpropagatesbgto child buffers, so every widget in the tree inherits the canvas background colour automatically.- New
resolveStyle: substitutesb.bgforColorDefaultinstyle.BGbefore passing to tcell. - New
resolveBorderStyle: same substitution forstyle.BorderBG. SetCell,DrawText,DrawTextAlignedall callresolveStyle.DrawBorderTitlecallsresolveBorderStylefor border runes andresolveStylefor title text.- Custom widgets that draw with
BG == ColorDefaultautomatically inherit the canvas background — no code changes required. Only overrideBGwhen a widget intentionally wants a specific background colour.
Changed
latte/colors.go— two newLight*palette constants (LightBgSelected,LightBgHeader) and updated values forLightBgScrimandLightMuted.
v0.2.6
widget.ComponentList — component-row list
Added
-
widget.ComponentList— a vertically scrollable, keyboard-navigable list whose rows are arbitraryComponentvalues instead of plain label strings. This allows each row to contain rich layouts such asHBox(Text, Flex(Text), Text).widget.ComponentListItem{Component oat.Component, Value interface{}}— the item type.Componentis the widget rendered for the row;Valueis an opaque identifier the caller can use to correlate a row with application data (e.g. a record ID).widget.NewComponentList(items []ComponentListItem) *ComponentList— constructor.- Builder options mirror
Listexactly:WithStyle,WithID,WithSelectedStyle,WithHighlight,WithCursor,WithOnSelect,WithOnDelete,WithOnCursorChange. SetItems,SelectedItem() (ComponentListItem, bool),SelectedIndex() int.- Row heights are variable: each row's component is measured via
Measure(unconstrained on Y) to determine how many terminal lines it occupies. Scroll is tracked by item index so the viewport always shows complete rows. - Implements
oat.Layout(Children()/AddChild) so theme propagation and the focus collector recurse into every row component automatically. - Implements
oat.ValueGetter:Canvas.GetValue(id)returns theValuefield of the currently selected item. - Theme tokens:
t.Text(base),t.ListSelected(highlight),t.FocusBorder(focused border colour) — identical toList.
makeRow := func(name, role, status string, id int) widget.ComponentListItem {
row := layout.NewHBox(
widget.NewText(name),
layout.NewFlexChild(widget.NewText(role), 1),
widget.NewText(status),
)
return widget.ComponentListItem{Component: row, Value: id}
}
list := widget.NewComponentList([]widget.ComponentListItem{
makeRow("Alice", "Backend engineer", "active", 1),
makeRow("Bob", "Frontend engineer", "inactive", 2),
makeRow("Charlie", "DevOps", "active", 3),
}).
WithID("people-list").
WithOnSelect(func(idx int, item widget.ComponentListItem) {
id := item.Value.(int)
// open record
})
v0.2.5
Named true-color palette · widget.Divider · oat.VAnchor
Added
-
latte/colors.go— ~120 named true-colorColorconstants grouped into utility scales and design-system palettes. All values arelatte.Colorvariables (vialatte.RGB), compatible with any API that accepts alatte.Color.Utility scales (Tailwind-style, shade numbers 50–950):
Slate,Zinc,Stone,Sky,Blue,Indigo,Cornflower,Cyan,Teal,Emerald,Green,Lime,Yellow,Amber,Orange,Red,Rose,Pink,Violet,Purple,Fuchsia.Design-system palettes — named constants extracted from every hex literal that appears in the four true-color built-in themes:
Dark*—DarkBg,DarkBgElevated,DarkBgScrim,DarkBorder,DarkMuted,DarkAccent,DarkSuccess,DarkWarning,DarkErrorLight*—LightBg,LightBgElevated,LightBgScrim,LightBorder,LightMuted,LightText,LightAccent,LightSuccess,LightWarning,LightErrorDracula*—DraculaBg,DraculaBgElevated,DraculaBgScrim,DraculaFg,DraculaComment,DraculaSelection,DraculaPurple,DraculaCyan,DraculaGreen,DraculaOrange,DraculaRed,DraculaYellow,DraculaPinkNord0–Nord15+NordBg,NordBgElevated,NordBgScrim
-
widget.Divider— axis-agnostic rule widget for placing horizontal (─) or vertical (│) separators between layout children.widget.NewHDivider()/widget.NewVDivider()— convenience constructors.widget.NewDivider(axis widget.Axis)— explicit-axis constructor (widget.AxisHorizontal/widget.AxisVertical).DividerSize— controls how much of the allocated space the visible rule occupies:widget.DividerFill(default, full span),widget.DividerFixed(n)(exactlyncells),widget.DividerPercent(p)(1–100% of the allocated length).(*Divider).WithRune(r rune)— override the line character (e.g.'═'for a double rule).(*Divider).WithMaxSize(size DividerSize, anchor ...oat.Anchor)— forAxisHorizontaldividers: limits width and positions the rule horizontally.(*Divider).WithMaxSizeV(size DividerSize, anchor ...oat.VAnchor)— forAxisVerticaldividers: limits height and positions the rule vertically.(*Divider).WithStyle(s latte.Style)— override the display style.ApplyThememaps theMutedtheme token onto the divider; override withWithStyle.
-
oat.VAnchor— new vertical-axis positioning type (VAnchorTop,VAnchorMiddle,VAnchorBottom), the V-axis counterpart tooat.Anchor. The two types are kept separate so the compiler enforces correct axis usage — APIs that accept H-axis placement cannot accidentally receive aVAnchorand vice versa.
Changed
ThemeDark,ThemeLight,ThemeDracula,ThemeNord— all rawHex("...")literals replaced with the new named constants. No visual change; pure readability / maintainability improvement.
// Named constants instead of raw hex strings.
myTheme := latte.ThemeDark.
WithAccent(latte.Style{FG: latte.Pink500}).
WithFocusBorder(latte.Pink500).
WithName("dark-pink")
// Horizontal rule — place in a VBox between items
hd := widget.NewHDivider()
hd := widget.NewHDivider().WithRune('═')
hd := widget.NewHDivider().WithMaxSize(widget.DividerPercent(60), oat.AnchorCenter)
// Vertical rule — place in an HBox between items
vd := widget.NewVDivider()
vd := widget.NewVDivider().WithMaxSizeV(widget.DividerFixed(8), oat.VAnchorMiddle)
v0.2.4
Theme fluent builder methods
Added
(Theme).WithName(string) Theme— returns a copy of the theme with a new name, useful when naming a derived theme.(Theme).WithFocusBorder(Color) Theme— replaces theFocusBordercolour (a plainColor, not aStyle).- One
With<Token>(latte.Style) Thememethod for everyStyle-typed field onTheme:WithCanvas,WithText,WithMuted,WithAccent,WithSuccess,WithWarning,WithError,WithPanel,WithPanelTitle,WithInput,WithInputFocus,WithListSelected,WithButton,WithButtonFocus,WithCheckBox,WithCheckBoxFocus,WithHeader,WithFooter,WithDialog,WithDialogTitle,WithScrim,WithTag,WithNotificationInfo,WithNotificationSuccess,WithNotificationWarning,WithNotificationError.
All methods return Theme by value — built-in theme variables (ThemeDark, ThemeNord, etc.) are never mutated. Style-typed methods use Style.Merge internally so only the non-zero fields of the supplied Style are applied; the rest of the token is preserved.
// Nord but with no borders anywhere
borderless := latte.ThemeNord.
WithPanel(latte.Style{Border: latte.BorderExplicitNone}).
WithInput(latte.Style{Border: latte.BorderExplicitNone}).
WithButton(latte.Style{Border: latte.BorderExplicitNone}).
WithDialog(latte.Style{Border: latte.BorderExplicitNone}).
WithName("nord-borderless")
// Dark theme with a custom accent and focus colour
pink := latte.ThemeDark.
WithAccent(latte.Style{FG: latte.Hex("#ff69b4")}).
WithFocusBorder(latte.Hex("#ff69b4")).
WithName("dark-pink")
app.SetTheme(borderless)
v0.2.3
layout.FlexChild · Button.WithRoundedCorner · Label.WithHighlight · Dialog percent-height fix · Button render fix · Label FillBG fix
(v0.2.2 was skipped — these changes were shipped directly as v0.2.3.)
Added
layout.NewFlexChild(child oat.Component, weight ...int) *FlexChild— wraps anyComponentas a flex slot so it can be passed to the variadicNewVBox/NewHBoxconstructors without a separateAddFlexChildcall. Weight defaults to1; minimum effective weight is1.FlexChildimplementsoat.LayoutviaChildren()so theme propagation and focus collection recurse into the wrapped component automatically.(*Button).WithRoundedCorner(bool)— draws arc corners (╭╮╰╯) on the button border whentrue. Incompatible border styles (BorderDouble,BorderThick,BorderDashed) are silently ignored. (Panic behaviour was removed in v0.2.8.)(*Button).WithStyle(latte.Style)— new builder; accepts any border style.(*Label).WithHighlight(bool)— controls whether chip badges render with their background colour fill.falsekeeps the foreground colour and text attributes but strips the background, useful for minimal or transparent UIs. Default istrue(existing behaviour).
Changed
- All built-in themes:
Buttontoken now carriesBorder: BorderSingleso buttons always render with a visible border regardless of focus state.ButtonFocusnow carries onlyReverse: trueand an accentBorderFG— the border shape is no longer placed inButtonFocus. This makesButton.Measurereturn a stableHeight: 3(border + label + border) at all times, fixing a layout instability where the button would grow from 1 to 3 rows upon receiving focus. Button.MeasureandButton.Rendernow derive border presence fromb.Style(the unfocused base style) rather thanEffectiveStyle(IsFocused()).EffectiveStyleis still used for colour and attribute rendering. This is the correct separation: shape is stable, colour changes on focus.cmd/example/tasklist:showNewDialogupdated toWithMaxSize(50, 13)(was 11) to accommodate the stableHeight: 3that buttons now always report (border + label + border) whenBorder: BorderSingleis set by the theme.
Fixed
Dialog.MeasureandDialog.Renderno longer shrink the dialog panel to its content height. Both now derive the panel dimensions solely frommaxDimensions(region), so percent-based dialogs (DialogPercent) correctly fill the requested fraction of the terminal each frame and adapt when the terminal is resized.Button.Renderinner label is now drawn intosub.Sub({X:1, Y:1, ...})(a clipped sub-buffer of the button's allocated region) rather thanbuf.Sub({X: region.X+1, ...}). The old code bypassed the clipping fence of the enclosingBorderand could paint the label one character outside the button's allocated area.Label.Renderwas callingsub.FillBG(latte.Style{BG: l.Style.BG}), which leaked the tag background colour across the entire row width — bleeding into separators and trailing space beyond the last chip. Changed tosub.FillBG(latte.Style{})so the parent background shows through outside chip boundaries.
v0.2.1
oat.Anchor · ProgressBar.WithPercentage · Border.WithTitle anchor
Added
oat.Anchoriota type (AnchorLeft,AnchorCenter,AnchorRight) — general-purpose horizontal-position enum for layout and widget APIs.(*ProgressBar).WithPercentage(show bool, anchor ...oat.Anchor)— controls whether theXX%label is rendered and where it appears (left, center, or right of the bar fill).WithShowPercent(bool)is kept as a deprecated backward-compatible alias.(*Border).WithTitle(title string, anchor ...oat.Anchor)— variadic anchor parameter to position the title in the top border rule. Omitting the anchor preserves left-alignment; all existing call sites compile and behave identically.Buffer.DrawBorderTitlenow accepts a finalanchor Anchorparameter. Internal callers that do not expose anchor control passoat.AnchorLeftexplicitly.
v0.2.0
WithNotificationManager canvas option · oat.NotificationOverlay interface
Added
oat.WithNotificationManager(nm)— canvas option that wires the notification re-render channel and mounts the manager as a persistent overlay in a single call, replacing the previous two-stepSetNotifyChannel+ShowPersistentOverlaypattern.oat.NotificationOverlayinterface — introduced to decouple theoatandwidgetpackages and avoid a circular import.
Removed
app.NotifyChannel()public method — useoat.WithNotificationManagerinstead.
v0.1.1
Border.WithRoundedCorner
Added
(*Border).WithRoundedCorner(bool)— switches the effective border corner style to arc (╭─╮│╰─╯) whentrue. Stores intent in an internal field; does not mutateStyle.Border. Incompatible styles (BorderDouble,BorderThick,BorderDashed) are silently ignored. (Original panic behaviour was removed in v0.2.8.)
v0.1.0
Initial release
Added
- Two-pass layout engine (
Measure/Render) built on tcell. - Core interfaces:
Component,Layout,Focusable,FocusGuard. BaseComponentandFocusBehaviorembeds for custom widgets.- Layout containers:
VBox,HBox,Grid,Stack,Border,Padding,VFill,HFill. - Widget library:
Text,Title,Button,CheckBox,EditText(single- and multi-line),List,Label,ProgressBar,StatusBar,NotificationManager,Dialog. - Style system with
Style.Merge, border sentinels (BorderNone,BorderExplicitNone,BorderSingle,BorderRounded,BorderDouble,BorderThick,BorderDashed), and true-color support (latte.RGB,latte.Hex). - Five built-in themes:
ThemeDefault,ThemeDark,ThemeLight,ThemeDracula,ThemeNord. CanvaswithWithTheme,WithHeader,WithBody,WithAutoStatusBar,WithPrimary,WithGlobalKeyBinding,SetTheme,GetTheme,ShowDialog,HideDialog,FocusByRef,InvalidateLayout.- Cooperative focus system with Tab / Shift+Tab cycling,
FocusGuardfor context-sensitive subtrees, and programmatic focus viaFocusByRef. - Three example apps:
tasklist,notes,kanban.