1 module evael.graphics.gui.controls.ScrollBar; 2 3 import std.math; 4 5 import evael.graphics.gui.controls.Container; 6 import evael.graphics.gui.controls.Button; 7 8 import evael.utils.Math; 9 import evael.utils.Size; 10 import evael.utils.Color; 11 12 13 interface IScrollable 14 { 15 public void onScroll(ScrollBar.ScrollDirection direction, in float scrollBarPosition); 16 } 17 18 class ScrollBar : Container 19 { 20 public enum ScrollDirection 21 { 22 Top, 23 Bottom, 24 Right, 25 Left 26 } 27 28 public enum Alignement 29 { 30 Vertical, 31 Horizontal 32 } 33 34 private float m_scrollIncrementation; 35 private uint m_invisibleItemsHeight; 36 37 private Button m_scrollButton; 38 private Button m_scrollBottomButton; 39 private Button m_scrollTopButton; 40 41 public this(const float x, const float y, const int width, const int height) 42 { 43 this(vec2(x, y), Size!int(width, height)); 44 45 this.m_scrollIncrementation = 0.0f; 46 } 47 48 public this(const(vec2) position, const(Size!int) size) 49 { 50 super(position, size); 51 52 this.m_name = "scrollBar"; 53 this.m_dock = Dock.Right; 54 55 this.m_scrollIncrementation = 0.0f; 56 57 this.m_scrollTopButton = new Button(0, 0, size.width, 20); 58 this.m_scrollTopButton.dock = Dock.Top; 59 this.m_scrollTopButton.type = Button.Type.Icon; 60 this.m_scrollTopButton.icon = Icon.UpDir; 61 62 this.m_scrollBottomButton = new Button(0, 0, size.width, 20); 63 this.m_scrollBottomButton.dock = Dock.Bottom; 64 this.m_scrollBottomButton.type = Button.Type.Icon; 65 this.m_scrollBottomButton.icon = Icon.DownDir; 66 67 this.m_scrollButton = new Button(0, 20, size.width, 20); 68 69 this.addChild(this.m_scrollTopButton); 70 this.addChild(this.m_scrollBottomButton); 71 this.addChild(this.m_scrollButton); 72 } 73 74 /** 75 * Renders the scrollbar 76 */ 77 public override void draw(in float deltaTime) 78 { 79 super.draw(deltaTime); 80 } 81 82 /** 83 * Computes scrollbar's incrementation 84 * Params: 85 * controlHeight : scrollbar's parent control height 86 * itemsHeight : total height of scrollable items in parent control 87 */ 88 public void computeIncrementation(in uint controlHeight, in uint itemsHeight) 89 { 90 if(itemsHeight <= controlHeight) 91 { 92 this.m_scrollButton.hide(); 93 return; 94 } 95 96 this.m_scrollButton.show(); 97 98 this.m_invisibleItemsHeight = itemsHeight - controlHeight; 99 100 if(this.m_invisibleItemsHeight > controlHeight) 101 { 102 // This is the formula. 103 this.m_scrollIncrementation = cast(float)m_invisibleItemsHeight / (controlHeight - 10); 104 105 this.m_scrollTopButton.onClickEvent = (sender) => (cast(IScrollable)this.m_parent).onScroll(ScrollDirection.Top, this.m_scrollIncrementation); 106 this.m_scrollBottomButton.onClickEvent = (sender) => (cast(IScrollable)this.m_parent).onScroll(ScrollDirection.Bottom, -this.m_scrollIncrementation); 107 } 108 else 109 { 110 // Scrolling is easy we don't need top and bottom buttons 111 this.m_scrollBottomButton.hide(); 112 this.m_scrollTopButton.hide(); 113 114 115 this.m_scrollButton.size = Size!int(20, controlHeight - this.m_invisibleItemsHeight); 116 this.m_scrollButton.position = vec2(0, 0); 117 118 } 119 } 120 121 public override void initialize() 122 { 123 super.initialize(); 124 } 125 126 127 /** 128 * Event called when mouse enters in control's rect 129 * Params: 130 * mousePosition : mouse position 131 */ 132 public override void onMouseMove(in ref vec2 mousePosition) 133 { 134 super.onMouseMove(mousePosition); 135 136 static vec2 lastMousePosition; 137 138 scope(exit) 139 { 140 lastMousePosition = mousePosition; 141 } 142 143 if(this.m_scrollButton.isClicked && mousePosition.y != lastMousePosition.y) 144 { 145 vec2 buttonPosition = this.m_scrollButton.realPosition; 146 147 immutable mouseDifference = (mousePosition.y - lastMousePosition.y); 148 149 // Scroll top 150 if(mousePosition.y > lastMousePosition.y) 151 { 152 // If we scroll over the limit, we go back 153 if(this.m_scrollButton.realPosition.y + this.m_scrollButton.size.height + mouseDifference >= this.m_realPosition.y + this.m_size.height) 154 { 155 156 buttonPosition.y = this.m_realPosition.y + this.m_invisibleItemsHeight; 157 this.m_scrollButton.realPosition = buttonPosition; 158 159 (cast(IScrollable)this.m_parent).onScroll(ScrollDirection.Top, this.m_parent.realPosition.y - this.m_scrollButton.realPosition.y); 160 161 return; 162 } 163 164 } 165 else 166 { 167 if(this.m_scrollButton.realPosition.y <= this.m_realPosition.y) 168 { 169 buttonPosition.y = this.m_realPosition.y; 170 this.m_scrollButton.realPosition = buttonPosition; 171 172 return; 173 } 174 } 175 176 // Scrolling is between the limits 177 buttonPosition.y = buttonPosition.y + mouseDifference; 178 this.m_scrollButton.realPosition = buttonPosition; 179 180 (cast(IScrollable)this.m_parent).onScroll(ScrollDirection.Top, this.m_parent.realPosition.y - this.m_scrollButton.realPosition.y); 181 } 182 } 183 184 /** 185 * Event called when mouse leaves control's rect 186 */ 187 public override void onMouseLeave() 188 { 189 if(this.m_scrollButton.isClicked) 190 { 191 this.m_scrollButton.onMouseUp(MouseButton.Left); 192 } 193 } 194 195 @property 196 public float incrementation() const 197 { 198 return this.m_scrollIncrementation; 199 } 200 201 @property 202 public void incrementation(float value) 203 { 204 this.m_scrollIncrementation = value; 205 } 206 207 @property 208 public Button middleButton() 209 { 210 return this.m_scrollButton; 211 } 212 213 }