1 /* 2 nanogui.widget -- Base class of all widgets 3 4 NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch> and 5 ported to D by Alexandr Druzhinin <drug2004@bk.ru> 6 The widget drawing code is based on the NanoVG demo application 7 by Mikko Mononen. 8 9 All rights reserved. Use of this source code is governed by a 10 BSD-style license that can be found in the LICENSE.txt file. 11 */ 12 /// 13 module nanogui.widget; 14 15 import std.container.array; 16 17 import nanogui.theme; 18 import nanogui.layout; 19 import nanogui.common : Cursor, Vector2i, Vector2f, MouseButton, KeyAction; 20 import nanogui.screen : Screen; 21 public import nanogui.common; 22 23 /** 24 * Base class of all widgets. 25 * 26 * Widget is the base class of all widgets in nanogui. It can 27 * also be used as an panel to arrange an arbitrary number of child 28 * widgets using a layout generator (see `nanogui.layout.Layout`). 29 */ 30 class Widget 31 { 32 public: 33 /// Construct a new widget with the given parent widget 34 this(Widget parent) 35 { 36 mVisible = true; 37 mEnabled = true; 38 mTooltip = ""; 39 mFontSize = -1; 40 mIconExtraScale = 1.0f; 41 mCursor = Cursor.Arrow; 42 if (parent) 43 parent.addChild(this); 44 } 45 46 /// Return the parent widget 47 final Widget parent() { return mParent; } 48 /// Return the parent widget 49 auto parent() const { return mParent; } 50 /// Set the parent widget 51 final void parent(Widget parent) { mParent = parent; } 52 53 /// Return the used `nanogui.layout.Layout` generator 54 final Layout layout() { return mLayout; } 55 /// Return the used `nanogui.layout.Layout` generator 56 auto layout() const { return mLayout; } 57 /// Set the used `nanogui.layout.Layout` generator 58 final void layout(Layout layout) { mLayout = layout; } 59 60 /// Return the `nanogui.theme.Theme` used to draw this widget 61 final const(Theme) theme() const { return mTheme; } 62 /// Set the `nanogui.theme.Theme` used to draw this widget 63 void theme(Theme theme) 64 { 65 if (mTheme is theme) 66 return; 67 mTheme = theme; 68 foreach(child; mChildren) 69 child.theme = theme; 70 } 71 72 /// Return the position relative to the parent widget 73 final Vector2i position() const { return mPos; } 74 /// Set the position relative to the parent widget 75 final void position(Vector2i pos) { mPos = pos; } 76 77 /// Return the absolute position on screen 78 final Vector2i absolutePosition() const 79 { 80 return mParent ? 81 (parent.absolutePosition + mPos) : mPos; 82 } 83 84 /// Return the size of the widget 85 final Vector2i size() const { return mSize; } 86 /// set the size of the widget 87 final void size(Vector2i size) { mSize = size; } 88 89 /// Return the width of the widget 90 final int width() const { return mSize.x; } 91 /// Set the width of the widget 92 final void width(int width) { mSize.x = width; } 93 94 /// Return the height of the widget 95 final int height() const { return mSize.y; } 96 /// Set the height of the widget 97 final void height(int height) { mSize.y = height; } 98 99 /** 100 * Set the fixed size of this widget 101 * 102 * If nonzero, components of the fixed size attribute override any values 103 * computed by a layout generator associated with this widget. Note that 104 * just setting the fixed size alone is not enough to actually change its 105 * size; this is done with a call to `nanogui.widget.Widget.size` or a call to `nanogui.widget.Widget.performLayout` 106 * in the parent widget. 107 */ 108 final void fixedSize(Vector2i fixedSize) { mFixedSize = fixedSize; } 109 110 /// Return the fixed size 111 final Vector2i fixedSize() const { return mFixedSize; } 112 113 /// Return the fixed width (see `fixedSize`) 114 final int fixedWidth() const { return mFixedSize.x; } 115 /// Return the fixed height (see `fixedSize`) 116 final int fixedHeight() const { return mFixedSize.y; } 117 /// Set the fixed width (see `fixedSize`) 118 final void fixedWidth(int width) { mFixedSize.x = width; } 119 /// Set the fixed height (see `fixedSize`) 120 final void fixedHeight(int height) { mFixedSize.y = height; } 121 122 /// Return whether or not the widget is currently visible (assuming all parents are visible) 123 final bool visible() const { return mVisible; } 124 /// Set whether or not the widget is currently visible (assuming all parents are visible) 125 final void visible(bool visible) { mVisible = visible; } 126 127 /// Check if this widget is currently visible, taking parent widgets into account 128 final bool visibleRecursive() const { 129 import std.typecons : Rebindable; 130 bool visible = true; 131 Rebindable!(const Widget) widget = this; 132 while (widget) { 133 visible &= widget.visible; 134 widget = widget.parent; 135 } 136 return visible; 137 } 138 139 /// Return the number of child widgets 140 final int childCount() const 141 { 142 import std.conv : castFrom; 143 return castFrom!size_t.to!int(mChildren.length); 144 } 145 146 /// Return the list of child widgets of the current widget 147 auto children() { return mChildren; } 148 /// ditto 149 auto children() const { return mChildren; } 150 151 /** 152 * Add a child widget to the current widget at 153 * the specified index. 154 * 155 * This function almost never needs to be called by hand, 156 * since the constructor of `Widget` automatically 157 * adds the current widget to its parent 158 */ 159 void addChild(int index, Widget widget) 160 { 161 assert(index <= childCount); 162 mChildren.insertBefore(mChildren[index..$], widget); 163 widget.parent = this; 164 widget.theme = mTheme; 165 } 166 167 /// Convenience function which appends a widget at the end 168 void addChild(Widget widget) 169 { 170 addChild(childCount(), widget); 171 } 172 173 /// Remove a child widget by index 174 void removeChild(int index) 175 { 176 import std.range : takeOne; 177 mChildren.linearRemove(mChildren[index..$].takeOne); 178 } 179 180 /// Remove a child widget by value 181 void removeChild(Widget widget) 182 { 183 import std.algorithm : find; 184 import std.range : takeOne; 185 mChildren.linearRemove(mChildren[].find(widget).takeOne); 186 } 187 188 /// Retrieves the child at the specific position 189 auto childAt(int index) const { return mChildren[index]; } 190 191 /// Retrieves the child at the specific position 192 auto childAt(int index) { return mChildren[index]; } 193 194 /// Returns the index of a specific child or -1 if not found 195 int childIndex(Widget widget) const 196 { 197 import std.algorithm : countUntil; 198 return cast(int) countUntil!(a=>a is widget)(mChildren[]); 199 } 200 201 /// Variadic shorthand notation to construct and add a child widget 202 auto add(W, Args...)(Args args) 203 { 204 return new WidgetClass(this, args); 205 } 206 207 /// Walk up the hierarchy and return the parent window 208 final Window window() 209 { 210 Widget widget = this; 211 while (true) { 212 if (!widget) 213 throw new Exception( 214 "Widget:internal error (could not find parent window)"); 215 Window window = cast(Window)(widget); 216 if (window) 217 return window; 218 widget = widget.parent; 219 } 220 } 221 222 /// Walk up the hierarchy and return the parent screen 223 final Screen screen() 224 { 225 auto widget = this; 226 while (true) { 227 if (!widget) 228 throw new Exception( 229 "Widget:internal error (could not find parent screen)"); 230 auto screen = cast(Screen) widget; 231 if (screen) 232 return screen; 233 widget = widget.parent; 234 } 235 } 236 237 /// Associate this widget with an ID value (optional) 238 void setId(string id) { mId = id; } 239 /// Return the ID value associated with this widget, if any 240 auto id() const { return mId; } 241 242 /// Return whether or not this widget is currently enabled 243 final bool enabled() const { return mEnabled; } 244 /// Set whether or not this widget is currently enabled 245 final void enabled(bool enabled) { mEnabled = enabled; } 246 247 /// Return whether or not this widget is currently focused 248 bool focused() const { return mFocused; } 249 /// Set whether or not this widget is currently focused 250 void focused(bool focused) { mFocused = focused; } 251 /// Request the focus to be moved to this widget 252 void requestFocus() 253 { 254 import nanogui.screen : Screen; 255 Widget widget = this; 256 while (widget.parent()) 257 widget = widget.parent(); 258 (cast(Screen) widget).updateFocus(this); 259 } 260 261 string tooltip() const { return mTooltip; } 262 void tooltip(string tooltip) { mTooltip = tooltip; } 263 264 /// Return current font size. If not set the default of the current theme will be returned 265 final int fontSize() const 266 { 267 return (mFontSize < 0 && mTheme) ? mTheme.mStandardFontSize : mFontSize; 268 } 269 /// Set the font size of this widget 270 final void fontSize(int fontSize) { mFontSize = fontSize; } 271 /// Return whether the font size is explicitly specified for this widget 272 final bool hasFontSize() const { return mFontSize > 0; } 273 274 /** 275 * The amount of extra scaling applied to *icon* fonts. 276 * See `nanogui.Widget.mIconExtraScale`. 277 */ 278 float iconExtraScale() const { return mIconExtraScale; } 279 280 /** 281 * Sets the amount of extra scaling applied to *icon* fonts. 282 * See `nanogui.Widget.mIconExtraScale`. 283 */ 284 void iconExtraScale(float scale) { mIconExtraScale = scale; } 285 286 /// Return a pointer to the cursor of the widget 287 Cursor cursor() const { return mCursor; } 288 /// Set the cursor of the widget 289 void cursor(Cursor value) { mCursor = value; } 290 291 /// Check if the widget contains a certain position 292 final bool contains(Vector2i p) const { 293 import std.algorithm : all; 294 // the widget contains a position if it more than 295 // the widget position and less than widget position 296 // + widget size 297 auto d = (p-mPos); 298 return d[].all!"a>=0" && (d-mSize)[].all!"a<=0"; 299 } 300 301 /// Determine the widget located at the given position value (recursive) 302 Widget findWidget(Vector2i p) 303 { 304 foreach_reverse(child; mChildren) 305 { 306 if (child.visible() && child.contains(p - mPos)) 307 return child.findWidget(p - mPos); 308 } 309 return contains(p) ? this : null; 310 } 311 312 /// Handle a mouse button event (default implementation: propagate to children) 313 bool mouseButtonEvent(Vector2i p, MouseButton button, bool down, int modifiers) 314 { 315 foreach_reverse(ch; mChildren) 316 { 317 Widget child = ch; 318 if (child.visible && child.contains(p - mPos) && 319 child.mouseButtonEvent(p - mPos, button, down, modifiers)) 320 return true; 321 } 322 if (button == MouseButton.Left && down && !mFocused) 323 requestFocus(); 324 return false; 325 } 326 327 /// Handle a mouse motion event (default implementation: propagate to children) 328 bool mouseMotionEvent(Vector2i p, Vector2i rel, MouseButton button, int modifiers) 329 { 330 foreach_reverse(it; mChildren) 331 { 332 Widget child = it; 333 if (!child.visible) 334 continue; 335 const contained = child.contains(p - mPos); 336 const prevContained = child.contains(p - mPos - rel); 337 if (contained != prevContained) 338 child.mouseEnterEvent(p, contained); 339 if ((contained || prevContained) && 340 child.mouseMotionEvent(p - mPos, rel, button, modifiers)) 341 return true; 342 } 343 return false; 344 } 345 346 /// Handle a mouse drag event (default implementation: do nothing) 347 bool mouseDragEvent(Vector2i p, Vector2i rel, MouseButton button, int modifiers) 348 { 349 return false; 350 } 351 352 /// Handle a mouse enter/leave event (default implementation: record this fact, but do nothing) 353 bool mouseEnterEvent(Vector2i p, bool enter) 354 { 355 mMouseFocus = enter; 356 return false; 357 } 358 359 /// Handle a mouse scroll event (default implementation: propagate to children) 360 bool scrollEvent(Vector2i p, Vector2f rel) 361 { 362 foreach_reverse (child; mChildren) 363 { 364 if (!child.visible) 365 continue; 366 if (child.contains(p - mPos) && child.scrollEvent(p - mPos, rel)) 367 return true; 368 } 369 return false; 370 } 371 372 /// Handle a focus change event (default implementation: record the focus status, but do nothing) 373 bool focusEvent(bool focused) 374 { 375 mFocused = focused; 376 return false; 377 } 378 379 /// Handle a keyboard event (default implementation: do nothing) 380 bool keyboardEvent(int key, int scancode, KeyAction action, int modifiers) 381 { 382 return false; 383 } 384 385 /// Handle text input (UTF-32 format) (default implementation: do nothing) 386 bool keyboardCharacterEvent(dchar codepoint) 387 { 388 return false; 389 } 390 391 /// Compute the preferred size of the widget 392 Vector2i preferredSize(NanoContext ctx) const 393 { 394 if (mLayout) 395 return mLayout.preferredSize(ctx, this); 396 else 397 return mSize; 398 } 399 400 /// Compute the preferred size of the widget considering its child except 401 /// skipped one (for example button panel of window) 402 final Vector2i preferredSize(NanoContext ctx, const Widget skipped) const 403 { 404 if (mLayout) 405 return mLayout.preferredSize(ctx, this, skipped); 406 else 407 return mSize; 408 } 409 410 /// Invoke the associated layout generator to properly place child widgets, if any 411 void performLayout(NanoContext ctx) 412 { 413 if (mLayout) { 414 mLayout.performLayout(ctx, this); 415 } else { 416 foreach(c; mChildren) { 417 Vector2i pref = c.preferredSize(ctx), fix = c.fixedSize(); 418 c.size(Vector2i( 419 fix[0] ? fix[0] : pref[0], 420 fix[1] ? fix[1] : pref[1] 421 )); 422 c.performLayout(ctx); 423 } 424 } 425 } 426 427 /// Draw the widget (and all child widgets) 428 void draw(ref NanoContext ctx) 429 { 430 version(NANOGUI_SHOW_WIDGET_BOUNDS) 431 { 432 ctx.strokeWidth(1.0f); 433 ctx.beginPath; 434 ctx.rect(mPos.x + 1.0f, mPos.y + 0.0f, mSize.x - 1, mSize.y - 1); 435 ctx.strokeColor(Color(255, 0, 0, 255)); 436 ctx.stroke; 437 } 438 439 if (mChildren.length == 0) 440 return; 441 442 ctx.save; 443 ctx.translate(mPos.x, mPos.y); 444 foreach(child; mChildren) 445 { 446 if (child.visible) 447 { 448 ctx.save; 449 scope(exit) ctx.restore; 450 ctx.intersectScissor(child.mPos.x, child.mPos.y, child.mSize.x, child.mSize.y); 451 child.draw(ctx); 452 } 453 } 454 ctx.restore; 455 } 456 457 // // Save the state of the widget into the given \ref Serializer instance 458 //virtual void save(Serializer &s) const; 459 460 // // Restore the state of the widget from the given \ref Serializer instance 461 //virtual bool load(Serializer &s); 462 463 protected: 464 /// Free all resources used by the widget and any children 465 ~this() 466 { 467 //foreach(child; mChildren) { 468 // if (child) 469 // child.decRef(); 470 //} 471 } 472 473 /** 474 * Convenience definition for subclasses to get the full icon scale for this 475 * class of Widget. It simple returns the value 476 * `mTheme.mIconScale * this.mIconExtraScale`. 477 * 478 * See_also: 479 * `Theme.mIconScale` and `Widget.mIconExtraScale`. This tiered scaling 480 * strategy may not be appropriate with fonts other than `entypo.ttf`. 481 */ 482 pragma(inline, true) 483 float icon_scale() const { return mTheme.mIconScale * mIconExtraScale; } 484 485 Widget mParent; 486 Theme mTheme; 487 Layout mLayout; 488 string mId; 489 Vector2i mPos, mSize, mFixedSize; 490 Array!Widget mChildren; 491 492 /** 493 * Whether or not this Widget is currently visible. When a Widget is not 494 * currently visible, no time is wasted executing its drawing method. 495 */ 496 bool mVisible; 497 498 /** 499 * Whether or not this Widget is currently enabled. Various different kinds 500 * of derived types use this to determine whether or not user input will be 501 * accepted. For example, when ``mEnabled == false``, the state of a 502 * CheckBox cannot be changed, or a TextBox will not allow new input. 503 */ 504 bool mEnabled; 505 bool mFocused, mMouseFocus; 506 string mTooltip; 507 int mFontSize; 508 509 /** 510 * The amount of extra icon scaling used in addition the the theme's 511 * default icon font scale. Default value is ``1.0``, which implies 512 * that `icon_scale` simply returns the value of `nanogui.Theme.mIconScale`. 513 * 514 * Most widgets do not need extra scaling, but some (e.g., `CheckBox`, `TextBox`) 515 * need to adjust the Theme's default icon scaling 516 * `nanogui.Theme.mIconScale` to properly display icons within their 517 * bounds (upscale, or downscale). 518 * 519 * Summary: 520 * 521 * When using `nvgFontSize` for icons in subclasses, make sure to call 522 * the `icon_scale` function. Expected usage when *drawing* icon fonts 523 * is something like: 524 * 525 * --- 526 * 527 * void draw(NanoContext ctx) 528 * { 529 * // fontSize depends on the kind of `Widget`. Search for `FontSize` 530 * // in the `Theme` class (e.g., standard vs button) 531 * float ih = fontSize; 532 * // assuming your Widget has a declared `mIcon` 533 * if (isFontIcon(mIcon)) { 534 * ih *= icon_scale(); 535 * ctx.fontFace("icons"); 536 * ctx.fontSize(ih); 537 * /// remaining drawing code (see button.d for more) 538 * } 539 * } 540 * --- 541 */ 542 float mIconExtraScale; 543 Cursor mCursor; 544 }