1 /*
2  * This file is part of EasyRPG Player.
3  *
4  * EasyRPG Player is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * EasyRPG Player is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 // Headers
19 #include "window_selectable.h"
20 #include "game_system.h"
21 #include "input.h"
22 #include "util_macro.h"
23 #include "bitmap.h"
24 
25 constexpr int arrow_animation_frames = 20;
26 
27 // Constructor
Window_Selectable(int ix,int iy,int iwidth,int iheight)28 Window_Selectable::Window_Selectable(int ix, int iy, int iwidth, int iheight) :
29 	Window_Base(ix, iy, iwidth, iheight) { }
30 
CreateContents()31 void Window_Selectable::CreateContents() {
32 	SetContents(Bitmap::Create(width - 16, max(height - border_y * 2, GetRowMax() * menu_item_height)));
33 }
34 
35 // Properties
36 
GetIndex() const37 int Window_Selectable::GetIndex() const {
38 	return index;
39 }
SetIndex(int nindex)40 void Window_Selectable::SetIndex(int nindex) {
41 	index = min(nindex, item_max - 1);
42 	if (active && help_window != NULL) {
43 		UpdateHelp();
44 	}
45 	UpdateCursorRect();
46 }
GetRowMax() const47 int Window_Selectable::GetRowMax() const {
48 	return (item_max + column_max - 1) / column_max;
49 }
GetTopRow() const50 int Window_Selectable::GetTopRow() const {
51 	return oy / menu_item_height;
52 }
SetTopRow(int row)53 void Window_Selectable::SetTopRow(int row) {
54 	if (row < 0) row = 0;
55 	if (row > GetRowMax() - 1) row = GetRowMax() - 1;
56 	SetOy(row * menu_item_height);
57 }
GetPageRowMax() const58 int Window_Selectable::GetPageRowMax() const {
59 	return (height - border_y * 2) / menu_item_height;
60 }
GetPageItemMax()61 int Window_Selectable::GetPageItemMax() {
62 	return GetPageRowMax() * column_max;
63 }
64 
GetItemRect(int index)65 Rect Window_Selectable::GetItemRect(int index) {
66 	Rect rect = Rect();
67 	rect.width = (width / column_max - 16);
68 	rect.x = (index % column_max * (rect.width + 16));
69 	rect.height = menu_item_height - 4;
70 	rect.y = index / column_max * menu_item_height + menu_item_height / 8;
71 	return rect;
72 }
73 
GetHelpWindow()74 Window_Help* Window_Selectable::GetHelpWindow() {
75 	return help_window;
76 }
77 
SetHelpWindow(Window_Help * nhelp_window)78 void Window_Selectable::SetHelpWindow(Window_Help* nhelp_window) {
79 	help_window = nhelp_window;
80 	if (active && help_window != NULL) {
81 		UpdateHelp();
82 	}
83 }
84 
UpdateHelp()85 void Window_Selectable::UpdateHelp() {
86 	if (UpdateHelpFn && help_window != nullptr) {
87 		UpdateHelpFn(*help_window, index);
88 	}
89 }
90 
91 // Update Cursor Rect
UpdateCursorRect()92 void Window_Selectable::UpdateCursorRect() {
93 	int cursor_width = 0;
94 	int x = 0;
95 	if (index < 0) {
96 		SetCursorRect(Rect());
97 		return;
98 	}
99 	int row = index / column_max;
100 	if (row < GetTopRow()) {
101 		SetTopRow(row);
102 	} else if (row > GetTopRow() + (GetPageRowMax() - 1)) {
103 		SetTopRow(row - (GetPageRowMax() - 1));
104 	}
105 
106 	cursor_width = (width / column_max - 16) + 8;
107 	x = (index % column_max * (cursor_width + 8)) - 4;
108 
109 	int y = index / column_max * menu_item_height - oy;
110 	SetCursorRect(Rect(x, y, cursor_width, menu_item_height));
111 }
112 
UpdateArrows()113 void Window_Selectable::UpdateArrows() {
114 	bool show_up_arrow = (GetTopRow() > 0);
115 	bool show_down_arrow = (GetTopRow() < (GetRowMax() - GetPageRowMax()));
116 
117 	if (show_up_arrow || show_down_arrow) {
118 		arrow_frame = (arrow_frame + 1) % (arrow_animation_frames * 2);
119 	}
120 	bool arrow_visible = (arrow_frame < arrow_animation_frames);
121 	SetUpArrow(show_up_arrow && arrow_visible);
122 	SetDownArrow(show_down_arrow && arrow_visible);
123 }
124 
125 // Update
Update()126 void Window_Selectable::Update() {
127 	Window_Base::Update();
128 	if (active && item_max > 0 && index >= 0) {
129 		if (scroll_dir != 0) {
130 			scroll_progress++;
131 			SetOy(GetOy() + (menu_item_height * scroll_progress / 4 - menu_item_height * (scroll_progress - 1) / 4) * scroll_dir);
132 			UpdateArrows();
133 			if (scroll_progress < 4) {
134 				return;
135 			} else {
136 				scroll_dir = 0;
137 				scroll_progress = 0;
138 				if (active && help_window != NULL) {
139 					UpdateHelp();
140 				}
141 				UpdateCursorRect();
142 			}
143 		}
144 
145 		int old_index = index;
146 
147 		auto move_down = [&]() {
148 			if (index < item_max - column_max || column_max == 1 ) {
149 				Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor));
150 				index = (index + column_max) % item_max;
151 			}
152 		};
153 		if (Input::IsTriggered(Input::DOWN) || Input::IsTriggered(Input::SCROLL_DOWN)) {
154 			move_down();
155 		} else if (Input::IsRepeated(Input::DOWN)) {
156 			if (endless_scrolling || (index + column_max) % item_max > index) {
157 				move_down();
158 			}
159 		}
160 
161 		auto move_up = [&]() {
162 			if (index >= column_max || column_max == 1) {
163 				Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor));
164 				index = (index - column_max + item_max) % item_max;
165 			}
166 		};
167 		if (Input::IsTriggered(Input::UP) || Input::IsTriggered(Input::SCROLL_UP)) {
168 			move_up();
169 		} else if (Input::IsRepeated(Input::UP)) {
170 			if (endless_scrolling || (index - column_max + item_max) % item_max < index) {
171 				move_up();
172 			}
173 		}
174 
175 		// page up/down is limited to selectables with one column
176 		if (column_max == 1) {
177 			if (Input::IsRepeated(Input::PAGE_DOWN) && index < item_max - 1) {
178 				Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor));
179 				int new_pos = index + GetPageRowMax();
180 				index = (new_pos <= item_max - 1) ? new_pos : item_max - 1;
181 			}
182 			if (Input::IsRepeated(Input::PAGE_UP) && index > 0) {
183 				Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor));
184 				int new_pos = index - GetPageRowMax();
185 				index = (new_pos >= 0) ? new_pos : 0;
186 			}
187 		}
188 		if (Input::IsRepeated(Input::RIGHT)) {
189 			if (column_max >= 2 && index < item_max - 1) {
190 				Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor));
191 				index += 1;
192 			}
193 		}
194 		if (Input::IsRepeated(Input::LEFT)) {
195 			if (column_max >= 2 && index > 0) {
196 				Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor));
197 				index -= 1;
198 			}
199 		}
200 
201 		if (std::abs(index - old_index) <= column_max) {
202 			int row = index / column_max;
203 			if (row < GetTopRow() && old_index < item_max - 1) {
204 				scroll_dir = -1;
205 				return;
206 			} else if (row > GetTopRow() + (GetPageRowMax() - 1) && old_index > 0) {
207 				scroll_dir = 1;
208 				return;
209 			}
210 		}
211 	}
212 	if (active && help_window != NULL) {
213 		UpdateHelp();
214 	}
215 	UpdateCursorRect();
216 	UpdateArrows();
217 }
218 
219 // Set endless scrolling state
SetEndlessScrolling(bool state)220 void Window_Selectable::SetEndlessScrolling(bool state) {
221 	endless_scrolling = state;
222 }
223 
224 // Set menu item height
SetMenuItemHeight(int height)225 void Window_Selectable::SetMenuItemHeight(int height) {
226 	menu_item_height = height;
227 }
228