Reputation: 2056
I am starting to think about how I am structuring my menu system and have a few questions that maybe some with experience can offer me insights.
There are a few questions that I have and I'll relate them the best I can to my current code and give my code when necessary.
I have a class system:
KObect->KControl->(KMenu,KPanel,KButton) KControl also inherits from KEvent
I created a menu and I can move them around by clicking and dragging the menu item. When I made two menus, that's when I realized there is something that I haven't thought about.
I want this menu system to be a library that I can reuse in other code. It would be nice if I could create the menu objects and call one function, like KObject::Render()
and have that handle everything for the object such as events, on loop updates, and render order. Currently, the way I have it set up would be the application would be such:
KMenu Menu1;
KMenu Menu2;
//Some code like size/hight/color set up if default is unwanted
while(Running) {
//SDL Poll events code
Menu1.Render();
Menu2.Render();
Menu1.Loop();
Menu2.Loop();
}
The issue is that if I have it set up this way, Main1 will also render before Menu2. The solution to this would probably make it more obvious how to handle events based on which object is actually visible (as it stands right now if my two menus over lap, both move when I click the top one).
The next question is I have is regarding function behavior. Right now I am doing a lot of openGL primitives to construct the menu. Let's say I have a button, but I"m not sure if the user of the library wants to put a texture, a string (I'll handle font internally with SDL), or a openGL primitive, I'd have to have radically altered render functions for the button. I'm not sure the best way to handle such a thing.
Finally, when I handle events such as clicking buttons, I need a way to let the user define the function behavior. One consideration is to declare a function in the button class that takes in a function pointer. Then, when the button is clicked, it just calls the function. When I looked into this, I found I couldn't figure out how to create this so that the user could specify arguments and values for the function. (i.e. I had to declare a function pointer as void (*foo)(int)
or something like that to declare a pointer to a function that would need an int for input and returned void. It seemed rather limiting to only allow one function structure (return type/arguments) for a general button.
The only other thing I've found online didn't explain much but it talked about 'call back functions'. I couldn't figure out how to declare these, but I saw someone do something like
int FooFunc(int Foo);
int FooFunc(int Foo) {
Foo += Foo
return Foo;
}
//Magic they didn't explain
RegisterCallback( FooFunc );
I'm confused because they don't use & sign, just the function identifier without (). I also didn't see the declaration so I can't figure out if they declared it as
void RegisterCallback( * ); //What in good name could * be?
I bring both questions up now because if I could figure this out, then I could probably solve the other question about multiple rendering functions be defining several and then having a pointer I just switch back and forth depending on the user's initialization.
If you guys want to see any code specifically let me know. I have these classes (KApp,KButton,KControl,KEvent,KMenu,KMenuSystem,KObject,KPanel)
class KEvent
completely virtual, captures events and forwards them to inheriting classes
class KApp : public KEvent
This would the user defined program that makes use of my menu
class KMenuSystem
An include file to just include all the derived menu items.
class KObject
Base Class. Very minimal
class KControl : public KObject
This class is to handle anything visual that the user may see or interact with.
class KMenu : public KControl
This is a menu item. This needs to declared before anything else (although I don't think I have it set up that way yet) I'll post a picture of a menu item below. KMenu contains 3 KButtons and 2 KPanels right now.
class KPanel : public KControl
Panels would allow grouping of items for ease of hiding, moving, arragnement, radio buttons, etc.
class KButton : public KControl
I think this is obvious
My Menu, I was moving the one in the back http://imageshack.us/a/img515/6958/a56.png
I was moving the one in the back and it doesn't render on top because in my render loop it's second. I am not sure how to handle this. Any advice, I would appreciate it!
Upvotes: 0
Views: 1993
Reputation: 2056
A variation of the comments and answers have fixed the issues. I will select the answer above for his help leading me in the direction and the fact that lambda function is a perfect answer for my second question.
Instead of making a vector and a new class. I just created a few pointers and functions in my base class. Instead of users declaring and calling individual render functions for each item they create, they will add them to a base object and call the objects render function. Events will manipulate the base class pointer system to alter render order. Here is how I did it.
base class stripped of other vars/fnc
class KObject {
protected:
static KObject* _Top;
static KObject* _Bottom;
KObject* _Down;
KObject* _Up;
public:
virtual void Up(KObject* up);
virtual void Down(KObject* down);
virtual void Top(KObject* top);
virtual void Bottom(KObject* bottom);
virtual void BringToFront(KObject* obj);
virtual void SendToBack(KObject* obj);
virtual void Promote(KObject* obj);
virtual void Demote(KObject* obj);
virtual void Add(KObject* obj);
virtual void Del(KObject* obj);
virtual void Render();
virtual void Render(int x, int y)
};
The implementation of the pointer system is here. I haven't tested all the functions but bring to front and add works :) I got pointer violation with my first time around so the others might be a little faulty. I'm not sure if I even need the others really.
#include "KObject.h"
KObject* KObject::_Top = NULL;
KObject* KObject::_Bottom = NULL;
void KObject::Render() {
Render(0,0);
}
void KObject::Render(int x, int y) {
if(_Bottom != NULL) {
KObject* Current = _Bottom;
while( Current != NULL ) {
Current->Render(x,y);
Current = Current->Up();
}
}
}
KObject* KObject::Up() {
return _Up;
}
KObject* KObject::Down() {
return _Down;
}
KObject* KObject::Top() {
return _Top;
}
KObject* KObject::Bottom() {
return _Bottom;
}
void KObject::Up(KObject* up) {
_Up = up;
}
void KObject::Down(KObject* down) {
_Down = down;
}
void KObject::Top(KObject* top) {
_Top = top;
}
void KObject::Bottom(KObject* bottom) {
_Bottom = bottom;
}
void KObject::BringToFront(KObject* obj) {
if( obj != NULL && _Top != obj ) {
if( _Bottom == obj ) _Bottom = obj->_Up;
else obj->_Down->_Up = obj->_Up;
obj->_Up->_Down = obj->_Down;
_Top->_Up = obj;
obj->_Down = _Top;
obj->_Up = NULL;
_Top = obj;
}
}
void KObject::SendToBack(KObject* obj) {
if( obj->_Down != NULL ) {
obj->_Down->Up(obj->_Up);
obj->_Up->Down(obj->_Down);
obj->_Up = _Bottom;
obj->_Down = NULL;
_Bottom = obj;
}
}
void KObject::Promote(KObject* obj) {
if( obj->_Up != NULL ) {
KObject* Temp;
Temp = obj->_Up;
obj->_Up = Temp->Up();
Temp->Up(obj);
Temp->Down( obj->_Down);
obj->_Down = Temp;
}
}
void KObject::Demote(KObject* obj) {
if( obj->_Down != NULL ) {
KObject* Temp;
Temp = obj->_Down;
obj->_Down = Temp->Down();
Temp->Up(obj->_Up);
Temp->Down(obj);
obj->_Up = Temp;
}
}
void KObject::Add(KObject* obj) {
if( obj != NULL ) {
if( _Top != NULL ) {
obj->Down(_Top);
_Top->Up(obj);
_Top = obj;
}else{
_Top = obj;
_Bottom = obj;
}
}
}
void KObject::Del(KObject* obj) {
if( obj != NULL ) {
if( _Top == obj ) {
_Top = obj->Down();
_Top->Up(NULL);
}else if ( _Bottom == obj ) {
_Bottom = obj->Up();
_Bottom->Down(NULL);
}else {
obj->Up()->Down(obj->Down());
obj->Down()->Up(obj->Up());
}
}
}
In my menu.cpp file, when I handle the click and drag event for instance:
void KMenu::OnLButtonDown(int mx, int my) {
if( my < _Y+_MenuBarHeight ) {
tX = mx;
tY = my;
Floating = true;
if( _Top != this ) {
BringToFront(this);
}
}
focus = this;
}
void KMenu::OnLoop() {
if(Floating) {
int x;
int y;
if(SDL_GetMouseState(&x,&y)&SDL_BUTTON(1)) {
_X = _X - ( tX - x );
_Y = _Y - ( tY - y );
tX = x;
tY = y;
}else{
Floating = false;
}
}
if(focus != NULL) {
focus->OnFocus();
}
}
In the application I have
#include "KMenuSystem.h"
//Inititilzations
KMenu Menu1;
KMenu Menu2;
KObject MenuGroup;
MenuGroup.Add(&Menu1);
MenuGroup.Add(&Menu2);
//Program Render Loop
MenuGroup.Render();
It allows me to move both menus and adjust render order completely behind the scenes to the calling program.
Menu1 http://imageshack.us/a/img854/6450/ce6.png
Menu2 http://imageshack.us/a/img198/4926/pzjb.png
Upvotes: 0
Reputation: 5684
I have done something similar to this not long ago.
I would have a list of Events and their corresponding callbacks functions.
enum Event {
MOUSE_CLICK = 0,
NUM_EVENTS
};
std::array< std::list<std::function<void()>, NUM_EVENTS > events_callbacks;
create a EventListener
class
class EventListener {
public:
virtual ~EventListener() {
// stop listening all events
}
void listen_event( Event e, const std::function<void()> & callback ) {
// insert callback into corresponding event slot in event_callbacks
}
void stop_listen_event( Event e ) {
// remove callback from corresponding event slot in event_callbacks
private:
// this keep track of which event is being listen and allow removal through the iterator
std::list< std::pair<Event, std::list<std::function<void()>::iterator> > events;
};
now you can have a UIComponent
class which all your UI object derived from.
class UIComponent : public Eventlistener {
public:
virtual void render() const = 0;
};
now all your UI object can listen to event using the listen_event
method.
all you menu, button and border can be put into a UIDecorator
class, aka decorator
pattern.
class UIDecorator : public UIComponent {
public:
UIDecorator( UIComponent * component ) {
component_ = component;
}
private:
UIComponent * component_;
}
creating a window with border, menu, button become something like this
UIWindow window;
UIBorder b( &window );
UIMenu m( &b );
UIButton bu( &m );
then insert it into an array of UIComponents on screen.
std::vector<UIComponent *> components;
components.push_back( &bu );
to solve the render order problem, you can take care of it in the event callback function.
bu.listen_event( MOUSE_CLICK, []{
// do nothing if mouse click position is outside the region
// test if there is any component in front of this component
// decide to bring this component to the front or not.
});
to render simply iterate through the components vector, and call their render method.
this is a rough outline of what I would do to approach a problem like this, hope it make sense.
Upvotes: 1