I'm trying to make a click and drag selection system in c++ and SDL2 like the kind used in real time strategy games. You click with the mouse and drag it over what you want to select. How can I go about making it?
Edit: I know how to handle the mouse inputs. I have used a Rect structure to track the size of the selection zone. However while the zone draws correctly, the objects inside the zone don't react at all. However individually clicking on them works fine every time.
I guess my question is what is the best way to check for a group selection vs individual object selection?
You have to check what's inside the rect you are gonna drag (the test depends on the shapes that are included) and the drag those shapes aswell
I can describe how i have implemented this.
In the event handling routine, do something like the code below. I think the method names explain quite well what's happening and how i'm thinking (this is copied from my hobby-hack-RTS-engine which is based on SDL2):
case SDL_MOUSEBUTTONDOWN:
{
// Calculate index, x and y for the tile that was clicked in the map.
int iClick = m_Map.getTileIndex(event.button.x, event.button.y);
if(iClick >= 0)
{
int xClick = m_Map.getTileX(iClick);
int yClick = m_Map.getTileY(iClick);
if((int)event.button.button == 1)
{
// Unmark all MO..
for(std::list<MGMovingObject>::iterator it = m_MO.begin(); it != m_MO.end(); it++)
{
it->unMark();
}
activateFraming(event.button.x, event.button.y);
}
else
{
...
}
}
break;
}
case SDL_MOUSEBUTTONUP:
{
if((int)event.button.button == 1)
{
int endClickX = m_Map.getTileX(m_Map.getTileIndex(getFrameEndX(), getFrameEndY()));
int endClickY = m_Map.getTileY(m_Map.getTileIndex(getFrameEndX(), getFrameEndY()));
int startClickX = m_Map.getTileX(m_Map.getTileIndex(getFrameStartX(), getFrameStartY()));
int startClickY = m_Map.getTileY(m_Map.getTileIndex(getFrameStartX(), getFrameStartY()));
if(endClickX > 0 && endClickY > 0 && startClickX > 0 && startClickY > 0)
{
for(int x = std::min(startClickX, endClickX); x <= std::max(startClickX, endClickX); x++)
{
for(int y = std::min(startClickY, endClickY); y <= std::max(startClickY, endClickY); y++)
{
for(std::list<MGMovingObject>::iterator it = m_MO.begin(); it != m_MO.end(); it++)
{
if(it->getTileX() == x && it->getTileY() == y)
{
it->mark();
}
}
}
}
}
deactivateFraming();
}
else
{
...
}
break;
}
My selectable objects are stored as std::list<MGMovingObject> in m_MO.
The idea is that i save tile coordinates of the start of the selection frame and the end of the selection frame. I then iterate over the selectable objects and detect the ones inside the selection frame. I select these (mark()) and when i iterate over the objsects at a later stage, say during rendering, i can read out if they are selected (isMarked()).
If you want to steal code or ideas, here is the actual source file i copied it from: https://github.com/qmargyl/mgframework/blob/master/src/mgframework/mgframework.cpp
Related
I am trying to make a program where you are allowed to select between an option of shapes, and then drawing it. To allow for multiple shapes I created a vector of a class which creates shapes (Shapes are set up with the chosen function). My problem is the mouse click is too long, so it assigns it to everything in the vector, so you can't create a new shape. Is there a problem in my logic, or is there a problem in the code?
Here is my attempt:
for (auto& it : onCanvas) {
if (Mouse::isButtonPressed(Mouse::Left)) {
if (mousepointer.getGlobalBounds().intersects(circleOption.getGlobalBounds())) {
it.chosen(circles);
}
if (mousepointer.getGlobalBounds().intersects(rectOption.getGlobalBounds())) {
it.chosen(rectangle);
}
if (mousepointer.getGlobalBounds().intersects(triOption.getGlobalBounds())) {
it.chosen(triangles);
}
if (mousepointer.getGlobalBounds().intersects(it.shape.getGlobalBounds()) || it.dragging) {
it.shape.setPosition(mousepointer.getPosition());
it.dragging = true;
}
}
if (!Mouse::isButtonPressed) {
it.dragging = false;
}
win.draw(it.shape);
}
Your source-code is a bit incomplete (what is onCanvas and mousepointer). But I guess the problem is that this snippet is called multiple times while your mouse is clicked. To avoid that you can do two thing.
In the first solution you use events, so you only add shapes when the state of the mousebutton changes (you can additionally listen to the MouseButtonReleased to simulate a full click):
if (event.type == sf::Event::MouseButtonPressed)
{
if (event.mouseButton.button == sf::Mouse::Left)
{
// Hit Detection
}
}
or second solution you remember the last state of the button (probably do the mouse check once outside of the for loop):
bool mouse_was_up = true;
if (mouse_was_up && Mouse::isButtonPressed(Mouse::Left)) {
mouse_was_up = false;
for (auto& it : onCanvas) {
// Hit Detection
}
}
else if (!Mouse::isButtonPressed(Mouse::Left))
mouse_was_up = true;
I would rather stick to the first solution because when your click is too short and your gameloop is in another part of the game logic, you can miss the click.
I use C++ Builder 2009 and have implemented a Listview virtually.
Recently I learned of an issue that (so far) only happens for 3 people out of thousands. (Obviously there must be more instances, but they remain unreported so far).
What happens is that when the user hovers over the items in the ListView control, from bottom to top, the items in the list change.
Since the control is implemented virtually the software provides the information via an event and for 'a' reason the software gets the wrong index to work with when hovering from bottom to top.
See this little video to understand the behaviour better:
https://www.youtube.com/watch?v=jNZkaBH24PY
I have been searching for a reason, on and off, for some time, hampered by the fact that I cannot repeat this problem myself, so I needed to send special builds to 1 of the 3 willing to help. And I was finally able to pinpoint it to the use of Overlay icons !
I assign an icon to the items in the list by asking Windows what icon is relevant via SHGetFileInfo
The event:
void __fastcall TFinder::ListViewData(TObject *Sender, TListItem *Item)
{
DListView->OnData(Item, DirInListView->Object(Item->Index)) ;
}
Code called by the event:
HANDLE ObjectDisplayInListView::OnData (TListItem *ListItem, DataObject *Object)
{
ListItem->Data = (void*) Object ;
ListItem->Caption = String(Object->DisplayName().c_str()) ;
if (!SystemIconsSet)
{
ListItem->ImageIndex = Object->Icon() ;
ListItem->OverlayIndex = Object->IconOverlay() ;
}
else
{
int Icon = SystemIcon(Object) ;
ListItem->ImageIndex = (Icon & 0x00FFFFFF) ;
ListItem->OverlayIndex = (Icon >> 24) - 1 ;
}
ListItem->Cut = Object->IconGreyed() ;
ListItem->StateIndex = Object->IconState() ;
return (HANDLE) ListItem ;
}
// The SystemIcon() routine:
int ObjectDisplayInListView::SystemIcon (DataObject *Object)
{
// SHFILEINFO info ; The class declared one will do !
info.hIcon = NULL ; // Just making sure
DWORD Res = SHGetFileInfoW( Object->DisplayName().c_str(),
FILE_ATTRIBUTE_NORMAL,
&info,
sizeof(SHFILEINFO) ,
SHGFI_ICON |
SHGFI_USEFILEATTRIBUTES |
SHGFI_OVERLAYINDEX
) ;
DestroyIcon(info.hIcon) ;
return info.iIcon ;
}
The subclass code (which can be removed completely and the problem still exists !!)
void __fastcall TFinder::LVNewWindowProc(Messages::TMessage &Msg)
{
if( LVOldWindowProc ) LVOldWindowProc( Msg );
if (Msg.Msg == WM_NOTIFY) // Sort arrows
{
// Not relevant for this Q but added since it is in my code
switch(((LPNMHDR)Msg.LParam)->code)
{
case HDN_ENDTRACKA:
case HDN_ENDTRACKW:
case HDN_ITEMCHANGEDA:
case HDN_ITEMCHANGEDW:
{
if(((LPNMHDR)Msg.LParam)->hwndFrom == ListView_GetHeader(ListView->Handle))
{
if (ObjInListView && dynamic_cast<FileDirObject*>(ObjInListView) && ListView->Items->Count)
{
SetSortArrow(SortingTypeToDesignColumn(UISortingType), UISortingDirection) ; // Show the arrow based on what sorting was used
}
}
}
break;
}
}
else if (Msg.Msg == CN_NOTIFY && Platform > OS_PLATF_WINXP)
{
if (reinterpret_cast<LPNMHDR>(Msg.LParam)->code == LVN_GETDISPINFOW)
{
LV_ITEM &item = reinterpret_cast<LV_DISPINFO*>(Msg.LParam)->item ;
int OverlayIndex = -1 ;
TListItem *ListItem = ListView->Items->Item[item.iItem] ;
if (ListItem) OverlayIndex = ListItem->OverlayIndex ;
if (OverlayIndex >= 0)
{
item.mask |= LVIF_STATE ;
item.state |= INDEXTOOVERLAYMASK(OverlayIndex + 1) ;
item.stateMask |= LVIS_OVERLAYMASK ;
}
}
}
}
I implemented this based on an older question, asked here as well: Can't get Windows Overlay icons to work in TListView
To be more precise, this function has been working great for me for over 10 years, but about a year ago I added SHGFI_OVERLAYINDEX as well
The issue, as explained and as can be seen in the video, disappears entirely when I simply and only remove SHGFI_OVERLAYINDEX from the function call.
For whatever reason also asking for the overlay icon, causes the weird behaviour.
Even when I completely disable the CN_NOTIFY message subclassing that goes along with this functionality (see: Can't get Windows Overlay icons to work in TListView ) but call SHGetFileInfo() with SHGFI_OVERLAYINDEX, the problem happens !
Does anybody have an idea what this could be ?
A solution could be to not ask for the overlay icon on these problematic systems, but then I need to find a way to actually detect that the problem is going to happen (or is happening) somehow.
I am trying to make my basic loading screen transition over to game level screen. So what i wanted to do is, once the loading screen is active (or has appeared onscreen), I want at this point to start loading my game state. What it is doing at the moment is loading everything at the start, and this does take a while.
So currently my project starts off with a main menu. Then when i press enter, its starts the loading screen. I have my manual state change using keypresses like so:
void Game::update()
{
static bool enterPreviouslyPressed = false;
static bool escapePreviousPressed = false;
const Uint8 *keys = SDL_GetKeyboardState(NULL);
if (keys[::SDL_SCANCODE_ESCAPE] && !escapePreviousPressed && typeid(*fsm->getState()) == typeid(GameState))
{
fsm->setState(menuState);
}
else if (keys[::SDL_SCANCODE_RETURN] && !enterPreviouslyPressed && typeid(*fsm->getState()) == typeid(MainMenuState))
{
fsm->setState(loadingState);
}
else if ((keys[::SDL_SCANCODE_RETURN] && !enterPreviouslyPressed) && typeid(*fsm->getState()) == typeid(LoadScreenState))
{
fsm->setState(gameState);
}
else if (keys[::SDL_SCANCODE_ESCAPE] && !escapePreviousPressed && typeid(*fsm->getState()) == typeid(MainMenuState))
{
exit(0);
}
enterPreviouslyPressed = keys[::SDL_SCANCODE_RETURN] != 0;
escapePreviousPressed = keys[::SDL_SCANCODE_ESCAPE] != 0;
fsm->update();
}
I did this to initially does this so i could change states manually to check that everything works. I was wondering if there was an easy(ish) way, like boolean flags for example or another simpler way to do this. I wasn't able find any tutorials online so wondering if someone knows the best solution as to how to do this. I did see a question on here, kindda similar but I wasn't sure if it answered my question as the person did this in threads which I am not familiar with how to implement. Apologies if I dont seem to have the logic correct - so please advise otherwise.
Looks fairly standard, except I would simplify it by keeping two keyboard state variables declared as class variables, like:
const Uint8 *curKeys = SDL_GetKeyboardState(NULL), *prevKeys;
// ...
void Game::update() {
prevKeys = curKeys;
curKeys = = SDL_GetKeyboardState(NULL);
//and so then compare curKeys to prevkeys
//and ditch the booleans
// ...
}
I have 3 0f 8 sprites on screen that I would like the reader to hit in order:
touching the egges first, then the sugar and finally the lemon (if done correctly a gold cup appears)
EggsSprite
SugarSprite
LemonSprite
the functions I have work but i would like to know if there an easier way to maintain and extend to other sprites onscreen (I have 8 sprites and hope to produce different "recipies")
ccTouchesBegan I detect touches on the sprites
if(CGRectContainsPoint(EggsSprite.boundingBox, location))
{
EggsSprite_is_hit = YES;
NSLog(#"EggsSprite_is_hit = YES");
}
if(CGRectContainsPoint(SugarSprite.boundingBox, location))
{
SugarSprite_is_hit = YES;
NSLog(#"RunCheckSugar");
[self CheckSugar];
}
if(CGRectContainsPoint(LemonSprite.boundingBox, location))
{
NSLog(#"RunCheckLemon");
LemonSprite_is_hit = YES;
[self CheckLemon];
}
this runs
-(void)CheckLemon
{
NSLog(#"LemonSprite is hit");
if(MeringueUnlocked == YES)
{
NSLog(#"You made a merangue");
Award.opacity=255;
}
else if(MeringueUnlocked == NO)
{
NSLog(#"Incorrect order");
EggsSprite_is_hit = NO;
LemonSprite_is_hit = NO;
SugarSprite_is_hit = NO;
MeringueUnlocked = NO;
}
}
-(void)CheckSugar
{
if(SugarSprite_is_hit == YES && EggsSprite_is_hit== YES)
{
NSLog(#"SugarSprite is hit");
NSLog(#"And Egg Sprite is hit");
SugarSprite_is_hit = NO;
MeringueUnlocked = YES;
}
else if(SugarSprite_is_hit == YES && EggsSprite_is_hit== YES)
{
NSLog(#"SugarSprite not hit ressetting all");
EggsSprite_is_hit = NO;
LemonSprite_is_hit = NO;
SugarSprite_is_hit = NO;
MeringueUnlocked = NO;
}
}
It seems to work ok, but it would be horrible to extend, I cant seem to find any examples on touching sprites in order so any psudo code ideas will be welcome :) as its the approach that Im more stuck on since I'm new to coding.
with thanks :)
N
When your sprites are created, assign to their 'tag' properties order in which they should be hit:
EggsSprite.tag = 1;
SugarSprite.tag = 2;
LemonSprite.tag = 3;
Then make an instance variable to store index of last sprite hit:
int _lastSpriteHitIndex;
and for index of last sprite in your sequence:
int _finalSpriteIndex;
Set its value somewhere in init (or whatever method you use to create your layer):
_finalSpriteIndex = 3;
Then in your touch handler:
// find sprite which was touched
// compare its tag with tag of last touched sprite
if (_lastSpriteHitIndex == touchedSprite.tag - 1)
{
// if it is next sprite in our planned order of sprites,
// store its tag as _lastSpriteHitIndex
_lastSpriteHitIndex = touchedSprite.tag;
}
else
{
// if it's wrong sprite, reset sequence
_lastSpriteHitIndex = 0;
}
if (_lastSpriteHitIndex == _finalSpriteIndex)
{
// Congrats! You hit sprites in correct order!
}
Basically it's a finite-state machine in which hitting sprites in correct order advances machine to next state, and hitting wrong sprite resets machine to initial state. _lastSpriteHitIndex repesents current state, _finalSpriteIndex represents final state.
If you don't want to reset to initial state on wrong sprite hit, just remove else clause - without it machine will simply not advance when hitting wrong sprite.
I'm writing an application that has a QTreeWidget that is populated by parsing an XML file containing the tree levels. If I select a top level checkbox, I need all of the sub-level checkboxes to be checked also.
I already have the XML parser working and populating the QTreeWidget with QTreeWidgetItems that have checkboxes but they can only be individually checked.
To do this, keep the code you have to generate the tree with your XML. Then connect to the itemChanged() signal and update the check states in a slot. It should look something like:
connect(treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*, int)),
this, SLOT(updateChecks(QTreeWidgetItem*, int)));
void ClassName::updateChecks(QTreewidgetItem* item, int column)
{
// Checkstate is stored on column 0
if(column != 0)
return;
recursiveChecks(item);
}
void ClassName::recursiveChecks(QTreeWidgetItem* parent)
{
Qt::CheckState checkState = parent->checkState(0);
for(int i = 0; i < parent->childCount(); ++i)
{
parent->child(i)->setCheckState(0, checkState);
recursiveChecks(parent->child(i));
}
}
A few notes to consider:
You may be tempted to use the itemClicked signal instead of the itemChanged signal. This usually works, but will not work when the user uses the arrow keys and the space bar to change checkstates.
You will need to think about what will happen when you uncheck one of the sub-items that have been checked by clicking on its parent. Usually this means you need to make all ancestors either uncheck or partially-checked. This may not be true for your case.
itemUpdated will also get fired for other changes to the item (like the text changing), so be aware that this is not a super efficient way of doing this.
I just worked on this a little and got nice results based on Rick's answer. Maybe it can help other out there.
It updates the state of parents and children with a tristate status for parents only (checked, unchecked, partially-checked).
void ClassName::updateChecks(QTreeWidgetItem *item, int column)
{
bool diff = false;
if(column != 0 && column!=-1)
return;
if(item->childCount()!=0 && item->checkState(0)!=Qt::PartiallyChecked && column!=-1){
Qt::CheckState checkState = item->checkState(0);
for (int i = 0; i < item->childCount(); ++i) {
item->child(i)->setCheckState(0, checkState);
}
} else if (item->childCount()==0 || column==-1) {
if(item->parent()==0)
return;
for (int j = 0; j < item->parent()->childCount(); ++j) {
if(j != item->parent()->indexOfChild(item) && item->checkState(0)!=item->parent()->child(j)->checkState(0)){
diff = true;
}
}
if(diff)
item->parent()->setCheckState(0,Qt::PartiallyChecked);
else
item->parent()->setCheckState(0,item->checkState(0));
if(item->parent()!=0)
updateChecks(item->parent(),-1);
}
}
Doesn't need recursiveChecks() anymore. Connect between the treeWidget and updateChecks still active.
This appears still quite high in search engines, and is outdated.
Just set the flag Qt::ItemIsAutoTristate on your top-level item.