1 /// 2 module fluid.field_slot; 3 4 import fluid.node; 5 import fluid.utils; 6 import fluid.input; 7 import fluid.actions; 8 import fluid.backend; 9 10 import fluid.io.hover; 11 import fluid.io.focus; 12 13 @safe: 14 15 16 /// A field slot is a node meant to hold an input node along with associated nodes, like labels. It's functionally 17 /// equivalent to the [`<label>` element in HTML](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label). 18 /// 19 /// Fields expand the interactable (clickable) area of input nodes by other nodes that are placed inside the slot. For 20 /// example, in the code snippet below, if the user clicks on the "username" label, the text input underneath will gain 21 /// focus. 22 /// 23 /// --- 24 /// fieldSlot!vframe( 25 /// label("Username"), 26 /// textInput(), 27 /// ) 28 /// --- 29 alias fieldSlot(alias node) = simpleConstructor!(FieldSlot, node); 30 31 /// ditto 32 class FieldSlot(T : Node) : T, FluidHoverable, Hoverable, Focusable { 33 34 mixin makeHoverable; 35 mixin FluidHoverable.enableInputActions; 36 mixin Hoverable.enableInputActions; 37 38 private { 39 Focusable _focusableChild; 40 } 41 42 this(Args...)(Args args) { 43 super(args); 44 } 45 46 /// Pass focus to the field contained by this slot and press it. 47 @(FluidInputAction.press) 48 void press() { 49 50 focusAnd.then((Node node) { 51 if (auto focusable = cast(FluidFocusable) node) { 52 focusable.runInputAction!(FluidInputAction.press); 53 } 54 }); 55 56 } 57 58 /// Pass focus to the field contained by this slot. 59 void focus() { 60 cast(void) focusAnd(); 61 } 62 63 /// ditto 64 FocusRecurseAction focusAnd() { 65 66 auto action = this.focusRecurseChildren(); 67 action.then(&setFocusableChild); 68 return action; 69 70 } 71 72 private void setFocusableChild(Focusable focus) { 73 _focusableChild = focus; 74 } 75 76 override void drawImpl(Rectangle outer, Rectangle inner) { 77 78 const previousHover = tree.hover; 79 80 // Draw children 81 super.drawImpl(outer, inner); 82 83 // Test if hover has switched to any of them 84 const isChildHovered = tree.hover !is previousHover; 85 86 // If the node doesn't handle hover itself, take over 87 // (pun not intended) 88 if (isChildHovered && !cast(FluidHoverable) tree.hover) { 89 90 tree.hover = this; 91 92 } 93 94 } 95 96 // implements FluidHoverable 97 override bool isHovered() const { 98 return tree.hover is this 99 || super.isHovered(); 100 } 101 102 // implements FluidHoverable 103 override void mouseImpl() { 104 105 } 106 107 override bool blocksInput() const { 108 return isDisabled || isDisabledInherited; 109 } 110 111 override bool hoverImpl(HoverPointer) { 112 return false; 113 } 114 115 override bool focusImpl() { 116 return false; 117 } 118 119 override bool isFocused() const { 120 return _focusableChild && _focusableChild.isFocused(); 121 } 122 123 } 124