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 }