1 module jcli.text.widgets.border; 2 3 import jcli.text; 4 5 enum BorderStyle 6 { 7 none, 8 top = 1 << 0, 9 right = 1 << 1, 10 bottom = 1 << 2, 11 left = 1 << 3, 12 13 all = top | right | bottom | left 14 } 15 16 struct BorderWidget 17 { 18 Rect blockArea; 19 BorderStyle borders; 20 AnsiColour fg; 21 AnsiColour bg; 22 string title; 23 Alignment titleAlign; 24 25 void render(const Layout layout, scope TextBuffer buffer) 26 { 27 const area = layout.blockRectToRealRect(this.blockArea); 28 const style = AnsiStyleSet.init.bg(this.bg).fg(this.fg); 29 30 buffer.fillCells(this.innerArea(layout), " ", style); 31 32 if(borders & BorderStyle.top) buffer.fillCells(Rect(area.left, area.top, area.right, area.top+1), "─", style); 33 if(borders & BorderStyle.bottom) buffer.fillCells(Rect(area.left, area.bottom-1, area.right, area.bottom), "─", style); 34 if(borders & BorderStyle.left) buffer.fillCells(Rect(area.left, area.top, area.left+1, area.bottom), "│", style); 35 if(borders & BorderStyle.right) buffer.fillCells(Rect(area.right-1, area.top, area.right, area.bottom), "│", style); 36 37 if((borders & BorderStyle.left) && (borders & BorderStyle.top)) 38 buffer.setCell(Vector(area.left, area.top), "┌", style); 39 if((borders & BorderStyle.right) && (borders & BorderStyle.top)) 40 buffer.setCell(Vector(area.right-1, area.top), "┐", style); 41 if((borders & BorderStyle.left) && (borders & BorderStyle.bottom)) 42 buffer.setCell(Vector(area.left, area.bottom-1), "└", style); 43 if((borders & BorderStyle.right) && (borders & BorderStyle.bottom)) 44 buffer.setCell(Vector(area.right-1, area.bottom-1), "┘", style); 45 46 if(this.title) 47 { 48 const EXTRA_CHARS = 2; 49 const SIDE_MARGIN = 2; 50 Vector start; 51 final switch(this.titleAlign) with(Alignment) 52 { 53 case left: 54 start = Vector(area.left + SIDE_MARGIN, area.top); 55 break; 56 case right: 57 start = Vector(area.right - (SIDE_MARGIN + EXTRA_CHARS + cast(int)this.title.length), area.top); 58 break; 59 case center: 60 start = Vector(area.left + (area.width / 2) - ((EXTRA_CHARS + cast(int)this.title.length) / 2), area.top); 61 break; 62 } 63 auto end = Vector(start.x + EXTRA_CHARS + cast(int)this.title.length, start.y + 1); 64 start = oobVector(OnOOB.constrain, Vector(buffer.width, buffer.height), start); 65 end = oobVector(OnOOB.constrain, Vector(buffer.width, buffer.height), end); 66 67 Vector _1; 68 const rect = Rect(start.x+1, start.y, end.x, end.y); 69 buffer.setCell(start, " ", style); 70 buffer.setString(rect, this.title, _1, style); 71 buffer.setCell(Vector(start.x + 1 + cast(int)this.title.length, start.y), " ", style); 72 } 73 } 74 75 @safe const: 76 77 Rect innerArea(const Layout parent) 78 { 79 const area = parent.blockRectToRealRect(this.blockArea); 80 return Rect( 81 (this.borders & BorderStyle.left) ? area.left + 1 : area.left, 82 (this.borders & BorderStyle.top) ? area.top + 1 : area.top, 83 (this.borders & BorderStyle.right) ? area.right - 1 : area.right, 84 (this.borders & BorderStyle.bottom) ? area.bottom - 1 : area.bottom, 85 ); 86 } 87 } 88 89 struct BorderWidgetBuilder 90 { 91 private BorderWidget _widget; 92 93 @safe @nogc nothrow pure: 94 95 BorderWidgetBuilder withBlockArea(Rect area) 96 { 97 this._widget.blockArea = area; 98 return this; 99 } 100 101 BorderWidgetBuilder withBorderStyle(BorderStyle style) 102 { 103 this._widget.borders = style; 104 return this; 105 } 106 107 BorderWidgetBuilder withForeground(AnsiColour fg) 108 { 109 this._widget.fg = fg; 110 return this; 111 } 112 113 BorderWidgetBuilder withBackground(AnsiColour bg) 114 { 115 this._widget.bg = bg; 116 return this; 117 } 118 119 BorderWidgetBuilder withTitle(string title) 120 { 121 this._widget.title = title; 122 return this; 123 } 124 125 BorderWidgetBuilder withTitleAlignment(Alignment alignment) 126 { 127 this._widget.titleAlign = alignment; 128 return this; 129 } 130 131 BorderWidget build() 132 { 133 return this._widget; 134 } 135 }