diff --git a/.travis.yml b/.travis.yml index 981ba12..b3e49cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -108,8 +108,8 @@ matrix: - g++-4.9 env: COMPILER=g++-4.9 OPENCV=3.4.1 -before_install: - - sudo apt-get update -qq +before_install: + install: - ./.travis/install.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 0faacd2..6145f8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Changelog All notable changes to this project are documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [2.7.2](https://github.com/Dovyski/cvui/releases/tag/v2.7.0) - 2021-08-11 +- Button with images - Add tooltip, add 3d effect +## [2.7.1](https://github.com/Dovyski/cvui/releases/tag/v2.7.0) - 2020-12-20 +- Support mouse wheel +- Support mosue double click +- Fix problem when image is not the same color space of background ## [2.7.0](https://github.com/Dovyski/cvui/releases/tag/v2.7.0) - 2018-10-08 ### Added - Python implementation of cvui, i.e. `cvui.py` ([read more](https://dovyski.github.io/cvui/usage/)) diff --git a/EnhancedWindow.h b/EnhancedWindow.h index c266261..8c0e3f2 100644 --- a/EnhancedWindow.h +++ b/EnhancedWindow.h @@ -71,7 +71,8 @@ class EnhancedWindow if (mMinimizable && cvui::button(frame, mX + mWidth - scaledTitleHeight, mY + 1, scaledTitleHeight-1, scaledTitleHeight-1, mMinimized ? "+" : "-", mFontScale)) { mMinimized = !mMinimized; } - cvui::beginRow(frame, mX + std::lround(10*mFontScale/cvui::DEFAULT_FONT_SCALE), mY + std::lround(30*mFontScale/cvui::DEFAULT_FONT_SCALE), mWidth - scaledTitleHeight, mHeight - scaledTitleHeight); + //cvui::beginRow(frame, mX + std::lround(10*mFontScale/cvui::DEFAULT_FONT_SCALE), mY + std::lround(30*mFontScale/cvui::DEFAULT_FONT_SCALE), mWidth - scaledTitleHeight, mHeight - scaledTitleHeight); + cvui::beginRow(frame, mX,mY+ scaledTitleHeight, mWidth - scaledTitleHeight, mHeight - scaledTitleHeight); cvui::beginColumn(mWidth - std::lround(10*mFontScale/cvui::DEFAULT_FONT_SCALE), mHeight - scaledTitleHeight); } diff --git a/cvui.h b/cvui.h index c884522..bb10362 100755 --- a/cvui.h +++ b/cvui.h @@ -1,6 +1,6 @@ /* A (very) simple UI lib built on top of OpenCV drawing primitives. - Version: 2.7.0 + Version: 2.7.2 Usage: @@ -328,13 +328,15 @@ bool button(cv::Mat& theWhere, int theX, int theY, int theWidth, int theHeight, \param theIdle an image that will be rendered when the button is not interacting with the mouse cursor. \param theOver an image that will be rendered when the mouse cursor is over the button. \param theDown an image that will be rendered when the mouse cursor clicked the button (or is clicking). + \param tooltip - string to display in tooltip window + \param draw3d (defualt = true) add 3d effect drawing lines on edge of button, makes it raised , looks like default button with text on it. works best if background color of button is gray \return `true` everytime the user clicks the button. \sa button() \sa image() \sa iarea() */ -bool button(cv::Mat& theWhere, int theX, int theY, cv::Mat& theIdle, cv::Mat& theOver, cv::Mat& theDown); +bool button(cv::Mat& theWhere, int theX, int theY, cv::Mat& theIdle, cv::Mat& theOver, cv::Mat& theDown, std::string tooltip = "", bool draw3d = true); /** Display an image (cv::Mat). @@ -1119,6 +1121,9 @@ void handleMouse(int theEvent, int theX, int theY, int theFlags, void* theData); #undef LEFT_BUTTON #undef MIDDLE_BUTTON #undef RIGHT_BUTTON +#undef WHEEL_DOWN +#undef WHEEL_UP +#undef DOUBLE_CLICK // Check for Unix stuff #ifdef __GNUC__ @@ -1128,7 +1133,7 @@ void handleMouse(int theEvent, int theX, int theY, int theFlags, void* theData); #endif // Lib version -static const char *VERSION = "2.7.0"; +static const char *VERSION = "2.7.2"; const int ROW = 0; const int COLUMN = 1; @@ -1138,6 +1143,9 @@ const int OVER = 4; const int OUT = 5; const int UP = 6; const int IS_DOWN = 7; +const int WHEEL_DOWN = 8; +const int WHEEL_UP = 9; +const int DOUBLE_CLICK = 10; // Constants regarding mouse buttons const int LEFT_BUTTON = 0; @@ -1180,6 +1188,8 @@ typedef struct { bool justReleased; // if the mouse button was released, i.e. click event. bool justPressed; // if the mouse button was just pressed, i.e. true for a frame when a button is down. bool pressed; // if the mouse button is pressed or not. + bool doubleClick; //if the mosue button is double clicked + int wheel; // if < 0 wheel down, if > 0 wheel up } cvui_mouse_btn_t; // Describe the information of the mouse cursor @@ -1248,7 +1258,7 @@ namespace internal int iarea(int theX, int theY, int theWidth, int theHeight); bool button(cvui_block_t& theBlock, int theX, int theY, int theWidth, int theHeight, const cv::String& theLabel, bool theUpdateLayout, double theFontScale, unsigned int theInsideColor); bool button(cvui_block_t& theBlock, int theX, int theY, const cv::String& theLabel, double theFontScale, unsigned int theInsideColor); - bool button(cvui_block_t& theBlock, int theX, int theY, cv::Mat& theIdle, cv::Mat& theOver, cv::Mat& theDown, bool theUpdateLayout); + bool button(cvui_block_t& theBlock, int theX, int theY, cv::Mat& theIdle, cv::Mat& theOver, cv::Mat& theDown, bool theUpdateLayout,std::string tooltip,bool draw3d); void image(cvui_block_t& theBlock, int theX, int theY, cv::Mat& theImage); bool checkbox(cvui_block_t& theBlock, int theX, int theY, const cv::String& theLabel, bool *theState, unsigned int theColor, double theFontScale); void text(cvui_block_t& theBlock, int theX, int theY, const cv::String& theText, double theFontScale, unsigned int theColor, bool theUpdateLayout); @@ -1266,7 +1276,7 @@ namespace internal cv::Scalar hexToScalar(unsigned int theColor); unsigned int brightenColor(unsigned int theColor, unsigned int theDelta); unsigned int darkenColor(unsigned int theColor, unsigned int theDelta); - uint8_t brightnessOfColor(unsigned int theColor); + unsigned int brightnessOfColor(unsigned int theColor); void resetRenderingBuffer(cvui_block_t& theScreen); template // T can be any floating point type (float, double, long double) @@ -1391,6 +1401,12 @@ namespace internal aRet = theButton.justPressed; break; case cvui::IS_DOWN: aRet = theButton.pressed; break; + case cvui::WHEEL_DOWN: + aRet = (theButton.wheel < 0) ? true : false; break; + case cvui::WHEEL_UP: + aRet = (theButton.wheel > 0) ? true : false; break; + case cvui::DOUBLE_CLICK: + aRet = theButton.doubleClick; } return aRet; @@ -1400,6 +1416,8 @@ namespace internal theButton.justPressed = false; theButton.justReleased = false; theButton.pressed = false; + theButton.wheel = 0; + theButton.doubleClick = false; } void init(const cv::String& theWindowName, int theDelayWaitKey) { @@ -1615,7 +1633,7 @@ namespace internal return (aAlpha << 24) | (aRed << 16) | (aGreen << 8) | aBlue; } - uint8_t brightnessOfColor(unsigned int theColor) { + unsigned int brightnessOfColor(unsigned int theColor) { cv::Mat gray; cv::Mat rgb(1, 1, CV_8UC3, internal::hexToScalar(theColor)); cv::cvtColor(rgb, gray, cv::COLOR_BGR2GRAY); @@ -1738,7 +1756,7 @@ namespace internal return internal::button(theBlock, theX, theY, aTextSize.width + std::lround(30*theFontScale/DEFAULT_FONT_SCALE), aTextSize.height + std::lround(18* theFontScale / DEFAULT_FONT_SCALE), theLabel, true, theFontScale, theInsideColor); } - bool button(cvui_block_t& theBlock, int theX, int theY, cv::Mat& theIdle, cv::Mat& theOver, cv::Mat& theDown, bool theUpdateLayout) { + bool button(cvui_block_t& theBlock, int theX, int theY, cv::Mat& theIdle, cv::Mat& theOver, cv::Mat& theDown, bool theUpdateLayout, std::string tooltip,bool draw3d) { cv::Rect aRect(theX, theY, theIdle.cols, theIdle.rows); int aStatus = cvui::iarea(theX, theY, aRect.width, aRect.height); @@ -1747,14 +1765,52 @@ namespace internal case cvui::OVER: render::image(theBlock, aRect, theOver); break; case cvui::DOWN: render::image(theBlock, aRect, theDown); break; } + if (draw3d) + { + auto theShape = aRect; + unsigned int brightColor = internal::brightenColor(DEFAULT_BUTTON_COLOR, 0x505050); + unsigned int darkColor = internal::darkenColor(DEFAULT_BUTTON_COLOR, 0x505050); + unsigned int topLeftColor, bottomRightColor; + // 3D effect depending on if the button is down or up. Light comes from top left. + if (aStatus == OVER || aStatus == OUT) // button is up + { + topLeftColor = brightColor; + bottomRightColor = darkColor; + } + else // button is down + { + bottomRightColor = brightColor; + topLeftColor = darkColor; + } + // 3D Outline. Note that cv::rectangle exludes theShape.br(), so we have to also exclude this point when drawing lines with cv::line + unsigned int thicknessOf3DOutline = (int)(DEFAULT_FONT_SCALE / 0.6); // On high DPI displayed we need to make the border thicker. We scale it together with the font size the user chose. + do + { + cv::line(theBlock.where, theShape.br() - cv::Point(1, 1), cv::Point(theShape.tl().x, theShape.br().y - 1), internal::hexToScalar(bottomRightColor)); + cv::line(theBlock.where, theShape.br() - cv::Point(1, 1), cv::Point(theShape.br().x - 1, theShape.tl().y), internal::hexToScalar(bottomRightColor)); + cv::line(theBlock.where, theShape.tl(), cv::Point(theShape.tl().x, theShape.br().y - 1), internal::hexToScalar(topLeftColor)); + cv::line(theBlock.where, theShape.tl(), cv::Point(theShape.br().x - 1, theShape.tl().y), internal::hexToScalar(topLeftColor)); + theShape.x++; theShape.y++; theShape.width -= 2; theShape.height -= 2; + } while (thicknessOf3DOutline--); // we want at least 1 pixel 3D outline, even for very small fonts + + } // Update the layout flow according to button size // if we were told to update. if (theUpdateLayout) { cv::Size aSize(aRect.width, aRect.height); updateLayoutFlow(theBlock, aSize); } + if (aStatus == cvui::OVER && !tooltip.empty()) + { + int baseline = 0; + auto txt_size = cv::getTextSize(tooltip, cv::FONT_HERSHEY_SIMPLEX, cvui::DEFAULT_FONT_SCALE, 1,&baseline); + int margin = 6; + cvui_mouse_t& aMouse = internal::getContext().mouse; + cvui::rect(theBlock.where, std::max(0, aMouse.position.x - txt_size.width), aMouse.position.y + margin, txt_size.width+margin, txt_size.height*2, 0x323235, 0xf1f4f7); + cvui::text(theBlock.where, std::max(0, aMouse.position.x - txt_size.width) + margin/2, aMouse.position.y + margin + 5, tooltip, cvui::DEFAULT_FONT_SCALE, 0x110000); + } // Return true if the button was clicked return aStatus == cvui::CLICK; } @@ -1763,6 +1819,9 @@ namespace internal cv::Rect aRect(theX, theY, theImage.cols, theImage.rows); // TODO: check for render outside the frame area + //resize before placing + + render::image(theBlock, aRect, theImage); // Update the layout flow according to image size @@ -2016,7 +2075,23 @@ namespace render } void image(cvui_block_t& theBlock, cv::Rect& theRect, cv::Mat& theImage) { - theImage.copyTo(theBlock.where(theRect)); + if (theRect.x < 0 || theRect.y <0 ||theRect.x + theRect.width > theBlock.where.cols || theRect.y + theRect.height > theBlock.where.rows) + return; + if (theImage.channels() != theBlock.where.channels()) + { + auto src_ch = theImage.channels(); + auto dst_ch = theBlock.where.channels(); + if (src_ch == 3 && dst_ch == 4) + cv::cvtColor(theImage, theBlock.where(theRect), cv::COLOR_BGR2BGRA); + else if (src_ch == 1 && dst_ch == 4) + cv::cvtColor(theImage, theBlock.where(theRect), cv::COLOR_GRAY2BGRA); + else if (src_ch == 4 && dst_ch == 3) + cv::cvtColor(theImage, theBlock.where(theRect), cv::COLOR_BGRA2BGR); + else if (src_ch == 1 && dst_ch ==3) + cv::cvtColor(theImage, theBlock.where(theRect), cv::COLOR_GRAY2BGR); + } + else + theImage.copyTo(theBlock.where(theRect)); } void counter(cvui_block_t& theBlock, cv::Rect& theShape, const cv::String& theValue, double theFontScale) { @@ -2332,9 +2407,9 @@ bool button(cv::Mat& theWhere, int theX, int theY, int theWidth, int theHeight, return internal::button(internal::gScreen, theX, theY, theWidth, theHeight, theLabel, true, theFontScale, theInsideColor); } -bool button(cv::Mat& theWhere, int theX, int theY, cv::Mat& theIdle, cv::Mat& theOver, cv::Mat& theDown) { +bool button(cv::Mat& theWhere, int theX, int theY, cv::Mat& theIdle, cv::Mat& theOver, cv::Mat& theDown,std::string tooltip,bool draw3d) { internal::gScreen.where = theWhere; - return internal::button(internal::gScreen, theX, theY, theIdle, theOver, theDown, true); + return internal::button(internal::gScreen, theX, theY, theIdle, theOver, theDown, true, tooltip,draw3d); } void image(cv::Mat& theWhere, int theX, int theY, cv::Mat& theImage) { @@ -2448,7 +2523,7 @@ bool button(int theWidth, int theHeight, const cv::String& theLabel, double theF bool button(cv::Mat& theIdle, cv::Mat& theOver, cv::Mat& theDown) { cvui_block_t& aBlock = internal::topBlock(); - return internal::button(aBlock, aBlock.anchor.x, aBlock.anchor.y, theIdle, theOver, theDown, true); + return internal::button(aBlock, aBlock.anchor.x, aBlock.anchor.y, theIdle, theOver, theDown, true,"",true); } void image(cv::Mat& theImage) { @@ -2522,6 +2597,8 @@ void update(const cv::String& theWindowName) { for (int i = cvui::LEFT_BUTTON; i <= cvui::RIGHT_BUTTON; i++) { aContext.mouse.buttons[i].justReleased = false; aContext.mouse.buttons[i].justPressed = false; + aContext.mouse.buttons[i].wheel = 0; + aContext.mouse.buttons[i].doubleClick = false; } internal::resetRenderingBuffer(internal::gScreen); @@ -2537,7 +2614,7 @@ void update(const cv::String& theWindowName) { } } -void handleMouse(int theEvent, int theX, int theY, int /*theFlags*/, void* theData) { +void handleMouse(int theEvent, int theX, int theY, int theFlags, void* theData) { int aButtons[3] = { cvui::LEFT_BUTTON, cvui::MIDDLE_BUTTON, cvui::RIGHT_BUTTON }; int aEventsDown[3] = { cv::EVENT_LBUTTONDOWN, cv::EVENT_MBUTTONDOWN, cv::EVENT_RBUTTONDOWN }; int aEventsUp[3] = { cv::EVENT_LBUTTONUP, cv::EVENT_MBUTTONUP, cv::EVENT_RBUTTONUP }; @@ -2558,11 +2635,61 @@ void handleMouse(int theEvent, int theX, int theY, int /*theFlags*/, void* theDa aContext->mouse.anyButton.pressed = false; aContext->mouse.buttons[aBtn].justReleased = true; aContext->mouse.buttons[aBtn].pressed = false; + aContext->mouse.buttons[aBtn].doubleClick = false; } } aContext->mouse.position.x = theX; aContext->mouse.position.y = theY; + + //double click support + if (theEvent == cv::EVENT_LBUTTONDBLCLK) + { + aContext->mouse.buttons[cvui::LEFT_BUTTON].doubleClick = true; + } + if (theEvent == cv::EVENT_RBUTTONDBLCLK) + { + aContext->mouse.buttons[cvui::RIGHT_BUTTON].doubleClick = true; + } + //wheel info for ver 3.x and above +#if (CV_MAJOR_VERSION >= 3) + if (theEvent == cv::EVENT_MOUSEWHEEL) + { + if (theFlags < 0) + { + aContext->mouse.buttons[MIDDLE_BUTTON].wheel = -1; + // std::cout << "wheel down " << std::endl; + } + else if (theFlags > 0) + { + aContext->mouse.buttons[MIDDLE_BUTTON].wheel = 1; + // std::cout << "wheel up " << std::endl; + } + + } + + if (theEvent == cv::EVENT_MOUSEWHEEL) + { + if (theFlags < 0 || cv::getMouseWheelDelta(theFlags)<0) + { + aContext->mouse.buttons[MIDDLE_BUTTON].wheel = -1; + // std::cout << "wheel down " << std::endl; + } + else if (theFlags > 0 || cv::getMouseWheelDelta(theFlags) > 0) + { + aContext->mouse.buttons[MIDDLE_BUTTON].wheel = 1; + + + // std::cout << "wheel up " << std::endl; + } + else if (theFlags == 0) + { + aContext->mouse.buttons[MIDDLE_BUTTON].wheel = 0; + //std::cout << "wheel not turned " << std::endl; + } + } +#endif + } } // namespace cvui diff --git a/cvui.py b/cvui.py index d98047d..f2e89c0 100644 --- a/cvui.py +++ b/cvui.py @@ -59,6 +59,8 @@ def main(): OUT = 5 UP = 6 IS_DOWN = 7 +WHEEL_DOWN = 8 +WHEEL_UP = 9 # Constants regarding mouse buttons LEFT_BUTTON = 0 @@ -156,11 +158,12 @@ def __init__(self): self.justReleased = False # if the mouse button was released, i.e. click event. self.justPressed = False # if the mouse button was just pressed, i.e. true for a frame when a button is down. self.pressed = False # if the mouse button is pressed or not. - + self.wheel = 0 def reset(self): self.justPressed = False self.justReleased = False self.pressed = False + self.wheel = 0 # Describe the information of the mouse cursor class Mouse: @@ -216,6 +219,10 @@ def isMouseButton(self, theButton, theQuery): aRet = theButton.justPressed elif theQuery == IS_DOWN: aRet = theButton.pressed + elif theQuery == WHEEL_DOWN: + aRet = True if theButton.wheel < 0 else False + elif theQuery == WHEEL_UP: + aRet = True if theButton.wheel > 0 else False return aRet @@ -550,7 +557,7 @@ def button(self, theBlock, theX, theY, theLabel): # Create a button based on the size of the text return self.buttonWH(theBlock, theX, theY, aTextSize.width + 30, aTextSize.height + 18, theLabel, True) - def buttonI(self, theBlock, theX, theY, theIdle, theOver, theDown, theUpdateLayout): + def buttonI(self, theBlock, theX, theY, theIdle, theOver, theDown, theUpdateLayout,tooltip): aIdleRows = theIdle.shape[0] aIdleCols = theIdle.shape[1] @@ -566,7 +573,15 @@ def buttonI(self, theBlock, theX, theY, theIdle, theOver, theDown, theUpdateLayo if theUpdateLayout: aSize = Size(aRect.width, aRect.height) self.updateLayoutFlow(theBlock, aSize) - + #draw tooltip + if aStatus == OVER and tooltip!='': + # baseline = 0; + ((txt_w, txt_h), _) = cv2.getTextSize(tooltip, cv2.FONT_HERSHEY_SIMPLEX, 0.4, 1) + margin = 8 + aMouse = self.getContext().mouse + aRect = Rect(max(0, aMouse.position.x - txt_w), aMouse.position.y + margin, txt_w +margin, txt_h*2) + self._render.rect (theBlock,aRect , 0x323235, 0xf1f4f7) + self._render.text(theBlock,tooltip,Point(max(0, aMouse.position.x - txt_w) + margin/2, aMouse.position.y + margin + 13), 0.4, 0x110000) # Return true if the button was clicked return aStatus == CLICK @@ -615,7 +630,7 @@ def window(self, theBlock, theX, theY, theWidth, theHeight, theTitle): def rect(self, theBlock, theX, theY, theWidth, theHeight, theBorderColor, theFillingColor): aAnchor = Point(theX, theY); - aRect = Rect(theX, theY, theWidth, theHeight); + aRect = Rect(theX, theY, theWidth, theHeight) aRect.x = aAnchor.x + aRect.width if aRect.width < 0 else aAnchor.x aRect.y = aAnchor.y + aRect.height if aRect.height < 0 else aAnchor.y @@ -1034,6 +1049,13 @@ def _handleMouse(theEvent, theX, theY, theFlags, theContext): theContext.mouse.position.x = theX theContext.mouse.position.y = theY + #add wheel info + if theEvent == cv2.EVENT_MOUSEWHEEL: + if theFlags < 0: + theContext.mouse.buttons[MIDDLE_BUTTON].wheel = -1 + elif theFlags > 0 : + theContext.mouse.buttons[MIDDLE_BUTTON].wheel = 1 + def init(theWindowName, theDelayWaitKey = -1, theCreateNamedWindow = True): """ @@ -1387,7 +1409,7 @@ def button(theWhere, theX, theY, theWidth, theHeight, theLabel): """ print('This is wrapper function to help code autocompletion.') -def button(theWhere, theX, theY, theIdle, theOver, theDown): +def button(theWhere, theX, theY, theIdle, theOver, theDown,tooltip): """ Display a button whose graphics are images (np.ndarray). The button accepts three images to describe its states, which are idle (no mouse interaction), over (mouse is over the button) and down (mouse clicked the button). @@ -1407,7 +1429,8 @@ def button(theWhere, theX, theY, theIdle, theOver, theDown): an image that will be rendered when the mouse cursor is over the button. theDown: np.ndarray an image that will be rendered when the mouse cursor clicked the button (or is clicking). - + tooltip: string + tooltip string display when mouse hovers over button Returns ---------- `true` everytime the user clicks the button. @@ -2412,6 +2435,7 @@ def update(theWindowName = ''): for i in range(LEFT_BUTTON, RIGHT_BUTTON + 1): aContext.mouse.buttons[i].justReleased = False aContext.mouse.buttons[i].justPressed = False + aContext.mouse.buttons[i].wheel = 0 __internal.screen.reset() @@ -2609,12 +2633,13 @@ def button(*theArgs): aHeight = aArgs[1] aLabel = aArgs[2] return __internal.buttonWH(aBlock, aX, aY, aWidth, aHeight, aLabel, True) - else: + elif len(aArgs) == 4: # Signature: button(theIdle, theOver, theDown) aIdle = aArgs[0] aOver = aArgs[1] aDown= aArgs[2] - return __internal.buttonI(aBlock, aX, aY, aIdle, aOver, aDown, True) + tooltip = aArgs[3] + return __internal.buttonI(aBlock, aX, aY, aIdle, aOver, aDown, True,tooltip) else: # TODO: check this case here print('Problem?') diff --git a/docs/advanced-mouse.md b/docs/advanced-mouse.md index 29c6835..9d20ac0 100644 --- a/docs/advanced-mouse.md +++ b/docs/advanced-mouse.md @@ -90,7 +90,23 @@ if (cvui::mouse(cvui::LEFT_BUTTON, cvui::CLICK)) { std::cout << "Left mouse button click just happened." << std::endl; } ``` - +## mouse wheel +Test if mouse wheel was tured up or down(for example zoom in or out of image): +```cpp +if (cvui::mouse(cvui::MIDDLE_BUTTON, cvui::WHEEL_UP)) //zoom out +{ + std::cout<< "Wheel tured up"<