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 }