2 * Copyright (C) 2002,2003,2004,2005,2006 Daniel Heck
3 * Copyright (C) 2006,2007 Ronald Lamprecht
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "gui/LevelWidget.hh"
22 #include "gui/LevelMenu.hh"
23 #include "gui/LevelInspector.hh"
29 #include "SoundEffectManager.hh"
30 #include "StateManager.hh"
33 #include "lev/Proxy.hh"
40 namespace enigma
{ namespace gui
{
41 /* -------------------- LevelWidget -------------------- */
43 LevelWidget::LevelWidget(bool withScoreIcons
, bool withEditBorder
) :
44 displayScoreIcons (withScoreIcons
), displayEditBorder (withEditBorder
),
45 listener(0), width (0), height (0), m_areas(),
46 isInvalidateUptodate (true), lastUpdate (0)
48 const video::VMInfo
&vminfo
= *video::GetInfo();
49 const int vshrink
= vminfo
.width
< 640 ? 1 : 0;
51 buttonw
= vminfo
.thumbw
+ (vshrink
?13:27); // min should be +30 for all modes but 640x480
52 buttonh
= vminfo
.thumbh
+ (vshrink
?14:28);
53 curIndex
= lev::Index::getCurrentIndex();
54 iselected
= curIndex
->getCurrentPosition();
55 ifirst
= curIndex
->getScreenFirstPosition();
56 preview_cache
= LevelPreviewCache::instance();
57 scoreMgr
= lev::ScoreManager::instance();
58 img_link
= enigma::GetImage("ic-link");
59 img_copy
= enigma::GetImage("ic-copy");
60 img_feather
= enigma::GetImage("ic-feather");
61 img_easy
= enigma::GetImage("completed-easy");
62 img_hard
= enigma::GetImage("completed");
63 img_obsolete
= enigma::GetImage(("ic-obsolete" + vminfo
.thumbsext
).c_str());
64 img_outdated
= enigma::GetImage(("ic-outdated" + vminfo
.thumbsext
).c_str());
65 img_unavailable
= enigma::GetImage("unavailable");
66 img_par
= enigma::GetImage("par");
67 img_wrEasy
= enigma::GetImage("ic-wr-easy");
68 img_wrDifficult
= enigma::GetImage("ic-wr-difficult");
69 img_border
= enigma::GetImage(("thumbborder" + vminfo
.thumbsext
).c_str());
70 img_editborder
= enigma::GetImage(("editborder" + vminfo
.thumbsext
).c_str());
71 thumbmode
= (vminfo
.thumbw
== 160) ? 2 : ((vminfo
.thumbw
== 120) ? 1 : 0);
74 void LevelWidget::syncFromIndexMgr() {
75 if (curIndex
!= lev::Index::getCurrentIndex()) {
76 curIndex
= lev::Index::getCurrentIndex();
77 iselected
= curIndex
->getCurrentPosition();
78 ifirst
= curIndex
->getScreenFirstPosition();
80 sound::EmitSoundEvent ("menumove");
81 } else if (iselected
!= curIndex
->getCurrentPosition()) {
82 sound::EmitSoundEvent ("menumove");
84 // repair ifirst on boot and screen resolution changes
85 set_selected(curIndex
->getScreenFirstPosition(),
86 curIndex
->getCurrentPosition());
89 void LevelWidget::syncToIndexMgr() {
90 curIndex
->setCurrentPosition(iselected
);
91 curIndex
->setScreenFirstPosition(ifirst
);
94 void LevelWidget::realize(const ecl::Rect
&area_
) {
95 Widget::realize(area_
);
96 width
= area_
.w
/ buttonw
;
97 height
= area_
.h
/ buttonh
;
100 void LevelWidget::trigger_action() {
102 listener
->on_action(this);
106 void LevelWidget::scroll_up(int nlines
)
108 int newFirst
= ifirst
;
109 int newSelected
= iselected
;
110 for (; nlines
; --nlines
) {
111 if (newFirst
+width
*height
>= curIndex
->size())
114 if (newSelected
< newFirst
)
115 newSelected
+= width
;
117 set_selected(newFirst
, newSelected
);
120 void LevelWidget::scroll_down(int nlines
)
122 int newFirst
= ifirst
;
123 int newSelected
= iselected
;
124 for (; nlines
; --nlines
) {
127 } else if (newFirst
< width
) {
129 if (newSelected
>= width
*height
)
130 newSelected
= width
*height
- 1;
133 if (newSelected
>= newFirst
+width
*height
)
134 newSelected
-= width
;
137 set_selected(newFirst
, newSelected
);
140 void LevelWidget::page_up() {
141 set_selected((ifirst
>= width
*height
? ifirst
- width
*height
: 0),
142 (iselected
>= width
*height
? iselected
- width
*height
: 0));
146 void LevelWidget::page_down()
148 int s
= lev::Index::getCurrentIndex()->size() - 1; // last position
149 int lastPageFirst
= (s
>= width
*height
? (s
/ width
- height
+ 1) * width
: 0);
151 // make sure last page is shown as a whole
152 int first
= std::min
<int> (lastPageFirst
, ifirst
+ width
*height
);
153 // set_selected (first, s-1);
154 set_selected(first
, iselected
+ width
*height
);
158 void LevelWidget::start() {
163 void LevelWidget::end() {
164 int s
= lev::Index::getCurrentIndex()->size() - 1; // last position
165 int lastPageFirst
= (s
>= width
*height
? (s
/ width
- height
+ 1) * width
: 0);
166 set_selected(lastPageFirst
, s
);
170 void LevelWidget::set_current(int newsel
)
172 set_selected(ifirst
, newsel
);
175 void LevelWidget::set_selected(int newfirst
, int newsel
)
177 int numlevels
= curIndex
->size();
178 newsel
= Clamp
<int> (newsel
, 0, numlevels
-1);
179 if (newsel
< 0) newsel
= 0;
181 if (newsel
< newfirst
)
182 newfirst
= (newsel
/width
)*width
;
183 if (newsel
>= newfirst
+width
*height
)
184 newfirst
= (newsel
/width
-height
+1)*width
;
186 newfirst
= ecl::Clamp
<int>(newfirst
, 0, numlevels
-1);
187 if (newfirst
< 0) newfirst
= 0;
189 size_t oldsel
= iselected
;
190 if (newfirst
!= ifirst
) {
194 if (!m_areas
.empty()) {
195 sound::EmitSoundEvent ("menumove");
196 if ((int)oldsel
!= newsel
)
197 sound::EmitSoundEvent ("menuswitch");
201 else if (newsel
!= iselected
) {
204 if (!m_areas
.empty()) {
205 sound::EmitSoundEvent ("menuswitch");
206 invalidate_area(m_areas
[oldsel
-ifirst
]); // old selection
207 invalidate_area(m_areas
[iselected
-ifirst
]); // new selection
212 int LevelWidget::thumb_off(int small
, int medium
, int large
) {
213 return (thumbmode
== 2) ? large
: ((thumbmode
== 1) ? medium
: small
);
216 bool LevelWidget::draw_level_preview(ecl::GC
&gc
, int x
, int y
, int borderWidth
,
217 lev::Proxy
*proxy
, bool selected
, bool isCross
, bool locked
,
218 bool allowGeneration
, bool &didGenerate
) {
219 // Draw button with level preview
221 Surface
*img
= preview_cache
->getPreview(proxy
, allowGeneration
, didGenerate
);
226 blit (gc
, x
- borderWidth
, y
- borderWidth
, displayEditBorder
? img_editborder
: img_border
);
227 blit (gc
, x
, y
, img
);
229 img
->set_alpha (127);
230 blit (gc
, x
, y
, img
);
234 // Shade unavailable levels
236 blit (gc
, x
, y
, img_unavailable
);
238 if (displayScoreIcons
) {
239 // Draw solved/changed icons on top of level preview
240 // Easy/difficult mode and solved status:
241 // None: Level not beaten - no easy mode available
242 // Feather: Level not beaten - easy mode available
243 // Feather + Gold: Level beaten in normal mode - easy available
244 // Silver: Level beaten in easy mode (normal mode available)
245 // Gold: Level beaten in normal mode - easy not availabe
246 // Silver + Gold: Level beaten in all modes - easy available
247 Surface
*useAsEasy
= NULL
;
248 Surface
*useAsDifficult
= NULL
;
249 if (proxy
->hasEasyMode()) {
250 useAsEasy
= img_feather
;
251 if (scoreMgr
->isSolved(proxy
, DIFFICULTY_EASY
))
252 useAsEasy
= img_easy
;
254 if (scoreMgr
->isSolved(proxy
, DIFFICULTY_HARD
))
255 useAsDifficult
= img_hard
;
257 if (app
.state
->getInt("Difficulty") == DIFFICULTY_HARD
) {
258 // draw golden medal over silber medal
259 if (useAsEasy
!= NULL
)
260 blit (gc
, x
+thumb_off(3,3,24), y
, useAsEasy
);
261 if (useAsDifficult
!= NULL
)
262 blit (gc
, x
+thumb_off(8,8,29), y
, useAsDifficult
);
265 // draw silver medal over golden medal
266 if (useAsDifficult
!= NULL
)
267 blit (gc
, x
+thumb_off(8,8,29), y
, useAsDifficult
);
268 if (useAsEasy
!= NULL
)
269 blit (gc
, x
+thumb_off(3,3,24), y
, useAsEasy
);
272 // Add warning sign if level has been changed since player solved it
273 if (scoreMgr
->isObsolete(proxy
, app
.state
->getInt("Difficulty")))
274 blit(gc
, x
-2, y
-2, img_obsolete
);
275 else if (scoreMgr
->isOutdated(proxy
, app
.state
->getInt("Difficulty")))
276 blit(gc
, x
-2, y
-2, img_outdated
);
278 // Add icon if worldrecord or par
279 if (scoreMgr
->bestScoreReached(proxy
, app
.state
->getInt("Difficulty"))) {
280 blit(gc
, x
+thumb_off(5,35,59), y
+thumb_off(2,5,20),
281 (app
.state
->getInt("Difficulty") != DIFFICULTY_HARD
&&
282 proxy
->hasEasyMode()) ? img_wrEasy
: img_wrDifficult
);
283 } else if (scoreMgr
->parScoreReached(proxy
, app
.state
->getInt("Difficulty"))){
284 blit(gc
, x
+thumb_off(33,33,55), y
+thumb_off(12,12,12), img_par
);
287 // Draw solved/changed icons on top of level preview
289 blit (gc
, x
+4, y
+4, img_link
);
291 blit (gc
, x
+4, y
+4, img_copy
);
296 void LevelWidget::draw(ecl::GC
&gc
, const ecl::Rect
&r
) {
297 const video::VMInfo
&vminfo
= *video::GetInfo();
298 const int imgw
= vminfo
.thumbw
; // Size of the preview images
299 const int imgh
= vminfo
.thumbh
;
300 const int bwidth
= vminfo
.thumbborder_width
;
302 const int hgap
= Max(0, (get_w() - width
*buttonw
) / (width
));
303 const int vgap
= Max(0, (get_h() - height
*buttonh
)/ (height
-1));
305 unsigned i
=ifirst
; // level index
306 bool allowGeneration
= true;
308 for (int y
=0; y
<height
; y
++) {
309 for (int x
=0; x
<width
; x
++, i
++) {
310 if ((int)i
>= curIndex
->size())
313 int xpos
= get_x() + hgap
/2 + x
*(buttonw
+ hgap
);
314 int ypos
= get_y() + y
*(buttonh
+ vgap
);
316 Rect
buttonarea(xpos
, ypos
, buttonw
, buttonh
);
317 if (!(r
.overlaps(buttonarea
) || r
.w
== 0))
318 continue; // r.w==0 if repainting whole screen
320 if( (i
-ifirst
) >= m_areas
.size()) {
321 m_areas
.push_back(buttonarea
);
322 pending_redraws
.push_back(false);
324 m_areas
[(i
-ifirst
)] = buttonarea
;
326 // Draw level preview
327 lev::Proxy
*levelProxy
= curIndex
->getProxy(i
);
328 int imgx
= xpos
+ (buttonw
-imgw
)/2;
329 int imgy
= ypos
+ bwidth
;
330 if (levelProxy
!= NULL
) {
332 bool didDraw
= draw_level_preview(gc
, imgx
, imgy
, bwidth
, levelProxy
,
333 (int) i
== iselected
, !curIndex
->isSource(levelProxy
),
334 !curIndex
->mayPlayLevel(i
+1),
335 allowGeneration
, didGenerate
);
337 // do not generate more than 1 preview from level source
339 allowGeneration
= false;
342 pending_redraws
[(i
-ifirst
)] = false;
344 // the button is not drawn - mark it to be drawn on
346 pending_redraws
[(i
-ifirst
)] = true;
347 isInvalidateUptodate
= false;
351 Font
*smallfnt
= enigma::GetFont("levelmenu");
352 Font
*altsmallfnt
= enigma::GetFont("smallalternative");;
353 std::string caption
= levelProxy
->getTitle(); // TODO: may be null! Otherise line 330 redundant
354 smallfnt
->render (gc
,
355 xpos
+ buttonw
/2 - ecl::Min(smallfnt
->get_width(caption
.c_str(), altsmallfnt
)/2, (buttonw
+hgap
)/2),
357 caption
, altsmallfnt
, buttonw
+ hgap
);
361 m_areas
.resize(i
-ifirst
); // Remove unused areas (if any) from the list
365 void LevelWidget::tick(double time
) {
366 if (!isInvalidateUptodate
) {
367 // invalidate just 1 button for redraw
369 for (unsigned i
= 0; i
< pending_redraws
.size(); i
++) {
370 if (pending_redraws
[i
] == true) {
372 invalidate_area(m_areas
[i
]);
373 isInvalidateUptodate
= true;
376 isInvalidateUptodate
= false;
384 bool LevelWidget::on_event(const SDL_Event
&e
) {
385 bool handled
= Widget::on_event(e
);
388 case SDL_MOUSEMOTION
:
389 if (get_area().contains(e
.motion
.x
, e
.motion
.y
)) {
390 int newsel
=iselected
;
391 for (unsigned i
=0; i
<m_areas
.size(); ++i
)
392 if (m_areas
[i
].contains(e
.motion
.x
, e
.motion
.y
))
401 case SDL_MOUSEBUTTONDOWN
:
402 if (get_area().contains(e
.button
.x
, e
.button
.y
))
403 handled
= handle_mousedown (&e
);
406 handled
= handle_keydown (&e
);
413 bool LevelWidget::handle_mousedown(const SDL_Event
*e
) {
414 switch (e
->button
.button
) {
415 case SDL_BUTTON_LEFT
:
416 for (unsigned i
=0; i
<m_areas
.size(); ++i
)
417 if (m_areas
[i
].contains(e
->button
.x
, e
->button
.y
))
419 sound::EmitSoundEvent ("menuok");
420 iselected
= ifirst
+i
;
422 if (SDL_GetModState() & KMOD_CTRL
&& !(SDL_GetModState() & KMOD_SHIFT
)) {
423 // control key pressed - level inspector
424 LevelInspector
m(curIndex
->getProxy(iselected
));
426 get_parent()->draw_all();
428 // no control key - start level
434 case SDL_BUTTON_RIGHT
:
435 for (unsigned i
=0; i
<m_areas
.size(); ++i
)
436 if (m_areas
[i
].contains(e
->button
.x
, e
->button
.y
))
438 sound::EmitSoundEvent ("menuok");
439 iselected
= ifirst
+i
;
441 LevelInspector
m(curIndex
->getProxy(iselected
));
443 get_parent()->draw_all();
447 case 4: scroll_down(1); return true;
448 case 5: scroll_up(1); return true;
453 bool LevelWidget::handle_keydown(const SDL_Event
*e
) {
454 switch (e
->key
.keysym
.sym
) {
456 // Generate new level preview for current level
457 preview_cache
->updatePreview(curIndex
->getProxy(iselected
));
462 if (!(SDL_GetModState() & KMOD_ALT
)) {
463 set_current (iselected
>1 ? iselected
-1 : 0);
468 if (!(SDL_GetModState() & KMOD_ALT
)) {
469 set_current (iselected
+1);
473 case SDLK_DOWN
: set_current (iselected
+width
); break;
474 case SDLK_UP
: set_current (iselected
>width
? iselected
-width
: 0); break;
475 case SDLK_PAGEDOWN
: page_down(); break;
476 case SDLK_PAGEUP
: page_up(); break;
477 case SDLK_HOME
: start(); break;
478 case SDLK_END
: end(); break;
485 return false; // key not handled
489 }} // namespace enigma::gui