1 /** 2 * Widget module. 3 * 4 * License: 5 * MIT. See LICENSE for full details. 6 */ 7 module tkd.widget.widget; 8 9 /** 10 * Imports. 11 */ 12 import std.algorithm; 13 import std.array; 14 import std..string; 15 import tkd.element.element; 16 import tkd.element.uielement; 17 import tkd.widget.anchorposition; 18 import tkd.widget.state; 19 20 /** 21 * The widget base class. 22 * 23 * See_Also: 24 * $(LINK2 ../element/uielement.html, tkd.element.uielement) 25 */ 26 abstract class Widget : UiElement 27 { 28 /** 29 * Construct the widget. 30 * 31 * Params: 32 * parent = An optional parent of this widget. 33 * 34 * See_Also: 35 * $(LINK2 ../element/uielement.html, tkd.element.uielement) 36 */ 37 public this(UiElement parent = null) 38 { 39 super(parent); 40 41 this._elementId = "widget"; 42 } 43 44 /** 45 * Set the widget's state. 46 * 47 * Params: 48 * state = An array of widget states. 49 * 50 * Returns: 51 * This widget to aid method chaining. 52 * 53 * See_Also: 54 * $(LINK2 ./state.html, tkd.widget.state) 55 */ 56 public auto setState(this T)(string[] state) 57 { 58 this._tk.eval("%s state { %s }", this.id, state.join(" ")); 59 60 return cast(T) this; 61 } 62 63 /** 64 * Get the widget's state. 65 * 66 * Returns: 67 * An array of widget states. 68 * 69 * See_Also: 70 * $(LINK2 ./state.html, tkd.widget.state) 71 */ 72 public string[] getState() 73 { 74 this._tk.eval("%s state", this.id); 75 return this._tk.getResult!(string).split(); 76 } 77 78 /** 79 * Test if a widget is in a particular state. 80 * 81 * Params: 82 * state = An array of widget states. 83 * 84 * Returns: 85 * true is the widget is in that state, false if not. 86 * 87 * See_Also: 88 * $(LINK2 ./state.html, tkd.widget.state) 89 */ 90 public bool inState(string[] state) 91 { 92 if (state.canFind(State.normal)) 93 { 94 throw new Exception("State.normal is not supported by inState method."); 95 } 96 97 this._tk.eval("%s instate { %s }", this.id, state.join(" ")); 98 return this._tk.getResult!(int) == 1; 99 } 100 101 /** 102 * Remove the widget's state. 103 * 104 * Params: 105 * state = An array of widget states. 106 * 107 * Returns: 108 * This widget to aid method chaining. 109 * 110 * See_Also: 111 * $(LINK2 ./state.html, tkd.widget.state) 112 */ 113 public auto removeState(this T)(string[] state) 114 { 115 this._tk.eval("%s state { !%s }", this.id, state.join(" !")); 116 117 return cast(T) this; 118 } 119 120 /** 121 * Reset the widget's state to default. 122 * 123 * Returns: 124 * This widget to aid method chaining. 125 * 126 * See_Also: 127 * $(LINK2 ./state.html, tkd.widget.state) 128 */ 129 public auto resetState(this T)() 130 { 131 this.removeState(this.getState()); 132 133 return cast(T) this; 134 } 135 136 /** 137 * Set the widget's style. 138 * 139 * Params: 140 * style = A widget style. 141 * 142 * Returns: 143 * This widget to aid method chaining. 144 * 145 * See_Also: 146 * $(LINK2 ./style.html, tkd.widget.style) 147 */ 148 public auto setStyle(this T)(string style) 149 { 150 this._tk.eval("%s configure -style %s", this.id, style); 151 152 return cast(T) this; 153 } 154 155 /** 156 * Get the widget's style. 157 * 158 * Returns: 159 * The widget's style. 160 * 161 * See_Also: 162 * $(LINK2 ./style.html, tkd.widget.style) 163 */ 164 public string getStyle() 165 { 166 this._tk.eval("%s cget -style", this.id); 167 if (this._tk.getResult!(string).empty()) 168 { 169 return this.getClass(); 170 } 171 return this._tk.getResult!(string); 172 } 173 174 /** 175 * Set if the widget can recieve focus during keyboard traversal. 176 * 177 * Params: 178 * focus = A focus setting. 179 * 180 * Returns: 181 * This widget to aid method chaining. 182 * 183 * See_Also: 184 * $(LINK2 ./keyboardfocus.html, tkd.widget.keyboardfocus) 185 */ 186 public auto setKeyboardFocus(this T)(string focus) 187 { 188 this._tk.eval("%s configure -takefocus %s", this.id, focus); 189 190 return cast(T) this; 191 } 192 193 /** 194 * Get if the widget can recieve focus during keyboard traversal. 195 * 196 * Returns: 197 * The widget's focus setting. 198 * 199 * See_Also: 200 * $(LINK2 ./keyboardfocus.html, tkd.widget.keyboardfocus) 201 */ 202 public string getKeyboardFocus() 203 { 204 this._tk.eval("%s cget -takefocus", this.id); 205 return this._tk.getResult!(string); 206 } 207 208 /** 209 * Geometry method for loosely placing this widget inside its parent using 210 * a web browser model. Widgets flow around each other in the available space. 211 * 212 * Params: 213 * outerPadding = The amound of padding to add around the widget. 214 * innerPadding = The amound of padding to add inside the widget. 215 * side = The side to place the widget inside its parent. 216 * fill = The space to fill inside its parent. 217 * anchor = The anchor position of the widget inside its parent. 218 * expand = Whether or not to expand to fill the entire given space. 219 * 220 * Returns: 221 * This widget to aid method chaining. 222 * 223 * See_Also: 224 * $(LINK2 ../element/uielement.html#UiElement.enableGeometryAutoSize, tkd.element.uielement.UiElement.enableGeometryAutoSize) $(BR) 225 * $(LINK2 ./anchorposition.html, tkd.widget.anchorposition) $(BR) 226 * $(LINK2 ./widget.html#GeometryFill, tkd.widget.widget.GeometryFill) $(BR) 227 * $(LINK2 ./widget.html#GeometrySide, tkd.widget.widget.GeometrySide) $(BR) 228 */ 229 public auto pack(this T)(int outerPadding = 0, int innerPadding = 0, string side = GeometrySide.top, string fill = GeometryFill.none, string anchor = AnchorPosition.center, bool expand = false) 230 { 231 this._tk.eval("pack %s -padx %s -pady %s -ipadx %s -ipady %s -side {%s} -fill {%s} -anchor {%s} -expand %s", this.id, outerPadding, outerPadding, innerPadding, innerPadding, side, fill, anchor, expand); 232 233 return cast(T) this; 234 } 235 236 /** 237 * Geometry method for placing this widget inside its parent using an 238 * imaginary grid. Somewhat more direct and intuitive than pack. Choose 239 * grid for tabular layouts, and when there's no good reason to choose 240 * something else. 241 * 242 * If a widget's cell is larger than its default dimensions, the sticky 243 * parameter may be used to position (or stretch) the widget within its 244 * cell. The sticky argument is a string that contains zero or more of the 245 * characters n, s, e or w. Each letter refers to a side (north, south, 246 * east, or west) that the widget will 'stick' to. If both n and s (or e 247 * and w) are specified, the slave will be stretched to fill the entire 248 * height (or width) of its cell. The sticky parameter subsumes the 249 * combination of anchor and fill that is used by pack. The default is an 250 * empty string, which causes the widget to be centered in its cell, at its 251 * default size. The $(LINK2 ./anchorposition.html, anchorposition) enum 252 * may be of use here but doesn't provide all combinations. 253 * 254 * Params: 255 * column = The column in which to place this widget. 256 * row = The row in which to place this widget. 257 * outerPadding = The amound of padding to add around the widget. 258 * innerPadding = The amound of padding to add inside the widget. 259 * columnSpan = The amount of column this widget should span across. 260 * rowSpan = The amount of rows this widget should span across. 261 * sticky = Which edges of the cell the widget should touch. See note above. 262 * 263 * Returns: 264 * This widget to aid method chaining. 265 * 266 * Caveats: 267 * In order for a gridded UI to be fully dynamic to expand and contract 268 * when resizing elements, you must to assign a weight to at least one 269 * column and row using $(LINK2 270 * ../element/uielement.html#UiElement.configureGeometryColumn, 271 * configureGeometryColumn) and $(LINK2 272 * ../element/uielement.html#UiElement.configureGeometryRow, 273 * configureGeometryRow) respectively. 274 * 275 * See_Also: 276 * $(LINK2 ../element/uielement.html#UiElement.configureGeometryColumn, tkd.element.uielement.UiElement.configureGeometryColumn) $(BR) 277 * $(LINK2 ../element/uielement.html#UiElement.configureGeometryRow, tkd.element.uielement.UiElement.configureGeometryRow) $(BR) 278 * $(LINK2 ../element/uielement.html#UiElement.enableGeometryAutoSize, tkd.element.uielement.UiElement.enableGeometryAutoSize) $(BR) 279 * $(LINK2 ./anchorposition.html, tkd.widget.anchorposition) $(BR) 280 */ 281 public auto grid(this T)(int column, int row, int outerPadding = 0, int innerPadding = 0, int columnSpan = 1, int rowSpan = 1, string sticky = "") 282 { 283 this._tk.eval("grid %s -column %s -row %s -padx %s -pady %s -ipadx %s -ipady %s -columnspan %s -rowspan %s -sticky {%s}", this.id, column, row, outerPadding, outerPadding, innerPadding, innerPadding, columnSpan, rowSpan, sticky); 284 285 return cast(T) this; 286 } 287 288 /** 289 * Geometry method for placing this widget inside its parent using absolute 290 * positioning. 291 * 292 * Params: 293 * xPos = The horizontal position of the widget inside its parent. 294 * yPos = The vertical position of the widget inside its parent. 295 * width = The width of the widget. 296 * height = The height of the widget. 297 * anchor = The anchor position of the widget inside its parent. 298 * borderMode = How the widget interacts with the parent's border. 299 * 300 * Returns: 301 * This widget to aid method chaining. 302 * 303 * See_Also: 304 * $(LINK2 ./anchorposition.html, tkd.widget.anchorposition) $(BR) 305 * $(LINK2 ./widget.html#GeometryBorderMode, tkd.widget.widget.GeometryBorderMode) $(BR) 306 */ 307 public auto place(this T)(int xPos, int yPos, int width, int height, string anchor = AnchorPosition.northWest, string borderMode = GeometryBorderMode.inside) 308 { 309 this._tk.eval("place %s -x %s -y %s -width %s -height %s -anchor {%s} -bordermode {%s}", this.id, xPos, yPos, width, height, anchor, borderMode); 310 311 return cast(T) this; 312 } 313 314 /** 315 * Geometry method for placing this widget inside its parent using relative 316 * positioning. In this case the position and size is specified as a 317 * floating-point number between 0.0 and 1.0 relative to the height of the 318 * parent. 0.5 means the widget will be half as high as the parent and 1.0 319 * means the widget will have the same height as the parent, and so on. 320 * 321 * Params: 322 * relativeXPos = The relative horizontal position of the widget inside its parent. 323 * relativeYPos = The relative vertical position of the widget inside its parent. 324 * relativeWidth = The relative width of the widget. 325 * relativeHeight = The relative height of the widget. 326 * anchor = The anchor position of the widget inside its parent. 327 * borderMode = How the widget interacts with the parent's border. 328 * 329 * Returns: 330 * This widget to aid method chaining. 331 * 332 * See_Also: 333 * $(LINK2 ./anchorposition.html, tkd.widget.anchorposition) $(BR) 334 * $(LINK2 ./widget.html#GeometryBorderMode, tkd.widget.widget.GeometryBorderMode) $(BR) 335 */ 336 public auto place(this T)(double relativeXPos, double relativeYPos, double relativeWidth, double relativeHeight, string anchor = AnchorPosition.northWest, string borderMode = GeometryBorderMode.inside) 337 { 338 assert(relativeXPos >= 0 && relativeXPos <= 1); 339 assert(relativeYPos >= 0 && relativeYPos <= 1); 340 assert(relativeWidth >= 0 && relativeWidth <= 1); 341 assert(relativeHeight >= 0 && relativeHeight <= 1); 342 343 this._tk.eval("place %s -relx %s -rely %s -relwidth %s -relheight %s -anchor {%s} -bordermode {%s}", this.id, relativeXPos, relativeYPos, relativeWidth, relativeHeight, anchor, borderMode); 344 345 return cast(T) this; 346 } 347 } 348 349 /** 350 * Side values for widget geometry layout. 351 */ 352 enum GeometrySide : string 353 { 354 left = "left", /// Set geometry to the left. 355 right = "right", /// Set geometry to the right. 356 top = "top", /// Set geometry to the top. 357 bottom = "bottom", /// Set geometry to the bottom. 358 } 359 360 /** 361 * Fill values for widget geometry layout. 362 */ 363 enum GeometryFill : string 364 { 365 none = "none", /// No filling. 366 x = "x", /// Fill the available horizontal space. 367 y = "y", /// Fill the available vertical space. 368 both = "both", /// Fill all available space. 369 } 370 371 /** 372 * Interaction modes for parent borders. 373 */ 374 enum GeometryBorderMode : string 375 { 376 inside = "inside", /// Take into consideration the border when placing widgets. 377 outside = "outside", /// Don't take consideration the border when placing widgets. 378 ignore = "ignore", /// Ignore borders. 379 }