1 /** 2 * Module for a component. 3 * 4 * Authors: 5 * Jacob Jensen 6 * License: 7 * https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE 8 */ 9 module poison.ui.component; 10 11 import std.algorithm : filter; 12 import std.array : array; 13 import std.concurrency : thisTid; 14 15 import poison.ui.space; 16 import poison.core : ActionArgs, Point, Size, EventArgs, ChangeEventArgs, executeUI, isUIThread, CrossThreadingExeption; 17 import poison.ui.window; 18 import poison.ui.graphics; 19 import poison.ui.paint; 20 import poison.ui.picture; 21 import poison.ui.container; 22 23 public import dsfml.graphics : RenderWindow; 24 25 /// The next component id. 26 private size_t nextId = 0; 27 28 /// A component, which is the base for all controls and elements. 29 class Component : Space { 30 private: 31 /// The name. 32 string _name; 33 34 /// The inner text. 35 dstring _innerText; 36 37 /// The outer text. 38 dstring _outerText; 39 40 /// Boolean determining whether it's disabled or not. 41 bool _disabled; 42 43 /// Boolean determining whether it's hidden or not. 44 bool _hidden; 45 46 /// The id. 47 size_t _id; 48 49 /// The layer. 50 ptrdiff_t _layer; 51 52 /// The parent window. 53 Window _parentWindow; 54 55 /// The parent container. 56 Container _parentContainer; 57 58 /// The graphics. 59 Graphics _graphics; 60 61 /// Style selectors. 62 string[] _selectors; 63 64 /// Selectors to render with. 65 string[] _renderSelectors; 66 67 /// The selector name. 68 string _selectorName; 69 70 protected: 71 /** 72 * Creates a new component. 73 * Params: 74 * name = The name of the component. 75 * initialSize = The initial size of the component. 76 */ 77 this(string name, Size initialSize) { 78 if (!isUIThread) { 79 throw new CrossThreadingExeption(thisTid, "Cannot create a component outside the UI thread. Consider using exeuteUI();"); 80 } 81 82 super(new Point(0, 0), initialSize); 83 84 _name = name; 85 _layer = -1; 86 _id = nextId++; 87 _graphics = new Graphics(); 88 _selectors = ["component"]; 89 _selectorName = "#" ~ _name; 90 } 91 92 /** 93 * Creates a new component. 94 * Params: 95 * name = The name of the component. 96 */ 97 this(string name) { 98 this(name, new Size(100, 100)); 99 } 100 101 @property { 102 /// Gets the graphics of the component. 103 Graphics graphics() { return _graphics; } 104 } 105 106 /** 107 * Draws the component. Override this! 108 * Params: 109 * window = The window to draw the component to. 110 */ 111 void draw(RenderWindow window) { 112 if (_graphics.displayableBackgroundRect) { 113 window.draw(_graphics.backgroundRect); 114 } 115 116 auto picture = _graphics.backgroundPicture; 117 118 if (picture && _graphics.displayableBackgroundPicture) { 119 if (picture.backgroundSprite) { 120 window.draw(picture.backgroundSprite); 121 } 122 123 if (picture.drawingSprite) { 124 window.draw(picture.drawingSprite); 125 } 126 } 127 } 128 129 /** 130 * Processes the component during application cycles. 131 * Params: 132 * window = The render window to process. 133 */ 134 void process(RenderWindow window) { 135 if (!_hidden) { 136 draw(window); 137 } 138 } 139 140 public: 141 @property { 142 /// Gets the name of the component. 143 string name() { return _name; } 144 145 /// Gets a boolean determining whether the component is enabled or not. 146 bool enabled() { return !_disabled; } 147 148 /// Sets a boolean determining whether the component is enabled or not. 149 void enabled(bool isEnabled) { 150 disabled = !isEnabled; 151 } 152 153 /// Gets a boolean determining whether the component is disabled or not. 154 bool disabled() { return _disabled; } 155 156 /// Sets a boolean determining whether the component is disabled. 157 void disabled(bool isDisabled) { 158 executeUI({ 159 _disabled = isDisabled; 160 161 fireEvent(_disabled ? "beforeDisabled" : "beforeEnabled", EventArgs.empty); 162 163 updateSelectors(); 164 render(); 165 166 fireEvent(_disabled ? "disabled" : "enabled", EventArgs.empty); 167 }); 168 } 169 170 /// Gets a boolean determining whether the component is visible or not. 171 bool visible() { return !_hidden; } 172 173 /// Sets a boolean determining whether the component is visible or not. 174 void visible(bool isVisible) { 175 hidden = !isVisible; 176 } 177 178 /// Gets a boolean determining whether the component is hidden or not. 179 bool hidden() { return _hidden; } 180 181 /// Sets a boolean determining whether the component is hidden or not. 182 void hidden(bool isHidden) { 183 executeUI({ 184 _hidden = isHidden; 185 186 fireEvent(_hidden ? "beforeHide" : "beforeShow", EventArgs.empty); 187 188 if (!_hidden) { 189 render(); 190 } 191 192 fireEvent(_hidden ? "hide" : "show", EventArgs.empty); 193 }); 194 } 195 196 /// Gets the inner text of the component. 197 dstring innerText() { return _innerText; } 198 199 /// Sets the inner text of the component. 200 void innerText(dstring newInnerText) { 201 executeUI({ 202 auto oldInnerText = _innerText; 203 _innerText = newInnerText; 204 205 fireEvent("innerText", new ChangeEventArgs!dstring(oldInnerText, _innerText)); 206 207 render(); 208 }); 209 } 210 211 /// Gets the outer text of the component. 212 dstring outerText() { return _outerText; } 213 214 /// Sets the outer text of the component. 215 void outerText(dstring newOuterText) { 216 executeUI({ 217 auto oldOuterText = _outerText; 218 _outerText = newOuterText; 219 220 fireEvent("outerText", new ChangeEventArgs!dstring(oldOuterText, _outerText)); 221 222 render(); 223 }); 224 } 225 226 /// Ges the position of the component. 227 override Point position() { return super.position; } 228 229 /// Sets the position of the component. 230 override void position(Point newPoint) { 231 executeUI({ 232 super.position = newPoint; 233 234 render(); 235 }); 236 } 237 238 /// Gets the size of the component. 239 override Size size() { return super.size; } 240 241 /// Sets the size of the component. 242 override void size(Size newSize) { 243 executeUI({ 244 super.size = newSize; 245 246 render(); 247 }); 248 } 249 250 /// Gets the layer of the component. 251 ptrdiff_t layer() { return _layer; } 252 253 /// Gets the parent window of the component. 254 Window parentWindow() { 255 return _parentWindow; 256 } 257 258 /// Gets the parent container of the component. 259 Container parentContainer() { 260 return _parentContainer; 261 } 262 } 263 264 /// Renders the component. Override this! 265 void render() { 266 _graphics.size = super.size; 267 _graphics.position = super.position; 268 269 renderSub(); 270 } 271 272 /// Shows the component. 273 void show() { 274 visible = true; 275 } 276 277 /// Hides the component. 278 void hide() { 279 hidden = true; 280 } 281 282 /// Enables the component. 283 void enable() { 284 enabled = true; 285 } 286 287 /// Disables the component. 288 void disable() { 289 disabled = true; 290 } 291 292 /** 293 * Adds a style selector. 294 * Params: 295 * selector = The selector to add. 296 */ 297 void addSelector(string selector) { 298 _selectors ~= selector; 299 300 updateSelectors(); 301 } 302 303 /** 304 * Removes a style selector. 305 * Params: 306 * The style selector to remove. 307 */ 308 void removeSelector(string selector) { 309 _selectors = _selectors.filter!((s) { return s != selector; }).array; 310 311 updateSelectors(); 312 } 313 314 /** 315 * Checks whether the component intersects with a point. 316 * Params: 317 * p = The point to check for intersection with. 318 * Returns: 319 * True if the component intersects with a point. 320 */ 321 override bool intersect(Point p) { 322 auto pIntersects = _parentContainer ? _parentContainer.intersect(p) : true; 323 324 return pIntersects && super.intersect(p); 325 } 326 327 /** 328 * Checks whether the component intersects with another space. 329 * Params: 330 * target = The space to check for intersection with. 331 * Returns: 332 * True if the component intersects with a space. 333 */ 334 override bool intersect(Space target) { 335 auto pIntersects = _parentContainer ? _parentContainer.intersect(target) : true; 336 337 return pIntersects && super.intersect(target); 338 } 339 340 private: 341 /// Updates the selectors. 342 void updateSelectors() { 343 auto prefix = (_disabled ? ":disabled" : ":enabled"); 344 345 _renderSelectors ~= _selectors; 346 347 foreach (selector; _selectors) { 348 _renderSelectors ~= selector ~ prefix; 349 } 350 351 _renderSelectors ~= _selectorName; 352 _renderSelectors ~= _selectorName ~ prefix; 353 354 updateStyles(); 355 } 356 357 package(poison): 358 /// Renders the sub rectangles for the graphics of the component. 359 void renderSub() { 360 if (_graphics && _parentContainer) { 361 _graphics.renderSub(_parentContainer.position, _parentContainer.size); 362 _hidden = !super.intersect(_parentContainer); // We set it directly to avoid events ... 363 } 364 } 365 366 /** 367 * Processes the component during application cycles. 368 * Params: 369 * window = The render window to process. 370 */ 371 void processInternal(RenderWindow window) { 372 process(window); 373 } 374 375 /// Updates the styles of the component. 376 void updateStyles() { 377 import poison.ui.styles; 378 379 if (_renderSelectors) { 380 Size newSize; 381 Point newPosition; 382 383 foreach (selector; _renderSelectors) { 384 auto styleEntry = getStyleEntry(selector); 385 386 if (styleEntry) { 387 if (styleEntry.backgroundPicture) { 388 _graphics.backgroundPicture = new Picture(styleEntry.backgroundPicture); 389 _graphics.position = super.position; 390 _graphics.backgroundPicture.finalize(); 391 } 392 393 _graphics.backgroundPaint = styleEntry.backgroundPaint; 394 _graphics.foregroundPaint = styleEntry.foregroundPaint; 395 396 _graphics.font = styleEntry.font; 397 _graphics.fontSize = styleEntry.fontSize; 398 399 if (styleEntry.hasSize) { 400 newSize = styleEntry.size; 401 } 402 403 if (styleEntry.hasPosition) { 404 newPosition = styleEntry.position; 405 } 406 } 407 } 408 409 if (newSize && !_graphics.hasSize) { 410 this.size = newSize; 411 _graphics.hasSize = true; 412 } 413 414 if (newPosition && !_graphics.hasPosition) { 415 this.position = newPosition; 416 _graphics.hasPosition = true; 417 } 418 } 419 420 render(); 421 } 422 423 @property { 424 /// Gets the id of the component. 425 size_t id() { return _id; } 426 427 /// Sets the layer of the component. 428 void layer(ptrdiff_t newLayer) { 429 _layer = newLayer; 430 } 431 432 /// Sets the parent window of the component. 433 void parentWindow(Window newParentWindow) { 434 _parentWindow = newParentWindow; 435 } 436 437 /// Sets the parent container of the component. 438 void parentContainer(Container newParentContainer) { 439 _parentContainer = newParentContainer; 440 } 441 } 442 }