diff options
author | B Stack <bgstack15@gmail.com> | 2020-07-22 16:56:03 +0000 |
---|---|---|
committer | B Stack <bgstack15@gmail.com> | 2020-07-22 16:56:03 +0000 |
commit | e5633fb1c0db91f01ab967330b76baf4ecdb0512 (patch) | |
tree | 10260e25ae905564f7978b83fc4e316670f987c6 /wx+ | |
parent | Merge branch '10.25' into 'master' (diff) | |
parent | add upstream 11.0 (diff) | |
download | FreeFileSync-e5633fb1c0db91f01ab967330b76baf4ecdb0512.tar.gz FreeFileSync-e5633fb1c0db91f01ab967330b76baf4ecdb0512.tar.bz2 FreeFileSync-e5633fb1c0db91f01ab967330b76baf4ecdb0512.zip |
Merge branch '11.0' into 'master'11.0
add upstream 11.0
See merge request opensource-tracking/FreeFileSync!24
Diffstat (limited to 'wx+')
-rw-r--r-- | wx+/app_main.h | 12 | ||||
-rw-r--r-- | wx+/async_task.h | 21 | ||||
-rw-r--r-- | wx+/bitmap_button.h | 86 | ||||
-rw-r--r-- | wx+/context_menu.h | 8 | ||||
-rw-r--r-- | wx+/dc.h | 35 | ||||
-rw-r--r-- | wx+/graph.cpp | 10 | ||||
-rw-r--r-- | wx+/grid.cpp | 32 | ||||
-rw-r--r-- | wx+/image_holder.h | 22 | ||||
-rw-r--r-- | wx+/image_resources.cpp | 223 | ||||
-rw-r--r-- | wx+/image_resources.h | 9 | ||||
-rw-r--r-- | wx+/image_tools.cpp | 284 | ||||
-rw-r--r-- | wx+/image_tools.h | 59 | ||||
-rw-r--r-- | wx+/popup_dlg.cpp | 6 | ||||
-rw-r--r-- | wx+/popup_dlg_generated.cpp | 106 | ||||
-rw-r--r-- | wx+/popup_dlg_generated.h | 47 | ||||
-rw-r--r-- | wx+/rtl.h | 54 | ||||
-rw-r--r-- | wx+/toggle_button.h | 37 | ||||
-rw-r--r-- | wx+/tooltip.cpp | 9 | ||||
-rw-r--r-- | wx+/tooltip.h | 4 |
19 files changed, 616 insertions, 448 deletions
diff --git a/wx+/app_main.h b/wx+/app_main.h index 8d2a6eeb..570a2a9c 100644 --- a/wx+/app_main.h +++ b/wx+/app_main.h @@ -14,8 +14,8 @@ namespace zen { //just some wrapper around a global variable representing the (logical) main application window -void setMainWindow(wxWindow* window); //set main window and enable "exit on frame delete" -bool mainWindowWasSet(); +void setGlobalWindow(wxWindow* window); //set main window and enable "exit on frame delete" +bool globalWindowWasSet(); @@ -26,7 +26,7 @@ bool mainWindowWasSet(); namespace impl { inline -bool& refMainWndStatus() +bool& refGlobalWindowStatus() { static bool status = false; //external linkage! return status; @@ -35,15 +35,15 @@ bool& refMainWndStatus() inline -void setMainWindow(wxWindow* window) +void setGlobalWindow(wxWindow* window) { wxTheApp->SetTopWindow(window); wxTheApp->SetExitOnFrameDelete(true); - impl::refMainWndStatus() = true; + impl::refGlobalWindowStatus() = true; } -inline bool mainWindowWasSet() { return impl::refMainWndStatus(); } +inline bool globalWindowWasSet() { return impl::refGlobalWindowStatus(); } } #endif //APP_MAIN_H_08215601837818347575856 diff --git a/wx+/async_task.h b/wx+/async_task.h index 1599c4d7..47660a6a 100644 --- a/wx+/async_task.h +++ b/wx+/async_task.h @@ -16,18 +16,16 @@ namespace zen { -/* -Run a task in an async thread, but process result in GUI event loop -------------------------------------------------------------------- -1. put AsyncGuiQueue instance inside a dialog: - AsyncGuiQueue guiQueue; +/* Run a task in an async thread, but process result in GUI event loop + ------------------------------------------------------------------- + 1. put AsyncGuiQueue instance inside a dialog: + AsyncGuiQueue guiQueue; -2. schedule async task and synchronous continuation: - guiQueue.processAsync(evalAsync, evalOnGui); + 2. schedule async task and synchronous continuation: + guiQueue.processAsync(evalAsync, evalOnGui); -Alternative: use wxWidgets' inter-thread communication (wxEvtHandler::QueueEvent) https://wiki.wxwidgets.org/Inter-Thread_and_Inter-Process_communication - => don't bother, probably too many MT race conditions lurking around -*/ + Alternative: wxWidgets' inter-thread communication (wxEvtHandler::QueueEvent) https://wiki.wxwidgets.org/Inter-Thread_and_Inter-Process_communication + => don't bother, probably too many MT race conditions lurking around */ namespace impl { @@ -44,7 +42,8 @@ class ConcreteTask : public Task { public: template <class Fun2> - ConcreteTask(std::future<ResultType>&& asyncResult, Fun2&& evalOnGui) : asyncResult_(std::move(asyncResult)), evalOnGui_(std::forward<Fun2>(evalOnGui)) {} + ConcreteTask(std::future<ResultType>&& asyncResult, Fun2&& evalOnGui) : + asyncResult_(std::move(asyncResult)), evalOnGui_(std::forward<Fun2>(evalOnGui)) {} bool resultReady () const override { return isReady(asyncResult_); } void evaluateResult() override diff --git a/wx+/bitmap_button.h b/wx+/bitmap_button.h index b40d8dd3..508a72fc 100644 --- a/wx+/bitmap_button.h +++ b/wx+/bitmap_button.h @@ -8,6 +8,7 @@ #define BITMAP_BUTTON_H_83415718945878341563415 #include <wx/bmpbuttn.h> +#include <wx/settings.h> #include "image_tools.h" #include "dc.h" @@ -26,17 +27,20 @@ public: long style = 0, const wxValidator& validator = wxDefaultValidator, const wxString& name = wxButtonNameStr) : - wxBitmapButton(parent, id, wxNullBitmap, pos, size, style | wxBU_AUTODRAW, validator, name) { SetLabel(label); } + wxBitmapButton(parent, id, wxNullBitmap, pos, size, style, validator, name) + { + SetLabel(label); + } }; //wxButton::SetBitmap() also supports "image + text", but screws up proper gap and border handling void setBitmapTextLabel(wxBitmapButton& btn, const wxImage& img, const wxString& text, int gap = fastFromDIP(5), int border = fastFromDIP(5)); //set bitmap label flicker free: -void setImage(wxBitmapButton& button, const wxBitmap& bmp); - - +void setImage(wxBitmapButton& button, const wxImage& bmp); +wxBitmap renderSelectedButton(const wxSize& sz); +wxBitmap renderPressedButton(const wxSize& sz); @@ -53,34 +57,80 @@ void setBitmapTextLabel(wxBitmapButton& btn, const wxImage& img, const wxString& gap = std::max(0, gap); border = std::max(0, border); - wxImage dynImage = createImageFromText(text, btn.GetFont(), btn.GetForegroundColour()); + wxImage imgTxt = createImageFromText(text, btn.GetFont(), btn.GetForegroundColour()); if (img.IsOk()) - dynImage = btn.GetLayoutDirection() != wxLayout_RightToLeft ? - stackImages(img, dynImage, ImageStackLayout::horizontal, ImageStackAlignment::center, gap) : - stackImages(dynImage, img, ImageStackLayout::horizontal, ImageStackAlignment::center, gap); + imgTxt = btn.GetLayoutDirection() != wxLayout_RightToLeft ? + stackImages(img, imgTxt, ImageStackLayout::horizontal, ImageStackAlignment::center, gap) : + stackImages(imgTxt, img, ImageStackLayout::horizontal, ImageStackAlignment::center, gap); //SetMinSize() instead of SetSize() is needed here for wxWindows layout determination to work correctly const int defaultHeight = wxButton::GetDefaultSize().GetHeight(); - btn.SetMinSize({dynImage.GetWidth () + 2 * border, - std::max(dynImage.GetHeight() + 2 * border, defaultHeight)}); + btn.SetMinSize({imgTxt.GetWidth () + 2 * border, + std::max(imgTxt.GetHeight() + 2 * border, defaultHeight)}); - btn.SetBitmapLabel(wxBitmap(dynImage)); + btn.SetBitmapLabel(imgTxt); //SetLabel() calls confuse wxBitmapButton in the disabled state and it won't show the image! workaround: - btn.SetBitmapDisabled(wxBitmap(dynImage.ConvertToDisabled())); + btn.SetBitmapDisabled(imgTxt.ConvertToDisabled()); } inline -void setImage(wxBitmapButton& button, const wxBitmap& bmp) +void setImage(wxBitmapButton& button, const wxImage& img) { - if (!button.GetBitmapLabel().IsSameAs(bmp)) + if (!img.IsOk()) { - button.SetBitmapLabel(bmp); + button.SetBitmapLabel (wxNullBitmap); + button.SetBitmapDisabled(wxNullBitmap); + return; + } - //wxWidgets excels at screwing up consistently once again: - //the first call to SetBitmapLabel() *implicitly* sets the disabled bitmap, too, subsequent calls, DON'T! - button.SetBitmapDisabled(bmp.ConvertToDisabled()); //inefficiency: wxBitmap::ConvertToDisabled() implicitly converts to wxImage! + button.SetBitmapLabel(img); + + //wxWidgets excels at screwing up consistently once again: + //the first call to SetBitmapLabel() *implicitly* sets the disabled bitmap, too, subsequent calls, DON'T! + button.SetBitmapDisabled(img.ConvertToDisabled()); //inefficiency: wxBitmap::ConvertToDisabled() implicitly converts to wxImage! +} + + +inline +wxBitmap renderSelectedButton(const wxSize& sz) +{ + wxBitmap bmp(sz); //seems we don't need to pass 24-bit depth here even for high-contrast color schemes + { + wxMemoryDC dc(bmp); + dc.SetBrush(wxColor(0xcc, 0xe4, 0xf8)); //light blue + dc.SetPen (wxColor(0x79, 0xbc, 0xed)); //medium blue + dc.DrawRectangle(wxRect(bmp.GetSize())); + } + return bmp; +} + + +inline +wxBitmap renderPressedButton(const wxSize& sz) +{ + wxBitmap bmp(sz); //seems we don't need to pass 24-bit depth here even for high-contrast color schemes + { + //draw rectangle border with gradient + const wxColor colFrom = wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); + const wxColor colTo(0x11, 0x79, 0xfe); //light blue + + wxMemoryDC dc(bmp); + dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + wxRect rect(bmp.GetSize()); + + const int borderSize = fastFromDIP(3); + for (int i = 1 ; i <= borderSize; ++i) + { + const wxColor colGradient((colFrom.Red () * (borderSize - i) + colTo.Red () * i) / borderSize, + (colFrom.Green() * (borderSize - i) + colTo.Green() * i) / borderSize, + (colFrom.Blue () * (borderSize - i) + colTo.Blue () * i) / borderSize); + dc.SetPen(colGradient); + dc.DrawRectangle(rect); + rect.Deflate(1); + } } + return bmp; } } diff --git a/wx+/context_menu.h b/wx+/context_menu.h index 08a313bf..7096da33 100644 --- a/wx+/context_menu.h +++ b/wx+/context_menu.h @@ -29,10 +29,10 @@ class ContextMenu : private wxEvtHandler public: ContextMenu() {} - void addItem(const wxString& label, const std::function<void()>& command, const wxBitmap* bmp = nullptr, bool enabled = true) + void addItem(const wxString& label, const std::function<void()>& command, const wxImage& img = wxNullImage, bool enabled = true) { wxMenuItem* newItem = new wxMenuItem(menu_.get(), wxID_ANY, label); //menu owns item! - if (bmp) newItem->SetBitmap(*bmp); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason + if (img.IsOk()) newItem->SetBitmap(img); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason menu_->Append(newItem); if (!enabled) newItem->Enable(false); //do not enable BEFORE appending item! wxWidgets screws up for yet another crappy reason commandList_[newItem->GetId()] = command; //defer event connection, this may be a submenu only! @@ -56,7 +56,7 @@ public: void addSeparator() { menu_->AppendSeparator(); } - void addSubmenu(const wxString& label, ContextMenu& submenu, const wxBitmap* bmp = nullptr, bool enabled = true) //invalidates submenu! + void addSubmenu(const wxString& label, ContextMenu& submenu, const wxImage& img = wxNullImage, bool enabled = true) //invalidates submenu! { //transfer submenu commands: commandList_.insert(submenu.commandList_.begin(), submenu.commandList_.end()); @@ -65,7 +65,7 @@ public: submenu.menu_->SetNextHandler(menu_.get()); //on wxGTK submenu events are not propagated to their parent menu by default! wxMenuItem* newItem = new wxMenuItem(menu_.get(), wxID_ANY, label, L"", wxITEM_NORMAL, submenu.menu_.release()); //menu owns item, item owns submenu! - if (bmp) newItem->SetBitmap(*bmp); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason + if (img.IsOk()) newItem->SetBitmap(img); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason menu_->Append(newItem); if (!enabled) newItem->Enable(false); } @@ -17,37 +17,38 @@ namespace zen { -/* - 1. wxDCClipper does *not* stack: another fix for yet another poor wxWidgets implementation +/* 1. wxDCClipper does *not* stack: another fix for yet another poor wxWidgets implementation - class RecursiveDcClipper - { - RecursiveDcClipper(wxDC& dc, const wxRect& r) - }; + class RecursiveDcClipper + { + RecursiveDcClipper(wxDC& dc, const wxRect& r) + }; 2. wxAutoBufferedPaintDC skips one pixel on left side when RTL layout is active: a fix for a poor wxWidgets implementation - class BufferedPaintDC - { - BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer) - }; -*/ + class BufferedPaintDC + { + BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer) + }; */ inline void clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) { + if (rect.width > 0 && //clearArea() is surprisingly expensive + rect.height > 0) + { wxDCPenChanger dummy (dc, col); wxDCBrushChanger dummy2(dc, col); dc.DrawRectangle(rect); + } } -/* -Standard DPI: - Windows/Ubuntu: 96 x 96 - macOS: wxWidgets uses DIP (note: wxScreenDC().GetPPI() returns 72 x 72 which is a lie; looks like 96 x 96) -*/ +/* Standard DPI: + Windows/Ubuntu: 96 x 96 + macOS: wxWidgets uses DIP (note: wxScreenDC().GetPPI() returns 72 x 72 which is a lie; looks like 96 x 96) */ + inline int fastFromDIP(int d) //like wxWindow::FromDIP (but tied to primary monitor and buffered) { @@ -60,7 +61,7 @@ int fastFromDIP(int d) //like wxWindow::FromDIP (but tied to primary monitor and assert(wxTheApp); //only call after wxWidgets was initalized! static const int dpiY = wxScreenDC().GetPPI().y; //perf: buffering for calls to ::GetDeviceCaps() needed!? const int defaultDpi = 96; - return numeric::round(1.0 * d * dpiY / defaultDpi); + return 1.0 * d * dpiY / defaultDpi + 0.49 /*round values like 1.5 down, e.g. 1 pixel on 150% scale*/; #endif } diff --git a/wx+/graph.cpp b/wx+/graph.cpp index 1fb3775c..f3805fca 100644 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -150,7 +150,7 @@ void drawXLabel(wxDC& dc, double xMin, double xMax, int blockCount, const Conver if (blockCount <= 0) return; - wxDCPenChanger dummy(dc, wxColor(192, 192, 192)); //light grey => not accessible! but no big deal... + wxDCPenChanger dummy(dc, wxPen(wxColor(192, 192, 192), fastFromDIP(1))); //light grey => not accessible! but no big deal... wxDCTextColourChanger textColor(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); const double valRangePerBlock = (xMax - xMin) / blockCount; @@ -178,7 +178,7 @@ void drawYLabel(wxDC& dc, double yMin, double yMax, int blockCount, const Conver if (blockCount <= 0) return; - wxDCPenChanger dummy(dc, wxColor(192, 192, 192)); //light grey => not accessible! but no big deal... + wxDCPenChanger dummy(dc, wxPen(wxColor(192, 192, 192), fastFromDIP(1))); //light grey => not accessible! but no big deal... wxDCTextColourChanger textColor(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); const double valRangePerBlock = (yMax - yMin) / blockCount; @@ -591,7 +591,7 @@ void Graph2D::render(wxDC& dc) const { //paint graph background (excluding label area) - wxDCPenChanger dummy (dc, getBorderColor()); + wxDCPenChanger dummy (dc, wxPen(getBorderColor(), fastFromDIP(1))); wxDCBrushChanger dummy2(dc, attr_.colorBack); //accessibility: consider system text and background colors; small drawback: color of graphs is NOT connected to the background! => responsibility of client to use correct colors @@ -749,7 +749,7 @@ void Graph2D::render(wxDC& dc) const if (points.size() >= 3) { wxDCBrushChanger dummy(dc, it->second.fillColor); - wxDCPenChanger dummy2(dc, it->second.fillColor); + wxDCPenChanger dummy2(dc, wxPen(it->second.fillColor, fastFromDIP(1))); dc.DrawPolygon(static_cast<int>(points.size()), &points[0]); } } @@ -761,7 +761,7 @@ void Graph2D::render(wxDC& dc) const { //alpha channel not supported on wxMSW, so draw selection before curves wxDCBrushChanger dummy(dc, wxColor(168, 202, 236)); //light blue - wxDCPenChanger dummy2(dc, wxColor( 51, 153, 255)); //dark blue + wxDCPenChanger dummy2(dc, wxPen(wxColor(51, 153, 255), fastFromDIP(1))); //dark blue auto shrink = [](double* low, double* high) { diff --git a/wx+/grid.cpp b/wx+/grid.cpp index 0ce5c978..3f1599f3 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -123,7 +123,7 @@ int GridData::getBestSize(wxDC& dc, size_t row, ColumnType colType) wxRect GridData::drawCellBorder(wxDC& dc, const wxRect& rect) //returns remaining rectangle { - wxDCPenChanger dummy2(dc, getColorGridLine()); + wxDCPenChanger dummy2(dc, wxPen(getColorGridLine(), fastFromDIP(1))); dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight()); dc.DrawLine(rect.GetBottomRight(), rect.GetTopRight() + wxPoint(0, -1)); @@ -156,7 +156,7 @@ void GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& te - wxDC::DrawLabel results in GetTextExtent() call even for empty strings!!! => skip the wxDC::DrawLabel() cruft and directly call wxDC::DrawText()! */ assert(!contains(text, L'\n')); - if (text.empty()) + if (rect.width <= 0 || rect.height <= 0 || text.empty()) return; //truncate large texts and add ellipsis @@ -166,7 +166,7 @@ void GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& te if (extentTrunc.GetWidth() > rect.width) { - //unlike Windows 7 Explorer, we truncate UTF-16 correctly: e.g. CJK-Ideogramm encodes to TWO wchar_t: utfTo<std::wstring>("\xf0\xa4\xbd\x9c"); + //unlike Windows Explorer, we truncate UTF-16 correctly: e.g. CJK-Ideogramm encodes to TWO wchar_t: utfTo<std::wstring>("\xf0\xa4\xbd\x9c"); size_t low = 0; //number of unicode chars! size_t high = unicodeLength(text); // if (high > 1) @@ -208,7 +208,7 @@ void GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& te else if (alignment & wxALIGN_CENTER_VERTICAL) pt.y += static_cast<int>(std::floor((rect.height - extentTrunc.GetHeight()) / 2.0)); //round down negative values, too! - //std::unique_ptr<RecursiveDcClipper> clip; -> redundant!? RecursiveDcClipper already used during Grid cell rendering + //std::unique_ptr<RecursiveDcClipper> clip; -> redundant!? RecursiveDcClipper already used during grid cell rendering //if (extentTrunc.GetWidth() > rect.width) // clip = std::make_unique<RecursiveDcClipper>(dc, rect); @@ -230,12 +230,12 @@ wxRect GridData::drawColumnLabelBackground(wxDC& dc, const wxRect& rect, bool hi { //left border { - wxDCPenChanger dummy(dc, *wxWHITE_PEN); + wxDCPenChanger dummy(dc, wxPen(*wxWHITE, fastFromDIP(1))); dc.DrawLine(rect.GetTopLeft(), rect.GetBottomLeft()); } //bottom, right border { - wxDCPenChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW)); + wxDCPenChanger dummy(dc, wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW), fastFromDIP(1))); dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight()), getColorLabelGradientFrom(), dc.GetPen().GetColour(), wxSOUTH); dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); } @@ -419,10 +419,10 @@ private: dc.GradientFillLinear(clientRect, getColorLabelGradientFrom(), getColorLabelGradientTo(), wxSOUTH); - dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW)); + dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW), fastFromDIP(1))); { - wxDCPenChanger dummy(dc, getColorLabelGradientFrom()); + wxDCPenChanger dummy(dc, wxPen(getColorLabelGradientFrom(), fastFromDIP(1))); dc.DrawLine(clientRect.GetTopLeft(), clientRect.GetTopRight()); } @@ -433,7 +433,7 @@ private: wxRect rectShrinked = clientRect; rectShrinked.Deflate(1); - dc.SetPen(*wxWHITE_PEN); + dc.SetPen(wxPen(*wxWHITE, fastFromDIP(1))); //dc.DrawLine(clientRect.GetTopLeft(), clientRect.GetTopRight() + wxPoint(1, 0)); dc.DrawLine(rectShrinked.GetTopLeft(), rectShrinked.GetBottomLeft() + wxPoint(0, 1)); @@ -534,11 +534,11 @@ private: //border lines { - wxDCPenChanger dummy(dc, *wxWHITE_PEN); + wxDCPenChanger dummy(dc, wxPen(*wxWHITE, fastFromDIP(1))); dc.DrawLine(rect.GetTopLeft(), rect.GetTopRight()); } { - wxDCPenChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW)); + wxDCPenChanger dummy(dc, wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW), fastFromDIP(1))); dc.DrawLine(rect.GetTopLeft(), rect.GetBottomLeft()); dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight()); dc.DrawLine(rect.GetBottomRight(), rect.GetTopRight() + wxPoint(0, -1)); @@ -1520,8 +1520,14 @@ wxSize Grid::GetSizeAvailableForScrollTarget(const wxSize& size) //luckily "scrollbar-spacing" is stable on GTK3 const wxSize scrollBarSizeTmp = GetSize() - GetClientSize(); - assert(scrollBarSizeTmp.x == 0 || scrollBarSizeTmp.x == 6 || scrollBarSizeTmp.x == 13); //lame hard-coded numbers (from Ubuntu 19.10) - assert(scrollBarSizeTmp.y == 0 || scrollBarSizeTmp.y == 6 || scrollBarSizeTmp.y == 13); //=> but let's have a *close* eye on scrollbar fluctuation! + //lame hard-coded numbers (from Ubuntu 19.10) and openSuse + //=> let's have a *close* eye on scrollbar fluctuation! + assert(scrollBarSizeTmp.x == 0 || + scrollBarSizeTmp.x == 6 || scrollBarSizeTmp.x == 13 || //Ubuntu 19.10 + scrollBarSizeTmp.x == 16); //openSuse + assert(scrollBarSizeTmp.y == 0 || + scrollBarSizeTmp.y == 6 || scrollBarSizeTmp.y == 13 || //Ubuntu 19.10 + scrollBarSizeTmp.y == 16); //openSuse #else #error unknown GTK version! #endif diff --git a/wx+/image_holder.h b/wx+/image_holder.h index b11ae451..32f8a863 100644 --- a/wx+/image_holder.h +++ b/wx+/image_holder.h @@ -8,7 +8,7 @@ #define IMAGE_HOLDER_H_284578426342567457 #include <memory> - + #include <gio/gio.h> //used by fs/abstract.h => check carefully before adding dependencies! //DO NOT add any wx/wx+ includes! @@ -47,6 +47,26 @@ private: std::unique_ptr<unsigned char, CLibFree> rgb_; //optional std::unique_ptr<unsigned char, CLibFree> alpha_; // }; + + +struct FileIconHolder +{ + //- GTK is NOT thread-safe! The most we can do from worker threads is retrieve a GIcon and later *try*(!) to convert it on the MAIN THREAD! >:( what a waste + //- at least g_file_query_info() *always* returns G_IS_THEMED_ICON(gicon) for native file systems => main thread won't block https://gitlab.gnome.org/GNOME/glib/blob/master/gio/glocalfileinfo.c#L1733 + //- what about G_IS_FILE_ICON(gicon), G_IS_LOADABLE_ICON(gicon)? => may block! => do NOT convert on main thread! (no big deal: doesn't seem to occur in practice) + FileIconHolder() {}; + + FileIconHolder(GIcon* gi, int maxSz) : //takes ownership! + gicon(gi), + maxSize(maxSz) {} + + struct GiconFree { void operator()(GIcon* icon) const { ::g_object_unref(icon); } }; + + std::unique_ptr<GIcon, GiconFree> gicon; + int maxSize = 0; + + explicit operator bool() const { return static_cast<bool>(gicon); } +}; } #endif //IMAGE_HOLDER_H_284578426342567457 diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index 5328b845..d09e5dfa 100644 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -5,10 +5,8 @@ // ***************************************************************************** #include "image_resources.h" -#include <memory> #include <map> #include <zen/utf.h> -#include <zen/globals.h> #include <zen/perf.h> #include <zen/thread.h> #include <zen/file_io.h> @@ -27,20 +25,19 @@ using namespace zen; namespace { -ImageHolder dpiScale(int width, int height, int dpiWidth, int dpiHeight, const unsigned char* imageRgb, const unsigned char* imageAlpha, int hqScale) +ImageHolder xbrzScale(int width, int height, const unsigned char* imageRgb, const unsigned char* imageAlpha, int hqScale) { - assert(imageRgb && imageAlpha); //see convertToVanillaImage() - if (width <= 0 || height <= 0 || dpiWidth <= 0 || dpiHeight <= 0) + assert(imageRgb && imageAlpha && width > 0 && height > 0); //see convertToVanillaImage() + if (width <= 0 || height <= 0) return ImageHolder(0, 0, true /*withAlpha*/); const int hqWidth = width * hqScale; const int hqHeight = height * hqScale; //get rid of allocation and buffer std::vector<> at thread-level? => no discernable perf improvement - std::vector<uint32_t> buf(hqWidth * hqHeight + std::max(width * height, dpiWidth * dpiHeight)); + std::vector<uint32_t> buf(hqWidth * hqHeight + width * height); uint32_t* const argbSrc = &buf[0] + hqWidth * hqHeight; uint32_t* const xbrTrg = &buf[0]; - uint32_t* const dpiTrg = argbSrc; //convert RGB (RGB byte order) to ARGB (BGRA byte order) { @@ -60,18 +57,13 @@ ImageHolder dpiScale(int width, int height, int dpiWidth, int dpiHeight, const u xbrz::ColorFormat::ARGB_UNBUFFERED); //ColorFormat colFmt, //test: total xBRZ scaling time with ARGB: 300ms, ARGB_UNBUFFERED: 50ms //----------------------------------------------------- - xbrz::bilinearScale(xbrTrg, //const uint32_t* src, - hqWidth, hqHeight, //int srcWidth, int srcHeight, - dpiTrg, //uint32_t* trg, - dpiWidth, dpiHeight); //int trgWidth, int trgHeight - //----------------------------------------------------- //convert BGRA to RGB + alpha - ImageHolder trgImg(dpiWidth, dpiHeight, true /*withAlpha*/); + ImageHolder trgImg(hqWidth, hqHeight, true /*withAlpha*/); { unsigned char* rgb = trgImg.getRgb(); unsigned char* alpha = trgImg.getAlpha(); - std::for_each(dpiTrg, dpiTrg + dpiWidth * dpiHeight, [&](uint32_t col) + std::for_each(xbrTrg, xbrTrg + hqWidth * hqHeight, [&](uint32_t col) { *alpha++ = xbrz::getAlpha(col); *rgb++ = xbrz::getRed (col); @@ -86,28 +78,24 @@ ImageHolder dpiScale(int width, int height, int dpiWidth, int dpiHeight, const u auto getScalerTask(const std::string& imageName, const wxImage& img, int hqScale, Protected<std::vector<std::pair<std::string, ImageHolder>>>& result) { return [imageName, - width = img.GetWidth(), - height = img.GetHeight(), - dpiWidth = fastFromDIP(img.GetWidth()), - dpiHeight = fastFromDIP(img.GetHeight()), //don't call (wxWidgets function!) fastFromDIP() from worker thread - rgb = img.GetData(), - alpha = img.GetAlpha(), + width = img.GetWidth(), // + height = img.GetHeight(), //don't call wxWidgets functions from worker thread + rgb = img.GetData(), // + alpha = img.GetAlpha(), // hqScale, &result] { - ImageHolder ih = dpiScale(width, height, - dpiWidth, dpiHeight, - rgb, alpha, hqScale); + ImageHolder ih = xbrzScale(width, height, rgb, alpha, hqScale); result.access([&](std::vector<std::pair<std::string, ImageHolder>>& r) { r.emplace_back(imageName, std::move(ih)); }); }; } -class DpiParallelScaler +class HqParallelScaler { public: - DpiParallelScaler(int hqScale) : hqScale_(hqScale) { assert(hqScale > 1); } + HqParallelScaler(int hqScale) : hqScale_(hqScale) { assert(hqScale > 1); } - ~DpiParallelScaler() { threadGroup_ = {}; } //DpiParallelScaler must out-live threadGroup!!! + ~HqParallelScaler() { threadGroup_ = {}; } //imgKeeper_ must out-live threadGroup!!! void add(const std::string& imageName, const wxImage& img) { @@ -115,11 +103,11 @@ public: threadGroup_->run(getScalerTask(imageName, img, hqScale_, result_)); } - std::map<std::string, wxBitmap> waitAndGetResult() + std::unordered_map<std::string, wxImage> waitAndGetResult() { threadGroup_->wait(); - std::map<std::string, wxBitmap> output; + std::unordered_map<std::string, wxImage> output; result_.access([&](std::vector<std::pair<std::string, ImageHolder>>& r) { @@ -147,46 +135,48 @@ private: //================================================================================================ //================================================================================================ -class GlobalBitmaps +class ImageBuffer { public: - static std::shared_ptr<GlobalBitmaps> instance() - { - static FunStatGlobal<GlobalBitmaps> inst; - inst.initOnce([] { return std::make_unique<GlobalBitmaps>(); }); - assert(runningMainThread()); //wxWidgets is not thread-safe! - return inst.get(); - } + explicit ImageBuffer(const Zstring& filePath); //throw FileError - GlobalBitmaps() {} - ~GlobalBitmaps() { assert(bitmaps_.empty() && anims_.empty()); } //don't leave wxWidgets objects for static destruction! + const wxImage& getImage(const std::string& name, int maxWidth /*optional*/, int maxHeight /*optional*/); - void init(const Zstring& filePath); - void cleanup() - { - bitmaps_.clear(); - anims_ .clear(); - dpiScaler_.reset(); - } +private: + ImageBuffer (const ImageBuffer&) = delete; + ImageBuffer& operator=(const ImageBuffer&) = delete; - const wxBitmap& getImage (const std::string& name); - const wxAnimation& getAnimation(const std::string& name) const; + const wxImage& getRawImage (const std::string& name); + const wxImage& getScaledImage(const std::string& name); -private: - GlobalBitmaps (const GlobalBitmaps&) = delete; - GlobalBitmaps& operator=(const GlobalBitmaps&) = delete; + std::unordered_map<std::string, wxImage> imagesRaw_; + std::unordered_map<std::string, wxImage> imagesScaled_; + + std::unique_ptr<HqParallelScaler> hqScaler_; + + using OutImageKey = std::tuple<std::string /*name*/, int /*height*/>; + + struct OutImageKeyHash + { + size_t operator()(const OutImageKey& imKey) const + { + const auto& [name, height] = imKey; + + FNV1aHash<size_t> hash; + for (const char c : name) + hash.add(c); - std::map<std::string, wxBitmap> bitmaps_; - std::map<std::string, wxAnimation> anims_; + hash.add(height); - std::unique_ptr<DpiParallelScaler> dpiScaler_; + return hash.get(); + } + }; + std::unordered_map<OutImageKey, wxImage, OutImageKeyHash> imagesOut_; }; -void GlobalBitmaps::init(const Zstring& zipPath) //throw FileError +ImageBuffer::ImageBuffer(const Zstring& zipPath) //throw FileError { - assert(bitmaps_.empty() && anims_.empty()); - std::vector<std::pair<Zstring /*file name*/, std::string /*byte stream*/>> streams; try //to load from ZIP first: @@ -223,13 +213,12 @@ void GlobalBitmaps::init(const Zstring& zipPath) //throw FileError const int hqScale = std::clamp<int>(std::ceil(fastFromDIP(1000) / 1000.0), 1, xbrz::SCALE_FACTOR_MAX); //even for 125% DPI scaling, "2xBRZ + bilinear downscale" gives a better result than mere "125% bilinear upscale"! if (hqScale > 1) - dpiScaler_ = std::make_unique<DpiParallelScaler>(hqScale); + hqScaler_ = std::make_unique<HqParallelScaler>(hqScale); for (const auto& [fileName, stream] : streams) if (endsWith(fileName, Zstr(".png"))) { wxMemoryInputStream wxstream(stream.c_str(), stream.size()); //stream does not take ownership of data - //bonus: work around wxWidgets bug: wxAnimation::Load() requires seekable input stream (zip-input stream is not seekable) wxImage img(wxstream, wxBITMAP_TYPE_PNG); assert(img.IsOk()); @@ -239,88 +228,114 @@ void GlobalBitmaps::init(const Zstring& zipPath) //throw FileError convertToVanillaImage(img); const std::string imageName = utfTo<std::string>(beforeLast(fileName, Zstr("."), IF_MISSING_RETURN_NONE)); - if (dpiScaler_) - dpiScaler_->add(imageName, img); //scale in parallel! + + imagesRaw_.emplace(imageName, img); + if (hqScaler_) + hqScaler_->add(imageName, img); //scale in parallel! else - bitmaps_.emplace(imageName, img); + imagesScaled_.emplace(imageName, img); - //alternative: wxBitmap::NewFromPNGData(stream.c_str(), stream.size())? - // => Windows: just a (slow!) wrapper for wxBitmpat(wxImage())! + //wxBitmap::NewFromPNGData(stream.c_str(), stream.size())? + // => Windows: just a (slow!) wrapper for wxBitmap(wxImage())! } -#if 0 - else if (endsWith(fileName, Zstr(".gif"))) - { - [[maybe_unused]] const bool loadSuccess = anims_[fileName].Load(wxstream, wxANIMATION_TYPE_GIF); - assert(loadSuccess); - } -#endif else assert(false); } -const wxBitmap& GlobalBitmaps::getImage(const std::string& name) +const wxImage& ImageBuffer::getRawImage(const std::string& name) +{ + if (auto it = imagesRaw_.find(name); + it != imagesRaw_.end()) + return it->second; + + assert(false); + return wxNullImage; +} + + +const wxImage& ImageBuffer::getScaledImage(const std::string& name) { - //test: this function is first called about 220ms after GlobalBitmaps::init() has ended + //test: this function is first called about 220ms after ImageBuffer::ImageBuffer() has ended // => should be enough time to finish xBRZ scaling in parallel (which takes 50ms) //debug perf: extra 800-1000ms during startup - if (dpiScaler_) + if (hqScaler_) { - bitmaps_ = dpiScaler_->waitAndGetResult(); - dpiScaler_.reset(); + imagesScaled_ = hqScaler_->waitAndGetResult(); + hqScaler_.reset(); } - if (auto it = bitmaps_.find(name); - it != bitmaps_.end()) + if (auto it = imagesScaled_.find(name); + it != imagesScaled_.end()) return it->second; assert(false); - return wxNullBitmap; + return wxNullImage; } -const wxAnimation& GlobalBitmaps::getAnimation(const std::string& name) const +const wxImage& ImageBuffer::getImage(const std::string& name, int maxWidth /*optional*/, int maxHeight /*optional*/) { - if (auto it = anims_.find(name); - it != anims_.end()) - return it->second; - assert(false); - return wxNullAnimation; + const wxImage& rawImg = getRawImage(name); + + const wxSize dpiSize(fastFromDIP(rawImg.GetWidth ()), + fastFromDIP(rawImg.GetHeight())); + + int outHeight = dpiSize.y; + if (maxWidth >= 0 && maxWidth < dpiSize.x) + outHeight = rawImg.GetHeight() * maxWidth / rawImg.GetWidth(); + + if (maxHeight >= 0 && maxHeight < outHeight) + outHeight = maxHeight; + + const OutImageKey imkey{name, outHeight}; + + auto it = imagesOut_.find(imkey); + if (it == imagesOut_.end()) + { + if (rawImg.GetHeight() >= outHeight) //=> skip needless xBRZ upscaling + it = imagesOut_.emplace(imkey, shrinkImage(rawImg, -1 /*maxWidth*/, outHeight)).first; + else if (rawImg.GetHeight() >= 0.9 * outHeight) //almost there: also no need for xBRZ-scale + //however: for 125% DPI scaling, "2xBRZ + bilinear downscale" gives a better result than mere "125% bilinear upscale"! + it = imagesOut_.emplace(imkey, rawImg.Scale(rawImg.GetWidth() * outHeight / rawImg.GetHeight(), outHeight, wxIMAGE_QUALITY_BILINEAR)).first; + else + it = imagesOut_.emplace(imkey, shrinkImage(getScaledImage(name), -1 /*maxWidth*/, outHeight)).first; + } + return it->second; } + + +std::unique_ptr<ImageBuffer> globalImageBuffer; } -void zen::initResourceImages(const Zstring& zipPath) +void zen::imageResourcesInit(const Zstring& zipPath) //throw FileError { - if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance()) - inst->init(zipPath); - else - assert(false); + assert(runningMainThread()); //wxWidgets is not thread-safe! + assert(!globalImageBuffer); + globalImageBuffer = std::make_unique<ImageBuffer>(zipPath); //throw FileError } -void zen::cleanupResourceImages() +void zen::ImageResourcesCleanup() { - if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance()) - inst->cleanup(); - else - assert(false); + assert(runningMainThread()); //wxWidgets is not thread-safe! + assert(globalImageBuffer); + globalImageBuffer.reset(); } -const wxBitmap& zen::getResourceImage(const std::string& name) +const wxImage& zen::loadImage(const std::string& name, int maxWidth /*optional*/, int maxHeight /*optional*/) { - if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance()) - return inst->getImage(name); - assert(false); - return wxNullBitmap; + assert(runningMainThread()); //wxWidgets is not thread-safe! + assert(globalImageBuffer); + if (globalImageBuffer) + return globalImageBuffer->getImage(name, maxWidth, maxHeight); + return wxNullImage; } -const wxAnimation& zen::getResourceAnimation(const std::string& name) +const wxImage& zen::loadImage(const std::string& name, int maxSize) { - if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance()) - return inst->getAnimation(name); - assert(false); - return wxNullAnimation; + return loadImage(name, maxSize, maxSize); } diff --git a/wx+/image_resources.h b/wx+/image_resources.h index b996d977..2ac6f308 100644 --- a/wx+/image_resources.h +++ b/wx+/image_resources.h @@ -7,7 +7,6 @@ #ifndef IMAGE_RESOURCES_H_8740257825342532457 #define IMAGE_RESOURCES_H_8740257825342532457 -#include <wx/bitmap.h> #include <wx/animate.h> #include <zen/zstring.h> @@ -15,11 +14,11 @@ namespace zen { //pass resources .zip file at application startup -void initResourceImages(const Zstring& zipPath); //throw FileError -void cleanupResourceImages(); +void imageResourcesInit(const Zstring& zipPath); //throw FileError +void ImageResourcesCleanup(); -const wxBitmap& getResourceImage (const std::string& name); -const wxAnimation& getResourceAnimation(const std::string& name); +const wxImage& loadImage(const std::string& name, int maxWidth /*optional*/, int maxHeight /*optional*/); +const wxImage& loadImage(const std::string& name, int maxSize = -1); } #endif //IMAGE_RESOURCES_H_8740257825342532457 diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp index 9fcc5563..19ba6ba6 100644 --- a/wx+/image_tools.cpp +++ b/wx+/image_tools.cpp @@ -14,48 +14,116 @@ using namespace zen; namespace { -void writeToImage(wxImage& output, const wxImage& top, const wxPoint& pos) +template <int PixBytes> +void copyImageBlock(const unsigned char* src, int srcWidth, + /**/ unsigned char* trg, int trgWidth, int blockWidth, int blockHeight) { - const int topWidth = top.GetWidth (); - const int topHeight = top.GetHeight(); - const int outWidth = output.GetWidth(); + assert(srcWidth >= blockWidth && trgWidth >= blockWidth); + const int srcPitch = srcWidth * PixBytes; + const int trgPitch = trgWidth * PixBytes; + const int blockPitch = blockWidth * PixBytes; + for (int y = 0; y < blockHeight; ++y) + std::memcpy(trg + y * trgPitch, src + y * srcPitch, blockPitch); +} + - assert(0 <= pos.x && pos.x + topWidth <= outWidth ); //draw area must be a - assert(0 <= pos.y && pos.y + topHeight <= output.GetHeight()); //subset of output image! - assert(top.HasAlpha() && output.HasAlpha()); +//...what wxImage::Resize() wants to be when it grows up +void copySubImage(const wxImage& src, wxPoint srcPos, + /**/ wxImage& trg, wxPoint trgPos, wxSize blockSize) +{ + auto pointClamp = [](const wxPoint& pos, const wxImage& img) -> wxPoint + { + return { + std::clamp(pos.x, 0, img.GetWidth ()), + std::clamp(pos.y, 0, img.GetHeight())}; + }; + auto pointMinus = [](const wxPoint& lhs, const wxPoint& rhs) { return wxSize{lhs.x - rhs.x, lhs.y - rhs.y}; }; + //work around yet another wxWidgets screw up: WTF does "operator-(wxPoint, wxPoint)" return wxPoint instead of wxSize!?? + + const wxPoint trgPos2 = pointClamp(trgPos, trg); + const wxPoint trgPos2End = pointClamp(trgPos + blockSize, trg); + + blockSize = pointMinus(trgPos2End, trgPos2); + srcPos += pointMinus(trgPos2, trgPos); + trgPos = trgPos2; + if (blockSize.x <= 0 || blockSize.y <= 0) + return; + + const wxPoint srcPos2 = pointClamp(srcPos, src); + const wxPoint srcPos2End = pointClamp(srcPos + blockSize, src); + + blockSize = pointMinus(srcPos2End, srcPos2); + trgPos += pointMinus(srcPos2, srcPos); + srcPos = srcPos2; + if (blockSize.x <= 0 || blockSize.y <= 0) + return; + //what if target block size is bigger than source block size? should we clear the area that is not copied from source? + + copyImageBlock<3>(src.GetData() + 3 * (srcPos.x + srcPos.y * src.GetWidth()), src.GetWidth(), + trg.GetData() + 3 * (trgPos.x + trgPos.y * trg.GetWidth()), trg.GetWidth(), + blockSize.x, blockSize.y); + + copyImageBlock<1>(src.GetAlpha() + srcPos.x + srcPos.y * src.GetWidth(), src.GetWidth(), + trg.GetAlpha() + trgPos.x + trgPos.y * trg.GetWidth(), trg.GetWidth(), + blockSize.x, blockSize.y); +} + + +void copyImageLayover(const wxImage& src, + /**/ wxImage& trg, wxPoint trgPos) +{ + const int srcWidth = src.GetWidth (); + const int srcHeight = src.GetHeight(); + const int trgWidth = trg.GetWidth(); + + assert(0 <= trgPos.x && trgPos.x + srcWidth <= trgWidth ); //draw area must be a + assert(0 <= trgPos.y && trgPos.y + srcHeight <= trg.GetHeight()); //subset of target image! //https://en.wikipedia.org/wiki/Alpha_compositing - const unsigned char* topRgb = top.GetData(); - const unsigned char* topAlpha = top.GetAlpha(); + const unsigned char* srcRgb = src.GetData(); + const unsigned char* srcAlpha = src.GetAlpha(); - for (int y = 0; y < topHeight; ++y) + for (int y = 0; y < srcHeight; ++y) { - unsigned char* outRgb = output.GetData () + 3 * (pos.x + (pos.y + y) * outWidth); - unsigned char* outAlpha = output.GetAlpha() + pos.x + (pos.y + y) * outWidth; + unsigned char* trgRgb = trg.GetData () + 3 * (trgPos.x + (trgPos.y + y) * trgWidth); + unsigned char* trgAlpha = trg.GetAlpha() + trgPos.x + (trgPos.y + y) * trgWidth; - for (int x = 0; x < topWidth; ++x) + for (int x = 0; x < srcWidth; ++x) { - const int w1 = *topAlpha; //alpha-composition interpreted as weighted average - const int w2 = *outAlpha * (255 - w1) / 255; + const int w1 = *srcAlpha; //alpha-composition interpreted as weighted average + const int w2 = *trgAlpha * (255 - w1) / 255; const int wSum = w1 + w2; - auto calcColor = [w1, w2, wSum](unsigned char colTop, unsigned char colBot) + auto calcColor = [w1, w2, wSum](unsigned char colsrc, unsigned char colTrg) { - return static_cast<unsigned char>(wSum == 0 ? 0 : (colTop * w1 + colBot * w2) / wSum); + return static_cast<unsigned char>(wSum == 0 ? 0 : (colsrc * w1 + colTrg * w2) / wSum); }; - outRgb[0] = calcColor(topRgb[0], outRgb[0]); - outRgb[1] = calcColor(topRgb[1], outRgb[1]); - outRgb[2] = calcColor(topRgb[2], outRgb[2]); + trgRgb[0] = calcColor(srcRgb[0], trgRgb[0]); + trgRgb[1] = calcColor(srcRgb[1], trgRgb[1]); + trgRgb[2] = calcColor(srcRgb[2], trgRgb[2]); - *outAlpha = static_cast<unsigned char>(wSum); + *trgAlpha = static_cast<unsigned char>(wSum); - topRgb += 3; - outRgb += 3; - ++topAlpha; - ++outAlpha; + srcRgb += 3; + trgRgb += 3; + ++srcAlpha; + ++trgAlpha; } } } + + +std::vector<std::pair<wxString, wxSize>> getTextExtentInfo(const wxString& text, const wxFont& font) +{ + wxMemoryDC dc; //the context used for bitmaps + dc.SetFont(font); //the font parameter of GetMultiLineTextExtent() is not evaluated on OS X, wxWidgets 2.9.5, so apply it to the DC directly! + + std::vector<std::pair<wxString, wxSize>> lineInfo; //text + extent + for (const wxString& line : split(text, L'\n', SplitType::ALLOW_EMPTY)) + lineInfo.emplace_back(line, line.empty() ? wxSize() : dc.GetTextExtent(line)); + + return lineInfo; +} } @@ -69,27 +137,23 @@ wxImage zen::stackImages(const wxImage& img1, const wxImage& img2, ImageStackLay const int img2Width = img2.GetWidth (); const int img2Height = img2.GetHeight(); - int width = std::max(img1Width, img2Width); - int height = std::max(img1Height, img2Height); - - if (dir == ImageStackLayout::horizontal) - width = img1Width + gap + img2Width; - else - height = img1Height + gap + img2Height; + const wxSize newSize = dir == ImageStackLayout::horizontal ? + wxSize(img1Width + gap + img2Width, std::max(img1Height, img2Height)) : + wxSize(std::max(img1Width, img2Width), img1Height + gap + img2Height); - wxImage output(width, height); + wxImage output(newSize); output.SetAlpha(); - ::memset(output.GetAlpha(), wxIMAGE_ALPHA_TRANSPARENT, width * height); + std::memset(output.GetAlpha(), wxIMAGE_ALPHA_TRANSPARENT, newSize.x * newSize.y); auto calcPos = [&](int imageExtent, int totalExtent) { switch (align) { case ImageStackAlignment::center: - return static_cast<int>(std::floor((totalExtent - imageExtent) / 2.0)); //consistency: round down negative values, too! - case ImageStackAlignment::left: + return (totalExtent - imageExtent) / 2; + case ImageStackAlignment::left: //or top return 0; - case ImageStackAlignment::right: + case ImageStackAlignment::right: //or bottom return totalExtent - imageExtent; } assert(false); @@ -99,34 +163,19 @@ wxImage zen::stackImages(const wxImage& img1, const wxImage& img2, ImageStackLay switch (dir) { case ImageStackLayout::horizontal: - writeToImage(output, img1, wxPoint(0, calcPos(img1Height, height))); - writeToImage(output, img2, wxPoint(img1Width + gap, calcPos(img2Height, height))); + copySubImage(img1, wxPoint(), output, wxPoint(0, calcPos(img1Height, newSize.y)), img1.GetSize()); + copySubImage(img2, wxPoint(), output, wxPoint(img1Width + gap, calcPos(img2Height, newSize.y)), img2.GetSize()); break; case ImageStackLayout::vertical: - writeToImage(output, img1, wxPoint(calcPos(img1Width, width), 0)); - writeToImage(output, img2, wxPoint(calcPos(img2Width, width), img1Height + gap)); + copySubImage(img1, wxPoint(), output, wxPoint(calcPos(img1Width, newSize.x), 0), img1.GetSize()); + copySubImage(img2, wxPoint(), output, wxPoint(calcPos(img2Width, newSize.x), img1Height + gap), img2.GetSize()); break; } return output; } -namespace -{ -std::vector<std::pair<wxString, wxSize>> getTextExtentInfo(const wxString& text, const wxFont& font) -{ - wxMemoryDC dc; //the context used for bitmaps - dc.SetFont(font); //the font parameter of GetMultiLineTextExtent() is not evalated on OS X, wxWidgets 2.9.5, so apply it to the DC directly! - - std::vector<std::pair<wxString, wxSize>> lineInfo; //text + extent - for (const wxString& line : split(text, L'\n', SplitType::ALLOW_EMPTY)) - lineInfo.emplace_back(line, line.empty() ? wxSize() : dc.GetTextExtent(line)); - - return lineInfo; -} -} - wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const wxColor& col, ImageStackAlignment textAlign) { //assert(!contains(text, L"&")); //accelerator keys not supported here @@ -147,7 +196,7 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const lineHeight = std::max(lineHeight, lineSize.GetHeight()); //wxWidgets comment "GetTextExtent will return 0 for empty string" } if (maxWidth == 0 || lineHeight == 0) - return wxImage(); + return wxNullImage; wxBitmap newBitmap(maxWidth, lineHeight * lineInfo.size()); //seems we don't need to pass 24-bit depth here even for high-contrast color schemes { @@ -155,7 +204,7 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const dc.SetBackground(*wxWHITE_BRUSH); dc.Clear(); - dc.SetTextForeground(*wxBLACK); //for use in calcAlphaForBlackWhiteImage + dc.SetTextForeground(*wxBLACK); //for proper alpha-channel calculation dc.SetTextBackground(*wxWHITE); // dc.SetFont(font); @@ -175,7 +224,6 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const dc.DrawText(lineText, wxPoint((maxWidth - lineSize.GetWidth()) / 2, posY)); break; } - posY += lineHeight; } } @@ -193,11 +241,9 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const //black(0,0,0) becomes wxIMAGE_ALPHA_OPAQUE(255), while white(255,255,255) becomes wxIMAGE_ALPHA_TRANSPARENT(0) *alpha++ = static_cast<unsigned char>((255 - rgb[0] + 255 - rgb[1] + 255 - rgb[2]) / 3); //mixed-mode arithmetics! - rgb[0] = col.Red (); // - rgb[1] = col.Green(); //apply actual text color - rgb[2] = col.Blue (); // - - rgb += 3; + *rgb++ = col.Red (); // + *rgb++ = col.Green(); //apply actual text color + *rgb++ = col.Blue (); // } return output; } @@ -208,43 +254,91 @@ wxImage zen::layOver(const wxImage& back, const wxImage& front, int alignment) if (!front.IsOk()) return back; assert(front.HasAlpha() && back.HasAlpha()); - const int width = std::max(back.GetWidth(), front.GetWidth()); - const int height = std::max(back.GetHeight(), front.GetHeight()); + const wxSize newSize(std::max(back.GetWidth(), front.GetWidth()), + std::max(back.GetHeight(), front.GetHeight())); - const int offsetX = [&] + auto calcNewPos = [&](const wxImage& img) { - if (alignment & wxALIGN_RIGHT) - return back.GetWidth() - front.GetWidth(); - if (alignment & wxALIGN_CENTER_HORIZONTAL) - return (back.GetWidth() - front.GetWidth()) / 2; - - static_assert(wxALIGN_LEFT == 0); - return 0; - }(); + wxPoint newPos; + if (alignment & wxALIGN_RIGHT) //note: wxALIGN_LEFT == 0! + newPos.x = newSize.GetWidth() - img.GetWidth(); + else if (alignment & wxALIGN_CENTER_HORIZONTAL) + newPos.x = (newSize.GetWidth() - img.GetWidth()) / 2; + + if (alignment & wxALIGN_BOTTOM) //note: wxALIGN_TOP == 0! + newPos.y = newSize.GetHeight() - img.GetHeight(); + else if (alignment & wxALIGN_CENTER_VERTICAL) + newPos.y = (newSize.GetHeight() - img.GetHeight()) / 2; + + return newPos; + }; - const int offsetY = [&] - { - if (alignment & wxALIGN_BOTTOM) - return back.GetHeight() - front.GetHeight(); - if (alignment & wxALIGN_CENTER_VERTICAL) - return (back.GetHeight() - front.GetHeight()) / 2; + wxImage output(newSize); + output.SetAlpha(); + std::memset(output.GetAlpha(), wxIMAGE_ALPHA_TRANSPARENT, newSize.x * newSize.y); - static_assert(wxALIGN_TOP == 0); - return 0; - }(); + copySubImage(back, wxPoint(), output, calcNewPos(back), back.GetSize()); + //use resizeCanvas()? might return ref-counted copy! //can't use wxMemoryDC and wxDC::DrawBitmap(): no alpha channel support on wxGTK! - wxImage output(width, height); + copyImageLayover(front, output, calcNewPos(front)); + + return output; +} + + +wxImage zen::resizeCanvas(const wxImage& img, wxSize newSize, int alignment) +{ + if (newSize == img.GetSize()) + return img; //caveat: wxImage is ref-counted *without* copy on write + + wxPoint newPos; + if (alignment & wxALIGN_RIGHT) //note: wxALIGN_LEFT == 0! + newPos.x = newSize.GetWidth() - img.GetWidth(); + else if (alignment & wxALIGN_CENTER_HORIZONTAL) + newPos.x = static_cast<int>(std::floor((newSize.GetWidth() - img.GetWidth()) / 2)); //consistency: round down negative values, too! + + if (alignment & wxALIGN_BOTTOM) //note: wxALIGN_TOP == 0! + newPos.y = newSize.GetHeight() - img.GetHeight(); + else if (alignment & wxALIGN_CENTER_VERTICAL) + newPos.y = static_cast<int>(std::floor((newSize.GetHeight() - img.GetHeight()) / 2)); //consistency: round down negative values, too! + + wxImage output(newSize); output.SetAlpha(); - ::memset(output.GetAlpha(), wxIMAGE_ALPHA_TRANSPARENT, width * height); + std::memset(output.GetAlpha(), wxIMAGE_ALPHA_TRANSPARENT, newSize.x * newSize.y); - const wxPoint posBack(std::max(-offsetX, 0), std::max(-offsetY, 0)); - writeToImage(output, back, posBack); - writeToImage(output, front, posBack + wxPoint(offsetX, offsetY)); + copySubImage(img, wxPoint(), output, newPos, img.GetSize()); + //about 50x faster than e.g. wxImage::Resize!!! suprise :> return output; } +wxImage zen::shrinkImage(const wxImage& img, int maxWidth /*optional*/, int maxHeight /*optional*/) +{ + wxSize newSize = img.GetSize(); + + if (maxWidth >= 0) + if (maxWidth < newSize.x) + { + newSize.y = newSize.y * maxWidth / newSize.x; + newSize.x = maxWidth; + } + if (maxHeight >= 0) + if (maxHeight < newSize.y) + { + newSize = img.GetSize(); //avoid loss of precision + newSize.x = newSize.x * maxHeight / newSize.y; // + newSize.y = maxHeight; + } + + if (newSize == img.GetSize()) + return img; + + return img.Scale(newSize.x, newSize.y, wxIMAGE_QUALITY_BILINEAR); //looks sharper than wxIMAGE_QUALITY_HIGH! + //perf: use xbrz::bilinearScale instead? only about 10% shorter runtime +} + + void zen::convertToVanillaImage(wxImage& img) { if (!img.HasAlpha()) @@ -268,20 +362,20 @@ void zen::convertToVanillaImage(wxImage& img) if (haveMask) { img.SetMask(false); - unsigned char* alphaPtr = img.GetAlpha(); - const unsigned char* dataPtr = img.GetData(); + unsigned char* alpha = img.GetAlpha(); + const unsigned char* rgb = img.GetData(); const int pixelCount = width * height; for (int i = 0; i < pixelCount; ++ i) { - const unsigned char r = *dataPtr++; - const unsigned char g = *dataPtr++; - const unsigned char b = *dataPtr++; + const unsigned char r = *rgb++; + const unsigned char g = *rgb++; + const unsigned char b = *rgb++; if (r == mask_r && g == mask_g && b == mask_b) - alphaPtr[i] = wxIMAGE_ALPHA_TRANSPARENT; + alpha[i] = wxIMAGE_ALPHA_TRANSPARENT; } } } diff --git a/wx+/image_tools.h b/wx+/image_tools.h index 3e401f73..cd895c2e 100644 --- a/wx+/image_tools.h +++ b/wx+/image_tools.h @@ -8,10 +8,10 @@ #define IMAGE_TOOLS_H_45782456427634254 #include <numeric> -#include <wx/bitmap.h> #include <wx/image.h> #include <wx/dcmemory.h> #include <zen/basic_math.h> +#include <wx+/dc.h> namespace zen @@ -36,10 +36,8 @@ wxImage createImageFromText(const wxString& text, const wxFont& font, const wxCo wxImage layOver(const wxImage& back, const wxImage& front, int alignment = wxALIGN_CENTER); -wxImage greyScale(const wxImage& img); //greyscale + brightness adaption -wxBitmap greyScale(const wxBitmap& bmp); // -wxBitmap greyScaleIfDisabled(const wxBitmap& bmp, bool enabled); - +wxImage greyScale(const wxImage& img); //greyscale + brightness adaption +wxImage greyScaleIfDisabled(const wxImage& img, bool enabled); //void moveImage(wxImage& img, int right, int up); void adjustBrightness(wxImage& img, int targetLevel); @@ -52,7 +50,10 @@ void convertToVanillaImage(wxImage& img); //add alpha channel if missing + remov //wxColor hsvColor(double h, double s, double v); //h within [0, 360), s, v within [0, 1] -wxImage shrinkImage(const wxImage& img, int requestedSize); +wxImage shrinkImage(const wxImage& img, int maxWidth /*optional*/, int maxHeight /*optional*/); +inline wxImage shrinkImage(const wxImage& img, int maxSize) { return shrinkImage(img, maxSize, maxSize); } + +wxImage resizeCanvas(const wxImage& img, wxSize newSize, int alignment); inline @@ -65,48 +66,38 @@ wxImage getTransparentPixel() } +inline +int getDefaultMenuIconSize() +{ + return fastFromDIP(24); +} -//################################### implementation ################################### -/* -inline -void moveImage(wxImage& img, int right, int up) -{ - img = img.GetSubImage(wxRect(std::max(0, -right), std::max(0, up), img.GetWidth() - abs(right), img.GetHeight() - abs(up))); - img.Resize(wxSize(img.GetWidth() + abs(right), img.GetHeight() + abs(up)), wxPoint(std::max(0, right), std::max(0, -up))); -} -*/ + +//################################### implementation ################################### + inline wxImage greyScale(const wxImage& img) { wxImage output = img.ConvertToGreyscale(1.0 / 3, 1.0 / 3, 1.0 / 3); //treat all channels equally! - //wxImage output = bmp.ConvertToImage().ConvertToGreyscale(); adjustBrightness(output, 160); return output; } inline -wxBitmap greyScale(const wxBitmap& bmp) -{ - assert(!bmp.GetMask()); //wxWidgets screws up for the gazillionth time applying a mask instead of alpha channel if the .png image has only 0 and 0xff opacity values!!! - return greyScale(bmp.ConvertToImage()); -} - - -inline -wxBitmap greyScaleIfDisabled(const wxBitmap& bmp, bool enabled) +wxImage greyScaleIfDisabled(const wxImage& img, bool enabled) { if (enabled) //avoid ternary WTF - return bmp; + return img; else - return greyScale(bmp); + return greyScale(img); } @@ -162,20 +153,6 @@ void adjustBrightness(wxImage& img, int targetLevel) } -inline -wxImage shrinkImage(const wxImage& img, int requestedSize) -{ - const int maxExtent = std::max(img.GetWidth(), img.GetHeight()); - assert(requestedSize <= maxExtent); - - if (requestedSize >= maxExtent) - return img; - - return img.Scale(img.GetWidth () * requestedSize / maxExtent, - img.GetHeight() * requestedSize / maxExtent, wxIMAGE_QUALITY_BILINEAR); //looks sharper than wxIMAGE_QUALITY_HIGH! -} - - /* inline wxColor gradient(const wxColor& from, const wxColor& to, double fraction) diff --git a/wx+/popup_dlg.cpp b/wx+/popup_dlg.cpp index 2011a228..bdb904f2 100644 --- a/wx+/popup_dlg.cpp +++ b/wx+/popup_dlg.cpp @@ -103,14 +103,14 @@ public: case DialogInfoType::info: //"Information" is meaningless as caption text! //confirmation doesn't use info icon - //iconTmp = getResourceImage("msg_info"); + //iconTmp = loadImage("msg_info"); break; case DialogInfoType::warning: - iconTmp = getResourceImage("msg_warning"); + iconTmp = loadImage("msg_warning"); titleTmp = _("Warning"); break; case DialogInfoType::error: - iconTmp = getResourceImage("msg_error"); + iconTmp = loadImage("msg_error"); titleTmp = _("Error"); break; } diff --git a/wx+/popup_dlg_generated.cpp b/wx+/popup_dlg_generated.cpp index af43c85b..7fc05d53 100644 --- a/wx+/popup_dlg_generated.cpp +++ b/wx+/popup_dlg_generated.cpp @@ -11,91 +11,91 @@ PopupDialogGenerated::PopupDialogGenerated( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style ) { - this->SetSizeHints( wxSize( -1, -1 ), wxDefaultSize ); - this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); + this->SetSizeHints( wxSize( -1,-1 ), wxDefaultSize ); + this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); - wxBoxSizer* bSizer24; - bSizer24 = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* bSizer24; + bSizer24 = new wxBoxSizer( wxVERTICAL ); - m_panel33 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); - m_panel33->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + m_panel33 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panel33->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); - wxBoxSizer* bSizer165; - bSizer165 = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer* bSizer165; + bSizer165 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapMsgType = new wxStaticBitmap( m_panel33, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 ); - bSizer165->Add( m_bitmapMsgType, 0, wxALL, 10 ); + m_bitmapMsgType = new wxStaticBitmap( m_panel33, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 ); + bSizer165->Add( m_bitmapMsgType, 0, wxALL, 10 ); - wxBoxSizer* bSizer16; - bSizer16 = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* bSizer16; + bSizer16 = new wxBoxSizer( wxVERTICAL ); - bSizer16->Add( 0, 10, 0, 0, 5 ); + bSizer16->Add( 0, 10, 0, 0, 5 ); - m_staticTextMain = new wxStaticText( m_panel33, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticTextMain->Wrap( -1 ); - bSizer16->Add( m_staticTextMain, 0, wxRIGHT, 10 ); + m_staticTextMain = new wxStaticText( m_panel33, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextMain->Wrap( -1 ); + bSizer16->Add( m_staticTextMain, 0, wxRIGHT, 10 ); - bSizer16->Add( 0, 5, 0, 0, 5 ); + bSizer16->Add( 0, 5, 0, 0, 5 ); - m_textCtrlTextDetail = new wxTextCtrl( m_panel33, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxBORDER_NONE ); - bSizer16->Add( m_textCtrlTextDetail, 1, wxEXPAND, 5 ); + m_textCtrlTextDetail = new wxTextCtrl( m_panel33, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxBORDER_NONE ); + bSizer16->Add( m_textCtrlTextDetail, 1, wxEXPAND, 5 ); - bSizer165->Add( bSizer16, 1, wxEXPAND, 5 ); + bSizer165->Add( bSizer16, 1, wxEXPAND, 5 ); - m_panel33->SetSizer( bSizer165 ); - m_panel33->Layout(); - bSizer165->Fit( m_panel33 ); - bSizer24->Add( m_panel33, 1, wxEXPAND, 5 ); + m_panel33->SetSizer( bSizer165 ); + m_panel33->Layout(); + bSizer165->Fit( m_panel33 ); + bSizer24->Add( m_panel33, 1, wxEXPAND, 5 ); - m_staticline6 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bSizer24->Add( m_staticline6, 0, wxEXPAND, 5 ); + m_staticline6 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer24->Add( m_staticline6, 0, wxEXPAND, 5 ); - wxBoxSizer* bSizer25; - bSizer25 = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* bSizer25; + bSizer25 = new wxBoxSizer( wxVERTICAL ); - m_checkBoxCustom = new wxCheckBox( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); - bSizer25->Add( m_checkBoxCustom, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 ); + m_checkBoxCustom = new wxCheckBox( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); + bSizer25->Add( m_checkBoxCustom, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 ); - bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL ); + bSizerStdButtons = new wxBoxSizer( wxHORIZONTAL ); - m_buttonAccept = new wxButton( this, wxID_YES, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); + m_buttonAccept = new wxButton( this, wxID_YES, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); - m_buttonAccept->SetDefault(); - bSizerStdButtons->Add( m_buttonAccept, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + m_buttonAccept->SetDefault(); + bSizerStdButtons->Add( m_buttonAccept, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - m_buttonAcceptAll = new wxButton( this, wxID_YESTOALL, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); - bSizerStdButtons->Add( m_buttonAcceptAll, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 ); + m_buttonAcceptAll = new wxButton( this, wxID_YESTOALL, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + bSizerStdButtons->Add( m_buttonAcceptAll, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 ); - m_buttonDecline = new wxButton( this, wxID_NO, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); - bSizerStdButtons->Add( m_buttonDecline, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 ); + m_buttonDecline = new wxButton( this, wxID_NO, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + bSizerStdButtons->Add( m_buttonDecline, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 ); - m_buttonCancel = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1, -1 ), 0 ); - bSizerStdButtons->Add( m_buttonCancel, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 ); + m_buttonCancel = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + bSizerStdButtons->Add( m_buttonCancel, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxRIGHT, 5 ); - bSizer25->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 ); + bSizer25->Add( bSizerStdButtons, 0, wxALIGN_RIGHT, 5 ); - bSizer24->Add( bSizer25, 0, wxEXPAND, 5 ); + bSizer24->Add( bSizer25, 0, wxEXPAND, 5 ); - this->SetSizer( bSizer24 ); - this->Layout(); - bSizer24->Fit( this ); + this->SetSizer( bSizer24 ); + this->Layout(); + bSizer24->Fit( this ); - this->Centre( wxBOTH ); + this->Centre( wxBOTH ); - // Connect Events - this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( PopupDialogGenerated::OnClose ) ); - m_checkBoxCustom->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnCheckBoxClick ), NULL, this ); - m_buttonAccept->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnButtonAccept ), NULL, this ); - m_buttonAcceptAll->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnButtonAcceptAll ), NULL, this ); - m_buttonDecline->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnButtonDecline ), NULL, this ); - m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnCancel ), NULL, this ); + // Connect Events + this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( PopupDialogGenerated::OnClose ) ); + m_checkBoxCustom->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnCheckBoxClick ), NULL, this ); + m_buttonAccept->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnButtonAccept ), NULL, this ); + m_buttonAcceptAll->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnButtonAcceptAll ), NULL, this ); + m_buttonDecline->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnButtonDecline ), NULL, this ); + m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PopupDialogGenerated::OnCancel ), NULL, this ); } PopupDialogGenerated::~PopupDialogGenerated() diff --git a/wx+/popup_dlg_generated.h b/wx+/popup_dlg_generated.h index 33cc4872..8191ca0f 100644 --- a/wx+/popup_dlg_generated.h +++ b/wx+/popup_dlg_generated.h @@ -38,33 +38,34 @@ /////////////////////////////////////////////////////////////////////////////// class PopupDialogGenerated : public wxDialog { -private: + private: -protected: - wxPanel* m_panel33; - wxStaticBitmap* m_bitmapMsgType; - wxStaticText* m_staticTextMain; - wxTextCtrl* m_textCtrlTextDetail; - wxStaticLine* m_staticline6; - wxCheckBox* m_checkBoxCustom; - wxBoxSizer* bSizerStdButtons; - wxButton* m_buttonAccept; - wxButton* m_buttonAcceptAll; - wxButton* m_buttonDecline; - wxButton* m_buttonCancel; + protected: + wxPanel* m_panel33; + wxStaticBitmap* m_bitmapMsgType; + wxStaticText* m_staticTextMain; + wxTextCtrl* m_textCtrlTextDetail; + wxStaticLine* m_staticline6; + wxCheckBox* m_checkBoxCustom; + wxBoxSizer* bSizerStdButtons; + wxButton* m_buttonAccept; + wxButton* m_buttonAcceptAll; + wxButton* m_buttonDecline; + wxButton* m_buttonCancel; - // Virtual event handlers, overide them in your derived class - virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } - virtual void OnCheckBoxClick( wxCommandEvent& event ) { event.Skip(); } - virtual void OnButtonAccept( wxCommandEvent& event ) { event.Skip(); } - virtual void OnButtonAcceptAll( wxCommandEvent& event ) { event.Skip(); } - virtual void OnButtonDecline( wxCommandEvent& event ) { event.Skip(); } - virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } + // Virtual event handlers, overide them in your derived class + virtual void OnClose( wxCloseEvent& event ) { event.Skip(); } + virtual void OnCheckBoxClick( wxCommandEvent& event ) { event.Skip(); } + virtual void OnButtonAccept( wxCommandEvent& event ) { event.Skip(); } + virtual void OnButtonAcceptAll( wxCommandEvent& event ) { event.Skip(); } + virtual void OnButtonDecline( wxCommandEvent& event ) { event.Skip(); } + virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } -public: + public: - PopupDialogGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("dummy"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); - ~PopupDialogGenerated(); + PopupDialogGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("dummy"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); + ~PopupDialogGenerated(); }; + @@ -15,12 +15,11 @@ namespace zen { //functions supporting right-to-left GUI layout -void drawBitmapRtlMirror (wxDC& dc, const wxBitmap& bmp, const wxRect& rect, int alignment, std::optional<wxBitmap>& buffer); -void drawBitmapRtlNoMirror(wxDC& dc, const wxBitmap& bmp, const wxRect& rect, int alignment); +void drawBitmapRtlMirror (wxDC& dc, const wxImage& img, const wxRect& rect, int alignment, std::optional<wxBitmap>& buffer); +void drawBitmapRtlNoMirror(wxDC& dc, const wxImage& img, const wxRect& rect, int alignment); //wxDC::DrawIcon DOES mirror by default -> implement RTL support when needed -wxBitmap mirrorIfRtl(const wxBitmap& bmp); -wxImage mirrorIfRtl(const wxImage& bmp); +wxImage mirrorIfRtl(const wxImage& img); //manual text flow correction: https://www.w3.org/International/articles/inline-bidi-markup/ @@ -34,34 +33,35 @@ wxImage mirrorIfRtl(const wxImage& bmp); //---------------------- implementation ------------------------ namespace impl { -//don't use wxDC::DrawLabel: it results in expensive GetTextExtent() call even when passing an empty string!!! -//also avoid wxDC::DrawLabel 1-off alignment bugs +//don't use wxDC::DrawLabel: +// - expensive GetTextExtent() call even when passing an empty string!!! +// - 1-off alignment bugs! inline -void drawBitmapAligned(wxDC& dc, const wxBitmap& bmp, const wxRect& rect, int alignment) +void drawBitmapAligned(wxDC& dc, const wxImage& img, const wxRect& rect, int alignment) { wxPoint pt = rect.GetTopLeft(); if (alignment & wxALIGN_RIGHT) //note: wxALIGN_LEFT == 0! - pt.x += rect.width - bmp.GetWidth(); + pt.x += rect.width - img.GetWidth(); else if (alignment & wxALIGN_CENTER_HORIZONTAL) - pt.x += (rect.width - bmp.GetWidth()) / 2; + pt.x += (rect.width - img.GetWidth()) / 2; if (alignment & wxALIGN_BOTTOM) //note: wxALIGN_TOP == 0! - pt.y += rect.height - bmp.GetHeight(); + pt.y += rect.height - img.GetHeight(); else if (alignment & wxALIGN_CENTER_VERTICAL) - pt.y += (rect.height - bmp.GetHeight()) / 2; + pt.y += (rect.height - img.GetHeight()) / 2; - dc.DrawBitmap(bmp, pt); + dc.DrawBitmap(img, pt); } } inline -void drawBitmapRtlMirror(wxDC& dc, const wxBitmap& bmp, const wxRect& rect, int alignment, std::optional<wxBitmap>& buffer) +void drawBitmapRtlMirror(wxDC& dc, const wxImage& img, const wxRect& rect, int alignment, std::optional<wxBitmap>& buffer) { switch (dc.GetLayoutDirection()) { case wxLayout_LeftToRight: - return impl::drawBitmapAligned(dc, bmp, rect, alignment); + return impl::drawBitmapAligned(dc, img, rect, alignment); case wxLayout_RightToLeft: { @@ -71,7 +71,7 @@ void drawBitmapRtlMirror(wxDC& dc, const wxBitmap& bmp, const wxRect& rect, int wxMemoryDC memDc(*buffer); memDc.Blit(wxPoint(0, 0), rect.GetSize(), &dc, rect.GetTopLeft()); //blit in: background is mirrored due to memDc, dc having different layout direction! - impl::drawBitmapAligned(memDc, bmp, wxRect(0, 0, rect.width, rect.height), alignment); + impl::drawBitmapAligned(memDc, img, wxRect(0, 0, rect.width, rect.height), alignment); //note: we cannot simply use memDc.SetLayoutDirection(wxLayout_RightToLeft) due to some strange 1 pixel bug! dc.Blit(rect.GetTopLeft(), rect.GetSize(), &memDc, wxPoint(0, 0)); //blit out: mirror once again @@ -80,37 +80,27 @@ void drawBitmapRtlMirror(wxDC& dc, const wxBitmap& bmp, const wxRect& rect, int case wxLayout_Default: //CAVEAT: wxPaintDC/wxMemoryDC on wxGTK/wxMAC does not implement SetLayoutDirection()!!! => GetLayoutDirection() == wxLayout_Default if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) - return impl::drawBitmapAligned(dc, bmp.ConvertToImage().Mirror(), rect, alignment); + return impl::drawBitmapAligned(dc, img.Mirror(), rect, alignment); else - return impl::drawBitmapAligned(dc, bmp, rect, alignment); + return impl::drawBitmapAligned(dc, img, rect, alignment); } } inline -void drawBitmapRtlNoMirror(wxDC& dc, const wxBitmap& bmp, const wxRect& rect, int alignment) +void drawBitmapRtlNoMirror(wxDC& dc, const wxImage& img, const wxRect& rect, int alignment) { - return impl::drawBitmapAligned(dc, bmp, rect, alignment); //wxDC::DrawBitmap does NOT mirror by default + return impl::drawBitmapAligned(dc, img, rect, alignment); //wxDC::DrawBitmap does NOT mirror by default } inline -wxImage mirrorIfRtl(const wxImage& bmp) +wxImage mirrorIfRtl(const wxImage& img) { if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) - return bmp.Mirror(); + return img.Mirror(); else - return bmp; -} - - -inline -wxBitmap mirrorIfRtl(const wxBitmap& bmp) -{ - if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) - return bmp.ConvertToImage().Mirror(); - else - return bmp; + return img; } } diff --git a/wx+/toggle_button.h b/wx+/toggle_button.h index 98ca32b3..0a359c5c 100644 --- a/wx+/toggle_button.h +++ b/wx+/toggle_button.h @@ -16,6 +16,7 @@ namespace zen class ToggleButton : public wxBitmapButton { public: + //wxBitmapButton constructor ToggleButton(wxWindow* parent, wxWindowID id, const wxBitmap& bitmap, @@ -23,13 +24,25 @@ public: const wxSize& size = wxDefaultSize, long style = 0, const wxValidator& validator = wxDefaultValidator, - const wxString& name = wxButtonNameStr) : wxBitmapButton(parent, id, bitmap, pos, size, style, validator, name) + const wxString& name = wxButtonNameStr) : + wxBitmapButton(parent, id, bitmap, pos, size, style, validator, name) {} + + //wxButton constructor + ToggleButton(wxWindow* parent, + wxWindowID id, + const wxString& label, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0, + const wxValidator& validator = wxDefaultValidator, + const wxString& name = wxButtonNameStr) : + wxBitmapButton(parent, id, wxNullBitmap, pos, size, style, validator, name) { - SetLayoutDirection(wxLayout_LeftToRight); //avoid mirroring RTL languages like Hebrew or Arabic + SetLabel(label); } - void init(const wxBitmap& bmpActive, - const wxBitmap& bmpInactive); + void init(const wxImage& imgActive, + const wxImage& imgInactive); void setActive(bool value); bool isActive() const { return active_; } @@ -37,8 +50,8 @@ public: private: bool active_ = false; - wxBitmap bmpActive_; - wxBitmap bmpInactive_; + wxImage imgActive_; + wxImage imgInactive_; }; @@ -49,13 +62,13 @@ private: //######################## implementation ######################## inline -void ToggleButton::init(const wxBitmap& bmpActive, - const wxBitmap& bmpInactive) +void ToggleButton::init(const wxImage& imgActive, + const wxImage& imgInactive) { - bmpActive_ = bmpActive; - bmpInactive_ = bmpInactive; + imgActive_ = imgActive; + imgInactive_ = imgInactive; - setImage(*this, active_ ? bmpActive_ : bmpInactive_); + setImage(*this, active_ ? imgActive_ : imgInactive_); } @@ -65,7 +78,7 @@ void ToggleButton::setActive(bool value) if (active_ != value) { active_ = value; - setImage(*this, active_ ? bmpActive_ : bmpInactive_); + setImage(*this, active_ ? imgActive_ : imgInactive_); } } } diff --git a/wx+/tooltip.cpp b/wx+/tooltip.cpp index 0beef0bf..e3c021c6 100644 --- a/wx+/tooltip.cpp +++ b/wx+/tooltip.cpp @@ -54,16 +54,17 @@ public: }; -void Tooltip::show(const wxString& text, wxPoint mousePos, const wxBitmap* bmp) +void Tooltip::show(const wxString& text, wxPoint mousePos, const wxImage* img) { if (!tipWindow_) tipWindow_ = new TooltipDlgGenerated(&parent_); //ownership passed to parent - const wxBitmap& newBmp = bmp ? *bmp : wxNullBitmap; + const wxImage& newImg = img ? *img : wxNullImage; - if (!tipWindow_->bitmapLeft_->GetBitmap().IsSameAs(newBmp)) + if (!lastUsedImg_.IsSameAs(newImg)) { - tipWindow_->bitmapLeft_->SetBitmap(newBmp); + lastUsedImg_ = newImg; + tipWindow_->bitmapLeft_->SetBitmap(newImg); tipWindow_->Refresh(); //needed if bitmap size changed! } diff --git a/wx+/tooltip.h b/wx+/tooltip.h index d74beb9d..b496f36c 100644 --- a/wx+/tooltip.h +++ b/wx+/tooltip.h @@ -8,6 +8,7 @@ #define TOOLTIP_H_8912740832170515 #include <wx/window.h> +#include <wx/image.h> namespace zen @@ -19,13 +20,14 @@ public: void show(const wxString& text, wxPoint mousePos, //absolute screen coordinates - const wxBitmap* bmp = nullptr); + const wxImage* img = nullptr); void hide(); private: class TooltipDlgGenerated; TooltipDlgGenerated* tipWindow_ = nullptr; wxWindow& parent_; + wxImage lastUsedImg_; }; } |