Proste menu w allegro

elendilek

(Wersja zmodyfikowana)
W zwiazku z komentarzem, ze ten text bardziej by sie nadawal na gotowca a nie na artykul, postaram sie go mimo wszystko owym artykulem uczynic ;)

Zaczniemy od utworzenia obiektu reprezentujacego przycisk (pozycje) menu. Zawierac on bedzie wskaznik do funkcji wywolywanej w momencie klikniecia na przycisk, oraz tablice typu char do przechowywania napisu na nim.

class Pozycja{ // obiekt reprezenujacy pozycje w menu
      public: // sklada sie z:
             char nazwa[20]; // napisu wyswietlanego na przycisku
             int (*func)(); // wsk. do funkcji
             bool wyjscie; 
             Pozycja(int (*wsk)(), const char *naz, bool x=false) : func(wsk), wyjscie(x){
                strcpy(nazwa, naz);
             };
};

w naszym przykladzie funkcje zwracaja wartosc typu int i nie posiadaja argumentow:

int (*func)()

zmienna "wyjscie" przechowuje informacje czy po zakonczeniu funkcji program ma przeniesc sie na 'wyzszy' poziom menu (powinnismy ustawic ja na true dla przyciskow takich jak 'wstecz', czy 'zakoncz program'). Domyslnie jest jej przypisywana wartosc false.

Kolejnym krokiem jest utworzenie klasy odpowiedzialnej za cale menu, czyli:

  • przechowywanie wskaznikow do przyciskow,
  • rysowanie ich (przyciskow a nie wskaznikow :p), dopoki uzytkownik nie wybierze ktorejs z pozycji,
  • wywolywanie funkcji obslugujacych przyciski,
    Do przechowywania wskaznikow uzyjemy kontenera vector ze standardowej biblioteki.
class Menu{
      int licznik;
      vector<Pozycja*> tab;
      int up, s;
      int S, W;
public:
       Menu(int si, int wi, int sInit=100) : licznik(0), up(0), s(sInit/2),
                                           S(si), W(wi) {};
       ~Menu(){
         for(int i=0;i<tab.size();i++)
           delete tab[i];
       }
       void add(Pozycja *wsk){
            tab.push_back(wsk);
            licznik++; 
       }
       void draw(){
            for(int i=0;i<tab.size();i++){
              rect(bufor, S/2-s, up+(i+1)*30, S/2+s, up+(i+1)*30+20, makecol(0,250,200));
              textout_centre(bufor,font, tab[i]->nazwa , S/2, up+(i+1)*30+7, 
                                 makecol(250,250,250));
            }
       }
       int run(){
           if(licznik==0) return 1;
           up=(W/2)-((licznik*30)/2)-20;
           bool koniec = false;
           bool tmp=false;
           do{
             clear_to_color(bufor, makecol(0,0,0));
             draw(); // rysuje przyciski menu
             if(!(mouse_b & 1)) tmp = true;
             else if((mouse_b & 1) && tmp){
               if(mouse_x>S/2-s && mouse_x<S/2+s){
                 for(int i=0;i<tab.size();i++){
                   if(mouse_y>(up+(i+1)*30) && mouse_y<(up+(i+1)*30+20)){
                     tab[i]->func();
                     tmp = false;
                     if(tab[i]->wyjscie) koniec = true;
                     break;
                   }
                 }
               }
             }
             show_mouse(bufor); // pokazuje kursor myszy
             blit(bufor,screen,0,0,0,0,S,W); // likwiduje efekt mrugania ekranu
           }while(!koniec); 
       }
};

Na pierwszy rzut oka kod moze sie wydawac troche zagmatwany, ale wiaze sie to z wykorzystaniem w nim funkcji z biblioteki graficznej allegro.
Po koleji:

  • licznik przechowuje ilosc przyciskow w menu,
  • linijka "vector<Pozycja*> tab;" tworzy kontener przechowujacy obiekty typu 'Pozycja *', czyli wskazniki do naszych przyciskow. (kontener to taka dynamiczna tablica do ktorej mozesz bezpiecznie dorzucac nowe elementy [do czasu wyczerpania sie pamieci], nie martwiac sie o... e... no o to, o co sie zwykle martwimy uzywajac zwyklych tablic =),
  • up jest to gorna wspolrzedna y od ktorej powinny zostac rysowane przyciski,
  • s (male) to szerokosc przycisku,
  • S (duze), W - szerokosc i wysokosc ekranu
    (trzy ostatnie zmienne maja za zadanie ulatwic narysowanie calego menu, o wybranej przez uzytkownika szerokosci, na srodku ekranu.)
    Konstruktor inicjalizuje szerokosc przyc. domyslna wartoscia 100px:
Menu(int si, int wi, int sInit=100) : licznik(0), up(0), s(sInit/2),
                                           S(si), W(wi) {};

add() - przyjmuje wskaznik do Pozycji(przycisku) i doklada go na koniec vectora, oraz zwieksza licznik:

void add(Pozycja *wsk){
            tab.push_back(wsk);
            licznik++; 
       }

Do jego elementow mozna sie odwolac tak samo jak do elementow tablicy, poprzez operator[].

funkcja draw() rysuje przyciski, w postaci napisu otoczonego ramka koloru zielono niebieskiego.
[ makecol(int R, int G, int B) zwraca reprezentacje koloru... cholera jak to powiedziec... stworzonego metoda RGB (?!). -_-'
textout_centre() wypisuje wysrodkowany text, a rect() maluje prostokatna ramke.]

run()...
Otoz najpierw sprawdzamy czy menu posiada jakies elementy, jesli nie to wychodzimy z funkcji.
nastepnie wyliczamy zmienna up, uwzgledniajac wymiary przyciskow i ich liczbe,
przypisujemy zmiennej koniec wartosc false, rysowanie menu bedzie sie odbywac dopoki jej stan sie nie zmieni.
kazdy przebieg petli do...while sklada sie z:

  • czyszczenia ekranu,
  • narysowania przyciskow,
  • sprawdzenia czy nacisniety jest lewy przycisk myszy, jesli tak, to sprawdzamy w ktorym miejscu znajduje sie kursow myszy. Jesli jest on akurat nad jakims przyciskiem, to wywolujemy odpowiadajaca mu funkcje. Po jej wywolaniu sprawdzamy czy ten przycisk mial ustawiona flage wyjscia (byl to naprzyklad przycisk 'wstecz'), jesli tak to zmiennej koniec nadajemy wartosc true, co spowoduje wyjscie z petli while. Ufff... !
  • narysowania kursora myszy,
  • skopiowania zawartosci bufora na ekran (likwiduje to efekt mrugania ekranu)
  • no i to tyle...

teraz wystarczy napisac funkcje postaci:

int nazwa(){
//...
}

moga one ustawiac np. rozdzielczosc ekranu, czy ustawienia dzwieku.
a nastepnie utorzyc obiekt menu:

Menu m;

i dodac do niego nowy przycisk:

m.add(nazwa, "Przycisk 1");

i ostatecznie wywolac menu:

m.run()

do naszej funkcji rowniez mozemy wstawic w analogiczny sposob inne menu, co w efekcie pozwoli utworzyc menu skladajace sie z wielu poziomow:

int nazwa(){
	Menu m;
	m.add(jakas_tam_inna_funkcja, "przycisk 2");
	m.run();
}

jesli teraz uzytkownik kliknie na 'przycisk 1' to wywolana zostanie funkcja 'nazwa()', ktora tworzy wlasne menu, posiadajace 'przycisk 2' ! czyz to nie cudowne ??? ;)

Kazde menu powinno dodatkowo posiadac przycisk odpowiadajacy za wyjscie z niego. Do jego utworzenia wystarczy nam pusta funkcja, np.:

int koniec(){return 0;}
int nazwa(){
	Menu m;
	m.add(jakas_tam_inna_funkcja, "przycisk 2");
	m.add(new Pozycja(koniec, "wstecz <--", true));
	m.run();
}

i to wlasciwie koniec, ponizej przykladowa aplikacja z wykorzystaniem dopiero co utworzonej klasy:

#include <cstdlib>
#include <string> // strcpy()
#include <vector> // kontener vector
#include <allegro.h>
using namespace std;

const int S=800; // szerokosc ekranu...
const int W=600; // wysokosc ekranu...
BITMAP *bufor=NULL; // bufor, likwiduje efekt mrugania ekranu

/*
te makro tworzy funkcje aby latwiej bylo przetestowac dzialanie klasy
funkcje sa tworzone wedlug szablonu:
        int nazwa_func(){
            do{
                   clear_to_color(bufor, makecol(0,0,0));
                   if(licznik%30 < 15){ // efekt mrugania ramki
                     rect(bufor, S/2-40, W/2, S/2+40, W/2+20, makecol(250,0,0));
                   }
                   textout(bufor,font, #z ,S/2, W/2 , makecol(250,250,250));
                   show_mouse(bufor);
                   blit(bufor,screen,0,0,0,0,S,W);
                   licznik++;
            }while(!key[KEY_ESC]);
            return 0;
        }
jednym slowem zadaniem funkcji jest jedynie randerowanie swojej nazwy na srodku 
ekranu dopoki uzytkownik nie nacisnie klawisza esc...
*/
#define MAKRO(z) int z(){ \
        unsigned int licznik=0; \
        do{ \
           clear_to_color(bufor, makecol(0,0,0)); \
           if(licznik%30 < 15){ \
             rect(bufor, S/2-40, W/2, S/2+40, W/2+20, makecol(250,0,0)); \
           } \
           textout_centre(bufor,font, #z ,S/2, W/2+7 , makecol(250,250,250)); \
           show_mouse(bufor); \
           blit(bufor,screen,0,0,0,0,S,W); \
           licznik++; \
        }while(!key[KEY_ESC]); \
        return 0; \
        }
        
class Pozycja{ // obiekt reprezenujacy pozycje w menu
      public: // sklada sie z:
             char nazwa[20]; // nazwy wyswietlanej na przycisku menu
             int (*func)(); // funkcji wywolywanej po jego nacisnieciu
             bool wyjscie; // przechowuje informacje czy po zakonczeniu funkcji
                           // obslugi przejsc do wyzszego poziomu menu, a jesli
                           // jest to juz najwyzszy poziom, to wtedy konczy 
                           // dzialanie programu (domyslnie false)
             Pozycja(int (*wsk)(), const char *naz, bool x=false) : func(wsk), wyjscie(x){
                strcpy(nazwa, naz);
             };
};

class Menu{
      int licznik; // licznik przyciskow, jesli jest rowny 0 to run() wyswietli
                   // odpowiedni komunikat
      vector<Pozycja*> tab; // przechowuje pozycje menu i funkcje do ich obslugi
      int up, s; // s -> 1/2 szerokosci przycisku menu
      int S, W; // szerokosc i wyskosc ekranu
public:
       Menu(int si, int wi, int sInit=100) : licznik(0), up(0), s(sInit/2),
                                           S(si), W(wi) {};
       ~Menu(){
         for(int i=0;i<tab.size();i++)
           delete tab[i];
         // poniewaz kontener vector nie obsluguje destrukcji wskaznikow 
         // musimy to zrobic sami...
       }
       void add(Pozycja *wsk){ // dodaje nowa pozycje do menu
            tab.push_back(wsk);
            licznik++; 
       }
       void draw(){ // wyswietla przyciski menu
            for(int i=0;i<tab.size();i++){
              rect(bufor, S/2-s, up+(i+1)*30, S/2+s, up+(i+1)*30+20, makecol(0,250,200));
              textout_centre(bufor,font, tab[i]->nazwa , S/2, up+(i+1)*30+7, 
                                 makecol(250,250,250));
            }
       }
       int run(){
           if(licznik==0) return 1; // brak elementow !
           up=(W/2)-((licznik*30)/2)-20; // polowa wys. ekranu - polowa wys. menu 
                                         // - 20 dla celow kosmetycznych ;)
           bool koniec = false; // 
           bool tmp=false; // zmienna pomocnicza
           do{
             clear_to_color(bufor, makecol(0,0,0)); // czysci ekran
             draw(); // rysuje przyciski menu
             if(!(mouse_b & 1)) tmp = true; // po wybraniu pozycji menu TRZEBA 
                                            // zwolnic przycisk myszy, co likwiduje
                                            // pewien ciekawy efekt
             else if((mouse_b & 1) && tmp){ // jesli chcesz przekonac sie co to za
                                            // efekt usun z tad '&& tmp' ;)
               if(mouse_x>S/2-s && mouse_x<S/2+s){
                 for(int i=0;i<tab.size();i++){
                   if(mouse_y>(up+(i+1)*30) && mouse_y<(up+(i+1)*30+20)){
                     tab[i]->func();
                     tmp = false;
                     if(tab[i]->wyjscie) koniec = true;
                     break; // jesli wiemy juz ktora pozycje uzytkownik wybral
                            // to nie ma sensu kontynuowanie petli...
                   }
                 }// for()
               }// if()
             }// if()
             //...
             show_mouse(bufor); // pokazuje kursor myszy
             blit(bufor,screen,0,0,0,0,S,W); // likwiduje efekt mrugania ekranu
           }while(!koniec); 
       }
};


/********************** definicje funckji menu ****************************/
MAKRO(f1);//  
MAKRO(f2);//  \ te funkcje maja za zadanie wyswietlac swoja nazwe na srodku
MAKRO(f4);//  / ekranu dopoki uzytkownik nie nacisnie klawisza ESC
MAKRO(f5);// 
int koniec(){return 0;}      // to jest funkcja wywolywana przed przejsciem na
                             // wyzszy poziom menu
int f3(){ // dodatkowy poziom menu...
    Menu m(S, W, 200); // ustawia szerokosc przycisku na 200px      
    m.add(new Pozycja(f4, "f4"));
    m.add(new Pozycja(f5, "f5"));
    m.add(new Pozycja(koniec, "wstecz <--", true));
    m.run();
    return 0;
}
/*************************************************************************/

void inicjuj_allegro(int S, int W);
int usun_allegro();


int main(){
    inicjuj_allegro(S, W);
    
 /********************* PRZYKLAD UZYCIA ************************/
 
    Menu m(S, W, 90); // szerokosc przycisku 90px (domyslnie 100px)
    m.add(new Pozycja(f1, "f1")); // utworzenie nowej pozycji menu,
                                  // po jej wybraniu wywolana zostanie f1()
    m.add(new Pozycja(f2, "f2")); // [...] wywolana zostanie f2()
    m.add(new Pozycja(f3, "podmenu-->")); // [...] wywolana zostanie f3(), ktora
                                          // rowniez zawiera obiekt Menu. W ten
                                          // sposob mozna latwo tworzyc wielo-
                                          // poziomowe menu
    m.add(new Pozycja(koniec, "koniec", true));
    m.run();
    
 /**************************************************************/
 
   return usun_allegro();
}
END_OF_MAIN();



/***************** DO OBSLUGI ALLEGRO **************************/
void inicjuj_allegro(int S, int W){
     allegro_init();         
     set_window_title("(?!)");
    
     set_color_depth(32);
     set_gfx_mode(GFX_AUTODETECT_WINDOWED, S, W, S, W);
     set_palette(default_palette);
    
     bufor = create_bitmap(S,W);
    
     install_keyboard();
     install_mouse();    
}

int usun_allegro(){
    remove_mouse();
    remove_keyboard();
    allegro_exit();
    return 0;        
}

3 komentarzy

kupa bledow i wywolan do czegos co nie istnieje :P <---- do bani.

Zdecydowanie się przyda. Kawał fajnego artykułu!

Rzecz może się komuś przyda, ale jak dla mnie to dużo bardziej pouczające by było, gdyby opatrzeć większą ikością komentarzy. Coś takiego to najwyżej na FAQ czy gotowiec, nie Artykuł.