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 /// Renders the component. Override this! 107 void render() { 108 _graphics.size = super.size; 109 _graphics.position = super.position; 110 } 111 112 /** 113 * Draws the component. Override this! 114 * Params: 115 * window = The window to draw the component to. 116 */ 117 void draw(RenderWindow window) { 118 if (_graphics.displayableBackgroundRect) { 119 window.draw(_graphics.backgroundRect); 120 } 121 122 auto picture = _graphics.backgroundPicture; 123 124 if (picture && _graphics.displayableBackgroundPicture) { 125 if (picture.backgroundSprite) { 126 window.draw(picture.backgroundSprite); 127 } 128 129 if (picture.drawingSprite) { 130 window.draw(picture.drawingSprite); 131 } 132 } 133 } 134 135 /** 136 * Processes the component during application cycles. 137 * Params: 138 * window = The render window to process. 139 */ 140 void process(RenderWindow window) { 141 if (!_hidden) { 142 draw(window); 143 } 144 } 145 146 public: 147 @property { 148 /// Gets the name of the component. 149 string name() { return _name; } 150 151 /// Gets a boolean determining whether the component is enabled or not. 152 bool enabled() { return !_disabled; } 153 154 /// Sets a boolean determining whether the component is enabled or not. 155 void enabled(bool isEnabled) { 156 disabled = !isEnabled; 157 } 158 159 /// Gets a boolean determining whether the component is disabled or not. 160 bool disabled() { return _disabled; } 161 162 /// Sets a boolean determining whether the component is disabled. 163 void disabled(bool isDisabled) { 164 executeUI({ 165 _disabled = isDisabled; 166 167 fireEvent(_disabled ? "beforeDisabled" : "beforeEnabled", EventArgs.empty); 168 169 updateSelectors(); 170 render(); 171 172 fireEvent(_disabled ? "disabled" : "enabled", EventArgs.empty); 173 }); 174 } 175 176 /// Gets a boolean determining whether the component is visible or not. 177 bool visible() { return !_hidden; } 178 179 /// Sets a boolean determining whether the component is visible or not. 180 void visible(bool isVisible) { 181 hidden = !isVisible; 182 } 183 184 /// Gets a boolean determining whether the component is hidden or not. 185 bool hidden() { return _hidden; } 186 187 /// Sets a boolean determining whether the component is hidden or not. 188 void hidden(bool isHidden) { 189 executeUI({ 190 _hidden = isHidden; 191 192 fireEvent(_hidden ? "beforeHide" : "beforeShow", EventArgs.empty); 193 194 if (!_hidden) { 195 render(); 196 } 197 198 fireEvent(_hidden ? "hide" : "show", EventArgs.empty); 199 }); 200 } 201 202 /// Gets the inner text of the component. 203 dstring innerText() { return _innerText; } 204 205 /// Sets the inner text of the component. 206 void innerText(dstring newInnerText) { 207 executeUI({ 208 auto oldInnerText = _innerText; 209 _innerText = newInnerText; 210 211 fireEvent("innerText", new ChangeEventArgs!dstring(oldInnerText, _innerText)); 212 213 render(); 214 renderSub(); 215 }); 216 } 217 218 /// Gets the outer text of the component. 219 dstring outerText() { return _outerText; } 220 221 /// Sets the outer text of the component. 222 void outerText(dstring newOuterText) { 223 executeUI({ 224 auto oldOuterText = _outerText; 225 _outerText = newOuterText; 226 227 fireEvent("outerText", new ChangeEventArgs!dstring(oldOuterText, _outerText)); 228 229 render(); 230 }); 231 } 232 233 /// Ges the position of the component. 234 override Point position() { return super.position; } 235 236 /// Sets the position of the component. 237 override void position(Point newPoint) { 238 executeUI({ 239 super.position = newPoint; 240 241 render(); 242 renderSub(); 243 }); 244 } 245 246 /// Gets the size of the component. 247 override Size size() { return super.size; } 248 249 /// Sets the size of the component. 250 override void size(Size newSize) { 251 executeUI({ 252 super.size = newSize; 253 254 render(); 255 renderSub(); 256 }); 257 } 258 259 /// Gets the layer of the component. 260 ptrdiff_t layer() { return _layer; } 261 262 /// Gets the parent window of the component. 263 Window parentWindow() { 264 return _parentWindow; 265 } 266 267 /// Gets the parent container of the component. 268 Container parentContainer() { 269 return _parentContainer; 270 } 271 } 272 273 /// Shows the component. 274 void show() { 275 visible = true; 276 } 277 278 /// Hides the component. 279 void hide() { 280 hidden = true; 281 } 282 283 /// Enables the component. 284 void enable() { 285 enabled = true; 286 } 287 288 /// Disables the component. 289 void disable() { 290 disabled = true; 291 } 292 293 /** 294 * Adds a style selector. 295 * Params: 296 * selector = The selector to add. 297 */ 298 void addSelector(string selector) { 299 _selectors ~= selector; 300 301 updateSelectors(); 302 } 303 304 /** 305 * Removes a style selector. 306 * Params: 307 * The style selector to remove. 308 */ 309 void removeSelector(string selector) { 310 _selectors = _selectors.filter!((s) { return s != selector; }).array; 311 312 updateSelectors(); 313 } 314 315 /** 316 * Checks whether the component intersects with a point. 317 * Params: 318 * p = The point to check for intersection with. 319 * Returns: 320 * True if the component intersects with a point. 321 */ 322 override bool intersect(Point p) { 323 auto pIntersects = _parentContainer ? _parentContainer.intersect(p) : true; 324 325 return pIntersects && super.intersect(p); 326 } 327 328 /** 329 * Checks whether the component intersects with another space. 330 * Params: 331 * target = The space to check for intersection with. 332 * Returns: 333 * True if the component intersects with a space. 334 */ 335 override bool intersect(Space target) { 336 auto pIntersects = _parentContainer ? _parentContainer.intersect(target) : true; 337 338 return pIntersects && super.intersect(target); 339 } 340 341 private: 342 /// Updates the selectors. 343 void updateSelectors() { 344 auto prefix = (_disabled ? ":disabled" : ":enabled"); 345 346 _renderSelectors ~= _selectors; 347 348 foreach (selector; _selectors) { 349 _renderSelectors ~= selector ~ prefix; 350 } 351 352 _renderSelectors ~= _selectorName; 353 _renderSelectors ~= _selectorName ~ prefix; 354 355 updateStyles(); 356 } 357 358 package(poison): 359 /// Renders the sub rectangles for the graphics of the component. 360 void renderSub() { 361 if (_graphics && _parentContainer) { 362 _graphics.renderSub(_parentContainer.position, _parentContainer.size); 363 _hidden = !intersect(_parentContainer); // We set it directly to avoid events ... 364 } 365 } 366 367 /** 368 * Processes the component during application cycles. 369 * Params: 370 * window = The render window to process. 371 */ 372 void processInternal(RenderWindow window) { 373 process(window); 374 } 375 376 /// Updates the styles of the component. 377 void updateStyles() { 378 import poison.ui.styles; 379 380 if (_renderSelectors) { 381 foreach (selector; _renderSelectors) { 382 auto styleEntry = getStyleEntry(selector); 383 384 if (styleEntry) { 385 if (styleEntry.backgroundPicture) { 386 _graphics.backgroundPicture = new Picture(styleEntry.backgroundPicture); 387 _graphics.position = super.position; 388 _graphics.backgroundPicture.finalize(); 389 } 390 391 _graphics.backgroundPaint = styleEntry.backgroundPaint; 392 _graphics.foregroundPaint = styleEntry.foregroundPaint; 393 394 _graphics.font = styleEntry.font; 395 _graphics.fontSize = styleEntry.fontSize; 396 397 if (styleEntry.hasSize) { 398 this.size = styleEntry.size; 399 } 400 401 if (styleEntry.hasPosition) { 402 this.position = styleEntry.position; 403 } 404 } 405 } 406 } 407 408 renderSub(); 409 } 410 411 @property { 412 /// Gets the id of the component. 413 size_t id() { return _id; } 414 415 /// Sets the layer of the component. 416 void layer(ptrdiff_t newLayer) { 417 _layer = newLayer; 418 } 419 420 /// Sets the parent window of the component. 421 void parentWindow(Window newParentWindow) { 422 _parentWindow = newParentWindow; 423 } 424 425 /// Sets the parent container of the component. 426 void parentContainer(Container newParentContainer) { 427 _parentContainer = newParentContainer; 428 } 429 } 430 }