package ui import ( "image" "gioui.org/layout" "gioui.org/op/clip" "gioui.org/text" "gioui.org/unit" "gioui.org/widget/material" ) // Renderer consumes a slice of elements and draws them. type Renderer struct { theme Theme shp *text.Shaper } // New creates a new Renderer. func New(th Theme, shp *text.Shaper) *Renderer { return &Renderer{theme: th, shp: shp} } // Draw iterates elements and draws each in slice order (back-to-front). func (r *Renderer) Draw(gtx layout.Context, elems []Element) { for _, e := range elems { if !e.Visible() { continue } switch v := e.(type) { case Label: r.drawLabel(gtx, v) case ListView: r.drawListView(gtx, v) case StatusBar: r.drawStatusBar(gtx, v) case BottomBar: r.drawBottomBar(gtx, v) case Button: r.drawButton(gtx, v) default: // unknown element type, skip } } } func (r *Renderer) drawLabel(gtx layout.Context, l Label) { reg := l.Region() c := clip.Rect{ Min: image.Point{X: int(reg.X), Y: int(reg.Y)}, Max: image.Point{X: int(reg.X + reg.W), Y: int(reg.Y + reg.H)}, }.Push(gtx.Ops) th := material.NewTheme() th.Shaper = r.shp size := l.FontSize if size == 0 { size = r.theme.FontSize } material.Label(th, size, l.Text).Layout(gtx) c.Pop() } func (r *Renderer) drawListView(gtx layout.Context, lv ListView) { reg := lv.Region() c := clip.Rect{ Min: image.Point{X: int(reg.X), Y: int(reg.Y)}, Max: image.Point{X: int(reg.X + reg.W), Y: int(reg.Y + reg.H)}, }.Push(gtx.Ops) th := material.NewTheme() th.Shaper = r.shp for _, item := range lv.Items { material.Label(th, r.theme.FontSize, item.Text).Layout(gtx) } c.Pop() } func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) { reg := sb.Region() th := material.NewTheme() th.Shaper = r.shp // Line 1: Filename (truncated with ellipsis if needed) filenameLineY := reg.Y filenameLineH := unit.Dp(24) filenameW := reg.W - unit.Dp(16) // 8 DP padding on each side // Truncate filename if needed displayFilename := sb.Filename // Simple truncation: if filename is too long, truncate it if len(displayFilename) > 30 { displayFilename = displayFilename[:27] + "..." } clipFilename := clip.Rect{ Min: image.Point{X: int(reg.X + unit.Dp(8)), Y: int(filenameLineY)}, Max: image.Point{X: int(reg.X + filenameW), Y: int(filenameLineY + filenameLineH)}, }.Push(gtx.Ops) material.Label(th, r.theme.FontSize, displayFilename).Layout(gtx) clipFilename.Pop() // Line 2: Icons iconsLineY := filenameLineY + filenameLineH // Left side: Cut, Copy, Paste icons leftX := reg.X + unit.Dp(8) iconY := iconsLineY if sb.CutCopy { r.drawIconButton(gtx, th, "✂", leftX, iconY) leftX += unit.Dp(36) } if sb.Copy { r.drawIconButton(gtx, th, "⎘", leftX, iconY) leftX += unit.Dp(36) } if sb.Paste { r.drawIconButton(gtx, th, "⎘", leftX, iconY) leftX += unit.Dp(36) } // Right side: Conflict, Search icons rightX := reg.X + reg.W - unit.Dp(8) if sb.Search { rightX -= unit.Dp(36) r.drawIconButton(gtx, th, "🔍", rightX, iconY) } if sb.ConflictIcon { rightX -= unit.Dp(36) r.drawIconButton(gtx, th, "⚠", rightX, iconY) } } func (r *Renderer) drawBottomBar(gtx layout.Context, bb BottomBar) { reg := bb.Region() th := material.NewTheme() th.Shaper = r.shp // Clip to bottom bar region c := clip.Rect{ Min: image.Point{X: int(reg.X), Y: int(reg.Y)}, Max: image.Point{X: int(reg.X + reg.W), Y: int(reg.Y + reg.H)}, }.Push(gtx.Ops) // Left: Cursor position cursorX := reg.X + unit.Dp(8) gtx.Constraints.Min.X = int(cursorX) material.Label(th, r.theme.FontSize, bb.CursorPos).Layout(gtx) // Center: Byte position byteX := reg.X + reg.W/2 - unit.Dp(60) // approximate center gtx.Constraints.Min.X = int(byteX) material.Label(th, r.theme.FontSize, bb.BytePos).Layout(gtx) // Right: Word wrap button wordWrapX := reg.X + reg.W - unit.Dp(120) gtx.Constraints.Min.X = int(wordWrapX) material.Label(th, r.theme.FontSize, "[Word Wrap: "+boolToString(bb.WordWrap)+"]").Layout(gtx) c.Pop() } func (r *Renderer) drawButton(gtx layout.Context, btn Button) { th := material.NewTheme() th.Shaper = r.shp // Draw button text material.Label(th, r.theme.FontSize, btn.Text).Layout(gtx) } func (r *Renderer) drawIconButton(gtx layout.Context, th *material.Theme, icon string, x, y unit.Dp) { // Draw icon button iconRegion := Region{X: x, Y: y, W: unit.Dp(24), H: unit.Dp(24)} c := clip.Rect{ Min: image.Point{X: int(iconRegion.X), Y: int(iconRegion.Y)}, Max: image.Point{X: int(iconRegion.X + iconRegion.W), Y: int(iconRegion.Y + iconRegion.H)}, }.Push(gtx.Ops) material.Label(th, r.theme.FontSize, icon).Layout(gtx) c.Pop() } func boolToString(b bool) string { if b { return "On" } return "Off" }