package door import ( "bytes" "log" "strings" ) type BorderStyle int const ( NONE BorderStyle = iota SINGLE DOUBLE DOUBLE_SINGLE SINGLE_DOUBLE ) type Panel struct { X int Y int Width int Style BorderStyle BorderColor []byte Lines []*Line Title string TitleColor []byte TitleOffset int output *bytes.Buffer } /* func (p *Panel) Range(withBorder bool) (sx, sy, ex, ey int) { sx = p.X sy = p.Y ex = p.X + p.Width ey = p.Y + len(p.Lines) if p.Style > 0 { if withBorder { ex += 2 ey += 2 } else { sx += 1 sy += 1 } } return } */ func (p *Panel) Length() int { return len(p.Lines) } func (p *Panel) RightBottomPos() (rx, by int) { rx = p.X + p.Width by = p.Y + len(p.Lines) if p.HasBorder() { rx += 1 by += 1 } return } func (p *Panel) HasBorder() bool { return int(p.Style) > 0 } /* Is X,Y within the Panel? */ func (p *Panel) Within(x, y int) (bool, int, int) { if x < p.X || y < p.Y { return false, 0, 0 } mx, my := p.RightBottomPos() if x > mx || y > my { return false, 0, 0 } // Ok, it must be within: return true, x - p.X, y - p.Y } /* // Panel Clicked? // If onBorder == true, returns true, 0 for border click. // Otherwise true, line clicked on (starting with 1)/ func (p *Panel) Clicked(m Mouse, onBorder bool) (bool, int) { var sx, sy, ex, ey = p.Range(onBorder) var mx = int(m.X) var my = int(m.Y) if (mx >= sx) && (mx <= ex) { if (my >= sy) && (my <= ey) { if onBorder { if my == sy || my == ey { return true, 0 } return true, my - sy } else { return true, (my - sy) + 1 } } } return false, 0 } */ // Clear out the panel func (p *Panel) Clear() []byte { var style int = int(p.Style) if p.output == nil { p.output = &bytes.Buffer{} } p.output.Reset() var row int = p.Y var blank []byte if p.HasBorder() { blank = bytes.Repeat([]byte{' '}, p.Width+2) p.output.Write(Goto(p.X, row)) p.output.Write(blank) row++ } else { blank = bytes.Repeat([]byte{' '}, p.Width) } for _ = range p.Lines { p.output.Write(Goto(p.X, row)) p.output.Write(blank) row++ } if style > 0 { p.output.Write(Goto(p.X, row)) p.output.Write(blank) } return p.output.Bytes() } // Output the panel func (p *Panel) Output() []byte { var style int = int(p.Style) var box_style *BoxStyle if p.output == nil { p.output = &bytes.Buffer{} } p.output.Reset() if p.HasBorder() { box_style = &BOXES[style-1] } var row int = p.Y if p.HasBorder() { // Top line / border // p.output.Write(Goto(p.X, row)) GotoW(p.X, row, p.output) p.output.Write(p.BorderColor) p.output.WriteString(box_style.Top_Left) if p.Title != "" { if p.TitleOffset+len(p.Title) > p.Width { log.Panicf("Panel (not wide enough) Width %d : Title size %d + offset %d = %d\n", p.Width, len(p.Title), p.TitleOffset, p.TitleOffset+len(p.Title)) } p.output.WriteString(strings.Repeat(box_style.Top, p.TitleOffset)) p.output.Write(p.TitleColor) p.output.WriteString(p.Title) p.output.Write(p.BorderColor) } p.output.WriteString(strings.Repeat(box_style.Top, p.Width-(p.TitleOffset+len(p.Title))) + box_style.Top_Right) row++ } for _, line := range p.Lines { // p.output.Write(Goto(p.X, row)) GotoW(p.X, row, p.output) line.LineLength(line.Text) var joined bool = false if p.HasBorder() { top := box_style.Top // This only works in non-unicode mode if bytes.Compare(line.Text.Bytes()[0:len(top)], []byte(top)) == 0 { // Yes, this line needs to be joined... p.output.Write(p.BorderColor) p.output.WriteString(box_style.Middle_Left) p.output.Write(line.Output()) p.output.Write(p.BorderColor) p.output.WriteString(box_style.Middle_Right) joined = true } } if !joined { if p.HasBorder() { p.output.Write(p.BorderColor) p.output.WriteString(box_style.Side) } p.output.Write(line.Output()) if style > 0 { p.output.Write(p.BorderColor) p.output.WriteString(box_style.Side) } } row++ } if p.HasBorder() { // Bottom / border // p.output.Write(Goto(p.X, row)) GotoW(p.X, row, p.output) p.output.Write(p.BorderColor) p.output.WriteString(box_style.Bottom_Left) p.output.WriteString(strings.Repeat(box_style.Top, p.Width) + box_style.Bottom_Right) } return p.output.Bytes() } // Output anything that has updated func (p *Panel) Update() []byte { if p.output == nil { p.output = &bytes.Buffer{} } p.output.Reset() var style int = int(p.Style) var row, col int row = p.Y col = p.X if style > 0 { row++ col++ } for idx := range p.Lines { if p.Lines[idx].Update() { // Yes, line was updated p.output.Write(Goto(col, row)) p.output.Write(p.Lines[idx].Output()) } row++ } return p.output.Bytes() } // Output the updated line func (p *Panel) UpdateLine(index int) []byte { if p.output == nil { p.output = &bytes.Buffer{} } p.output.Reset() // var style int = int(p.Style) p.Lines[index].Update() var row, col int row = p.Y + index col = p.X if p.HasBorder() { row++ col++ } var line *Line = p.Lines[index] p.output.Write(Goto(col, row)) p.output.Write(line.Output()) return p.output.Bytes() } // Position Cursor at the "end" of the panel func (p *Panel) GotoEnd() []byte { rx, by := p.RightBottomPos() return Goto(rx+1, by) } // Is the top line of this style a single line? func Single(bs BorderStyle) bool { switch bs { case SINGLE, SINGLE_DOUBLE: return true default: return false } } // Create a spacer line that will be connected maybe to the sides. func (p *Panel) Spacer() *Line { var l *Line = &Line{Text: &bytes.Buffer{}} var pos int if Single(p.Style) { pos = 0 } else { pos = 1 } l.Text.WriteString(strings.Repeat(BOXES[pos].Top, p.Width)) return l } func (p *Panel) Center() { p.CenterX() p.CenterY() } func (p *Panel) CenterX() { p.X = (Width - p.Width) / 2 } func (p *Panel) CenterY() { p.Y = (Height - len(p.Lines)) / 2 }