TSharedRef<SWidget> UJavascriptTileView::RebuildWidget()
{
	MyTileView = SNew(STileView< UObject* >)
		.SelectionMode(SelectionMode)
		.ListItemsSource(&Items)
		.ItemHeight(ItemHeight)
		.OnGenerateTile(BIND_UOBJECT_DELEGATE(STileView< UObject* >::FOnGenerateRow, HandleOnGenerateTile))
		.OnSelectionChanged_Lambda([this](UObject* Object, ESelectInfo::Type SelectInfo){
			OnSelectionChanged(Object, SelectInfo);
		})
		.OnMouseButtonDoubleClick_Lambda([this](UObject* Object){
			OnDoubleClick(Object);
		})
		//.OnContextMenuOpening(this, &SSocketManager::OnContextMenuOpening)
		//.OnItemScrolledIntoView(this, &SSocketManager::OnItemScrolledIntoView)
		//	.HeaderRow
		//	(
		//		SNew(SHeaderRow)
		//		.Visibility(EVisibility::Collapsed)
		//		+ SHeaderRow::Column(TEXT("Socket"))
		//	);
		;

	return BuildDesignTimeWidget(MyTileView.ToSharedRef());
}
Esempio n. 2
0
void World::OnLButtonDown(int nX, int nY)
{
    // Need to implement double click detection a different way for Linux
    int nDblClickTime = 500;
#ifdef __PRAXIS_WINDOWS__
    nDblClickTime = ::GetDoubleClickTime();
#endif

    int nCurrClickTime = glutGet(GLUT_ELAPSED_TIME);
    int nTimeBwClicks = nCurrClickTime - g_nLastClickTime;
    g_nLastClickTime = nCurrClickTime;
    if(nTimeBwClicks < nDblClickTime)
    {
        OnDoubleClick();
        return;
    }

    stringstream ss;
    ss << "LMBDown(" << nX << "," << nY << ")";
    luaCall(ss.str());

    m_bLeftMouseDown = true;
	m_bLeftMouseWentDown = true;

	if(m_bMouseMovesCamera)
        m_bUpdatePickPosition = false;

    // Map the mouse position to a character position in the editor, then send a callback with this information.

}
Esempio n. 3
0
int GWinControl::HandleNotifyMessage(u32 message, NMHDR* pNotifyData)
{
	switch ( message )
	{
		case NM_CLICK:		if ( OnButtonDown( GMouse::Left, int2(-1,-1) ) )	return 0;	break;
		case NM_DBLCLK:		if ( OnDoubleClick( GMouse::Left, int2(-1,-1) ) )	return 0;	break;
		case NM_RCLICK:		if ( OnButtonDown( GMouse::Right, int2(-1,-1) ) )	return 0;	break;
		case NM_RDBLCLK:	if ( OnDoubleClick( GMouse::Right, int2(-1,-1) ) )	return 0;	break;

		default:
			//GDebug::Print("Unhandled Notify Message 0x%04x\n",message);		
			break;
/*
		GWinTreeView* pTree = (GWinTreeView*)pControl;
		case TVN_BEGINLABELEDIT:
		{
			//	about to edit a label, return 0 to allow change, 1 to reject editing
			NMTVDISPINFO* pEditInfo = (NMTVDISPINFO*)pNotifyData;
			GWinTreeItem* pItem = pTree->FindItem( pEditInfo->item.hItem );
			Bool AllowEdit = pTree->AllowItemEdit( pItem, pEditInfo );
			return AllowEdit ? 0 : 1;
		}
		break;

		case TVN_ENDLABELEDIT:
		{
			//	editing label has finished. return FALSE to reject change
			NMTVDISPINFO* pEditInfo = (NMTVDISPINFO*)pNotifyData;

			//	if we have a null string, it was cancelled anyway
			if ( pEditInfo->item.pszText == NULL )
				return FALSE;

			GWinTreeItem* pItem = pTree->FindItem( pEditInfo->item.hItem );
			return pTree->FinishItemEdit( pItem, pEditInfo );
		}
		break;

		case TVN_SELCHANGED:
			pTree->Selected( (GWinTreeView*)pControl, (NMTREEVIEW*)pNotifyData );
			break;
*/
	};

	return 0;
}
Esempio n. 4
0
void Button::clickTimedOut(){
	if(!m_IsPressed){
		switch(m_HitCount){
			case 1:
				if(OnClick != 0){
					OnClick();
				}
				break;
			case 2:
				if(OnDoubleClick != 0){
					OnDoubleClick();
				}
				break;
		}
		reset();
	}
}
Esempio n. 5
0
void GenericListControl::HandleNotify(LPARAM lParam)
{
	LPNMHDR mhdr = (LPNMHDR) lParam;

	if (mhdr->code == NM_DBLCLK)
	{
		LPNMITEMACTIVATE item = (LPNMITEMACTIVATE) lParam;
		if ((item->iItem != -1 && item->iItem < GetRowCount()) || sendInvalidRows)
			OnDoubleClick(item->iItem,item->iSubItem);
		return;
	}

	if (mhdr->code == NM_RCLICK)
	{
		const LPNMITEMACTIVATE item = (LPNMITEMACTIVATE)lParam;
		if ((item->iItem != -1 && item->iItem < GetRowCount()) || sendInvalidRows)
			OnRightClick(item->iItem,item->iSubItem,item->ptAction);
		return;
	}

	if (mhdr->code == LVN_GETDISPINFO)
	{
		NMLVDISPINFO* dispInfo = (NMLVDISPINFO*)lParam;

		stringBuffer[0] = 0;
		GetColumnText(stringBuffer,dispInfo->item.iItem,dispInfo->item.iSubItem);
		
		if (stringBuffer[0] == 0)
			wcscat(stringBuffer,L"Invalid");

		dispInfo->item.pszText = stringBuffer;
		return;
	}
	 
	// handle checkboxes
	if (mhdr->code == LVN_ITEMCHANGED && updating == false)
	{
		NMLISTVIEW* item = (NMLISTVIEW*) lParam;
		if (item->iItem != -1 && (item->uChanged & LVIF_STATE) != 0)
		{
			// image is 1 if unchcked, 2 if checked
			int oldImage = (item->uOldState & LVIS_STATEIMAGEMASK) >> 12;
			int newImage = (item->uNewState & LVIS_STATEIMAGEMASK) >> 12;
			if (oldImage != newImage)
				OnToggle(item->iItem,newImage == 2);
		}
Esempio n. 6
0
LRESULT APIENTRY GraphLegendWndProc (HWND hWnd,
                                     WORD wMsg,
                                     WPARAM wParam,
                                     LONG lParam)
   {  // GraphLegendWndProc
   BOOL           bCallDefProc ;
   LRESULT        lReturnValue ;


   bCallDefProc = FALSE ;
   lReturnValue = 0L ;

   switch (wMsg)
      {  // switch
      case WM_DELETEITEM:
         break ;

      case WM_COMMAND:
         switch (HIWORD (wParam))
            {  // switch
            case LBN_DBLCLK:
               OnDoubleClick (hWnd) ;
               break ;

            case LBN_SELCHANGE:
               OnSelectionChanged (hWnd) ;
               break ;

            default:
               break ;
            }  // switch
         break ;

      case WM_CREATE:
         OnLegendCreate (hWnd, (LPCREATESTRUCT) lParam) ;
         break ;

      case WM_DESTROY:
         OnDestroy (hWnd) ;
         break ;

      case WM_DRAWITEM:
         OnDrawLegendItem (hWnd, (LPDRAWITEMSTRUCT) lParam) ;
         break ;

      case WM_MEASUREITEM:
         OnMeasureItem (hWnd, (LPMEASUREITEMSTRUCT) lParam) ;
         break ;

      case WM_PAINT:
         OnPaint (hWnd) ;
         break ;

      case WM_SIZE:
         OnSize (hWnd, LOWORD (lParam), HIWORD (lParam)) ;
         break ;

      case WM_SETFOCUS:
         OnSetFocus (hWnd) ;
         break ;

      default:
         bCallDefProc = TRUE ;
      }  // switch


   if (bCallDefProc)
      lReturnValue = DefWindowProc (hWnd, wMsg, wParam, lParam) ;

   return (lReturnValue);
   }  // GraphLegendWndProc
Esempio n. 7
0
  LRESULT cOpenGLControl::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  {
     switch (uMsg) {
        case WM_SETFOCUS:
        case WM_KILLFOCUS:
        case WM_ENABLE:
          Update();
          break;

        case WM_SIZING:
        case WM_SIZE:
          UpdateSize();
          OnSize();
          Update();
          break;

        case WM_PAINT:
          Paint();
          return FALSE;

        case WM_ERASEBKGND:
          return TRUE;

        case WM_MOUSEMOVE: {
           const int x = int((short)LOWORD(lParam));
           const int y = int((short)HIWORD(lParam));

           if (!bMouseIn) {
              TRACKMOUSEEVENT tme;
              tme.cbSize = sizeof(TRACKMOUSEEVENT);
              tme.dwFlags = TME_LEAVE | TME_HOVER;
              tme.hwndTrack = hwnd;
              tme.dwHoverTime = HOVER_DEFAULT;
              ::TrackMouseEvent(&tme);
              OnMouseIn();
              bMouseIn = true;
           }

           cKeyModifiers modifiers;
           GetModifiersForMouseEvent(modifiers, wParam);
           OnMouseMove(x, y, modifiers);
        } break;

        case WM_MOUSELEAVE:
           if (bMouseIn) {
              OnMouseOut();
              bMouseIn = false;
           }
           break;

        case WM_MOUSEHOVER:
          if (bMouseIn) {
            const int x = int((short)LOWORD(lParam));
            const int y = int((short)HIWORD(lParam));
            cKeyModifiers modifiers;
            GetModifiersForMouseEvent(modifiers, wParam);
            OnMouseHover(x, y, modifiers);

            // Reset the track mouse event
            TRACKMOUSEEVENT tme;
            tme.cbSize = sizeof(TRACKMOUSEEVENT);
            tme.dwFlags = TME_LEAVE | TME_HOVER;
            tme.hwndTrack = hwnd;
            tme.dwHoverTime = HOVER_DEFAULT;
            ::TrackMouseEvent(&tme);
          }
          break;

        case WM_LBUTTONDOWN: {
           const int x = int((short)LOWORD(lParam));
           const int y = int((short)HIWORD(lParam));
           cKeyModifiers modifiers;
           GetModifiersForMouseEvent(modifiers, wParam);
           OnLButtonDown(x, y, modifiers);
           break;
         }

        case WM_LBUTTONUP: {
           const int x = int((short)LOWORD(lParam));
           const int y = int((short)HIWORD(lParam));
           cKeyModifiers modifiers;
           GetModifiersForMouseEvent(modifiers, wParam);
           OnLButtonUp(x, y, modifiers);
           break;
         }

        case WM_RBUTTONDOWN: {
           const int x = int((short)LOWORD(lParam));
           const int y = int((short)HIWORD(lParam));
           cKeyModifiers modifiers;
           GetModifiersForMouseEvent(modifiers, wParam);
           OnRButtonDown(x, y, modifiers);
           break;
         }

        case WM_RBUTTONUP: {
           const int x = int((short)LOWORD(lParam));
           const int y = int((short)HIWORD(lParam));
           cKeyModifiers modifiers;
           GetModifiersForMouseEvent(modifiers, wParam);
           OnRButtonUp(x, y, modifiers);
           break;
         }

        case WM_MBUTTONDOWN: {
           const int x = int((short)LOWORD(lParam));
           const int y = int((short)HIWORD(lParam));
           cKeyModifiers modifiers;
           GetModifiersForMouseEvent(modifiers, wParam);
           OnMButtonDown(x, y, modifiers);
           break;
         }

        case WM_MBUTTONUP: {
           const int x = int((short)LOWORD(lParam));
           const int y = int((short)HIWORD(lParam));
           cKeyModifiers modifiers;
           GetModifiersForMouseEvent(modifiers, wParam);
           OnMButtonUp(x, y, modifiers);
           break;
         }

        case WM_LBUTTONDBLCLK: {
           const int x = int((short)LOWORD(lParam));
           const int y = int((short)HIWORD(lParam));
           cKeyModifiers modifiers;
           GetModifiersForMouseEvent(modifiers, wParam);
           OnDoubleClick(x, y, modifiers);
           break;
         }

        case WM_MOUSEWHEEL: {
           POINT pt;
           pt.x = LOWORD(lParam);
           pt.y = HIWORD(lParam);
           if (::ScreenToClient(hwnd, &pt) == TRUE) {
             cKeyModifiers modifiers;
             GetModifiersForMouseEvent(modifiers, wParam);
             OnMouseWheel(pt.x, pt.y, short(HIWORD(wParam)), modifiers);
           }
           break;
         }

        case WM_KEYDOWN:
        case WM_SYSKEYDOWN:
           if ((lParam & 0x40000000) == 0) {
              cKeyEvent event;
              event.key = wParam;
              event.modifiers.bControl = ((::GetKeyState(VK_CONTROL) & 0x8000) != 0);
              event.modifiers.bAlt = ((::GetKeyState(VK_MENU) & 0x8000) != 0);
              event.modifiers.bShift = ((::GetKeyState(VK_SHIFT) & 0x8000) != 0);
              OnKeyDown(event);
           }
           break;

        case WM_KEYUP:
        case WM_SYSKEYUP:
           if ((lParam & 0x40000000) == 0) {
              cKeyEvent event;
              event.key = wParam;
              event.modifiers.bControl = ((::GetKeyState(VK_CONTROL) & 0x8000) != 0);
              event.modifiers.bAlt = ((::GetKeyState(VK_MENU) & 0x8000) != 0);
              event.modifiers.bShift = ((::GetKeyState(VK_SHIFT) & 0x8000) != 0);
              OnKeyUp(event);
           }
           break;
     }

     return DefWindowProc(hwnd, uMsg, wParam, lParam);
  }
Esempio n. 8
0
void CModifiedButtonWidget::OnMouseEvent( CMouseEvent* event )
{
	// logger << event->GetButtons() << std::endl;
	switch( event->GetType() )
	{
	case types::mouse_button_down:
		
		if( event->GetButtons() & myClickableButtons ) 
		{
			mySpriteHandler->PlayAnimation( GetSprite(), "mouse_button_down" );
			myClicked = true;
			OnMouseDown( myParam );
		}

		if( myDragable && event->GetButtons() & myDragableButtons )
		{
			// mySpriteHandler->PlayAnimation( GetSprite(), "mouse_on_drag" );
			myDragging = true;
			myDragOffset = types::point( this->GetRect().x, this->GetRect().y ) - event->GetPosition();
			IMouse::AddConstantEventListeners( this );
		}

		if( myDoubleClickable && event->GetButtons() & myDoubleClickableButtons && myDoubleClicking == true )
		{
			if( myDoubleClickTimer.GetTime() < double_click_time )
				OnDoubleClick( myParam );
			myDoubleClicking = false;
		}
		else if( myDoubleClickable && event->GetButtons() & myDoubleClickableButtons && myDoubleClicking == false )
		{
			myDoubleClickTimer.Reset();
			myDoubleClickTimer.Resume();
			myDoubleClicking = true;
		}
		else if ( myDoubleClickable && event->GetButtons() & !myDoubleClickableButtons )
		{
			myDoubleClicking = false;
		}



		break;

	case types::mouse_button_up:
		if( myClicked && event->GetButtons() & myClickableButtons ) 
		{
			mySpriteHandler->PlayAnimation( GetSprite(), "mouse_button_up" );
			OnClick( myParam );

			myClicked = false;
			OnMouseUp( myParam );
		}

		if( myDragable && event->GetButtons() & myDragableButtons )
		{
			// mySpriteHandler->PlayAnimation( GetSprite(), "mouse_on_drag" );
			myDragging = false;
			IMouse::RemoveConstantEventListeners( this );
			OnDragEnd();
		}

		myClicked = false;
		break;

	case types::mouse_move:

		if( myDragging )
		{
			types::point where_to = event->GetPosition() + myDragOffset;

			// Fixed 12-09-2007 by Pete
			//--------------------------------------------------------------------------
			// BUGBUG: This should be the commented line, but the signal-slot 
			// doesn't support three parameters at the current time
			// OnDrag( where_to.x - GetRect().x, where_to.y - GetRect().y, myParam );

			if( config::automatic_dragging )
				MoveBy( types::point( where_to.x - GetRect().x, where_to.y - GetRect().y ) );	

			OnDrag( where_to.x - GetRect().x, where_to.y - GetRect().y, myParam );
		}

		// myDoubleClicking = false;
		break;

	case types::mouse_over:
		{
			const std::string animation = mySelectionOn?"select_mouse_over":"mouse_over";
			mySpriteHandler->PlayAnimation( GetSprite(), animation );
			OnMouseOver( myParam );
		}
		break;

	case types::mouse_out:
		{
			const std::string animation = mySelectionOn?"select_mouse_out":"mouse_out";
			mySpriteHandler->PlayAnimation( GetSprite(), animation );
			myClicked = false;
			myDoubleClicking = false;
			OnMouseOut( myParam );
		}
		break;

	default:
		break;
	}

}
LRESULT MainWindowPageScripts::MainWindowPageProc(UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch(uMsg) {
        case WM_SETFOCUS: {
            CHARRANGE cr = { 0, 0 };
            ::SendMessage(hWndPageItems[REDT_SCRIPTS_ERRORS], EM_EXSETSEL, 0, (LPARAM)&cr);
            ::SetFocus(hWndPageItems[REDT_SCRIPTS_ERRORS]);
            return 0;
		}
        case WM_WINDOWPOSCHANGED: {
            RECT rcMain = { 0, 0, ((WINDOWPOS*)lParam)->cx, ((WINDOWPOS*)lParam)->cy };
            SetSplitterRect(&rcMain);

            return 0;
        }
        case WM_COMMAND:
            switch(LOWORD(wParam)) {
                case BTN_OPEN_SCRIPT_EDITOR: {
                    OpenScriptEditor();
                    return 0;
                }
                case BTN_REFRESH_SCRIPTS:
                    RefreshScripts();
                    return 0;
                case BTN_MOVE_UP:
                    MoveUp();
                    return 0;
                case BTN_MOVE_DOWN:
                    MoveDown();
                    return 0;
                case BTN_RESTART_SCRIPTS:
                    RestartScripts();
                    return 0;
                case IDC_OPEN_IN_EXT_EDITOR:
                    OpenInExternalEditor();
                    return 0;
                case IDC_OPEN_IN_SCRIPT_EDITOR:
                    OpenInScriptEditor();
                    return 0;
                case IDC_DELETE_SCRIPT:
                    DeleteScript();
                    return 0;
            }

            if(RichEditCheckMenuCommands(hWndPageItems[REDT_SCRIPTS_ERRORS], LOWORD(wParam)) == true) {
                return 0;
            }

            break;
        case WM_CONTEXTMENU:
            OnContextMenu((HWND)wParam, lParam);
            break;
        case WM_NOTIFY:
            if(((LPNMHDR)lParam)->hwndFrom == hWndPageItems[LV_SCRIPTS]) {
                if(((LPNMHDR)lParam)->code == LVN_ITEMCHANGED) {
                    OnItemChanged((LPNMLISTVIEW)lParam);
                } else if(((LPNMHDR)lParam)->code == NM_DBLCLK) {
                    if(((LPNMITEMACTIVATE)lParam)->iItem == -1) {
                        break;
                    }

                    OnDoubleClick((LPNMITEMACTIVATE)lParam);

                    return 0;
                }
            } else if(((LPNMHDR)lParam)->hwndFrom == hWndPageItems[REDT_SCRIPTS_ERRORS] && ((LPNMHDR)lParam)->code == EN_LINK) {
                if(((ENLINK *)lParam)->msg == WM_LBUTTONUP) {
                    RichEditOpenLink(pMainWindowPageScripts->hWndPageItems[MainWindowPageScripts::REDT_SCRIPTS_ERRORS], (ENLINK *)lParam);
                    return 1;
                }
            }

            break;
        case WM_DESTROY:
            g_GuiSettingManager->SetInteger(GUISETINT_SCRIPTS_SPLITTER, iPercentagePos);
            g_GuiSettingManager->SetInteger(GUISETINT_SCRIPT_NAMES, (int)::SendMessage(hWndPageItems[LV_SCRIPTS], LVM_GETCOLUMNWIDTH, 0, 0));
            g_GuiSettingManager->SetInteger(GUISETINT_SCRIPT_MEMORY_USAGES, (int)::SendMessage(hWndPageItems[LV_SCRIPTS], LVM_GETCOLUMNWIDTH, 1, 0));

            break;
    }

    if(BasicSplitterProc(uMsg, wParam, lParam) == true) {
    	return 0;
    }

	return ::DefWindowProc(m_hWnd, uMsg, wParam, lParam);
}
Esempio n. 10
0
// Basic window proc. It simply forward interesting messages to the appropriate handlers (OnXXX).
// If child class defines this function, it should pass unhandled messages to CFlexWnd.
LRESULT CFlexWnd::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg)
	{
		case WM_GETFLEXWND:
		{
			if ((LPVOID)lParam == NULL)
				break;

			GETFLEXWNDSTRUCT &gfws = *((FAR GETFLEXWNDSTRUCT *)(LPVOID)lParam);

			switch (gfws.cbSize)
			{
				case sizeof(GETFLEXWNDSTRUCT):
					gfws.bFlexWnd = TRUE;
					gfws.pFlexWnd = this;
					return 0;

				default:
					assert(0);
					break;
			}
			break;
		}

		case WM_CREATE:
		{
			LPCREATESTRUCT lpCreateStruct = (LPCREATESTRUCT)lParam;
			
			LRESULT lr = OnCreate(lpCreateStruct);
			
			if (lr != -1)
				OnInit();

			return lr;
		}

		case WM_INITDIALOG:
		{
			BOOL b = OnInitDialog();
			OnInit();
			return b;
		}

		case WM_TIMER:
			OnTimer((UINT)wParam);
			return 0;

		case WM_ERASEBKGND:
			return OnEraseBkgnd((HDC)wParam);

		case WM_PAINT:
		{
			// Check the update rectangle.  If we don't have it, exit immediately.
			if (typeid(*this) == typeid(CDeviceView) && !GetUpdateRect(m_hWnd, NULL, FALSE))
				return 0;
			PAINTSTRUCT ps;
			HDC	hDC = BeginPaint(hWnd, &ps);
			if (InRenderMode())
				OnRender(TRUE);
			else
				DoOnPaint(hDC);
			EndPaint(hWnd, &ps);
			return 0;
		}

		case WM_COMMAND:
		{
			WORD wNotifyCode = HIWORD(wParam);
			WORD wID = LOWORD(wParam);
			HWND hWnd = (HWND)lParam;
			return OnCommand(wNotifyCode, wID, hWnd);
		}

		case WM_NOTIFY:	
			return OnNotify(wParam, lParam);

		case WM_MOUSEMOVE:
		case WM_LBUTTONDOWN:
		case WM_RBUTTONDOWN:
		case WM_LBUTTONDBLCLK:
		case WM_MOUSEWHEEL:
		{
			POINT point = {int(LOWORD(lParam)), int(HIWORD(lParam))};
			switch (msg)
			{
				case WM_MOUSEMOVE: OnMouseOver(point, wParam); break;
				case WM_LBUTTONDOWN: OnClick(point, wParam, TRUE); break;
				case WM_RBUTTONDOWN: OnClick(point, wParam, FALSE); break;
				case WM_LBUTTONDBLCLK: OnDoubleClick(point, wParam, TRUE); break;
				case WM_MOUSEWHEEL:
				{
					// Send wheel msg to the window beneath the cursor
					HWND hWnd = WindowFromPoint(point);
					CFlexWnd *pWnd = NULL;
					if (hWnd)
					{
						pWnd = GetFlexWnd(hWnd);
						if (pWnd)
							pWnd->OnWheel(point, wParam);
						else
							return DefWindowProc(hWnd, msg, wParam, lParam);
					}
					break;
				}
			}
			return 0;
		}

		case WM_DESTROY:
			OnDestroy();
			m_privhWnd = NULL;
			return 0;
	}

	if (!m_bIsDialog)
		return DefWindowProc(hWnd, msg, wParam, lParam);
	else
		return 0;
}
Esempio n. 11
0
void VisualTool<FeatureType>::OnMouseEvent(wxMouseEvent &event) {
	bool left_click = event.LeftDown();
	bool left_double = event.LeftDClick();
	shift_down = event.ShiftDown();
	ctrl_down = event.CmdDown();
	alt_down = event.AltDown();

	mouse_pos = event.GetPosition();

	if (event.Leaving()) {
		mouse_pos = Vector2D();
		parent->Render();
		return;
	}

	if (!dragging) {
		int max_layer = INT_MIN;
		active_feature = nullptr;
		for (auto& feature : features) {
			if (feature.IsMouseOver(mouse_pos) && feature.layer >= max_layer) {
				active_feature = &feature;
				max_layer = feature.layer;
			}
		}
	}

	if (dragging) {
		// continue drag
		if (event.LeftIsDown()) {
			for (auto sel : sel_features)
				sel->UpdateDrag(mouse_pos - drag_start, shift_down);
			for (auto sel : sel_features)
				UpdateDrag(sel);
			Commit();
		}
		// end drag
		else {
			dragging = false;

			// mouse didn't move, fiddle with selection
			if (active_feature && !active_feature->HasMoved()) {
				// Don't deselect stuff that was selected in this click's mousedown event
				if (!sel_changed) {
					if (ctrl_down)
						RemoveSelection(active_feature);
					else
						SetSelection(active_feature, true);
				}
			}

			active_feature = nullptr;
			parent->ReleaseMouse();
			parent->SetFocus();
		}
	}
	else if (holding) {
		if (!event.LeftIsDown()) {
			holding = false;

			parent->ReleaseMouse();
			parent->SetFocus();
		}

		UpdateHold();
		Commit();

	}
	else if (left_click) {
		drag_start = mouse_pos;

		// start drag
		if (active_feature) {
			if (!sel_features.count(active_feature)) {
				sel_changed = true;
				SetSelection(active_feature, !ctrl_down);
			}
			else
				sel_changed = false;

			if (active_feature->line)
				c->selectionController->SetActiveLine(active_feature->line);

			if (InitializeDrag(active_feature)) {
				for (auto sel : sel_features) sel->StartDrag();
				dragging = true;
				parent->CaptureMouse();
			}
		}
		// start hold
		else {
			if (!alt_down && features.size() > 1) {
				sel_features.clear();
				c->selectionController->SetSelectedSet({ c->selectionController->GetActiveLine() });
			}
			if (active_line && InitializeHold()) {
				holding = true;
				parent->CaptureMouse();
			}
		}
	}

	if (active_line && left_double)
		OnDoubleClick();

	parent->Render();

	// Only coalesce the changes made in a single drag
	if (!event.LeftIsDown())
		commit_id = -1;
}