Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,14 @@ Widget coverage is now materially complete versus GTK 4 + DaisyUI 4.
The remaining pre-1.0 work is around the *edges* of the widget
model, not the widget catalogue:

- **Font family plumbing** — the toolkit still ships one 5×7 bitmap
font. A `Font` interface (`GlyphAdvance`, `GlyphHeight`, `Draw`)
so a caller can plug a larger bitmap or a hand-rasterised
TrueType at boot. Unblocks `FontChooser` (long-standing deferral)
+ retina-size Label rendering.
- ~~**Font family plumbing**~~ — **done (v0.20)**: a `Font` interface
(`Advance`, `Height`, `Draw`) + a scalable built-in bitmap font.
`SetFont(NewBitmapFont(2))` doubles all text ("retina"), and every
widget re-lays-out because metrics are now read at draw time.
**Breaking:** `GlyphHeight` / `GlyphAdvance` and the metric-derived
dimensions (`CardHeaderH`, `DatePickerFieldH`, `DiffLineH`,
`FormFieldLabelH`, `TimelineEventH`, `CardFooterH`) are now
**functions** — append `()` at call sites when upgrading.
- ~~**First-class drag-and-drop event kinds**~~ — **done (v0.16)**:
`EventDragStart` / `EventDragMove` / `EventDragLeave` / `EventDrop`,
plus a `DragSource` / `DropTarget` interface pair and
Expand Down
2 changes: 1 addition & 1 deletion actionrow.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (a *ActionRow) Draw(p painter.Painter, theme *Theme) {
ty := r.Y + ActionRowPadY
DrawText(p, textX, ty, a.Title, theme.OnSurface)
if a.Subtitle != "" {
sy := ty + GlyphHeight + ActionRowSubtitleGap
sy := ty + GlyphHeight() + ActionRowSubtitleGap
DrawText(p, textX, sy, a.Subtitle, dimInk(theme))
}

Expand Down
6 changes: 3 additions & 3 deletions actionrow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestActionRowDrawBodyAndDivider(t *testing.T) {
}
// Title text present in OnSurface.
found := false
for y := ActionRowPadY; y < ActionRowPadY+GlyphHeight && !found; y++ {
for y := ActionRowPadY; y < ActionRowPadY+GlyphHeight() && !found; y++ {
for x := ActionRowPadX; x < ActionRowPadX+TextWidth("Title") && !found; x++ {
if pixelAt(buf, w, x, y) == theme.OnSurface {
found = true
Expand Down Expand Up @@ -114,9 +114,9 @@ func TestActionRowDrawWithSubtitle(t *testing.T) {
a.Draw(newP(buf, w), theme)

// Subtitle ink is drawn in dimInk tone below the title.
sy := ActionRowPadY + GlyphHeight + ActionRowSubtitleGap
sy := ActionRowPadY + GlyphHeight() + ActionRowSubtitleGap
found := false
for y := sy; y < sy+GlyphHeight && !found; y++ {
for y := sy; y < sy+GlyphHeight() && !found; y++ {
for x := ActionRowPadX; x < ActionRowPadX+TextWidth("sub") && !found; x++ {
if pixelAt(buf, w, x, y) == dimInk(theme) {
found = true
Expand Down
2 changes: 1 addition & 1 deletion avatar.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,6 @@ func (a *Avatar) Draw(p painter.Painter, theme *Theme) {

tw := TextWidth(a.Initials)
tx := r.X + (r.W-tw)/2
ty := r.Y + (r.H-GlyphHeight)/2
ty := r.Y + (r.H-GlyphHeight())/2
DrawText(p, tx, ty, a.Initials, accentInk(theme))
}
6 changes: 3 additions & 3 deletions badge.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import "github.com/go-widgets/painter"
//
// Auto-sizing: if the caller sets Bounds().W to 0, the first Draw()
// resizes the Bounds to the text width plus BadgePadX on each side
// (plus GlyphHeight + BadgePadY on each side vertically if H is also 0).
// (plus GlyphHeight() + BadgePadY on each side vertically if H is also 0).
// This spares the caller from having to compute glyph widths just to
// paint a two-digit counter. A pre-sized Bounds is honoured verbatim
// so a fixed-width layout column doesn't shift when the digit count
Expand Down Expand Up @@ -53,7 +53,7 @@ func (b *Badge) Draw(p painter.Painter, theme *Theme) {
if r.W == 0 {
r.W = TextWidth(b.Text) + 2*BadgePadX
if r.H == 0 {
r.H = GlyphHeight + 2*BadgePadY
r.H = GlyphHeight() + 2*BadgePadY
}
b.SetBounds(r)
}
Expand All @@ -65,6 +65,6 @@ func (b *Badge) Draw(p painter.Painter, theme *Theme) {
fillRect(p, r.X+r.W-1, r.Y+1, 1, r.H-2, theme.Accent)
tw := TextWidth(b.Text)
tx := r.X + (r.W-tw)/2
ty := r.Y + (r.H-GlyphHeight)/2
ty := r.Y + (r.H-GlyphHeight())/2
DrawText(p, tx, ty, b.Text, theme.Background)
}
2 changes: 1 addition & 1 deletion badge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestBadgeAutoSizesWhenWZero(t *testing.T) {
b.Draw(newP(makeSurface(100, 40), 100), theme)
got := b.Bounds()
wantW := TextWidth("42") + 2*BadgePadX
wantH := GlyphHeight + 2*BadgePadY
wantH := GlyphHeight() + 2*BadgePadY
if got.W != wantW {
t.Fatalf("auto-sized W = %d, want %d", got.W, wantW)
}
Expand Down
2 changes: 1 addition & 1 deletion banner.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (b *Banner) buttonRect() (Rect, bool) {
}
r := b.Bounds()
btnW := TextWidth(b.ButtonLabel) + 2*BannerButtonPadX
btnH := GlyphHeight + 2*BannerPadY
btnH := GlyphHeight() + 2*BannerPadY
return Rect{
X: r.X + r.W - btnW - BannerPadX,
Y: r.Y + (r.H-btnH)/2,
Expand Down
6 changes: 3 additions & 3 deletions breadcrumbs.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ func NewBreadcrumbs(segments []string) *Breadcrumbs {

// Draw paints each segment followed by a separator (except after the
// last one). Segments are vertically centred inside Bounds when
// Bounds.H exceeds GlyphHeight, otherwise they anchor at Bounds.Y.
// Bounds.H exceeds GlyphHeight(), otherwise they anchor at Bounds.Y.
func (b *Breadcrumbs) Draw(p painter.Painter, theme *Theme) {
r := b.Bounds()
ty := r.Y
if r.H > GlyphHeight {
ty = r.Y + (r.H-GlyphHeight)/2
if r.H > GlyphHeight() {
ty = r.Y + (r.H-GlyphHeight())/2
}
x := r.X
n := len(b.Segments)
Expand Down
6 changes: 3 additions & 3 deletions breadcrumbs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,13 @@ func TestBreadcrumbsDrawMultiSegmentPaintsChevron(t *testing.T) {
}
}

// Tight bounds (Bounds.H == GlyphHeight): the centring branch is
// Tight bounds (Bounds.H == GlyphHeight()): the centring branch is
// skipped and text anchors at Bounds.Y.
func TestBreadcrumbsDrawTightBoundsSkipsCentring(t *testing.T) {
const w, h = 40, GlyphHeight + 2
var w, h = 40, GlyphHeight() + 2
theme := DefaultLight()
b := NewBreadcrumbs([]string{"Hi"})
b.SetBounds(Rect{X: 0, Y: 0, W: 40, H: GlyphHeight})
b.SetBounds(Rect{X: 0, Y: 0, W: 40, H: GlyphHeight()})
buf := makeSurface(w, h)
b.Draw(newP(buf, w), theme)
// The centring branch skipped -> ink starts at y = Bounds.Y = 0.
Expand Down
2 changes: 1 addition & 1 deletion button.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (b *Button) Draw(p painter.Painter, theme *Theme) {
if b.Label != "" {
tw := TextWidth(b.Label)
tx := r.X + (r.W-tw)/2
ty := r.Y + (r.H-GlyphHeight)/2
ty := r.Y + (r.H-GlyphHeight())/2
DrawText(p, tx, ty, b.Label, ink)
}
}
Expand Down
8 changes: 4 additions & 4 deletions calendar.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func (c *Calendar) Draw(p painter.Painter, theme *Theme) {
// Header: month / year.
hdr := monthName(c.Month) + " " + itoa(c.Year)
hx := r.X + (r.W-TextWidth(hdr))/2
hy := r.Y + (CalendarHeaderH-GlyphHeight)/2
hy := r.Y + (CalendarHeaderH-GlyphHeight())/2
DrawText(p, hx, hy, hdr, theme.OnSurface)
// Weekday row.
weekdayY := r.Y + CalendarHeaderH
Expand All @@ -142,7 +142,7 @@ func (c *Calendar) Draw(p painter.Painter, theme *Theme) {
// Day grid.
first := WeekdayOfFirst(c.Year, c.Month)
dim := DaysInMonth(c.Year, c.Month)
gridY := weekdayY + GlyphHeight + 4
gridY := weekdayY + GlyphHeight() + 4
for d := 1; d <= dim; d++ {
idx := first + d - 1
col := idx % 7
Expand All @@ -160,7 +160,7 @@ func (c *Calendar) Draw(p painter.Painter, theme *Theme) {
}
fillRect(p, cx, cy, CalendarCellW, CalendarCellH, bg)
txt := itoa(d)
DrawText(p, cx+(CalendarCellW-TextWidth(txt))/2, cy+(CalendarCellH-GlyphHeight)/2, txt, ink)
DrawText(p, cx+(CalendarCellW-TextWidth(txt))/2, cy+(CalendarCellH-GlyphHeight())/2, txt, ink)
}
}

Expand All @@ -169,7 +169,7 @@ func (c *Calendar) OnEvent(ev Event) {
if ev.Kind != EventClick {
return
}
gridY := CalendarHeaderH + GlyphHeight + 4
gridY := CalendarHeaderH + GlyphHeight() + 4
if ev.Y < gridY {
return
}
Expand Down
27 changes: 15 additions & 12 deletions card.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,18 @@ const (
// CardPadY is the vertical inset for the body text above the first
// line + between the footer text and its strip border.
CardPadY = 6
// CardHeaderH is the height of the header strip when Title != "".
CardHeaderH = GlyphHeight + 2*CardPadY
// CardFooterH is the height of the footer strip when Footer != "".
CardFooterH = GlyphHeight + 2*CardPadY
// CardLineSpacing is the extra vertical gap inserted between two
// body lines so successive glyph rows don't touch.
CardLineSpacing = 2
)

// CardHeaderH is the height of the header strip when Title != "" (a function,
// as it derives from the active font's GlyphHeight).
func CardHeaderH() int { return GlyphHeight() + 2*CardPadY }

// CardFooterH is the height of the footer strip when Footer != "".
func CardFooterH() int { return GlyphHeight() + 2*CardPadY }

// NewCard constructs a Card with the given title, body + footer.
// Any of the three may be "" to skip that zone.
func NewCard(title, body, footer string) *Card {
Expand All @@ -69,25 +72,25 @@ func (c *Card) Draw(p painter.Painter, theme *Theme) {
bodyTop := r.Y

if c.Title != "" {
fillRect(p, r.X, r.Y, r.W, CardHeaderH, theme.SurfaceAlt)
ty := r.Y + (CardHeaderH-GlyphHeight)/2
fillRect(p, r.X, r.Y, r.W, CardHeaderH(), theme.SurfaceAlt)
ty := r.Y + (CardHeaderH()-GlyphHeight())/2
DrawText(p, r.X+CardPadX, ty, c.Title, theme.OnSurface)
// Divider between header and body.
fillRect(p, r.X, r.Y+CardHeaderH, r.W, 1, theme.Border)
bodyTop = r.Y + CardHeaderH + 1
fillRect(p, r.X, r.Y+CardHeaderH(), r.W, 1, theme.Border)
bodyTop = r.Y + CardHeaderH() + 1
}

if c.Footer != "" {
footerY := r.Y + r.H - CardFooterH
fillRect(p, r.X, footerY, r.W, CardFooterH, theme.SurfaceAlt)
ty := footerY + (CardFooterH-GlyphHeight)/2
footerY := r.Y + r.H - CardFooterH()
fillRect(p, r.X, footerY, r.W, CardFooterH(), theme.SurfaceAlt)
ty := footerY + (CardFooterH()-GlyphHeight())/2
DrawText(p, r.X+CardPadX, ty, c.Footer, theme.OnSurface)
// Divider between body and footer.
fillRect(p, r.X, footerY-1, r.W, 1, theme.Border)
}

if c.Body != "" {
lineH := GlyphHeight + CardLineSpacing
lineH := GlyphHeight() + CardLineSpacing
y := bodyTop + CardPadY
for _, ln := range strings.Split(c.Body, "\n") {
DrawText(p, r.X+CardPadX, y, ln, theme.OnSurface)
Expand Down
8 changes: 4 additions & 4 deletions card_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ func TestCardDrawAllZonesPopulated(t *testing.T) {
t.Fatalf("footer strip pixel = %+v, want SurfaceAlt", pixelAt(buf, w, w-4, h-2))
}
// Body area (between header and footer) should show the Surface fill.
bodyY := CardHeaderH + 4
bodyY := CardHeaderH() + 4
if pixelAt(buf, w, w-4, bodyY) != theme.Surface {
t.Fatalf("body area pixel = %+v, want Surface", pixelAt(buf, w, w-4, bodyY))
}
// The body ink lands somewhere inside the body area — at least one
// pixel painted in OnSurface confirms the split-on-\n loop ran.
painted := 0
for y := CardHeaderH + 1; y < h-CardFooterH-1; y++ {
for y := CardHeaderH() + 1; y < h-CardFooterH()-1; y++ {
for x := 0; x < w; x++ {
if pixelAt(buf, w, x, y) == theme.OnSurface {
painted++
Expand Down Expand Up @@ -98,7 +98,7 @@ func TestCardDrawEmptyBodyPaintsNoLines(t *testing.T) {
c.Draw(newP(buf, w), theme)
// Body area — between header divider and footer divider — no ink.
inked := 0
for y := CardHeaderH + 2; y < h-CardFooterH-2; y++ {
for y := CardHeaderH() + 2; y < h-CardFooterH()-2; y++ {
for x := 4; x < w-4; x++ {
if pixelAt(buf, w, x, y) == theme.OnSurface {
inked++
Expand Down Expand Up @@ -139,7 +139,7 @@ func TestCardDrawSingleLineBody(t *testing.T) {
buf := makeSurface(w, h)
c.Draw(newP(buf, w), theme)
painted := 0
for y := CardPadY; y < CardPadY+GlyphHeight; y++ {
for y := CardPadY; y < CardPadY+GlyphHeight(); y++ {
for x := 0; x < w; x++ {
if pixelAt(buf, w, x, y) == theme.OnSurface {
painted++
Expand Down
4 changes: 2 additions & 2 deletions chatbubble.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const (
// Sizing: the bubble grows to fit the widest text line plus 2*PadX,
// capped at ChatBubbleMaxW so a runaway paste doesn't spill the
// widget's Bounds. Height is len(lines) * lineH + 2*PadY where lineH
// = GlyphHeight + ChatBubbleLineSpacing.
// = GlyphHeight() + ChatBubbleLineSpacing.
//
// ChatBubble is a passive display widget — it does not intercept
// input (HitTest / OnEvent stay as Base defaults). A caller that
Expand Down Expand Up @@ -89,7 +89,7 @@ func (c *ChatBubble) Draw(p painter.Painter, theme *Theme) {
if bubbleW > ChatBubbleMaxW {
bubbleW = ChatBubbleMaxW
}
lineH := GlyphHeight + ChatBubbleLineSpacing
lineH := GlyphHeight() + ChatBubbleLineSpacing
bubbleH := len(lines)*lineH - ChatBubbleLineSpacing + 2*ChatBubblePadY

var bx int
Expand Down
4 changes: 2 additions & 2 deletions chatbubble_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func TestChatBubbleDrawMultiLine(t *testing.T) {
buf := makeSurface(w, h)
c.Draw(newP(buf, w), theme)

// The third line ("CCC", width=18) sits at y = padY + 2*(GlyphHeight+LineSpacing)
// The third line ("CCC", width=18) sits at y = padY + 2*(GlyphHeight()+LineSpacing)
// = 6 + 2*9 = 24. Its glyphs are 7 rows tall so ink should appear
// somewhere in rows 24..30. Just verify some OnSurface ink exists past
// the first-line region.
Expand Down Expand Up @@ -232,7 +232,7 @@ func TestChatBubbleDrawEmptyText(t *testing.T) {
c.SetBounds(Rect{X: 0, Y: 0, W: w, H: h})
buf := makeSurface(w, h)
c.Draw(newP(buf, w), theme)
// Empty text: bubble is 20x19 (2*PadX x GlyphHeight+2*PadY). Interior
// Empty text: bubble is 20x19 (2*PadX x GlyphHeight()+2*PadY). Interior
// pixel at (10,10) is inside the SurfaceAlt fill.
if got := pixelAt(buf, w, 10, 10); got != theme.SurfaceAlt {
t.Fatalf("empty bubble interior = %+v, want SurfaceAlt", got)
Expand Down
2 changes: 1 addition & 1 deletion check.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (c *CheckButton) Draw(p painter.Painter, theme *Theme) {
}
}
// Label to the right of the box, vertically centred on glyph row.
textY := r.Y + (r.H-GlyphHeight)/2
textY := r.Y + (r.H-GlyphHeight())/2
DrawText(p, r.X+checkBoxSize+4, textY, c.Label, theme.OnBackground)
}

Expand Down
6 changes: 3 additions & 3 deletions chip.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import "github.com/go-widgets/painter"
// Auto-sizing follows Badge's convention: if Bounds().W is zero the
// first Draw() sets W to the text width plus ChipPadX on each side
// (plus the close slot's ChipCloseGap + ChipCloseW when Closable is
// true), and H to GlyphHeight + 2*ChipPadY when it is also zero. A
// true), and H to GlyphHeight() + 2*ChipPadY when it is also zero. A
// pre-sized Bounds is honoured verbatim so a fixed-width layout row
// does not shift when the chip label changes.
type Chip struct {
Expand Down Expand Up @@ -67,14 +67,14 @@ func (c *Chip) Draw(p painter.Painter, theme *Theme) {
r.W += ChipCloseGap + ChipCloseW
}
if r.H == 0 {
r.H = GlyphHeight + 2*ChipPadY
r.H = GlyphHeight() + 2*ChipPadY
}
c.SetBounds(r)
}
fillRect(p, r.X, r.Y, r.W, r.H, theme.SurfaceAlt)
strokeRect(p, r.X, r.Y, r.W, r.H, theme.Border)
tx := r.X + ChipPadX
ty := r.Y + (r.H-GlyphHeight)/2
ty := r.Y + (r.H-GlyphHeight())/2
DrawText(p, tx, ty, c.Text, theme.OnSurface)
if c.Closable {
cx := r.X + r.W - ChipPadX - ChipCloseW + (ChipCloseW-TextWidth("x"))/2
Expand Down
2 changes: 1 addition & 1 deletion chip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestChipAutoSizesWhenWZero(t *testing.T) {
c.Draw(newP(makeSurface(80, 40), 80), theme)
got := c.Bounds()
wantW := TextWidth("hi") + 2*ChipPadX
wantH := GlyphHeight + 2*ChipPadY
wantH := GlyphHeight() + 2*ChipPadY
if got.W != wantW {
t.Fatalf("auto-sized W = %d, want %d", got.W, wantW)
}
Expand Down
2 changes: 1 addition & 1 deletion colorchooser.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (c *ColorChooser) Draw(p painter.Painter, theme *Theme) {
for i, ch := range [3]string{"R", "G", "B"} {
y := r.Y + ColorChooserChannelPadY + i*ColorChooserChannelH
labelX := r.X + 2
DrawText(p, labelX, y+(ColorChooserChannelH-GlyphHeight)/2, ch, theme.OnSurface)
DrawText(p, labelX, y+(ColorChooserChannelH-GlyphHeight())/2, ch, theme.OnSurface)
trackX := r.X + ColorChooserPadX + 12
trackY := y + ColorChooserChannelH/2 - 2
trackW := channelW - 12
Expand Down
6 changes: 3 additions & 3 deletions datepicker.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type DatePicker struct {
}

// DatePickerFieldH is the pixel height of the closed field.
const DatePickerFieldH = GlyphHeight + 10
func DatePickerFieldH() int { return GlyphHeight() + 10 }

// NewDatePicker builds a DatePicker initialised to (year, month, day).
func NewDatePicker(year, month, day int) *DatePicker {
Expand Down Expand Up @@ -55,7 +55,7 @@ func (dp *DatePicker) Text() string {
// calendar width below the field. Six week-rows is the worst case.
func (dp *DatePicker) PopoverBounds() Rect {
r := dp.Bounds()
h := CalendarHeaderH + GlyphHeight + 4 + 6*CalendarCellH + 4
h := CalendarHeaderH + GlyphHeight() + 4 + 6*CalendarCellH + 4
return Rect{X: r.X, Y: r.Y + r.H, W: 7 * CalendarCellW, H: h}
}

Expand All @@ -65,7 +65,7 @@ func (dp *DatePicker) Draw(p painter.Painter, theme *Theme) {
r := dp.Bounds()
fillRect(p, r.X, r.Y, r.W, r.H, theme.Surface)
strokeRect(p, r.X, r.Y, r.W, r.H, theme.Border)
textY := r.Y + (r.H-GlyphHeight)/2
textY := r.Y + (r.H-GlyphHeight())/2
DrawText(p, r.X+6, textY, dp.Text(), theme.OnSurface)
dp.drawIcon(p, r, theme)
if dp.Open {
Expand Down
Loading
Loading